calculationEngine = $calculationEngine; } /** * 모델셋 목록 조회 (카테고리 기반) */ public function getModelSets(array $filters = []): Collection { $query = Category::with(['fields', 'children']) ->where('tenant_id', $this->tenantId()) ->where('code_group', 'estimate') ->where('level', '>=', 2); // 루트 카테고리 제외 if (! empty($filters['category_type'])) { $query->where('code', $filters['category_type']); } if (! empty($filters['is_active'])) { $query->where('is_active', $filters['is_active']); } return $query->orderBy('sort_order')->get(); } /** * 모델셋 상세 조회 */ public function getModelSetDetail($categoryId): array { $category = Category::with(['fields', 'parent', 'children']) ->where('tenant_id', $this->tenantId()) ->findOrFail($categoryId); // 해당 카테고리의 제품들 $products = Product::where('tenant_id', $this->tenantId()) ->where('category_id', $categoryId) ->with(['components']) ->get(); // 해당 카테고리의 모델 및 BOM 템플릿들 $models = $this->getRelatedModels($categoryId); return [ 'category' => $category, 'products' => $products, 'models' => $models, 'field_schema' => $this->generateFieldSchema($category->fields), ]; } /** * 새로운 모델셋 생성 */ public function createModelSet(array $data): array { return DB::transaction(function () use ($data) { // 1. 카테고리 생성 $category = Category::create([ 'tenant_id' => $this->tenantId(), 'parent_id' => $data['parent_id'] ?? null, 'code_group' => 'estimate', 'code' => $data['code'], 'name' => $data['name'], 'description' => $data['description'] ?? '', 'level' => $data['level'] ?? 2, 'sort_order' => $data['sort_order'] ?? 999, 'profile_code' => $data['profile_code'] ?? 'custom_category', 'is_active' => $data['is_active'] ?? true, 'created_by' => $this->apiUserId(), ]); // 2. 동적 필드 생성 if (! empty($data['fields'])) { foreach ($data['fields'] as $fieldData) { CategoryField::create([ 'tenant_id' => $this->tenantId(), 'category_id' => $category->id, 'field_key' => $fieldData['key'], 'field_name' => $fieldData['name'], 'field_type' => $fieldData['type'], 'is_required' => $fieldData['required'] ?? false, 'sort_order' => $fieldData['order'] ?? 999, 'default_value' => $fieldData['default'] ?? null, 'options' => $fieldData['options'] ?? null, 'description' => $fieldData['description'] ?? '', 'created_by' => $this->apiUserId(), ]); } } // 3. 모델 및 BOM 템플릿 생성 (선택사항) if (! empty($data['create_model'])) { $this->createDefaultModel($category, $data['model_data'] ?? []); } return $this->getModelSetDetail($category->id); }); } /** * 모델셋 수정 */ public function updateModelSet($categoryId, array $data): array { return DB::transaction(function () use ($categoryId, $data) { $category = Category::where('tenant_id', $this->tenantId()) ->findOrFail($categoryId); // 카테고리 정보 업데이트 $category->update([ 'name' => $data['name'] ?? $category->name, 'description' => $data['description'] ?? $category->description, 'sort_order' => $data['sort_order'] ?? $category->sort_order, 'is_active' => $data['is_active'] ?? $category->is_active, 'updated_by' => $this->apiUserId(), ]); // 필드 업데이트 (기존 필드 삭제 후 재생성) if (isset($data['fields'])) { CategoryField::where('category_id', $categoryId)->delete(); foreach ($data['fields'] as $fieldData) { CategoryField::create([ 'tenant_id' => $this->tenantId(), 'category_id' => $categoryId, 'field_key' => $fieldData['key'], 'field_name' => $fieldData['name'], 'field_type' => $fieldData['type'], 'is_required' => $fieldData['required'] ?? false, 'sort_order' => $fieldData['order'] ?? 999, 'default_value' => $fieldData['default'] ?? null, 'options' => $fieldData['options'] ?? null, 'description' => $fieldData['description'] ?? '', 'created_by' => $this->apiUserId(), ]); } } return $this->getModelSetDetail($categoryId); }); } /** * 모델셋 삭제 */ public function deleteModelSet($categoryId): void { DB::transaction(function () use ($categoryId) { $category = Category::where('tenant_id', $this->tenantId()) ->findOrFail($categoryId); // 연관된 데이터들 확인 $hasProducts = Product::where('category_id', $categoryId)->exists(); $hasChildren = Category::where('parent_id', $categoryId)->exists(); if ($hasProducts || $hasChildren) { throw new \Exception(__('error.modelset.has_dependencies')); } // 필드 삭제 CategoryField::where('category_id', $categoryId)->delete(); // 카테고리 삭제 (소프트 삭제) $category->update(['deleted_by' => $this->apiUserId()]); $category->delete(); }); } /** * 모델셋 복제 */ public function cloneModelSet($categoryId, array $data): array { return DB::transaction(function () use ($categoryId, $data) { $originalCategory = Category::with('fields') ->where('tenant_id', $this->tenantId()) ->findOrFail($categoryId); // 새로운 카테고리 생성 $newCategory = Category::create([ 'tenant_id' => $this->tenantId(), 'parent_id' => $originalCategory->parent_id, 'code_group' => $originalCategory->code_group, 'code' => $data['code'], 'name' => $data['name'], 'description' => $data['description'] ?? $originalCategory->description, 'level' => $originalCategory->level, 'sort_order' => $data['sort_order'] ?? 999, 'profile_code' => $originalCategory->profile_code, 'is_active' => $data['is_active'] ?? true, 'created_by' => $this->apiUserId(), ]); // 필드 복제 foreach ($originalCategory->fields as $field) { CategoryField::create([ 'tenant_id' => $this->tenantId(), 'category_id' => $newCategory->id, 'field_key' => $field->field_key, 'field_name' => $field->field_name, 'field_type' => $field->field_type, 'is_required' => $field->is_required, 'sort_order' => $field->sort_order, 'default_value' => $field->default_value, 'options' => $field->options, 'description' => $field->description, 'created_by' => $this->apiUserId(), ]); } return $this->getModelSetDetail($newCategory->id); }); } /** * 모델셋의 카테고리 필드 구조 조회 */ public function getModelSetCategoryFields($categoryId): array { $category = Category::with('fields') ->where('tenant_id', $this->tenantId()) ->findOrFail($categoryId); return $this->generateFieldSchema($category->fields); } /** * 모델셋의 BOM 템플릿 목록 */ public function getModelSetBomTemplates($categoryId): Collection { // 해당 카테고리와 연관된 모델들의 BOM 템플릿들 $models = $this->getRelatedModels($categoryId); $bomTemplates = collect(); foreach ($models as $model) { foreach ($model['versions'] as $version) { $bomTemplates = $bomTemplates->merge($version['bom_templates']); } } return $bomTemplates; } /** * 견적 파라미터 조회 (동적 필드 기반) */ public function getEstimateParameters($categoryId, array $filters = []): array { $category = Category::with('fields') ->where('tenant_id', $this->tenantId()) ->findOrFail($categoryId); // 입력 파라미터 (사용자가 입력해야 하는 필드들) $inputFields = $category->fields ->filter(function ($field) { return in_array($field->field_key, [ 'open_width', 'open_height', 'quantity', 'model_name', 'guide_rail_type', 'shutter_box', ]); }) ->map(function ($field) { return [ 'key' => $field->field_key, 'name' => $field->field_name, 'type' => $field->field_type, 'required' => $field->is_required, 'default' => $field->default_value, 'options' => $field->options, 'description' => $field->description, ]; }); // 계산 결과 필드 (자동으로 계산되는 필드들) $calculatedFields = $category->fields ->filter(function ($field) { return in_array($field->field_key, [ 'make_width', 'make_height', 'calculated_weight', 'calculated_area', 'motor_bracket_size', 'motor_capacity', ]); }) ->map(function ($field) { return [ 'key' => $field->field_key, 'name' => $field->field_name, 'type' => $field->field_type, 'description' => $field->description, ]; }); return [ 'category' => [ 'id' => $category->id, 'name' => $category->name, 'code' => $category->code, ], 'input_fields' => $inputFields->values(), 'calculated_fields' => $calculatedFields->values(), 'calculation_schema' => $this->getCalculationSchema($category->code), ]; } /** * 모델셋 기반 BOM 계산 */ public function calculateModelSetBom($categoryId, array $parameters): array { $category = Category::where('tenant_id', $this->tenantId()) ->findOrFail($categoryId); // BOM 템플릿 찾기 (기본 템플릿 사용) $bomTemplate = $this->findDefaultBomTemplate($categoryId, $parameters); if (! $bomTemplate) { throw new \Exception(__('error.bom_template.not_found')); } // 기존 BOM 계산 엔진 사용 return $this->calculationEngine->calculateBOM( $bomTemplate->id, $parameters, $this->getCompanyName($category) ); } /** * 카테고리와 연관된 모델들 조회 */ protected function getRelatedModels($categoryId): Collection { // 카테고리 코드 기반으로 모델 찾기 $category = Category::findOrFail($categoryId); return Model::with(['versions.bomTemplates']) ->where('tenant_id', $this->tenantId()) ->where('code', 'like', $category->code.'%') ->get() ->map(function ($model) { return [ 'id' => $model->id, 'code' => $model->code, 'name' => $model->name, 'versions' => $model->versions->map(function ($version) { return [ 'id' => $version->id, 'version_no' => $version->version_no, 'status' => $version->status, 'bom_templates' => $version->bomTemplates, ]; }), ]; }); } /** * 필드 스키마 생성 */ protected function generateFieldSchema(Collection $fields): array { return $fields->map(function ($field) { return [ 'key' => $field->field_key, 'name' => $field->field_name, 'type' => $field->field_type, 'required' => $field->is_required, 'order' => $field->sort_order, 'default' => $field->default_value, 'options' => $field->options, 'description' => $field->description, ]; })->sortBy('order')->values()->toArray(); } /** * 기본 모델 생성 */ protected function createDefaultModel(Category $category, array $modelData): void { $model = Model::create([ 'tenant_id' => $this->tenantId(), 'code' => $modelData['code'] ?? $category->code.'_MODEL', 'name' => $modelData['name'] ?? $category->name.' 기본 모델', 'description' => $modelData['description'] ?? '', 'status' => 'DRAFT', 'created_by' => $this->apiUserId(), ]); $version = ModelVersion::create([ 'tenant_id' => $this->tenantId(), 'model_id' => $model->id, 'version_no' => 'v1.0', 'status' => 'DRAFT', 'created_by' => $this->apiUserId(), ]); BomTemplate::create([ 'tenant_id' => $this->tenantId(), 'model_version_id' => $version->id, 'name' => $category->name.' 기본 BOM', 'company_type' => $this->getCompanyName($category), 'formula_version' => 'v1.0', 'calculation_schema' => $this->getDefaultCalculationSchema($category), 'created_by' => $this->apiUserId(), ]); } /** * 계산 스키마 조회 */ protected function getCalculationSchema(string $categoryCode): array { if ($categoryCode === 'screen_product') { return [ 'size_calculation' => 'kyungdong_screen_size', 'weight_calculation' => 'screen_weight_calculation', 'bracket_calculation' => 'motor_bracket_size', ]; } elseif ($categoryCode === 'steel_product') { return [ 'size_calculation' => 'kyungdong_steel_size', 'bracket_calculation' => 'motor_bracket_size', 'round_bar_calculation' => 'round_bar_quantity', ]; } return []; } /** * 기본 BOM 템플릿 찾기 */ protected function findDefaultBomTemplate($categoryId, array $parameters): ?BomTemplate { $models = $this->getRelatedModels($categoryId); foreach ($models as $model) { foreach ($model['versions'] as $version) { if ($version['status'] === 'RELEASED' && $version['bom_templates']->isNotEmpty()) { return $version['bom_templates']->first(); } } } return null; } /** * 업체명 조회 */ protected function getCompanyName(Category $category): string { // 테넌트 정보에서 업체명 조회하거나 기본값 사용 return '경동기업'; // 임시 하드코딩 } /** * 기본 계산 스키마 생성 */ protected function getDefaultCalculationSchema(Category $category): array { return [ 'calculation_type' => $category->code, 'formulas' => $this->getCalculationSchema($category->code), ]; } }