diff --git a/app/Services/Production/BendingInfoBuilder.php b/app/Services/Production/BendingInfoBuilder.php new file mode 100644 index 0000000..d1f7ac3 --- /dev/null +++ b/app/Services/Production/BendingInfoBuilder.php @@ -0,0 +1,822 @@ +process_name !== '절곡') { + return null; + } + + // 2. 루트 노드 확인 (nodeIds가 있으면 해당 노드만 필터) + $nodes = $order->rootNodes; + if ($nodeIds !== null) { + $nodes = $nodes->whereIn('id', $nodeIds); + } + if ($nodes->isEmpty()) { + return null; + } + + // 3. 첫 번째 루트 노드에서 product_code 파싱 + $firstNode = $nodes->first(); + $productCode = $firstNode->options['product_code'] ?? ''; + if (empty($productCode)) { + return null; + } + + $productInfo = $this->parseProductCode($productCode); + if (empty($productInfo['productCode'])) { + return null; + } + + // 4. 재질 매핑 + $materials = $this->getMaterialMapping( + $productInfo['productCode'], + $productInfo['finishMaterial'] + ); + + // 5. 노드 집계 (치수별 그룹핑 + BOM 카테고리 분류) + $aggregated = $this->aggregateNodes($nodes); + + // 6. bending_info 조립 + return $this->assembleBendingInfo($productInfo, $materials, $aggregated); + } + + /** + * product_code 파싱 + * "FG-KSS02-벽면형-SUS" → productCode, guideType, finishMaterial + */ + private function parseProductCode(string $fullCode): array + { + $parts = explode('-', $fullCode); + + // FG 접두사 제거 + if (($parts[0] ?? '') === 'FG') { + array_shift($parts); + } + + $finish = $parts[2] ?? 'EGI'; + + return [ + 'productCode' => $parts[0] ?? '', + 'guideType' => $parts[1] ?? '벽면형', + 'finishMaterial' => $finish === 'SUS' ? 'SUS마감' : 'EGI마감', + ]; + } + + /** + * BOM 아이템 카테고리 분류 (item_code/item_name 패턴 매칭) + */ + private function categorizeBomItem(array $bomItem): ?string + { + $code = $bomItem['item_code'] ?? ''; + $name = $bomItem['item_name'] ?? ''; + + if (str_starts_with($code, 'BD-가이드레일')) { + return 'guideRail'; + } + if (str_starts_with($code, 'BD-케이스')) { + return 'shutterBox_case'; + } + if (str_starts_with($code, 'BD-마구리')) { + return 'shutterBox_finCover'; + } + if (str_contains($name, '하장바')) { + return 'bottomBar'; + } + if ($code === 'EST-SMOKE-레일용') { + return 'smokeBarrier_rail'; + } + if ($code === 'EST-SMOKE-케이스용') { + return 'smokeBarrier_case'; + } + if (str_starts_with($code, 'BD-L-BAR')) { + return 'detail_lbar'; + } + if (str_starts_with($code, 'BD-보강평철')) { + return 'detail_reinforce'; + } + + return null; + } + + /** + * 노드 집계: 치수별 그룹핑 + BOM 카테고리 분류 + */ + private function aggregateNodes(Collection $nodes): array + { + $dimensionGroups = []; + $totalNodeQty = 0; + $bomCategories = []; + + foreach ($nodes as $node) { + $opts = $node->options ?? []; + // 절곡 원자재 길이는 오픈 사이즈 기준 (제조치수는 +offset 포함으로 부적합) + $width = (int) ($opts['open_width'] ?? $opts['width'] ?? 0); + $height = (int) ($opts['open_height'] ?? $opts['height'] ?? 0); + $nodeQty = $node->quantity ?? 1; + $totalNodeQty += $nodeQty; + + // 치수별 그룹핑 + $dimKey = "{$height}_{$width}"; + if (! isset($dimensionGroups[$dimKey])) { + $dimensionGroups[$dimKey] = [ + 'height' => (int) $height, + 'width' => (int) $width, + 'qty' => 0, + ]; + } + $dimensionGroups[$dimKey]['qty'] += $nodeQty; + + // BOM 아이템 카테고리 분류 (첫 노드에서 메타데이터 추출) + $bomItems = $opts['bom_result']['items'] ?? []; + foreach ($bomItems as $bom) { + $cat = $this->categorizeBomItem($bom); + if ($cat && ! isset($bomCategories[$cat])) { + $bomCategories[$cat] = $bom; + } + } + } + + return [ + 'dimensionGroups' => array_values($dimensionGroups), + 'totalNodeQty' => $totalNodeQty, + 'bomCategories' => $bomCategories, + ]; + } + + /** + * bending_info JSON 조립 (BendingInfoExtended 구조) + */ + private function assembleBendingInfo(array $productInfo, array $materials, array $agg): array + { + $guideRailBom = $agg['bomCategories']['guideRail'] ?? null; + $caseBom = $agg['bomCategories']['shutterBox_case'] ?? null; + $finCoverBom = $agg['bomCategories']['shutterBox_finCover'] ?? null; + $lbarBom = $agg['bomCategories']['detail_lbar'] ?? null; + $dimGroups = $agg['dimensionGroups']; + + // 가이드레일 baseSize 추출: BD-가이드레일-KSS01-SUS-120*70 → "120*70" + $baseSize = $guideRailBom ? $this->extractBaseSize($guideRailBom['item_code'] ?? '') : ''; + + return [ + 'productCode' => $productInfo['productCode'], + 'finishMaterial' => $productInfo['finishMaterial'], + 'common' => $this->buildCommon($productInfo['guideType'], $baseSize, $dimGroups), + 'detailParts' => $this->buildDetailParts($guideRailBom, $lbarBom, $materials), + 'guideRail' => $this->buildGuideRail($productInfo['guideType'], $baseSize, $materials, $dimGroups, $productInfo['productCode']), + 'bottomBar' => $this->buildBottomBar($materials, $dimGroups), + 'shutterBox' => $this->buildShutterBox($caseBom, $finCoverBom, $baseSize, $dimGroups), + 'smokeBarrier' => $this->buildSmokeBarrier($dimGroups), + ]; + } + + // ───────────────────────────────────────────────── + // common 섹션 + // ───────────────────────────────────────────────── + + private function buildCommon(string $guideType, string $baseSize, array $dimGroups): array + { + return [ + 'kind' => $guideType.' '.str_replace('*', 'X', $baseSize), + 'type' => $guideType.'('.$baseSize.')', + 'lengthQuantities' => $this->heightLengthQuantities($dimGroups), + ]; + } + + // ───────────────────────────────────────────────── + // detailParts 섹션 + // ───────────────────────────────────────────────── + + private function buildDetailParts(?array $guideRailBom, ?array $lbarBom, array $materials): array + { + $parts = []; + + if ($guideRailBom) { + $baseSize = $this->extractBaseSize($guideRailBom['item_code'] ?? ''); + $parts[] = [ + 'partName' => '가이드레일', + 'material' => $materials['guideRailFinish'], + 'barcyInfo' => $baseSize, + ]; + } + + if ($lbarBom) { + // BD-L-BAR-KSS01-17*60 → "17*60" + $lbarSize = $this->extractLastSize($lbarBom['item_code'] ?? ''); + $parts[] = [ + 'partName' => 'L-BAR', + 'material' => $this->extractFinishFromCode($lbarBom['item_code'] ?? '', $materials), + 'barcyInfo' => $lbarSize, + ]; + } + + return $parts; + } + + // ───────────────────────────────────────────────── + // guideRail 섹션 + // ───────────────────────────────────────────────── + + private function buildGuideRail(string $guideType, string $baseSize, array $materials, array $dimGroups, string $productCode): array + { + $heightData = $this->heightLengthData($dimGroups); + + // 가이드레일은 개구부 양쪽 2개이므로 수량 ×2 + foreach ($heightData as &$entry) { + $entry['quantity'] *= 2; + } + unset($entry); + + $baseDims = $this->getBaseDimensions($productCode); + + if ($guideType === '혼합형') { + // 혼합형: 벽면+측면 둘 다 + return [ + 'wall' => [ + 'baseSize' => $baseSize, + 'baseDimension' => $baseDims['wall'], + 'lengthData' => $heightData, + ], + 'side' => [ + 'baseSize' => $baseSize, + 'baseDimension' => $baseDims['side'], + 'lengthData' => $heightData, + ], + ]; + } + + if ($guideType === '측면형') { + return [ + 'wall' => null, + 'side' => [ + 'baseSize' => $baseSize, + 'baseDimension' => $baseDims['side'], + 'lengthData' => $heightData, + ], + ]; + } + + // 벽면형 (기본) + return [ + 'wall' => [ + 'baseSize' => $baseSize, + 'baseDimension' => $baseDims['wall'], + 'lengthData' => $heightData, + ], + 'side' => null, + ]; + } + + // ───────────────────────────────────────────────── + // bottomBar 섹션 + // ───────────────────────────────────────────────── + + private function buildBottomBar(array $materials, array $dimGroups): array + { + $length3000Qty = 0; + $length4000Qty = 0; + + // 레거시 Slat_updateCol49to51() 동일 로직: 오픈폭 범위별 3000/4000 조합 배분 + foreach ($dimGroups as $group) { + $width = $group['width']; + $qty = $group['qty']; + + [$qty3000, $qty4000] = $this->bottomBarDistribution($width); + $length3000Qty += $qty3000 * $qty; + $length4000Qty += $qty4000 * $qty; + } + + return [ + 'material' => $materials['bottomBarFinish'], + 'extraFinish' => $materials['bottomBarExtraFinish'], + 'length3000Qty' => $length3000Qty, + 'length4000Qty' => $length4000Qty, + ]; + } + + // ───────────────────────────────────────────────── + // shutterBox 섹션 + // ───────────────────────────────────────────────── + + private function buildShutterBox(?array $caseBom, ?array $finCoverBom, string $guideBaseSize, array $dimGroups): array + { + if (! $caseBom) { + return []; + } + + // BD-케이스-500*380 → "500*380" + $caseSize = str_replace('BD-케이스-', '', $caseBom['item_code'] ?? ''); + $caseParts = explode('*', $caseSize); + $caseWidth = (int) ($caseParts[0] ?? 0); + $caseHeight = (int) ($caseParts[1] ?? 0); + + // railWidth: 가이드레일 baseSize에서 첫 번째 숫자 (120*70 → 120) + $guideParts = explode('*', $guideBaseSize); + $railWidth = (int) ($guideParts[0] ?? 0); + + // 상부덮개 수량: ceil(openWidth / 1219) × nodeQty (레거시 Slat_updateCol45) + $coverQty = 0; + // 마구리 수량: nodeQty × 2 (레거시 Slat_updateCol46: col15 * 2) + $finCoverQty = 0; + foreach ($dimGroups as $group) { + $coverQty += (int) ceil($group['width'] / 1219) * $group['qty']; + $finCoverQty += $group['qty'] * 2; + } + + // lengthData: 표준 원자재 조합 배분 (레거시 Slat_updateCol39to43) + $widthData = $this->shutterBoxCombinedLengthData($dimGroups); + + return [ + [ + 'size' => $caseSize, + 'direction' => '양면', + 'railWidth' => $railWidth, + 'frontBottom' => $caseHeight, + 'coverQty' => $coverQty, + 'finCoverQty' => $finCoverQty, + 'lengthData' => $widthData, + ], + ]; + } + + /** + * dimGroups를 조합 배분하여 셔터박스 lengthData 생성 + */ + private function shutterBoxCombinedLengthData(array $dimGroups): array + { + $combined = []; + + foreach ($dimGroups as $group) { + $dist = $this->shutterBoxDistribution($group['width']); + foreach ($dist as $length => $count) { + if ($count > 0) { + $combined[$length] = ($combined[$length] ?? 0) + $count * $group['qty']; + } + } + } + + $result = []; + ksort($combined); + foreach ($combined as $length => $qty) { + $result[] = ['length' => $length, 'quantity' => $qty]; + } + + return $result; + } + + /** + * 오픈폭에 따른 셔터박스 표준 원자재 조합 배분 + * 레거시 5130/estimate/common/common_addrowJS.php Slat_updateCol39to43() 동일 + * + * @return array [1219 => qty, 2438 => qty, 3000 => qty, 3500 => qty, 4000 => qty, 4150 => qty] + */ + private function shutterBoxDistribution(int $openWidth): array + { + $d = [1219 => 0, 2438 => 0, 3000 => 0, 3500 => 0, 4000 => 0, 4150 => 0]; + + // col39 (1219mm) + if ($openWidth <= 1219) { + $d[1219] = 1; + } elseif ($openWidth > 4150 && $openWidth <= 4219) { + $d[1219] = 1; + } elseif ($openWidth > 4219 && $openWidth <= 4719) { + $d[1219] = 1; + } elseif ($openWidth > 4876 && $openWidth <= 5219) { + $d[1219] = 1; + } elseif ($openWidth > 5219 && $openWidth <= 5369) { + $d[1219] = 1; + } elseif ($openWidth > 9026 && $openWidth <= 9219) { + $d[1219] = 1; + } + + // col40 (2438mm) + if ($openWidth > 1219 && $openWidth <= 2438) { + $d[2438] = 1; + } elseif ($openWidth > 4719 && $openWidth <= 4876) { + $d[2438] = 2; + } elseif ($openWidth > 5369 && $openWidth <= 5938) { + $d[2438] = 1; + } elseif ($openWidth > 6000 && $openWidth <= 6438) { + $d[2438] = 1; + } elseif ($openWidth > 6500 && $openWidth <= 6588) { + $d[2438] = 1; + } elseif ($openWidth > 8300 && $openWidth <= 8376) { + $d[2438] = 2; + } elseif ($openWidth > 8376 && $openWidth <= 8438) { + $d[2438] = 1; + } elseif ($openWidth > 8438 && $openWidth <= 8876) { + $d[2438] = 2; + } elseif ($openWidth > 9000 && $openWidth <= 9026) { + $d[2438] = 2; + } elseif ($openWidth > 9219 && $openWidth <= 9438) { + $d[2438] = 1; + } elseif ($openWidth > 10150 && $openWidth <= 10738) { + $d[2438] = 1; + } + + // col41 (3000mm) + if ($openWidth > 2438 && $openWidth <= 3000) { + $d[3000] = 1; + } elseif ($openWidth > 4150 && $openWidth <= 4219) { + $d[3000] = 1; + } elseif ($openWidth > 5369 && $openWidth <= 5438) { + $d[3000] = 1; + } elseif ($openWidth > 5938 && $openWidth <= 6000) { + $d[3000] = 2; + } elseif ($openWidth > 6438 && $openWidth <= 6500) { + $d[3000] = 1; + } elseif ($openWidth > 7000 && $openWidth <= 7150) { + $d[3000] = 1; + } elseif ($openWidth > 8376 && $openWidth <= 8438) { + $d[3000] = 2; + } elseif ($openWidth > 8876 && $openWidth <= 9000) { + $d[3000] = 3; + } elseif ($openWidth > 9438 && $openWidth <= 10150) { + $d[3000] = 2; + } elseif ($openWidth > 10738 && $openWidth <= 11000) { + $d[3000] = 1; + } + + // col42 (3500mm) + if ($openWidth > 3000 && $openWidth <= 3500) { + $d[3500] = 1; + } elseif ($openWidth > 4219 && $openWidth <= 4719) { + $d[3500] = 1; + } elseif ($openWidth > 5438 && $openWidth <= 5938) { + $d[3500] = 1; + } elseif ($openWidth > 6438 && $openWidth <= 6500) { + $d[3500] = 1; + } elseif ($openWidth > 6588 && $openWidth <= 7000) { + $d[3500] = 2; + } elseif ($openWidth > 7150 && $openWidth <= 7650) { + $d[3500] = 1; + } elseif ($openWidth > 8300 && $openWidth <= 8376) { + $d[3500] = 1; + } elseif ($openWidth > 9219 && $openWidth <= 9438) { + $d[3500] = 2; + } elseif ($openWidth > 9438 && $openWidth <= 9500) { + $d[3500] = 1; + } + + // col43 (4000mm) + if ($openWidth > 3500 && $openWidth <= 4000) { + $d[4000] = 1; + } elseif ($openWidth > 4876 && $openWidth <= 5219) { + $d[4000] = 1; + } elseif ($openWidth > 6000 && $openWidth <= 6438) { + $d[4000] = 1; + } elseif ($openWidth > 7150 && $openWidth <= 7500) { + $d[4000] = 1; + } elseif ($openWidth > 7650 && $openWidth <= 8000) { + $d[4000] = 2; + } elseif ($openWidth > 8000 && $openWidth <= 8150) { + $d[4000] = 1; + } elseif ($openWidth > 8438 && $openWidth <= 8876) { + $d[4000] = 1; + } elseif ($openWidth > 9026 && $openWidth <= 9219) { + $d[4000] = 2; + } elseif ($openWidth > 9500 && $openWidth <= 10000) { + $d[4000] = 1; + } elseif ($openWidth > 10150 && $openWidth <= 10438) { + $d[4000] = 2; + } elseif ($openWidth > 10738 && $openWidth <= 11000) { + $d[4000] = 2; + } + + // col44 (4150mm) + if ($openWidth > 4000 && $openWidth <= 4150) { + $d[4150] = 1; + } elseif ($openWidth > 5219 && $openWidth <= 5369) { + $d[4150] = 1; + } elseif ($openWidth > 6500 && $openWidth <= 6588) { + $d[4150] = 1; + } elseif ($openWidth > 7000 && $openWidth <= 7150) { + $d[4150] = 1; + } elseif ($openWidth > 7500 && $openWidth <= 7650) { + $d[4150] = 1; + } elseif ($openWidth > 8000 && $openWidth <= 8150) { + $d[4150] = 1; + } elseif ($openWidth > 8150 && $openWidth <= 8300) { + $d[4150] = 2; + } elseif ($openWidth > 9000 && $openWidth <= 9026) { + $d[4150] = 1; + } elseif ($openWidth > 10000 && $openWidth <= 10150) { + $d[4150] = 1; + } elseif ($openWidth > 10438 && $openWidth <= 10738) { + $d[4150] = 2; + } + + return $d; + } + + // ───────────────────────────────────────────────── + // smokeBarrier 섹션 + // ───────────────────────────────────────────────── + + /** + * 연기차단재 섹션 (레거시 Slat_updateCol24~29, Slat_updateCol48 동일) + * + * W50 (레일용): col24 = open_height + 250 → 범위별 표준 길이, qty = 2 × 셔터수 + * W80 (케이스용): col38 = open_width + 240, col48 = floor(col38*2/3000 + 1) × 셔터수 + */ + private function buildSmokeBarrier(array $dimGroups): array + { + $w50Combined = []; + $w80Qty = 0; + + foreach ($dimGroups as $group) { + $height = $group['height']; + $width = $group['width']; + $nodeQty = $group['qty']; + + // W50: col24 = open_height + 250 → 범위별 표준 길이 + $col24 = $height + 250; + $w50Length = null; + + if ($col24 <= 2438) { + $w50Length = 2438; + } elseif ($col24 <= 3000) { + $w50Length = 3000; + } elseif ($col24 <= 3500) { + $w50Length = 3500; + } elseif ($col24 <= 4000) { + $w50Length = 4000; + } elseif ($col24 <= 4300) { + $w50Length = 4300; + } + // > 4300: W50 없음 + + if ($w50Length !== null) { + $w50Combined[$w50Length] = ($w50Combined[$w50Length] ?? 0) + 2 * $nodeQty; + } + + // W80: col38 = open_width + 240, col48 = floor(col38*2/3000 + 1) × 셔터수 + $col38 = $width + 240; + $w80PerNode = (int) floor(($col38 * 2 / 3000) + 1); + $w80Qty += $w80PerNode * $nodeQty; + } + + // W50 결과 조립 + $w50Result = []; + ksort($w50Combined); + foreach ($w50Combined as $length => $qty) { + if ($qty > 0) { + $w50Result[] = ['length' => $length, 'quantity' => $qty]; + } + } + + return [ + 'w50' => $w50Result, + 'w80Qty' => $w80Qty, + ]; + } + + // ───────────────────────────────────────────────── + // 재질 매핑 (프론트 getMaterialMapping 동일 로직) + // ───────────────────────────────────────────────── + + /** + * @see react/src/components/production/WorkOrders/documents/bending/utils.ts:77 + */ + private function getMaterialMapping(string $productCode, string $finishMaterial): array + { + // Group 1: SUS 전용 (KQTS01, KSS01, KSS02) + if (in_array($productCode, ['KQTS01', 'KSS01', 'KSS02'])) { + return [ + 'guideRailFinish' => 'SUS 1.2T', + 'bodyMaterial' => 'EGI 1.55T', + 'guideRailExtraFinish' => '', + 'bottomBarFinish' => 'SUS 1.5T', + 'bottomBarExtraFinish' => '없음', + ]; + } + + // Group 2: KTE01 (마감유형 분기) + if ($productCode === 'KTE01') { + $isSUS = $finishMaterial === 'SUS마감'; + + return [ + 'guideRailFinish' => 'EGI 1.55T', + 'bodyMaterial' => 'EGI 1.55T', + 'guideRailExtraFinish' => $isSUS ? 'SUS 1.2T' : '', + 'bottomBarFinish' => 'EGI 1.55T', + 'bottomBarExtraFinish' => $isSUS ? 'SUS 1.2T' : '없음', + ]; + } + + // Group 3: 기타 (KSE01, KWE01 등) + $isSUS = str_contains($finishMaterial, 'SUS'); + + return [ + 'guideRailFinish' => 'EGI 1.55T', + 'bodyMaterial' => 'EGI 1.55T', + 'guideRailExtraFinish' => $isSUS ? 'SUS 1.2T' : '', + 'bottomBarFinish' => 'EGI 1.55T', + 'bottomBarExtraFinish' => $isSUS ? 'SUS 1.2T' : '없음', + ]; + } + + // ───────────────────────────────────────────────── + // 유틸리티 메서드 + // ───────────────────────────────────────────────── + + /** + * 가이드레일 item_code에서 baseSize 추출 + * BD-가이드레일-KSS01-SUS-120*70 → "120*70" + */ + private function extractBaseSize(string $itemCode): string + { + $parts = explode('-', $itemCode); + $last = end($parts); + + // 마지막 세그먼트가 "숫자*숫자" 패턴인지 확인 + if (preg_match('/^\d+\*\d+$/', $last)) { + return $last; + } + + return ''; + } + + /** + * item_code 마지막 사이즈 세그먼트 추출 + * BD-L-BAR-KSS01-17*60 → "17*60" + */ + private function extractLastSize(string $itemCode): string + { + return $this->extractBaseSize($itemCode); + } + + /** + * item_code에서 마감재 정보 추출 (L-BAR 등) + */ + private function extractFinishFromCode(string $itemCode, array $materials): string + { + if (str_contains($itemCode, '-SUS-')) { + return 'SUS'; + } + if (str_contains($itemCode, '-EGI-')) { + return 'EGI'; + } + + // BOM 코드에 마감재 표시 없으면 재질 매핑에서 추출 + return str_contains($materials['guideRailFinish'], 'SUS') ? 'SUS' : 'EGI'; + } + + /** + * height 기준 lengthQuantities (common 섹션용) + */ + private function heightLengthQuantities(array $dimGroups): array + { + $result = []; + $grouped = []; + + foreach ($dimGroups as $group) { + $h = $group['height']; + $grouped[$h] = ($grouped[$h] ?? 0) + $group['qty']; + } + + foreach ($grouped as $length => $qty) { + $result[] = ['length' => $length, 'quantity' => $qty]; + } + + return $result; + } + + /** + * height 기준 lengthData (guideRail, smokeBarrier 섹션용) + * 오픈높이를 표준 원자재 길이로 버킷팅 + */ + private function heightLengthData(array $dimGroups): array + { + return $this->bucketedLengthData($dimGroups, 'height', self::GUIDE_RAIL_LENGTHS); + } + + /** + * width 기준 lengthData (shutterBox 섹션용) + * 오픈폭을 표준 원자재 길이로 버킷팅 + */ + private function widthLengthData(array $dimGroups): array + { + return $this->bucketedLengthData($dimGroups, 'width', self::SHUTTER_BOX_LENGTHS); + } + + /** + * 치수를 표준 원자재 길이로 버킷팅하여 그룹핑 + */ + private function bucketedLengthData(array $dimGroups, string $dimKey, array $buckets): array + { + $result = []; + $grouped = []; + + foreach ($dimGroups as $group) { + $raw = (int) $group[$dimKey]; + $bucketed = $this->bucketToStandardLength($raw, $buckets); + $grouped[$bucketed] = ($grouped[$bucketed] ?? 0) + $group['qty']; + } + + foreach ($grouped as $length => $qty) { + $result[] = ['length' => $length, 'quantity' => $qty]; + } + + return $result; + } + + /** + * 오픈폭에 따른 하단마감재(하장바) 3000/4000 수량 배분 + * 레거시 5130/estimate/common/common_addrowJS.php Slat_updateCol49to51() 동일 + * + * @return array{0: int, 1: int} [3000mm수량, 4000mm수량] + */ + private function bottomBarDistribution(int $openWidth): array + { + // [3000mm 기본수, 4000mm 기본수] + if ($openWidth <= 3000) { + return [1, 0]; + } + if ($openWidth <= 4000) { + return [0, 1]; + } + if ($openWidth <= 6000) { + return [2, 0]; + } + if ($openWidth <= 7000) { + return [1, 1]; + } + if ($openWidth <= 8000) { + return [0, 2]; + } + if ($openWidth <= 9000) { + return [3, 0]; + } + if ($openWidth <= 10000) { + return [2, 1]; + } + if ($openWidth <= 11000) { + return [1, 2]; + } + if ($openWidth <= 12000) { + return [0, 3]; + } + + // 12000 초과: 4000mm 기준 올림 + return [0, (int) ceil($openWidth / 4000)]; + } + + /** + * 제품코드별 실제 하부BASE 물리 치수 반환 + * 대형 프로파일 (KQTS01, KTE01): 벽면 BASE = 135*130 + * 소형 프로파일 (KSS01, KSS02 등): 벽면 BASE = 135*80 + * 측면형 BASE는 항상 135*130 + */ + private function getBaseDimensions(string $productCode): array + { + // 레거시: 벽면형 BASE = 135*80 (line 519), 측면형 BASE = 135*130 (line 626) + // 제품코드와 무관하게 고정 + return [ + 'wall' => '135*80', + 'side' => '135*130', + ]; + } + + /** + * 치수를 표준 원자재 길이로 변환 (올림 버킷팅) + * dimension 이상인 최소 표준 길이 반환, 초과 시 원본 반환 + */ + private function bucketToStandardLength(int $dimension, array $buckets): int + { + foreach ($buckets as $bucket) { + if ($bucket >= $dimension) { + return $bucket; + } + } + + return $dimension; + } +}