diff --git a/LOGICAL_RELATIONSHIPS.md b/LOGICAL_RELATIONSHIPS.md index a449634..d981d05 100644 --- a/LOGICAL_RELATIONSHIPS.md +++ b/LOGICAL_RELATIONSHIPS.md @@ -1,6 +1,6 @@ # 논리적 데이터베이스 관계 문서 -> **자동 생성**: 2025-11-11 11:24:13 +> **자동 생성**: 2025-11-14 08:41:17 > **소스**: Eloquent 모델 관계 분석 ## 📊 모델별 관계 현황 diff --git a/app/Models/Products/Product.php b/app/Models/Products/Product.php index a0f231c..29b5c77 100644 --- a/app/Models/Products/Product.php +++ b/app/Models/Products/Product.php @@ -19,6 +19,23 @@ class Product extends Model 'product_type', // 라벨/분류용 'attributes', 'description', 'is_sellable', 'is_purchasable', 'is_producible', 'is_active', + // BP-MES: 공통 필드 + 'margin_rate', 'processing_cost', 'labor_cost', 'install_cost', + 'safety_stock', 'lead_time', 'is_variable_size', + // BP-MES: FG 전용 + 'product_category', 'lot_abbreviation', 'note', + // BP-MES: PT 전용 + 'part_type', 'part_usage', 'installation_type', 'assembly_type', + 'side_spec_width', 'side_spec_height', 'assembly_length', + 'guide_rail_model_type', 'guide_rail_model', + // BP-MES: 절곡품 + 'bending_diagram', 'bending_details', 'material', 'length', 'bending_length', + // BP-MES: 인증 + 'certification_number', 'certification_start_date', 'certification_end_date', + 'specification_file', 'specification_file_name', + 'certification_file', 'certification_file_name', + // BP-MES: 동적 확장 + 'options', 'created_by', 'updated_by', ]; @@ -28,6 +45,14 @@ class Product extends Model 'is_purchasable' => 'boolean', 'is_producible' => 'boolean', 'is_active' => 'boolean', + // BP-MES: 추가 boolean 필드 + 'is_variable_size' => 'boolean', + // BP-MES: JSON 필드 + 'bending_details' => 'array', + 'options' => 'array', + // BP-MES: Date 필드 + 'certification_start_date' => 'date', + 'certification_end_date' => 'date', ]; protected $hidden = [ diff --git a/app/Models/Products/ProductComponent.php b/app/Models/Products/ProductComponent.php index aebf176..45bff08 100644 --- a/app/Models/Products/ProductComponent.php +++ b/app/Models/Products/ProductComponent.php @@ -23,6 +23,13 @@ class ProductComponent extends Model 'ref_id', 'quantity', 'sort_order', + // BP-MES: 수식 계산 및 조건부 BOM + 'quantity_formula', + 'condition', + // BP-MES: 절곡품 + 'is_bending', + 'bending_diagram', + 'bending_details', 'created_by', 'updated_by', ]; @@ -32,6 +39,9 @@ class ProductComponent extends Model 'created_at' => 'datetime', 'updated_at' => 'datetime', 'deleted_at' => 'datetime', + // BP-MES: 추가 타입 캐스팅 + 'is_bending' => 'boolean', + 'bending_details' => 'array', ]; protected $hidden = [ diff --git a/database/migrations/2025_11_13_120000_extend_products_table_for_bp_mes.php b/database/migrations/2025_11_13_120000_extend_products_table_for_bp_mes.php new file mode 100644 index 0000000..69fe9ce --- /dev/null +++ b/database/migrations/2025_11_13_120000_extend_products_table_for_bp_mes.php @@ -0,0 +1,367 @@ +boolean('is_active') + ->default(true) + ->after('product_type') + ->comment('활성 상태'); + } + + if (! Schema::hasColumn('products', 'margin_rate')) { + $table->decimal('margin_rate', 5, 2) + ->nullable() + ->after('is_active') + ->comment('마진율 (%)'); + } + + if (! Schema::hasColumn('products', 'processing_cost')) { + $table->decimal('processing_cost', 10, 2) + ->nullable() + ->after('margin_rate') + ->comment('가공비'); + } + + if (! Schema::hasColumn('products', 'labor_cost')) { + $table->decimal('labor_cost', 10, 2) + ->nullable() + ->after('processing_cost') + ->comment('인건비'); + } + + if (! Schema::hasColumn('products', 'install_cost')) { + $table->decimal('install_cost', 10, 2) + ->nullable() + ->after('labor_cost') + ->comment('설치비'); + } + + if (! Schema::hasColumn('products', 'safety_stock')) { + $table->integer('safety_stock') + ->nullable() + ->after('install_cost') + ->comment('안전 재고'); + } + + if (! Schema::hasColumn('products', 'lead_time')) { + $table->integer('lead_time') + ->nullable() + ->after('safety_stock') + ->comment('리드타임 (일)'); + } + + if (! Schema::hasColumn('products', 'is_variable_size')) { + $table->boolean('is_variable_size') + ->default(false) + ->after('lead_time') + ->comment('가변 치수 여부'); + } + + // ================================================ + // FG (제품) 전용 필드 (3개) + // ================================================ + if (! Schema::hasColumn('products', 'product_category')) { + $table->string('product_category', 20) + ->nullable() + ->after('is_variable_size') + ->comment('제품 분류: SCREEN, STEEL 등'); + } + + if (! Schema::hasColumn('products', 'lot_abbreviation')) { + $table->string('lot_abbreviation', 10) + ->nullable() + ->after('product_category') + ->comment('로트 약어 (예: KD)'); + } + + if (! Schema::hasColumn('products', 'note')) { + $table->text('note') + ->nullable() + ->after('lot_abbreviation') + ->comment('비고'); + } + + // ================================================ + // PT (부품) 전용 필드 (9개) + // ================================================ + if (! Schema::hasColumn('products', 'part_type')) { + $table->string('part_type', 20) + ->nullable() + ->after('note') + ->comment('부품 타입: ASSEMBLY, BENDING, PURCHASED'); + } + + if (! Schema::hasColumn('products', 'part_usage')) { + $table->string('part_usage', 30) + ->nullable() + ->after('part_type') + ->comment('부품 용도: GUIDE_RAIL, BOTTOM_FINISH, CASE, DOOR, BRACKET, GENERAL'); + } + + if (! Schema::hasColumn('products', 'installation_type')) { + $table->string('installation_type', 20) + ->nullable() + ->after('part_usage') + ->comment('설치 타입'); + } + + if (! Schema::hasColumn('products', 'assembly_type')) { + $table->string('assembly_type', 20) + ->nullable() + ->after('installation_type') + ->comment('조립 타입'); + } + + if (! Schema::hasColumn('products', 'side_spec_width')) { + $table->string('side_spec_width', 20) + ->nullable() + ->after('assembly_type') + ->comment('측면 규격 폭'); + } + + if (! Schema::hasColumn('products', 'side_spec_height')) { + $table->string('side_spec_height', 20) + ->nullable() + ->after('side_spec_width') + ->comment('측면 규격 높이'); + } + + if (! Schema::hasColumn('products', 'assembly_length')) { + $table->string('assembly_length', 20) + ->nullable() + ->after('side_spec_height') + ->comment('조립 길이'); + } + + if (! Schema::hasColumn('products', 'guide_rail_model_type')) { + $table->string('guide_rail_model_type', 50) + ->nullable() + ->after('assembly_length') + ->comment('가이드레일 모델 타입'); + } + + if (! Schema::hasColumn('products', 'guide_rail_model')) { + $table->string('guide_rail_model', 50) + ->nullable() + ->after('guide_rail_model_type') + ->comment('가이드레일 모델'); + } + + // ================================================ + // 절곡품 필드 (5개) + // ================================================ + if (! Schema::hasColumn('products', 'bending_diagram')) { + $table->string('bending_diagram', 255) + ->nullable() + ->after('guide_rail_model') + ->comment('절곡 전개도 이미지 URL'); + } + + if (! Schema::hasColumn('products', 'bending_details')) { + $table->json('bending_details') + ->nullable() + ->after('bending_diagram') + ->comment('절곡 전개도 상세 데이터 (BendingDetail[])'); + } + + if (! Schema::hasColumn('products', 'material')) { + $table->string('material', 50) + ->nullable() + ->after('bending_details') + ->comment('재질'); + } + + if (! Schema::hasColumn('products', 'length')) { + $table->string('length', 20) + ->nullable() + ->after('material') + ->comment('길이'); + } + + if (! Schema::hasColumn('products', 'bending_length')) { + $table->string('bending_length', 20) + ->nullable() + ->after('length') + ->comment('절곡 길이'); + } + + // ================================================ + // 인증 정보 필드 (7개) + // ================================================ + if (! Schema::hasColumn('products', 'certification_number')) { + $table->string('certification_number', 50) + ->nullable() + ->after('bending_length') + ->comment('인증 번호'); + } + + if (! Schema::hasColumn('products', 'certification_start_date')) { + $table->date('certification_start_date') + ->nullable() + ->after('certification_number') + ->comment('인증 시작일'); + } + + if (! Schema::hasColumn('products', 'certification_end_date')) { + $table->date('certification_end_date') + ->nullable() + ->after('certification_start_date') + ->comment('인증 종료일'); + } + + if (! Schema::hasColumn('products', 'specification_file')) { + $table->string('specification_file', 255) + ->nullable() + ->after('certification_end_date') + ->comment('시방서 파일 경로'); + } + + if (! Schema::hasColumn('products', 'specification_file_name')) { + $table->string('specification_file_name', 255) + ->nullable() + ->after('specification_file') + ->comment('시방서 파일명'); + } + + if (! Schema::hasColumn('products', 'certification_file')) { + $table->string('certification_file', 255) + ->nullable() + ->after('specification_file_name') + ->comment('인증서 파일 경로'); + } + + if (! Schema::hasColumn('products', 'certification_file_name')) { + $table->string('certification_file_name', 255) + ->nullable() + ->after('certification_file') + ->comment('인증서 파일명'); + } + + // ================================================ + // 동적 확장 필드 (1개) + // ================================================ + if (! Schema::hasColumn('products', 'options')) { + $table->json('options') + ->nullable() + ->after('certification_file_name') + ->comment('동적 옵션 데이터 (JSON)'); + } + + // ================================================ + // 인덱스 추가 (조회 최적화) + // ================================================ + // is_active 인덱스 + $hasIsActiveIndex = collect(\DB::select('SHOW INDEX FROM `products`')) + ->contains(fn ($r) => $r->Key_name === 'idx_products_is_active'); + if (! $hasIsActiveIndex && Schema::hasColumn('products', 'is_active')) { + $table->index('is_active', 'idx_products_is_active'); + } + + // product_category 인덱스 + $hasProductCategoryIndex = collect(\DB::select('SHOW INDEX FROM `products`')) + ->contains(fn ($r) => $r->Key_name === 'idx_products_product_category'); + if (! $hasProductCategoryIndex && Schema::hasColumn('products', 'product_category')) { + $table->index('product_category', 'idx_products_product_category'); + } + + // part_type 인덱스 + $hasPartTypeIndex = collect(\DB::select('SHOW INDEX FROM `products`')) + ->contains(fn ($r) => $r->Key_name === 'idx_products_part_type'); + if (! $hasPartTypeIndex && Schema::hasColumn('products', 'part_type')) { + $table->index('part_type', 'idx_products_part_type'); + } + + // part_usage 인덱스 + $hasPartUsageIndex = collect(\DB::select('SHOW INDEX FROM `products`')) + ->contains(fn ($r) => $r->Key_name === 'idx_products_part_usage'); + if (! $hasPartUsageIndex && Schema::hasColumn('products', 'part_usage')) { + $table->index('part_usage', 'idx_products_part_usage'); + } + }); + } + + public function down(): void + { + Schema::table('products', function (Blueprint $table) { + // 인덱스 제거 + $indexes = ['idx_products_is_active', 'idx_products_product_category', 'idx_products_part_type', 'idx_products_part_usage']; + foreach ($indexes as $indexName) { + $hasIndex = collect(\DB::select('SHOW INDEX FROM `products`')) + ->contains(fn ($r) => $r->Key_name === $indexName); + if ($hasIndex) { + $table->dropIndex($indexName); + } + } + + // 컬럼 제거 (역순) + $columns = [ + 'options', + 'certification_file_name', + 'certification_file', + 'specification_file_name', + 'specification_file', + 'certification_end_date', + 'certification_start_date', + 'certification_number', + 'bending_length', + 'length', + 'material', + 'bending_details', + 'bending_diagram', + 'guide_rail_model', + 'guide_rail_model_type', + 'assembly_length', + 'side_spec_height', + 'side_spec_width', + 'assembly_type', + 'installation_type', + 'part_usage', + 'part_type', + 'note', + 'lot_abbreviation', + 'product_category', + 'is_variable_size', + 'lead_time', + 'safety_stock', + 'install_cost', + 'labor_cost', + 'processing_cost', + 'margin_rate', + 'is_active', + ]; + + foreach ($columns as $column) { + if (Schema::hasColumn('products', $column)) { + $table->dropColumn($column); + } + } + }); + } +}; diff --git a/database/migrations/2025_11_13_120001_extend_product_components_table_for_bp_mes.php b/database/migrations/2025_11_13_120001_extend_product_components_table_for_bp_mes.php new file mode 100644 index 0000000..afcf6b2 --- /dev/null +++ b/database/migrations/2025_11_13_120001_extend_product_components_table_for_bp_mes.php @@ -0,0 +1,104 @@ +text('quantity_formula') + ->nullable() + ->after('quantity') + ->comment('수량 계산 공식 (예: "W * 2", "H + 100", "G/1000*1.02")'); + } + + if (! Schema::hasColumn('product_components', 'condition')) { + $table->text('condition') + ->nullable() + ->after('quantity_formula') + ->comment('조건부 BOM 조건식 (예: "MOTOR=\'Y\'", "WIDTH > 3000")'); + } + + // ================================================ + // 절곡품 관련 필드 (3개) + // ================================================ + if (! Schema::hasColumn('product_components', 'is_bending')) { + $table->boolean('is_bending') + ->default(false) + ->after('condition') + ->comment('절곡품 여부'); + } + + if (! Schema::hasColumn('product_components', 'bending_diagram')) { + $table->string('bending_diagram', 255) + ->nullable() + ->after('is_bending') + ->comment('절곡 전개도 이미지 URL'); + } + + if (! Schema::hasColumn('product_components', 'bending_details')) { + $table->json('bending_details') + ->nullable() + ->after('bending_diagram') + ->comment('절곡 전개도 상세 데이터 (BendingDetail[])'); + } + + // ================================================ + // 인덱스 추가 (조회 최적화) + // ================================================ + // is_bending 인덱스 + $hasIsBendingIndex = collect(\DB::select('SHOW INDEX FROM `product_components`')) + ->contains(fn ($r) => $r->Key_name === 'idx_product_components_is_bending'); + if (! $hasIsBendingIndex && Schema::hasColumn('product_components', 'is_bending')) { + $table->index('is_bending', 'idx_product_components_is_bending'); + } + }); + } + + public function down(): void + { + Schema::table('product_components', function (Blueprint $table) { + // 인덱스 제거 + $hasIsBendingIndex = collect(\DB::select('SHOW INDEX FROM `product_components`')) + ->contains(fn ($r) => $r->Key_name === 'idx_product_components_is_bending'); + if ($hasIsBendingIndex) { + $table->dropIndex('idx_product_components_is_bending'); + } + + // 컬럼 제거 (역순) + $columns = [ + 'bending_details', + 'bending_diagram', + 'is_bending', + 'condition', + 'quantity_formula', + ]; + + foreach ($columns as $column) { + if (Schema::hasColumn('product_components', $column)) { + $table->dropColumn($column); + } + } + }); + } +};