parser = $parser; $this->validator = $validator; } /** * BOM 계산 실행 * @param int $bomTemplateId BOM 템플릿 ID * @param array $parameters 입력 파라미터 * @param string|null $companyName 업체명 (null시 기본값 사용) * @return array 계산 결과 */ public function calculateBOM(int $bomTemplateId, array $parameters, ?string $companyName = null): array { try { // BOM 템플릿 조회 $bomTemplate = BomTemplate::with(['items', 'modelVersion.model']) ->findOrFail($bomTemplateId); // 파라미터 검증 $this->validateParameters($bomTemplate, $parameters); // 중간 계산값 도출 (W1, H1, 면적, 중량 등) $calculatedValues = $this->calculateIntermediateValues($bomTemplate, $parameters, $companyName); // BOM 아이템별 수량 계산 $bomItems = $this->calculateBomItems($bomTemplate->items, $calculatedValues, $parameters, $companyName); return [ 'success' => true, 'bom_template' => [ 'id' => $bomTemplate->id, 'name' => $bomTemplate->name, 'company_type' => $bomTemplate->company_type, 'formula_version' => $bomTemplate->formula_version ], 'input_parameters' => $parameters, 'calculated_values' => $calculatedValues, 'bom_items' => $bomItems, 'calculation_timestamp' => now() ]; } catch (\Exception $e) { Log::error('BOM 계산 실패', [ 'bom_template_id' => $bomTemplateId, 'parameters' => $parameters, 'error' => $e->getMessage() ]); return [ 'success' => false, 'error' => $e->getMessage(), 'bom_template_id' => $bomTemplateId ]; } } /** * 견적시 필요한 파라미터 스키마 추출 * @param int $bomTemplateId BOM 템플릿 ID * @return array 파라미터 스키마 */ public function getRequiredParameters(int $bomTemplateId): array { $bomTemplate = BomTemplate::findOrFail($bomTemplateId); $schema = $bomTemplate->calculation_schema; if (!$schema) { return $this->getDefaultParameterSchema($bomTemplate); } return $schema; } /** * 중간 계산값 도출 (W1, H1, 면적, 중량 등) */ protected function calculateIntermediateValues(BomTemplate $bomTemplate, array $parameters, ?string $companyName): array { $company = $companyName ?: $bomTemplate->company_type; $calculated = []; // 기본 파라미터에서 추출 $W0 = $parameters['W0'] ?? 0; $H0 = $parameters['H0'] ?? 0; $productType = $parameters['product_type'] ?? 'screen'; // 업체별 제작사이즈 계산 $sizeFormula = $this->getCalculationConfig($company, 'manufacturing_size'); if ($sizeFormula) { $calculated = array_merge($calculated, $this->parser->execute($sizeFormula->formula_expression, [ 'W0' => $W0, 'H0' => $H0, 'product_type' => $productType ])); } else { // 기본 공식 (경동기업 기준) if ($productType === 'screen') { $calculated['W1'] = $W0 + 160; $calculated['H1'] = $H0 + 350; } else { $calculated['W1'] = $W0 + 110; $calculated['H1'] = $H0 + 350; } } // 면적 계산 $calculated['area'] = ($calculated['W1'] * $calculated['H1']) / 1000000; // 중량 계산 $weightFormula = $this->getCalculationConfig($company, 'weight_calculation'); if ($weightFormula) { $weightResult = $this->parser->execute($weightFormula->formula_expression, array_merge($parameters, $calculated)); $calculated['weight'] = $weightResult['weight'] ?? 0; } else { // 기본 중량 공식 (스크린) if ($productType === 'screen') { $calculated['weight'] = ($calculated['area'] * 2) + ($W0 / 1000 * 14.17); } else { $calculated['weight'] = $calculated['area'] * 40; } } return $calculated; } /** * BOM 아이템별 수량 계산 */ protected function calculateBomItems(Collection $bomItems, array $calculatedValues, array $parameters, ?string $companyName): array { $results = []; foreach ($bomItems as $item) { if (!$item->is_calculated) { // 고정 수량 아이템 $results[] = [ 'item_id' => $item->id, 'ref_type' => $item->ref_type, 'ref_id' => $item->ref_id, 'original_qty' => $item->qty, 'calculated_qty' => $item->qty, 'is_calculated' => false, 'calculation_formula' => null ]; continue; } // 계산식 적용 아이템 try { $allParameters = array_merge($parameters, $calculatedValues); $calculatedQty = $this->parser->execute($item->calculation_formula, $allParameters); $results[] = [ 'item_id' => $item->id, 'ref_type' => $item->ref_type, 'ref_id' => $item->ref_id, 'original_qty' => $item->qty, 'calculated_qty' => $calculatedQty['result'] ?? $item->qty, 'is_calculated' => true, 'calculation_formula' => $item->calculation_formula, 'depends_on' => $item->depends_on ]; } catch (\Exception $e) { Log::warning('BOM 아이템 계산 실패', [ 'item_id' => $item->id, 'formula' => $item->calculation_formula, 'error' => $e->getMessage() ]); // 계산 실패시 원래 수량 사용 $results[] = [ 'item_id' => $item->id, 'ref_type' => $item->ref_type, 'ref_id' => $item->ref_id, 'original_qty' => $item->qty, 'calculated_qty' => $item->qty, 'is_calculated' => false, 'calculation_error' => $e->getMessage() ]; } } return $results; } /** * 파라미터 검증 */ protected function validateParameters(BomTemplate $bomTemplate, array $parameters): void { $schema = $bomTemplate->calculation_schema; if (!$schema) return; $this->validator->validate($schema, $parameters); } /** * 업체별 산출식 설정 조회 */ protected function getCalculationConfig(string $companyName, string $formulaType): ?CalculationConfig { return CalculationConfig::where('company_name', $companyName) ->where('formula_type', $formulaType) ->where('is_active', true) ->latest('version') ->first(); } /** * 기본 파라미터 스키마 생성 */ protected function getDefaultParameterSchema(BomTemplate $bomTemplate): array { return [ 'required_parameters' => [ [ 'key' => 'W0', 'label' => '오픈사이즈 가로(mm)', 'type' => 'integer', 'required' => true, 'min' => 500, 'max' => 15000 ], [ 'key' => 'H0', 'label' => '오픈사이즈 세로(mm)', 'type' => 'integer', 'required' => true, 'min' => 500, 'max' => 5000 ], [ 'key' => 'product_type', 'label' => '제품타입', 'type' => 'select', 'options' => ['screen' => '스크린', 'steel' => '철재'], 'required' => true ], [ 'key' => 'installation_type', 'label' => '설치방식', 'type' => 'select', 'options' => ['wall' => '벽면형', 'side' => '측면형', 'mixed' => '혼합형'], 'required' => false ] ], 'company_type' => $bomTemplate->company_type ?: 'default', 'formula_version' => $bomTemplate->formula_version ?: 'v1.0' ]; } }