formulaEvaluator->reset(); // 입력값 검증 및 변수 설정 $validatedInputs = $this->validateInputs($inputs, $category); $this->formulaEvaluator->setVariables($validatedInputs); // 카테고리별 산출 로직 실행 $result = match ($category) { Quote::CATEGORY_SCREEN => $this->calculateScreen($validatedInputs), Quote::CATEGORY_STEEL => $this->calculateSteel($validatedInputs), default => $this->calculateScreen($validatedInputs), }; return [ 'inputs' => $validatedInputs, 'outputs' => $result['outputs'], 'items' => $result['items'], 'costs' => $result['costs'], 'errors' => $this->formulaEvaluator->getErrors(), ]; } /** * 견적 미리보기 (저장 없이 계산만) */ public function preview(array $inputs, ?string $productCategory = null): array { return $this->calculate($inputs, $productCategory); } /** * 견적 품목 재계산 (기존 견적 기준) */ public function recalculate(Quote $quote): array { $inputs = $quote->calculation_inputs ?? []; $category = $quote->product_category; return $this->calculate($inputs, $category); } /** * 입력값 검증 및 기본값 설정 */ private function validateInputs(array $inputs, string $category): array { // 공통 입력값 $validated = [ 'W0' => (float) ($inputs['W0'] ?? $inputs['open_size_width'] ?? 0), 'H0' => (float) ($inputs['H0'] ?? $inputs['open_size_height'] ?? 0), 'QTY' => (int) ($inputs['QTY'] ?? $inputs['quantity'] ?? 1), ]; // 카테고리별 추가 입력값 if ($category === Quote::CATEGORY_SCREEN) { $validated = array_merge($validated, [ 'INSTALL_TYPE' => $inputs['INSTALL_TYPE'] ?? 'wall', // wall, ceiling, floor 'MOTOR_TYPE' => $inputs['MOTOR_TYPE'] ?? 'standard', // standard, heavy 'CONTROL_TYPE' => $inputs['CONTROL_TYPE'] ?? 'switch', // switch, remote, smart 'CHAIN_SIDE' => $inputs['CHAIN_SIDE'] ?? 'left', // left, right ]); } elseif ($category === Quote::CATEGORY_STEEL) { $validated = array_merge($validated, [ 'MATERIAL' => $inputs['MATERIAL'] ?? 'ss304', // ss304, ss316, galvanized 'THICKNESS' => (float) ($inputs['THICKNESS'] ?? 1.5), 'FINISH' => $inputs['FINISH'] ?? 'hairline', // hairline, mirror, matte 'WELDING' => $inputs['WELDING'] ?? 'tig', // tig, mig, spot ]); } return $validated; } /** * 스크린 제품 산출 */ private function calculateScreen(array $inputs): array { $w = $inputs['W0']; $h = $inputs['H0']; $qty = $inputs['QTY']; // 파생 계산값 $outputs = []; // W1: 실제 폭 (케이스 마진 포함) $outputs['W1'] = $this->formulaEvaluator->evaluate('W0 + 100', ['W0' => $w]); // H1: 실제 높이 (브라켓 마진 포함) $outputs['H1'] = $this->formulaEvaluator->evaluate('H0 + 150', ['H0' => $h]); // 면적 (m²) $outputs['AREA'] = $this->formulaEvaluator->evaluate('(W1 * H1) / 1000000', [ 'W1' => $outputs['W1'], 'H1' => $outputs['H1'], ]); // 무게 (kg) - 대략 계산 $outputs['WEIGHT'] = $this->formulaEvaluator->evaluate('AREA * 5', [ 'AREA' => $outputs['AREA'], ]); // 모터 용량 결정 (면적 기준) $outputs['MOTOR_CAPACITY'] = $this->formulaEvaluator->evaluateRange($outputs['AREA'], [ ['min' => 0, 'max' => 5, 'result' => '50W'], ['min' => 5, 'max' => 10, 'result' => '100W'], ['min' => 10, 'max' => 20, 'result' => '200W'], ['min' => 20, 'max' => null, 'result' => '300W'], ], '100W'); // 품목 생성 $items = $this->generateScreenItems($inputs, $outputs, $qty); // 비용 계산 $costs = $this->calculateCosts($items); return [ 'outputs' => $outputs, 'items' => $items, 'costs' => $costs, ]; } /** * 철재 제품 산출 */ private function calculateSteel(array $inputs): array { $w = $inputs['W0']; $h = $inputs['H0']; $qty = $inputs['QTY']; $thickness = $inputs['THICKNESS']; // 파생 계산값 $outputs = []; // 실제 크기 (용접 마진 포함) $outputs['W1'] = $this->formulaEvaluator->evaluate('W0 + 50', ['W0' => $w]); $outputs['H1'] = $this->formulaEvaluator->evaluate('H0 + 50', ['H0' => $h]); // 면적 (m²) $outputs['AREA'] = $this->formulaEvaluator->evaluate('(W1 * H1) / 1000000', [ 'W1' => $outputs['W1'], 'H1' => $outputs['H1'], ]); // 중량 (kg) - 재질별 밀도 적용 $density = $this->formulaEvaluator->evaluateMapping($inputs['MATERIAL'], [ ['source' => 'ss304', 'result' => 7.93], ['source' => 'ss316', 'result' => 8.0], ['source' => 'galvanized', 'result' => 7.85], ], 7.85); $outputs['WEIGHT'] = $this->formulaEvaluator->evaluate('AREA * THICKNESS * DENSITY', [ 'AREA' => $outputs['AREA'], 'THICKNESS' => $thickness / 1000, // mm to m 'DENSITY' => $density * 1000, // kg/m³ ]); // 품목 생성 $items = $this->generateSteelItems($inputs, $outputs, $qty); // 비용 계산 $costs = $this->calculateCosts($items); return [ 'outputs' => $outputs, 'items' => $items, 'costs' => $costs, ]; } /** * 스크린 품목 생성 */ private function generateScreenItems(array $inputs, array $outputs, int $qty): array { $items = []; // 1. 스크린 원단 $items[] = [ 'item_code' => 'SCR-FABRIC-001', 'item_name' => '스크린 원단', 'specification' => sprintf('%.0f x %.0f mm', $outputs['W1'], $outputs['H1']), 'unit' => 'm²', 'base_quantity' => 1, 'calculated_quantity' => $outputs['AREA'] * $qty, 'unit_price' => 25000, 'total_price' => $outputs['AREA'] * $qty * 25000, 'formula' => 'AREA * QTY', 'formula_category' => 'material', ]; // 2. 케이스 $items[] = [ 'item_code' => 'SCR-CASE-001', 'item_name' => '알루미늄 케이스', 'specification' => sprintf('%.0f mm', $outputs['W1']), 'unit' => 'EA', 'base_quantity' => 1, 'calculated_quantity' => $qty, 'unit_price' => 85000, 'total_price' => $qty * 85000, 'formula' => 'QTY', 'formula_category' => 'material', ]; // 3. 모터 $motorPrice = $this->getMotorPrice($outputs['MOTOR_CAPACITY']); $items[] = [ 'item_code' => 'SCR-MOTOR-001', 'item_name' => '튜블러 모터', 'specification' => $outputs['MOTOR_CAPACITY'], 'unit' => 'EA', 'base_quantity' => 1, 'calculated_quantity' => $qty, 'unit_price' => $motorPrice, 'total_price' => $qty * $motorPrice, 'formula' => 'QTY', 'formula_category' => 'material', ]; // 4. 브라켓 $items[] = [ 'item_code' => 'SCR-BRACKET-001', 'item_name' => '설치 브라켓', 'specification' => $inputs['INSTALL_TYPE'], 'unit' => 'SET', 'base_quantity' => 2, 'calculated_quantity' => 2 * $qty, 'unit_price' => 15000, 'total_price' => 2 * $qty * 15000, 'formula' => '2 * QTY', 'formula_category' => 'material', ]; // 5. 인건비 $laborHours = $this->formulaEvaluator->evaluateRange($outputs['AREA'], [ ['min' => 0, 'max' => 5, 'result' => 2], ['min' => 5, 'max' => 10, 'result' => 3], ['min' => 10, 'max' => null, 'result' => 4], ], 2); $items[] = [ 'item_code' => 'LAB-INSTALL-001', 'item_name' => '설치 인건비', 'specification' => sprintf('%.1f시간', $laborHours * $qty), 'unit' => 'HR', 'base_quantity' => $laborHours, 'calculated_quantity' => $laborHours * $qty, 'unit_price' => 50000, 'total_price' => $laborHours * $qty * 50000, 'formula' => 'LABOR_HOURS * QTY', 'formula_category' => 'labor', ]; return $items; } /** * 철재 품목 생성 */ private function generateSteelItems(array $inputs, array $outputs, int $qty): array { $items = []; // 재질별 단가 $materialPrice = $this->formulaEvaluator->evaluateMapping($inputs['MATERIAL'], [ ['source' => 'ss304', 'result' => 4500], ['source' => 'ss316', 'result' => 6500], ['source' => 'galvanized', 'result' => 3000], ], 4500); // 1. 철판 $items[] = [ 'item_code' => 'STL-PLATE-001', 'item_name' => '철판 ('.$inputs['MATERIAL'].')', 'specification' => sprintf('%.0f x %.0f x %.1f mm', $outputs['W1'], $outputs['H1'], $inputs['THICKNESS']), 'unit' => 'kg', 'base_quantity' => $outputs['WEIGHT'], 'calculated_quantity' => $outputs['WEIGHT'] * $qty, 'unit_price' => $materialPrice, 'total_price' => $outputs['WEIGHT'] * $qty * $materialPrice, 'formula' => 'WEIGHT * QTY * MATERIAL_PRICE', 'formula_category' => 'material', ]; // 2. 용접 $weldLength = ($outputs['W1'] + $outputs['H1']) * 2 / 1000; // m $items[] = [ 'item_code' => 'STL-WELD-001', 'item_name' => '용접 ('.$inputs['WELDING'].')', 'specification' => sprintf('%.2f m', $weldLength * $qty), 'unit' => 'm', 'base_quantity' => $weldLength, 'calculated_quantity' => $weldLength * $qty, 'unit_price' => 15000, 'total_price' => $weldLength * $qty * 15000, 'formula' => 'WELD_LENGTH * QTY', 'formula_category' => 'labor', ]; // 3. 표면처리 $finishPrice = $this->formulaEvaluator->evaluateMapping($inputs['FINISH'], [ ['source' => 'hairline', 'result' => 8000], ['source' => 'mirror', 'result' => 15000], ['source' => 'matte', 'result' => 5000], ], 8000); $items[] = [ 'item_code' => 'STL-FINISH-001', 'item_name' => '표면처리 ('.$inputs['FINISH'].')', 'specification' => sprintf('%.2f m²', $outputs['AREA'] * $qty), 'unit' => 'm²', 'base_quantity' => $outputs['AREA'], 'calculated_quantity' => $outputs['AREA'] * $qty, 'unit_price' => $finishPrice, 'total_price' => $outputs['AREA'] * $qty * $finishPrice, 'formula' => 'AREA * QTY', 'formula_category' => 'labor', ]; // 4. 가공비 $items[] = [ 'item_code' => 'STL-PROCESS-001', 'item_name' => '가공비', 'specification' => '절단, 벤딩, 천공', 'unit' => 'EA', 'base_quantity' => 1, 'calculated_quantity' => $qty, 'unit_price' => 50000, 'total_price' => $qty * 50000, 'formula' => 'QTY', 'formula_category' => 'labor', ]; return $items; } /** * 비용 계산 */ private function calculateCosts(array $items): array { $materialCost = 0; $laborCost = 0; $installCost = 0; foreach ($items as $item) { $category = $item['formula_category'] ?? 'material'; $price = (float) ($item['total_price'] ?? 0); match ($category) { 'material' => $materialCost += $price, 'labor' => $laborCost += $price, 'install' => $installCost += $price, default => $materialCost += $price, }; } $subtotal = $materialCost + $laborCost + $installCost; return [ 'material_cost' => round($materialCost, 2), 'labor_cost' => round($laborCost, 2), 'install_cost' => round($installCost, 2), 'subtotal' => round($subtotal, 2), ]; } /** * 모터 단가 조회 */ private function getMotorPrice(string $capacity): int { return match ($capacity) { '50W' => 120000, '100W' => 150000, '200W' => 200000, '300W' => 280000, default => 150000, }; } /** * 입력 스키마 반환 (프론트엔드용) */ public function getInputSchema(?string $productCategory = null): array { $category = $productCategory ?? Quote::CATEGORY_SCREEN; $commonSchema = [ 'W0' => [ 'label' => '개구부 폭', 'type' => 'number', 'unit' => 'mm', 'required' => true, 'min' => 100, 'max' => 10000, ], 'H0' => [ 'label' => '개구부 높이', 'type' => 'number', 'unit' => 'mm', 'required' => true, 'min' => 100, 'max' => 10000, ], 'QTY' => [ 'label' => '수량', 'type' => 'integer', 'required' => true, 'min' => 1, 'default' => 1, ], ]; if ($category === Quote::CATEGORY_SCREEN) { return array_merge($commonSchema, [ 'INSTALL_TYPE' => [ 'label' => '설치 유형', 'type' => 'select', 'options' => [ ['value' => 'wall', 'label' => '벽면'], ['value' => 'ceiling', 'label' => '천장'], ['value' => 'floor', 'label' => '바닥'], ], 'default' => 'wall', ], 'MOTOR_TYPE' => [ 'label' => '모터 유형', 'type' => 'select', 'options' => [ ['value' => 'standard', 'label' => '일반형'], ['value' => 'heavy', 'label' => '고하중형'], ], 'default' => 'standard', ], 'CONTROL_TYPE' => [ 'label' => '제어 방식', 'type' => 'select', 'options' => [ ['value' => 'switch', 'label' => '스위치'], ['value' => 'remote', 'label' => '리모컨'], ['value' => 'smart', 'label' => '스마트'], ], 'default' => 'switch', ], 'CHAIN_SIDE' => [ 'label' => '체인 위치', 'type' => 'select', 'options' => [ ['value' => 'left', 'label' => '좌측'], ['value' => 'right', 'label' => '우측'], ], 'default' => 'left', ], ]); } if ($category === Quote::CATEGORY_STEEL) { return array_merge($commonSchema, [ 'MATERIAL' => [ 'label' => '재질', 'type' => 'select', 'options' => [ ['value' => 'ss304', 'label' => 'SUS304'], ['value' => 'ss316', 'label' => 'SUS316'], ['value' => 'galvanized', 'label' => '아연도금'], ], 'default' => 'ss304', ], 'THICKNESS' => [ 'label' => '두께', 'type' => 'number', 'unit' => 'mm', 'min' => 0.5, 'max' => 10, 'step' => 0.1, 'default' => 1.5, ], 'FINISH' => [ 'label' => '표면처리', 'type' => 'select', 'options' => [ ['value' => 'hairline', 'label' => '헤어라인'], ['value' => 'mirror', 'label' => '미러'], ['value' => 'matte', 'label' => '무광'], ], 'default' => 'hairline', ], 'WELDING' => [ 'label' => '용접 방식', 'type' => 'select', 'options' => [ ['value' => 'tig', 'label' => 'TIG'], ['value' => 'mig', 'label' => 'MIG'], ['value' => 'spot', 'label' => '스팟'], ], 'default' => 'tig', ], ]); } return $commonSchema; } }