fix: 견적 반올림 순서를 5130과 동일하게 수정 (단건→수량 곱셈)
- 케이스, 케이스용 연기차단재, 가이드레일, 레일용 연기차단재: round(단가 × 길이 × QTY) → round(단가 × 길이) × QTY - 5130 레거시와 동일한 반올림 순서 적용 - 검증: 스크린 44건 + 슬랫 32건 + 가이드타입 21건 = 97건 ALL PASS - 사이즈 범위: 3000×1500 ~ 12000×4000, QTY 1~5 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,63 @@
|
||||
## 2026-01-30 (목) - 5130↔SAM 견적 교차 검증 완료 + 마이그레이션 검증
|
||||
|
||||
### 작업 목표
|
||||
- SAM 견적 계산이 5130 레거시 시스템과 100% 일치하는지 교차 검증
|
||||
- FormulaEvaluatorService 슬랫/스틸 지원 완성
|
||||
- MigrateBDModelsPrices 커맨드 동작 검증
|
||||
|
||||
### 수정된 파일
|
||||
| 파일명 | 설명 |
|
||||
|--------|------|
|
||||
| `app/Services/Quote/FormulaEvaluatorService.php` | 제품타입별 면적/중량 공식 분기, 모터/브라켓 입력값 오버라이드, 디버그 포뮬러 동적 표시 |
|
||||
| `app/Services/Quote/Handlers/KyungdongFormulaHandler.php` | 제품타입별 면적/중량 공식, normalizeGuideType() 추가, guide_rail_spec 파라미터 별칭 |
|
||||
|
||||
### 핵심 수정 내용
|
||||
|
||||
#### 1. 제품타입별 면적/중량 공식 (FormulaEvaluatorService + Handler)
|
||||
- **Screen**: area = (W1 × (H1+550)) / 1M, weight = area×2 + (W0/1000)×14.17
|
||||
- **Slat**: area = (W0 × (H0+50)) / 1M, weight = area×25
|
||||
- **Steel**: area = (W1 × (H1+550)) / 1M, weight = area×25
|
||||
|
||||
#### 2. 모터/브라켓 입력값 오버라이드
|
||||
- 기존: 항상 자동 계산
|
||||
- 수정: `MOTOR_CAPACITY`, `BRACKET_SIZE` 입력값이 있으면 우선 사용
|
||||
|
||||
#### 3. 가이드타입 정규화
|
||||
- `normalizeGuideType()` 메서드 추가 (벽면↔벽면형, 측면↔측면형, 혼합↔혼합형)
|
||||
- `guide_rail_spec` 파라미터 별칭 지원
|
||||
|
||||
### 검증 결과
|
||||
|
||||
#### 전 모델 교차 검증 (Task #6) ✅
|
||||
```
|
||||
16/16 ALL PASS
|
||||
- 10개 스크린 조합 (KSS01, KSS02, KSE01, KWE01, KTE01, KQTS01, KDSS01 × SUS/EGI)
|
||||
- 6개 슬랫 조합 (KSS02, KSE01, KTE01 × SUS × 2사이즈)
|
||||
- 조건: 6800×2700, QTY=1, 300K 모터, 5인치 브라켓
|
||||
```
|
||||
|
||||
#### 가이드타입 교차 검증 (Task #7) ✅
|
||||
```
|
||||
21/21 ALL PASS
|
||||
- 벽면/측면/혼합 × 4모델(KSS02, KSE01, KTE01, KDSS01) × screen
|
||||
- 벽면/측면/혼합 × 3모델(KSS02, KSE01, KTE01) × slat
|
||||
- 혼합형: 5130은 col6에 "혼합 120*70/120*120" 두 규격 필요
|
||||
```
|
||||
|
||||
#### MigrateBDModelsPrices 커맨드 검증 (Task #4, #5) ✅
|
||||
```
|
||||
커맨드 정상 동작 확인
|
||||
- BD-* (절곡품): 58건 마이그레이션 완료
|
||||
- EST-* (모터/제어기/원자재 등): 71건 마이그레이션 완료
|
||||
- chandj 원본 가격 일치: 7/7 검증 통과
|
||||
- --dry-run, --fresh 옵션 정상 동작
|
||||
```
|
||||
|
||||
### Git 커밋
|
||||
- `f4a902f` - fix: FormulaEvaluatorService 슬랫/스틸 제품타입별 면적/중량/모터/가이드 수정
|
||||
|
||||
---
|
||||
|
||||
## 2026-01-29 (수) - 경동기업 견적 로직 Phase 4 완료
|
||||
|
||||
### 작업 목표
|
||||
|
||||
@@ -419,7 +419,8 @@ public function calculateSteelItems(array $params): array
|
||||
// 1. 케이스 (단가/1000 × 길이mm × 수량)
|
||||
$casePrice = $this->priceService->getCasePrice($caseSpec);
|
||||
if ($casePrice > 0 && $caseLength > 0) {
|
||||
$totalPrice = ($casePrice / 1000) * $caseLength * $quantity;
|
||||
// 5130: round($shutter_price * $total_length * 1000) * $su → 단건 반올림 후 × QTY
|
||||
$perUnitPrice = round(($casePrice / 1000) * $caseLength);
|
||||
$items[] = [
|
||||
'category' => 'steel',
|
||||
'item_name' => '케이스',
|
||||
@@ -427,14 +428,15 @@ public function calculateSteelItems(array $params): array
|
||||
'unit' => 'm',
|
||||
'quantity' => $caseLength / 1000 * $quantity,
|
||||
'unit_price' => $casePrice,
|
||||
'total_price' => round($totalPrice),
|
||||
'total_price' => $perUnitPrice * $quantity,
|
||||
];
|
||||
}
|
||||
|
||||
// 2. 케이스용 연기차단재 (단가 × 길이m × 수량)
|
||||
// 2. 케이스용 연기차단재 - 5130: round(단가 × 길이m) × QTY
|
||||
$caseSmokePrice = $this->priceService->getCaseSmokeBlockPrice();
|
||||
if ($caseSmokePrice > 0 && $caseLength > 0) {
|
||||
$lengthM = $caseLength / 1000;
|
||||
$perUnitSmoke = round($caseSmokePrice * $lengthM);
|
||||
$items[] = [
|
||||
'category' => 'steel',
|
||||
'item_name' => '케이스용 연기차단재',
|
||||
@@ -442,16 +444,15 @@ public function calculateSteelItems(array $params): array
|
||||
'unit' => 'm',
|
||||
'quantity' => $lengthM * $quantity,
|
||||
'unit_price' => $caseSmokePrice,
|
||||
'total_price' => round($caseSmokePrice * $lengthM * $quantity),
|
||||
'total_price' => $perUnitSmoke * $quantity,
|
||||
];
|
||||
}
|
||||
|
||||
// 3. 케이스 마구리 (단가 × 수량)
|
||||
// 마구리 규격 = 케이스 규격 각 치수 + 5mm (레거시 updateCol45 공식)
|
||||
// 3. 케이스 마구리 - 5130: round(단가 × QTY)
|
||||
$caseCapSpec = $this->convertToCaseCapSpec($caseSpec);
|
||||
$caseCapPrice = $this->priceService->getCaseCapPrice($caseCapSpec);
|
||||
if ($caseCapPrice > 0) {
|
||||
$capQty = $quantity; // 5130: maguriPrices × $su (수량)
|
||||
$capQty = $quantity;
|
||||
$items[] = [
|
||||
'category' => 'steel',
|
||||
'item_name' => '케이스 마구리',
|
||||
@@ -467,13 +468,12 @@ public function calculateSteelItems(array $params): array
|
||||
$guideItems = $this->calculateGuideRails($modelName, $finishingType, $guideType, $guideSpec, $guideLength, $quantity);
|
||||
$items = array_merge($items, $guideItems);
|
||||
|
||||
// 5. 레일용 연기차단재
|
||||
// 스크린: 단가 × 길이m × 2 × 수량 (좌우)
|
||||
// 슬랫: 단가 × 길이m × 수량 (×2 없음)
|
||||
// 5. 레일용 연기차단재 - 5130: round(단가 × 길이m) × multiplier × QTY
|
||||
$railSmokePrice = $this->priceService->getRailSmokeBlockPrice();
|
||||
if ($railSmokePrice > 0 && $guideLength > 0) {
|
||||
$railSmokeMultiplier = ($productType === 'slat') ? 1 : 2;
|
||||
$railSmokeQty = $railSmokeMultiplier * $quantity;
|
||||
$perUnitRailSmoke = round($railSmokePrice * $guideLength);
|
||||
$items[] = [
|
||||
'category' => 'steel',
|
||||
'item_name' => '레일용 연기차단재',
|
||||
@@ -481,7 +481,7 @@ public function calculateSteelItems(array $params): array
|
||||
'unit' => 'm',
|
||||
'quantity' => $guideLength * $railSmokeQty,
|
||||
'unit_price' => $railSmokePrice,
|
||||
'total_price' => round($railSmokePrice * $guideLength * $railSmokeQty),
|
||||
'total_price' => $perUnitRailSmoke * $railSmokeQty,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -604,11 +604,13 @@ private function calculateGuideRails(
|
||||
$wallSpec = $specs['wall'];
|
||||
$sideSpec = $specs['side'];
|
||||
|
||||
// 5130: round(단가 × 길이m) × QTY (단건 반올림 후 QTY 곱셈)
|
||||
switch ($guideType) {
|
||||
case '벽면형':
|
||||
$price = $this->priceService->getGuideRailPrice($modelName, $finishingType, $wallSpec);
|
||||
if ($price > 0) {
|
||||
$guideQty = 2 * $quantity;
|
||||
$perUnitGuide = round($price * $guideLength);
|
||||
$items[] = [
|
||||
'category' => 'steel',
|
||||
'item_name' => '가이드레일',
|
||||
@@ -616,7 +618,7 @@ private function calculateGuideRails(
|
||||
'unit' => 'm',
|
||||
'quantity' => $guideLength * $guideQty,
|
||||
'unit_price' => $price,
|
||||
'total_price' => round($price * $guideLength * $guideQty),
|
||||
'total_price' => $perUnitGuide * $guideQty,
|
||||
];
|
||||
}
|
||||
break;
|
||||
@@ -625,6 +627,7 @@ private function calculateGuideRails(
|
||||
$price = $this->priceService->getGuideRailPrice($modelName, $finishingType, $sideSpec);
|
||||
if ($price > 0) {
|
||||
$guideQty = 2 * $quantity;
|
||||
$perUnitGuide = round($price * $guideLength);
|
||||
$items[] = [
|
||||
'category' => 'steel',
|
||||
'item_name' => '가이드레일',
|
||||
@@ -632,7 +635,7 @@ private function calculateGuideRails(
|
||||
'unit' => 'm',
|
||||
'quantity' => $guideLength * $guideQty,
|
||||
'unit_price' => $price,
|
||||
'total_price' => round($price * $guideLength * $guideQty),
|
||||
'total_price' => $perUnitGuide * $guideQty,
|
||||
];
|
||||
}
|
||||
break;
|
||||
@@ -642,6 +645,7 @@ private function calculateGuideRails(
|
||||
$priceSide = $this->priceService->getGuideRailPrice($modelName, $finishingType, $sideSpec);
|
||||
|
||||
if ($priceWall > 0) {
|
||||
$perUnitWall = round($priceWall * $guideLength);
|
||||
$items[] = [
|
||||
'category' => 'steel',
|
||||
'item_name' => '가이드레일',
|
||||
@@ -649,10 +653,11 @@ private function calculateGuideRails(
|
||||
'unit' => 'm',
|
||||
'quantity' => $guideLength * $quantity,
|
||||
'unit_price' => $priceWall,
|
||||
'total_price' => round($priceWall * $guideLength * $quantity),
|
||||
'total_price' => $perUnitWall * $quantity,
|
||||
];
|
||||
}
|
||||
if ($priceSide > 0) {
|
||||
$perUnitSide = round($priceSide * $guideLength);
|
||||
$items[] = [
|
||||
'category' => 'steel',
|
||||
'item_name' => '가이드레일',
|
||||
@@ -660,7 +665,7 @@ private function calculateGuideRails(
|
||||
'unit' => 'm',
|
||||
'quantity' => $guideLength * $quantity,
|
||||
'unit_price' => $priceSide,
|
||||
'total_price' => round($priceSide * $guideLength * $quantity),
|
||||
'total_price' => $perUnitSide * $quantity,
|
||||
];
|
||||
}
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user