feat: 슬랫(철재) 견적 계산 지원 추가 및 5130 정합성 검증

- calculateSlatPrice() 메서드 추가 (면적 = W0×(H0+50), 원자재 단가 '방화')
- calculateDynamicItems() 주자재 분기 (screen→실리카, slat→방화)
- 레일용 연기차단재: 슬랫 ×1 / 스크린 ×2 분기 처리
- 절곡품: 슬랫일 때 L바/보강평철/환봉 제외 (5130 동일)
- 부자재 앵글: 슬랫은 앵글4T, 스크린은 앵글3T
- 모터 받침용 앵글: 슬랫 기본 비활성 (bracket_angle_enabled 파라미터)
- 조인트바: 슬랫 전용 항목 추가 (joint_bar_qty 파라미터)

검증: 치수 5종 × 수량 3종 모두 5130과 완벽 일치

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-30 22:04:04 +09:00
parent 86ec5c4185
commit e9639d1011

View File

@@ -247,6 +247,29 @@ public function calculateScreenPrice(float $width, float $height): array
];
}
/**
* 슬랫(철재) 주자재 가격 계산
* 5130 공식: W0 × (H0 + 50) / 1,000,000 × 단가
*
* @return array [unit_price, area, total_price]
*/
public function calculateSlatPrice(float $width, float $height): array
{
$calculateHeight = $height + 50;
$area = ($width * $calculateHeight) / 1000000;
// 원자재 단가 조회 (방화/슬랫)
$unitPrice = $this->getRawMaterialPrice('방화');
$roundedArea = round($area, 2);
return [
'unit_price' => $unitPrice,
'area' => $roundedArea,
'total_price' => round($unitPrice * $roundedArea),
];
}
// =========================================================================
// 단가 조회 메서드 (EstimatePriceService 사용)
// =========================================================================
@@ -360,6 +383,7 @@ public function calculateSteelItems(array $params): array
$width = (float) ($params['W0'] ?? 0);
$height = (float) ($params['H0'] ?? 0);
$quantity = (int) ($params['QTY'] ?? 1);
$productType = $params['product_type'] ?? 'screen';
$modelName = $params['model_name'] ?? $params['product_model'] ?? 'KSS01';
$rawFinish = $params['finishing_type'] ?? 'SUS';
// DB에는 'SUS', 'EGI'로 저장 → 'SUS마감' → 'SUS' 변환
@@ -443,14 +467,17 @@ public function calculateSteelItems(array $params): array
$guideItems = $this->calculateGuideRails($modelName, $finishingType, $guideType, $guideSpec, $guideLength, $quantity);
$items = array_merge($items, $guideItems);
// 5. 레일용 연기차단재 (단가 × 길이m × 2 × 수량)
// 5. 레일용 연기차단재
// 스크린: 단가 × 길이m × 2 × 수량 (좌우)
// 슬랫: 단가 × 길이m × 수량 (×2 없음)
$railSmokePrice = $this->priceService->getRailSmokeBlockPrice();
if ($railSmokePrice > 0 && $guideLength > 0) {
$railSmokeQty = 2 * $quantity; // 좌우 2개
$railSmokeMultiplier = ($productType === 'slat') ? 1 : 2;
$railSmokeQty = $railSmokeMultiplier * $quantity;
$items[] = [
'category' => 'steel',
'item_name' => '레일용 연기차단재',
'specification' => "{$guideLength}m × 2",
'specification' => ($railSmokeMultiplier > 1) ? "{$guideLength}m × 2" : "{$guideLength}m",
'unit' => 'm',
'quantity' => $guideLength * $railSmokeQty,
'unit_price' => $railSmokePrice,
@@ -472,8 +499,8 @@ public function calculateSteelItems(array $params): array
];
}
// 7. L바 (단가 × 길이m × 수량)
$lbarPrice = $this->priceService->getLBarPrice($modelName);
// 7. L바 (단가 × 길이m × 수량) - 스크린 전용, 슬랫 미사용
$lbarPrice = ($productType !== 'slat') ? $this->priceService->getLBarPrice($modelName) : 0;
if ($lbarPrice > 0 && $lbarLength > 0) {
$items[] = [
'category' => 'steel',
@@ -486,8 +513,8 @@ public function calculateSteelItems(array $params): array
];
}
// 8. 보강평철 (단가 × 길이m × 수량)
$flatBarPrice = $this->priceService->getFlatBarPrice();
// 8. 보강평철 (단가 × 길이m × 수량) - 스크린 전용, 슬랫 미사용
$flatBarPrice = ($productType !== 'slat') ? $this->priceService->getFlatBarPrice() : 0;
if ($flatBarPrice > 0 && $flatBarLength > 0) {
$items[] = [
'category' => 'steel',
@@ -514,8 +541,8 @@ public function calculateSteelItems(array $params): array
];
}
// 10. 환봉 (고정 2,000원 × 수량)
if ($roundBarQty > 0) {
// 10. 환봉 (고정 2,000원 × 수량) - 스크린 전용, 슬랫 미사용
if ($roundBarQty > 0 && $productType !== 'slat') {
$roundBarPrice = 2000;
$items[] = [
'category' => 'steel',
@@ -747,11 +774,13 @@ public function calculatePartItems(array $params): array
// 3. 모터 받침용 앵글 (bracket angle)
// 5130: calculateAngle(qty, itemList, '스크린용') → col2 검색, qty × $su × 4
// 5130 슬랫: col23(앵글사이즈) 비어있으면 생략
$motorCapacity = $params['MOTOR_CAPACITY'] ?? '300K';
$bracketAngleEnabled = (bool) ($params['bracket_angle_enabled'] ?? ($productType !== 'slat'));
if ($productType === 'screen') {
$angleSearchOption = '스크린용';
} else {
// 철재: bracketSize로 매핑 (530*320→철제300K, 600*350→철제400K, 690*390→철제800K)
// 철재/슬랫: bracketSize로 매핑
$angleSearchOption = match ($bracketSize) {
'530*320' => '철제300K',
'600*350' => '철제400K',
@@ -759,7 +788,7 @@ public function calculatePartItems(array $params): array
default => '철제300K',
};
}
$anglePrice = $this->getAnglePrice($angleSearchOption);
$anglePrice = $bracketAngleEnabled ? $this->getAnglePrice($angleSearchOption) : 0;
if ($anglePrice > 0) {
$angleQty = 4 * $quantity; // 5130: $su * 4
$items[] = [
@@ -774,10 +803,11 @@ public function calculatePartItems(array $params): array
}
// 4. 부자재 앵글 (main angle)
// 5130: calculateMainAngle(1, $itemList, '앵글3T', '2.5') × col71
$mainAngleType = $bracketSize === '690*390' ? '앵글4T' : '앵글3T';
// 스크린 5130: calculateMainAngle(1, $itemList, '앵글3T', '2.5') × col71
// 슬랫 5130: calculateMainAngle(1, $itemList, '앵글4T', '2.5') × col77
$mainAngleType = ($productType === 'slat') ? '앵글4T' : ($bracketSize === '690*390' ? '앵글4T' : '앵글3T');
$mainAngleSize = '2.5';
$mainAngleQty = (int) ($params['main_angle_qty'] ?? 2); // col71, default 2 (좌우)
$mainAngleQty = (int) ($params['main_angle_qty'] ?? 2); // col71/col77, default 2 (좌우)
$mainAnglePrice = $this->getMainAnglePrice($mainAngleType, $mainAngleSize);
if ($mainAnglePrice > 0 && $mainAngleQty > 0) {
$items[] = [
@@ -791,6 +821,25 @@ public function calculatePartItems(array $params): array
];
}
// 5. 조인트바 (슬랫 전용, 5130: price × col76, QTY 미적용)
if ($productType === 'slat') {
$jointBarQty = (int) ($params['joint_bar_qty'] ?? 0);
if ($jointBarQty > 0) {
$jointBarPrice = $this->getRawMaterialPrice('조인트바');
if ($jointBarPrice > 0) {
$items[] = [
'category' => 'parts',
'item_name' => '조인트바',
'specification' => '',
'unit' => 'EA',
'quantity' => $jointBarQty,
'unit_price' => $jointBarPrice,
'total_price' => round($jointBarPrice * $jointBarQty),
];
}
}
}
return $items;
}
@@ -844,17 +893,25 @@ public function calculateDynamicItems(array $inputs): array
];
}
// 1. 주자재 (스크린)
$screenResult = $this->calculateScreenPrice($width, $height);
// 1. 주자재 (스크린 또는 슬랫)
if ($productType === 'slat') {
$materialResult = $this->calculateSlatPrice($width, $height);
$materialName = '주자재(슬랫)';
$materialCode = 'KD-SLAT';
} else {
$materialResult = $this->calculateScreenPrice($width, $height);
$materialName = '주자재(스크린)';
$materialCode = 'KD-SCREEN';
}
$items[] = [
'category' => 'material',
'item_code' => 'KD-SCREEN',
'item_name' => '주자재(스크린)',
'specification' => "면적 {$screenResult['area']}",
'item_code' => $materialCode,
'item_name' => $materialName,
'specification' => "면적 {$materialResult['area']}",
'unit' => '㎡',
'quantity' => $screenResult['area'] * $quantity,
'unit_price' => $screenResult['unit_price'],
'total_price' => $screenResult['total_price'] * $quantity,
'quantity' => $materialResult['area'] * $quantity,
'unit_price' => $materialResult['unit_price'],
'total_price' => $materialResult['total_price'] * $quantity,
];
// 2. 모터