Files
sam-api/database/seeders/BpMesCategoryFieldsSeeder.php
hskwon 342d15196e refactor: BP-MES Phase 1 하이브리드 구조 전환
- 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)는
고정 컬럼으로 유지하고 인덱싱.
2025-11-14 10:57:02 +09:00

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(),
]);
}
}