- products 테이블: 6개 고정 필드 + attributes JSON - product_components 테이블: 수식/조건 + attributes JSON - tenant_stat_fields 테이블: 테넌트별 통계 필드 설정 - stat_snapshots 테이블: 통계 캐싱 - BP-MES CategoryFields Seeder: 제품/부품/절곡품 카테고리 필드 - BP-MES TenantStatFields Seeder: 통계 필드 설정 [변경 사항] 삭제: - 2025_11_13_120000_extend_products_table_for_bp_mes.php - 2025_11_13_120001_extend_product_components_table_for_bp_mes.php 추가: - 2025_11_14_000001_add_hybrid_fields_to_products_table.php - 2025_11_14_000002_add_attributes_to_product_components_table.php - 2025_11_14_000003_create_tenant_stat_fields_table.php - 2025_11_14_000004_create_stat_snapshots_table.php - BpMesCategoryFieldsSeeder.php - BpMesTenantStatFieldsSeeder.php [배경] 멀티테넌트 시스템의 유연성 확보를 위해 고정 필드를 최소화하고 동적 필드 시스템(category_fields + attributes JSON)으로 전환. 통계 성능을 위해 자주 조회하는 분류 필드(product_category, part_type)는 고정 컬럼으로 유지하고 인덱싱.
233 lines
10 KiB
PHP
233 lines
10 KiB
PHP
<?php
|
|
|
|
namespace Database\Seeders;
|
|
|
|
use Illuminate\Database\Seeder;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
/**
|
|
* BP-MES 품목 관리 시스템 카테고리 필드 시더
|
|
*
|
|
* 목적: BP-MES 테넌트를 위한 제품/부품/절곡품 카테고리 및 동적 필드 생성
|
|
*
|
|
* 구조:
|
|
* - bp_mes_products (루트)
|
|
* - fg_products (완제품/FG) - 마진율, 비용, LOT 관리
|
|
* - pt_products (부품/PT) - 용도, 설치방식, 조립 정보
|
|
* - bending_products (절곡품) - 절곡 상세, 재질, 길이
|
|
*
|
|
* 실행: php artisan db:seed --class=BpMesCategoryFieldsSeeder
|
|
*
|
|
* @see /claudedocs/mes/ITEM_MANAGEMENT_MIGRATION_GUIDE.md
|
|
*/
|
|
class BpMesCategoryFieldsSeeder extends Seeder
|
|
{
|
|
/**
|
|
* BP-MES 테넌트 ID (실제 환경에 맞게 수정 필요)
|
|
*/
|
|
private const TENANT_ID = 1;
|
|
|
|
public function run(): void
|
|
{
|
|
// 1. BP-MES 루트 카테고리 생성
|
|
$rootCategoryId = DB::table('categories')->insertGetId([
|
|
'tenant_id' => self::TENANT_ID,
|
|
'parent_id' => null,
|
|
'code_group' => 'bp_mes',
|
|
'code' => 'bp_mes_products',
|
|
'name' => 'BP-MES 품목 관리',
|
|
'description' => 'BP-MES 품목 기준관리 시스템 루트 카테고리',
|
|
'sort_order' => 1,
|
|
'profile_code' => 'bp_mes_root',
|
|
'is_active' => 1,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
// 2. 완제품(FG) 카테고리 생성
|
|
$fgCategoryId = DB::table('categories')->insertGetId([
|
|
'tenant_id' => self::TENANT_ID,
|
|
'parent_id' => $rootCategoryId,
|
|
'code_group' => 'bp_mes',
|
|
'code' => 'fg_products',
|
|
'name' => '완제품(FG)',
|
|
'description' => '판매 가능한 완제품 카테고리 (마진율, 비용 관리)',
|
|
'sort_order' => 1,
|
|
'profile_code' => 'fg_category',
|
|
'is_active' => 1,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
// 3. 부품(PT) 카테고리 생성
|
|
$ptCategoryId = DB::table('categories')->insertGetId([
|
|
'tenant_id' => self::TENANT_ID,
|
|
'parent_id' => $rootCategoryId,
|
|
'code_group' => 'bp_mes',
|
|
'code' => 'pt_products',
|
|
'name' => '부품(PT)',
|
|
'description' => '조립용 부품 카테고리 (용도, 설치방식, 규격)',
|
|
'sort_order' => 2,
|
|
'profile_code' => 'pt_category',
|
|
'is_active' => 1,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
// 4. 절곡품 카테고리 생성
|
|
$bendingCategoryId = DB::table('categories')->insertGetId([
|
|
'tenant_id' => self::TENANT_ID,
|
|
'parent_id' => $rootCategoryId,
|
|
'code_group' => 'bp_mes',
|
|
'code' => 'bending_products',
|
|
'name' => '절곡품',
|
|
'description' => '금속 절곡 가공품 카테고리 (절곡도, 재질, 길이)',
|
|
'sort_order' => 3,
|
|
'profile_code' => 'bending_category',
|
|
'is_active' => 1,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
// 5. 완제품(FG) 동적 필드 정의
|
|
$fgFields = [
|
|
// 비용 관리 (통계 필요)
|
|
['key' => 'margin_rate', 'name' => '마진율(%)', 'type' => 'decimal', 'required' => false, 'order' => 10,
|
|
'desc' => '제품 마진율 (통계 대상)'],
|
|
|
|
['key' => 'processing_cost', 'name' => '가공비(원)', 'type' => 'decimal', 'required' => false, 'order' => 11,
|
|
'desc' => '가공 비용 (통계 대상)'],
|
|
|
|
['key' => 'labor_cost', 'name' => '인건비(원)', 'type' => 'decimal', 'required' => false, 'order' => 12,
|
|
'desc' => '인건비 (통계 대상)'],
|
|
|
|
['key' => 'install_cost', 'name' => '설치비(원)', 'type' => 'decimal', 'required' => false, 'order' => 13,
|
|
'desc' => '설치 비용 (통계 대상)'],
|
|
|
|
// LOT 관리
|
|
['key' => 'lot_abbreviation', 'name' => 'LOT 약어', 'type' => 'text', 'required' => false, 'order' => 20,
|
|
'desc' => 'LOT 번호 생성용 약어'],
|
|
|
|
['key' => 'note', 'name' => '비고', 'type' => 'textarea', 'required' => false, 'order' => 21,
|
|
'desc' => '제품 관련 메모 및 특이사항'],
|
|
|
|
// 인증 정보
|
|
['key' => 'certification_number', 'name' => '인증번호', 'type' => 'text', 'required' => false, 'order' => 30,
|
|
'desc' => '제품 인증번호'],
|
|
|
|
['key' => 'certification_start_date', 'name' => '인증시작일', 'type' => 'date', 'required' => false, 'order' => 31,
|
|
'desc' => '인증 유효기간 시작일'],
|
|
|
|
['key' => 'certification_end_date', 'name' => '인증종료일', 'type' => 'date', 'required' => false, 'order' => 32,
|
|
'desc' => '인증 유효기간 종료일'],
|
|
|
|
// 파일 관리
|
|
['key' => 'specification_file', 'name' => '규격파일', 'type' => 'file', 'required' => false, 'order' => 40,
|
|
'desc' => '제품 규격서 파일 경로'],
|
|
|
|
['key' => 'specification_file_name', 'name' => '규격파일명', 'type' => 'text', 'required' => false, 'order' => 41,
|
|
'desc' => '규격서 파일 원본명'],
|
|
|
|
['key' => 'certification_file', 'name' => '인증파일', 'type' => 'file', 'required' => false, 'order' => 42,
|
|
'desc' => '인증서 파일 경로'],
|
|
|
|
['key' => 'certification_file_name', 'name' => '인증파일명', 'type' => 'text', 'required' => false, 'order' => 43,
|
|
'desc' => '인증서 파일 원본명'],
|
|
|
|
// 확장 옵션
|
|
['key' => 'options', 'name' => '옵션정보', 'type' => 'json', 'required' => false, 'order' => 50,
|
|
'desc' => '추가 옵션 정보 (JSON 형태)'],
|
|
];
|
|
|
|
// 6. 부품(PT) 동적 필드 정의
|
|
$ptFields = [
|
|
// 부품 분류 및 용도
|
|
['key' => 'part_usage', 'name' => '용도', 'type' => 'text', 'required' => false, 'order' => 10,
|
|
'desc' => '부품 사용 용도'],
|
|
|
|
['key' => 'installation_type', 'name' => '설치방식', 'type' => 'select', 'required' => false, 'order' => 11,
|
|
'options' => ['CEILING', 'WALL', 'FLOOR', 'HANGING'],
|
|
'desc' => '설치 방식 (천장형, 벽면형, 바닥형, 현가형)'],
|
|
|
|
['key' => 'assembly_type', 'name' => '조립타입', 'type' => 'select', 'required' => false, 'order' => 12,
|
|
'options' => ['WELDING', 'BOLT', 'RIVET', 'ADHESIVE'],
|
|
'desc' => '조립 방식 (용접, 볼트, 리벳, 접착)'],
|
|
|
|
// 치수 정보
|
|
['key' => 'side_spec_width', 'name' => '측면규격_폭(mm)', 'type' => 'decimal', 'required' => false, 'order' => 20,
|
|
'desc' => '측면 규격 폭'],
|
|
|
|
['key' => 'side_spec_height', 'name' => '측면규격_높이(mm)', 'type' => 'decimal', 'required' => false, 'order' => 21,
|
|
'desc' => '측면 규격 높이'],
|
|
|
|
['key' => 'assembly_length', 'name' => '조립길이(mm)', 'type' => 'decimal', 'required' => false, 'order' => 22,
|
|
'desc' => '조립 시 길이'],
|
|
|
|
// 가이드레일 정보
|
|
['key' => 'guide_rail_model_type', 'name' => '가이드레일모델타입', 'type' => 'select', 'required' => false, 'order' => 30,
|
|
'options' => ['TYPE_A', 'TYPE_B', 'TYPE_C', 'CUSTOM'],
|
|
'desc' => '가이드레일 모델 타입'],
|
|
|
|
['key' => 'guide_rail_model', 'name' => '가이드레일모델', 'type' => 'text', 'required' => false, 'order' => 31,
|
|
'desc' => '가이드레일 모델명'],
|
|
];
|
|
|
|
// 7. 절곡품 동적 필드 정의
|
|
$bendingFields = [
|
|
// 절곡 정보
|
|
['key' => 'bending_diagram', 'name' => '절곡도', 'type' => 'file', 'required' => false, 'order' => 10,
|
|
'desc' => '절곡 도면 파일 경로'],
|
|
|
|
['key' => 'bending_details', 'name' => '절곡상세', 'type' => 'json', 'required' => false, 'order' => 11,
|
|
'desc' => '절곡 상세 정보 (각도, 위치 등 JSON)'],
|
|
|
|
// 재질 및 치수
|
|
['key' => 'material', 'name' => '재질', 'type' => 'select', 'required' => true, 'order' => 20,
|
|
'options' => ['STEEL', 'STAINLESS', 'ALUMINUM', 'GALVANIZED'],
|
|
'desc' => '금속 재질 (강판, 스텐, 알루미늄, 아연도금)'],
|
|
|
|
['key' => 'length', 'name' => '길이(mm)', 'type' => 'decimal', 'required' => true, 'order' => 21,
|
|
'desc' => '절곡품 길이'],
|
|
|
|
['key' => 'bending_length', 'name' => '절곡길이(mm)', 'type' => 'decimal', 'required' => false, 'order' => 22,
|
|
'desc' => '절곡 부분 길이'],
|
|
];
|
|
|
|
// 8. 완제품(FG) 카테고리 필드 생성
|
|
foreach ($fgFields as $field) {
|
|
$this->insertCategoryField($fgCategoryId, $field);
|
|
}
|
|
|
|
// 9. 부품(PT) 카테고리 필드 생성
|
|
foreach ($ptFields as $field) {
|
|
$this->insertCategoryField($ptCategoryId, $field);
|
|
}
|
|
|
|
// 10. 절곡품 카테고리 필드 생성
|
|
foreach ($bendingFields as $field) {
|
|
$this->insertCategoryField($bendingCategoryId, $field);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* category_fields 테이블에 필드 삽입
|
|
*/
|
|
private function insertCategoryField(int $categoryId, array $field): void
|
|
{
|
|
DB::table('category_fields')->insert([
|
|
'tenant_id' => self::TENANT_ID,
|
|
'category_id' => $categoryId,
|
|
'field_key' => $field['key'],
|
|
'field_name' => $field['name'],
|
|
'field_type' => $field['type'],
|
|
'is_required' => $field['required'],
|
|
'sort_order' => $field['order'],
|
|
'default_value' => $field['default'] ?? null,
|
|
'options' => isset($field['options']) ? json_encode($field['options']) : null,
|
|
'description' => $field['desc'],
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
}
|