From f48ac54fe4a6ade03e00a5b3a06deedd53c0f3c1 Mon Sep 17 00:00:00 2001 From: hskwon Date: Thu, 4 Dec 2025 15:24:10 +0900 Subject: [PATCH] =?UTF-8?q?[fix]=20=EB=AF=B8=EC=A0=95=EC=9D=98=20=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EC=82=AC=EC=9A=A9=20=EC=88=98=EC=8B=9D=2012?= =?UTF-8?q?=EA=B0=9C=20=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - QuoteFormulaSeeder: is_active 플래그 추가 (기본 true) - 미정의 변수 사용 수식 비활성화: - GR_QTY_WALL, GR_QTY_SIDE, GR_QTY_MIXED (BASE_QTY 미정의) - EDGE_QTY (EDGE_WING_SIZE 미정의) - PRICE_GR, PRICE_CASE, PRICE_MOTOR, PRICE_CTRL, PRICE_EDGE (QTY, UNIT_PRICE 미정의) - PRICE_INSP (INSPECTION_FEE 미정의) - PRICE_AREA_SCREEN, PRICE_AREA_STEEL (AREA, UNIT_PRICE_PER_M2 미정의) --- .../seeders/QuoteFormulaCategorySeeder.php | 108 ++++ database/seeders/QuoteFormulaSeeder.php | 474 ++++++++++++++++++ 2 files changed, 582 insertions(+) create mode 100644 database/seeders/QuoteFormulaCategorySeeder.php create mode 100644 database/seeders/QuoteFormulaSeeder.php diff --git a/database/seeders/QuoteFormulaCategorySeeder.php b/database/seeders/QuoteFormulaCategorySeeder.php new file mode 100644 index 0000000..0c942fb --- /dev/null +++ b/database/seeders/QuoteFormulaCategorySeeder.php @@ -0,0 +1,108 @@ + 'OPEN_SIZE', + 'name' => '오픈사이즈', + 'description' => '제품의 설치 오픈 사이즈 (W0, H0)', + 'sort_order' => 1, + ], + [ + 'code' => 'MAKE_SIZE', + 'name' => '제작사이즈', + 'description' => '실제 제작 사이즈 (W1, H1)', + 'sort_order' => 2, + ], + [ + 'code' => 'AREA', + 'name' => '면적', + 'description' => '제품 면적 계산 (㎡)', + 'sort_order' => 3, + ], + [ + 'code' => 'WEIGHT', + 'name' => '중량', + 'description' => '제품 중량 계산 (kg)', + 'sort_order' => 4, + ], + [ + 'code' => 'GUIDE_RAIL', + 'name' => '가이드레일', + 'description' => '가이드레일 자동 선택 및 수량 계산', + 'sort_order' => 5, + ], + [ + 'code' => 'CASE', + 'name' => '케이스', + 'description' => '케이스(셔터박스) 자동 선택', + 'sort_order' => 6, + ], + [ + 'code' => 'MOTOR', + 'name' => '모터', + 'description' => '개폐전동기 자동 선택', + 'sort_order' => 7, + ], + [ + 'code' => 'CONTROLLER', + 'name' => '제어기', + 'description' => '연동제어기 자동 선택', + 'sort_order' => 8, + ], + [ + 'code' => 'EDGE_WING', + 'name' => '마구리', + 'description' => '마구리 날개 수량 계산', + 'sort_order' => 9, + ], + [ + 'code' => 'INSPECTION', + 'name' => '검사', + 'description' => '제품 검사비', + 'sort_order' => 10, + ], + [ + 'code' => 'PRICE_FORMULA', + 'name' => '단가수식', + 'description' => '품목별 단가 계산 수식', + 'sort_order' => 11, + ], + ]; + + foreach ($categories as $category) { + DB::table('quote_formula_categories')->updateOrInsert( + [ + 'tenant_id' => $tenantId, + 'code' => $category['code'], + ], + [ + 'tenant_id' => $tenantId, + 'code' => $category['code'], + 'name' => $category['name'], + 'description' => $category['description'], + 'sort_order' => $category['sort_order'], + 'is_active' => true, + 'created_at' => now(), + 'updated_at' => now(), + ] + ); + } + + $this->command->info('QuoteFormulaCategorySeeder: 11개 카테고리 생성 완료'); + } +} diff --git a/database/seeders/QuoteFormulaSeeder.php b/database/seeders/QuoteFormulaSeeder.php new file mode 100644 index 0000000..7e85e89 --- /dev/null +++ b/database/seeders/QuoteFormulaSeeder.php @@ -0,0 +1,474 @@ +where('tenant_id', $tenantId) + ->pluck('id', 'code') + ->toArray(); + + if (empty($categoryMap)) { + $this->command->error('QuoteFormulaSeeder: 카테고리가 없습니다. QuoteFormulaCategorySeeder를 먼저 실행하세요.'); + + return; + } + + $formulas = [ + // ============================== + // 1. 오픈사이즈 (OPEN_SIZE) - 2개 + // ============================== + [ + 'category_code' => 'OPEN_SIZE', + 'variable' => 'W0', + 'name' => '오픈사이즈 W0 (가로)', + 'type' => 'input', + 'formula' => null, + 'description' => '자동 견적 산출 섹션의 오픈사이즈 W0 입력값', + 'metadata' => ['unit' => 'mm'], + 'sort_order' => 1, + ], + [ + 'category_code' => 'OPEN_SIZE', + 'variable' => 'H0', + 'name' => '오픈사이즈 H0 (세로)', + 'type' => 'input', + 'formula' => null, + 'description' => '자동 견적 산출 섹션의 오픈사이즈 H0 입력값', + 'metadata' => ['unit' => 'mm'], + 'sort_order' => 2, + ], + + // ============================== + // 2. 제작사이즈 (MAKE_SIZE) - 4개 + // ============================== + [ + 'category_code' => 'MAKE_SIZE', + 'variable' => 'W1_SCREEN', + 'name' => '제작사이즈 W1 (스크린)', + 'type' => 'calculation', + 'formula' => 'W0 + 140', + 'description' => '스크린 제작 가로 = 오픈 가로 + 140', + 'metadata' => ['unit' => 'mm', 'product_type' => 'screen'], + 'sort_order' => 1, + ], + [ + 'category_code' => 'MAKE_SIZE', + 'variable' => 'H1_SCREEN', + 'name' => '제작사이즈 H1 (스크린)', + 'type' => 'calculation', + 'formula' => 'H0 + 350', + 'description' => '스크린 제작 세로 = 오픈 세로 + 350', + 'metadata' => ['unit' => 'mm', 'product_type' => 'screen'], + 'sort_order' => 2, + ], + [ + 'category_code' => 'MAKE_SIZE', + 'variable' => 'W1_STEEL', + 'name' => '제작사이즈 W1 (철재)', + 'type' => 'calculation', + 'formula' => 'W0 + 110', + 'description' => '철재 제작 가로 = 오픈 가로 + 110', + 'metadata' => ['unit' => 'mm', 'product_type' => 'steel'], + 'sort_order' => 3, + ], + [ + 'category_code' => 'MAKE_SIZE', + 'variable' => 'H1_STEEL', + 'name' => '제작사이즈 H1 (철재)', + 'type' => 'calculation', + 'formula' => 'H0 + 350', + 'description' => '철재 제작 세로 = 오픈 세로 + 350', + 'metadata' => ['unit' => 'mm', 'product_type' => 'steel'], + 'sort_order' => 4, + ], + + // ============================== + // 3. 면적 (AREA) - 1개 + // ============================== + [ + 'category_code' => 'AREA', + 'variable' => 'M', + 'name' => '면적 계산', + 'type' => 'calculation', + 'formula' => 'W1 * H1 / 1000000', + 'description' => '면적(㎡) = 제작가로(W1) × 제작세로(H1) ÷ 1,000,000', + 'metadata' => ['unit' => '㎡'], + 'sort_order' => 1, + ], + + // ============================== + // 4. 중량 (WEIGHT) - 2개 + // ============================== + [ + 'category_code' => 'WEIGHT', + 'variable' => 'K_SCREEN', + 'name' => '중량 계산 (스크린)', + 'type' => 'calculation', + 'formula' => 'M * 2 + W0 / 1000 * 14.17', + 'description' => '스크린 중량(kg) = 면적 × 2 + (오픈가로 ÷ 1000 × 14.17)', + 'metadata' => ['unit' => 'kg', 'product_type' => 'screen'], + 'sort_order' => 1, + ], + [ + 'category_code' => 'WEIGHT', + 'variable' => 'K_STEEL', + 'name' => '중량 계산 (철재)', + 'type' => 'calculation', + 'formula' => 'M * 25', + 'description' => '철재 중량(kg) = 면적 × 25', + 'metadata' => ['unit' => 'kg', 'product_type' => 'steel'], + 'sort_order' => 2, + ], + + // ============================== + // 5. 가이드레일 (GUIDE_RAIL) - 5개 + // ============================== + [ + 'category_code' => 'GUIDE_RAIL', + 'variable' => 'G', + 'name' => '가이드레일 제작길이', + 'type' => 'calculation', + 'formula' => 'H0 + 250', + 'description' => '가이드레일 제작길이(G) = 오픈세로(H0) + 250', + 'metadata' => ['unit' => 'mm'], + 'sort_order' => 1, + ], + [ + 'category_code' => 'GUIDE_RAIL', + 'variable' => 'GR_AUTO_SELECT', + 'name' => '가이드레일 자재 자동 선택', + 'type' => 'range', + 'formula' => null, + 'description' => '가이드레일 길이 및 수량 자동 산출 (기본 2개)', + 'metadata' => ['unit' => 'EA', 'input_variable' => 'G'], + 'sort_order' => 2, + 'ranges' => [ + ['min' => 1219, 'max' => 2438, 'result' => '2438 2개', 'quantity' => 2, 'description' => '1219 < G ≤ 2438'], + ['min' => 2438, 'max' => 3000, 'result' => '3000 2개', 'quantity' => 2, 'description' => '2438 < G ≤ 3000'], + ], + ], + [ + 'category_code' => 'GUIDE_RAIL', + 'variable' => 'GR_QTY_WALL', + 'name' => '가이드레일 수량 (벽면형)', + 'type' => 'calculation', + 'formula' => 'BASE_QTY', + 'description' => '벽면형: 기본 수량 그대로 (좌우 2개)', + 'metadata' => ['unit' => 'EA', 'install_type' => 'wall'], + 'sort_order' => 3, + 'is_active' => false, // 미정의 변수(BASE_QTY) 사용 + ], + [ + 'category_code' => 'GUIDE_RAIL', + 'variable' => 'GR_QTY_SIDE', + 'name' => '가이드레일 수량 (측면형)', + 'type' => 'calculation', + 'formula' => 'BASE_QTY / 2', + 'description' => '측면형: 기본 수량의 절반 (한쪽만)', + 'metadata' => ['unit' => 'EA', 'install_type' => 'side'], + 'sort_order' => 4, + 'is_active' => false, // 미정의 변수(BASE_QTY) 사용 + ], + [ + 'category_code' => 'GUIDE_RAIL', + 'variable' => 'GR_QTY_MIXED', + 'name' => '가이드레일 수량 (혼합형)', + 'type' => 'calculation', + 'formula' => 'BASE_QTY * 1.5', + 'description' => '혼합형: 기본 수량 × 1.5', + 'metadata' => ['unit' => 'EA', 'install_type' => 'mixed'], + 'sort_order' => 5, + 'is_active' => false, // 미정의 변수(BASE_QTY) 사용 + ], + + // ============================== + // 6. 케이스 (CASE) - 3개 + // ============================== + [ + 'category_code' => 'CASE', + 'variable' => 'S_SCREEN', + 'name' => '케이스 사이즈 (스크린)', + 'type' => 'calculation', + 'formula' => 'W0 + 220', + 'description' => '스크린 케이스 사이즈(S) = 오픈가로(W0) + 220', + 'metadata' => ['unit' => 'mm', 'product_type' => 'screen'], + 'sort_order' => 1, + ], + [ + 'category_code' => 'CASE', + 'variable' => 'S_STEEL', + 'name' => '케이스 사이즈 (철재)', + 'type' => 'calculation', + 'formula' => 'W0 + 240', + 'description' => '철재 케이스 사이즈(S) = 오픈가로(W0) + 240', + 'metadata' => ['unit' => 'mm', 'product_type' => 'steel'], + 'sort_order' => 2, + ], + [ + 'category_code' => 'CASE', + 'variable' => 'CASE_AUTO_SELECT', + 'name' => '케이스 자재 자동 선택', + 'type' => 'range', + 'formula' => null, + 'description' => '케이스 자재 길이 및 수량 자동 산출', + 'metadata' => ['unit' => 'EA', 'input_variable' => 'S'], + 'sort_order' => 3, + 'ranges' => [ + ['min' => 0, 'max' => 1219, 'result' => '1219 1개', 'item_code' => 'PT-CASE-1219', 'quantity' => 1, 'description' => '0 < S ≤ 1219'], + ['min' => 1219, 'max' => 2438, 'result' => '2438 1개', 'item_code' => 'PT-CASE-2438', 'quantity' => 1, 'description' => '1219 < S ≤ 2438'], + ['min' => 2438, 'max' => 3000, 'result' => '3000 1개', 'item_code' => 'PT-CASE-3000', 'quantity' => 1, 'description' => '2438 < S ≤ 3000'], + ], + ], + + // ============================== + // 7. 모터 (MOTOR) - 1개 + // ============================== + [ + 'category_code' => 'MOTOR', + 'variable' => 'MOTOR_AUTO_SELECT', + 'name' => '모터 자동 선택', + 'type' => 'range', + 'formula' => null, + 'description' => '모터 중량 기반 자동 선택 (전원 유형 반영)', + 'metadata' => ['unit' => 'EA', 'input_variable' => 'K'], + 'sort_order' => 1, + 'ranges' => [ + ['min' => 0, 'max' => 150, 'result' => '150k', 'quantity' => 1, 'description' => '0 < K ≤ 150kg'], + ['min' => 150, 'max' => 300, 'result' => '300k', 'quantity' => 1, 'description' => '150 < K ≤ 300kg'], + ['min' => 300, 'max' => 400, 'result' => '400k', 'quantity' => 1, 'description' => '300 < K ≤ 400kg'], + ], + ], + + // ============================== + // 8. 제어기 (CONTROLLER) - 1개 + // ============================== + [ + 'category_code' => 'CONTROLLER', + 'variable' => 'CTRL_AUTO_SELECT', + 'name' => '제어기 자동 선택', + 'type' => 'mapping', + 'formula' => null, + 'description' => '연동제어기 설치 유형(매립/노출)에 따라 자동 선택', + 'metadata' => ['unit' => 'EA', 'input_variable' => 'CONTROLLER_TYPE'], + 'sort_order' => 1, + ], + + // ============================== + // 9. 마구리 (EDGE_WING) - 1개 + // ============================== + [ + 'category_code' => 'EDGE_WING', + 'variable' => 'EDGE_QTY', + 'name' => '마구리 날개 수량 계산', + 'type' => 'calculation', + 'formula' => 'EDGE_WING_SIZE / 50', + 'description' => '마구리 날개치수 ÷ 50 = 수량', + 'metadata' => ['unit' => 'EA'], + 'sort_order' => 1, + 'is_active' => false, // 미정의 변수(EDGE_WING_SIZE) 사용 + ], + + // ============================== + // 10. 검사 (INSPECTION) - 1개 + // ============================== + [ + 'category_code' => 'INSPECTION', + 'variable' => 'INSP_FEE', + 'name' => '검사비', + 'type' => 'calculation', + 'formula' => '1', + 'description' => '검사비 고정 1EA (단가는 검사비 설정값 적용)', + 'metadata' => ['unit' => 'EA'], + 'sort_order' => 1, + ], + + // ============================== + // 11. 단가수식 (PRICE_FORMULA) - 8개 + // ============================== + [ + 'category_code' => 'PRICE_FORMULA', + 'variable' => 'PRICE_GR', + 'name' => '가이드레일 단가 수식', + 'type' => 'calculation', + 'formula' => 'QTY * UNIT_PRICE', + 'description' => '가이드레일 총액 = 수량 × 단가', + 'metadata' => ['unit' => '원', 'target' => 'guide_rail'], + 'sort_order' => 1, + 'is_active' => false, // 미정의 변수(QTY, UNIT_PRICE) 사용 + ], + [ + 'category_code' => 'PRICE_FORMULA', + 'variable' => 'PRICE_CASE', + 'name' => '케이스 단가 수식', + 'type' => 'calculation', + 'formula' => 'QTY * UNIT_PRICE', + 'description' => '케이스 총액 = 수량 × 단가', + 'metadata' => ['unit' => '원', 'target' => 'case'], + 'sort_order' => 2, + 'is_active' => false, // 미정의 변수(QTY, UNIT_PRICE) 사용 + ], + [ + 'category_code' => 'PRICE_FORMULA', + 'variable' => 'PRICE_MOTOR', + 'name' => '모터 단가 수식', + 'type' => 'calculation', + 'formula' => 'QTY * UNIT_PRICE', + 'description' => '모터 총액 = 수량 × 단가', + 'metadata' => ['unit' => '원', 'target' => 'motor'], + 'sort_order' => 3, + 'is_active' => false, // 미정의 변수(QTY, UNIT_PRICE) 사용 + ], + [ + 'category_code' => 'PRICE_FORMULA', + 'variable' => 'PRICE_CTRL', + 'name' => '제어기 단가 수식', + 'type' => 'calculation', + 'formula' => 'QTY * UNIT_PRICE', + 'description' => '제어기 총액 = 수량 × 단가', + 'metadata' => ['unit' => '원', 'target' => 'controller'], + 'sort_order' => 4, + 'is_active' => false, // 미정의 변수(QTY, UNIT_PRICE) 사용 + ], + [ + 'category_code' => 'PRICE_FORMULA', + 'variable' => 'PRICE_EDGE', + 'name' => '마구리 날개 단가 수식', + 'type' => 'calculation', + 'formula' => 'QTY * UNIT_PRICE', + 'description' => '마구리 날개 총액 = 수량 × 단가', + 'metadata' => ['unit' => '원', 'target' => 'edge_wing'], + 'sort_order' => 5, + 'is_active' => false, // 미정의 변수(QTY, UNIT_PRICE) 사용 + ], + [ + 'category_code' => 'PRICE_FORMULA', + 'variable' => 'PRICE_INSP', + 'name' => '검사비 단가 수식', + 'type' => 'calculation', + 'formula' => 'INSPECTION_FEE', + 'description' => '검사비 = 입력한 검사비 설정값', + 'metadata' => ['unit' => '원', 'target' => 'inspection'], + 'sort_order' => 6, + 'is_active' => false, // 미정의 변수(INSPECTION_FEE) 사용 + ], + [ + 'category_code' => 'PRICE_FORMULA', + 'variable' => 'PRICE_AREA_SCREEN', + 'name' => '면적 기반 단가 (스크린)', + 'type' => 'calculation', + 'formula' => 'AREA * UNIT_PRICE_PER_M2', + 'description' => '스크린 면적 기반 총액 = 면적(㎡) × ㎡당 단가', + 'metadata' => ['unit' => '원', 'target' => 'area', 'product_type' => 'screen'], + 'sort_order' => 7, + 'is_active' => false, // 미정의 변수(AREA, UNIT_PRICE_PER_M2) 사용 + ], + [ + 'category_code' => 'PRICE_FORMULA', + 'variable' => 'PRICE_AREA_STEEL', + 'name' => '면적 기반 단가 (철재)', + 'type' => 'calculation', + 'formula' => 'AREA * UNIT_PRICE_PER_M2', + 'description' => '철재 면적 기반 총액 = 면적(㎡) × ㎡당 단가', + 'metadata' => ['unit' => '원', 'target' => 'area', 'product_type' => 'steel'], + 'sort_order' => 8, + 'is_active' => false, // 미정의 변수(AREA, UNIT_PRICE_PER_M2) 사용 + ], + ]; + + $formulaCount = 0; + $rangeCount = 0; + + foreach ($formulas as $formula) { + $categoryId = $categoryMap[$formula['category_code']] ?? null; + + if (! $categoryId) { + $this->command->warn("카테고리 '{$formula['category_code']}'를 찾을 수 없습니다: {$formula['variable']}"); + + continue; + } + + // 수식 생성/업데이트 + DB::table('quote_formulas')->updateOrInsert( + [ + 'tenant_id' => $tenantId, + 'variable' => $formula['variable'], + ], + [ + 'tenant_id' => $tenantId, + 'category_id' => $categoryId, + 'variable' => $formula['variable'], + 'name' => $formula['name'], + 'type' => $formula['type'], + 'formula' => $formula['formula'], + 'output_type' => 'variable', + 'description' => $formula['description'], + 'sort_order' => $formula['sort_order'] ?? 0, + 'is_active' => $formula['is_active'] ?? true, + 'created_at' => now(), + 'updated_at' => now(), + ] + ); + $formulaCount++; + + // Range 데이터가 있으면 처리 + if (! empty($formula['ranges'])) { + // 먼저 수식 ID 조회 + $formulaRecord = DB::table('quote_formulas') + ->where('tenant_id', $tenantId) + ->where('variable', $formula['variable']) + ->first(); + + if ($formulaRecord) { + // 조건 변수 추출 (metadata에서) + $conditionVariable = $formula['metadata']['input_variable'] ?? $formula['variable']; + + foreach ($formula['ranges'] as $rangeOrder => $range) { + // result_value에 추가 정보를 JSON으로 포함 + $resultData = [ + 'value' => $range['result'], + 'item_code' => $range['item_code'] ?? null, + 'quantity' => $range['quantity'] ?? 1, + 'note' => $range['description'] ?? null, + ]; + + DB::table('quote_formula_ranges')->updateOrInsert( + [ + 'formula_id' => $formulaRecord->id, + 'min_value' => $range['min'], + 'max_value' => $range['max'], + ], + [ + 'formula_id' => $formulaRecord->id, + 'min_value' => $range['min'], + 'max_value' => $range['max'], + 'condition_variable' => $conditionVariable, + 'result_value' => json_encode($resultData), + 'result_type' => 'fixed', + 'sort_order' => $rangeOrder + 1, + 'created_at' => now(), + 'updated_at' => now(), + ] + ); + $rangeCount++; + } + } + } + } + + $this->command->info("QuoteFormulaSeeder: {$formulaCount}개 수식, {$rangeCount}개 범위 데이터 생성 완료"); + } +}