From 2d97ab1869d9543d1a154aecde1a01602343f338 Mon Sep 17 00:00:00 2001 From: kent Date: Sat, 13 Dec 2025 15:09:14 +0900 Subject: [PATCH 1/2] =?UTF-8?q?docs:=20items=20=ED=85=8C=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=20=ED=86=B5=ED=95=A9=20=EA=B3=84=ED=9A=8D=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20=EB=B0=8F=20=EC=99=84=EB=A3=8C=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - mng-item-field-management-plan.md 삭제 (MNG 구현 100% 완료) - items-table-unification-plan.md 업데이트: - Phase 0 데이터 정규화 섹션 추가 - item_type 표준화 체계 정립 (FG, PT, SM, RM, CS) - scopeProducts에서 레거시 코드 제거 - BOM API 요청/응답 변경 상세화 (프론트엔드 전달용) - Phase 5.1에 item_fields 테이블 추가 - 일정 테이블에 Phase 0 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../flow-tests/item-delete-force-delete.json | 410 ++++++++++++ plans/items-table-unification-plan.md | 586 ++++++++++++++++++ plans/mng-item-field-management-plan.md | 531 ---------------- 3 files changed, 996 insertions(+), 531 deletions(-) create mode 100644 plans/flow-tests/item-delete-force-delete.json create mode 100644 plans/items-table-unification-plan.md delete mode 100644 plans/mng-item-field-management-plan.md diff --git a/plans/flow-tests/item-delete-force-delete.json b/plans/flow-tests/item-delete-force-delete.json new file mode 100644 index 0000000..14bf097 --- /dev/null +++ b/plans/flow-tests/item-delete-force-delete.json @@ -0,0 +1,410 @@ +{ + "name": "품목 삭제 테스트 (Force Delete & 사용중 체크)", + "description": "품목 삭제 기능 테스트: 1) 사용되지 않는 품목은 Force Delete 2) 사용 중인 품목은 삭제 불가 에러 반환", + "version": "1.0", + "config": { + "baseUrl": "", + "timeout": 30000, + "stopOnFailure": true + }, + "variables": { + "user_id": "{{$env.FLOW_TESTER_USER_ID}}", + "user_pwd": "{{$env.FLOW_TESTER_USER_PWD}}", + "ts": "{{$timestamp}}" + }, + "steps": [ + { + "id": "login", + "name": "로그인", + "method": "POST", + "endpoint": "/login", + "body": { + "user_id": "{{user_id}}", + "user_pwd": "{{user_pwd}}" + }, + "expect": { + "status": [200], + "jsonPath": { + "$.access_token": "@isString" + } + }, + "extract": { + "token": "$.access_token" + } + }, + { + "id": "create_test_product", + "name": "테스트용 제품 생성 (FG)", + "method": "POST", + "endpoint": "/items", + "headers": { + "Authorization": "Bearer {{login.token}}" + }, + "body": { + "product_type": "FG", + "code": "TEST-DEL-FG-{{ts}}", + "name": "삭제테스트용 완제품", + "unit": "EA", + "is_active": true + }, + "expect": { + "status": [200, 201], + "jsonPath": { + "$.success": true, + "$.data.id": "@isNumber" + } + }, + "extract": { + "product_id": "$.data.id", + "product_code": "$.data.code" + } + }, + { + "id": "create_test_material", + "name": "테스트용 자재 생성 (RM)", + "method": "POST", + "endpoint": "/items", + "headers": { + "Authorization": "Bearer {{login.token}}" + }, + "body": { + "product_type": "RM", + "code": "TEST-DEL-RM-{{ts}}", + "name": "삭제테스트용 원자재", + "unit": "EA", + "is_active": true + }, + "expect": { + "status": [200, 201], + "jsonPath": { + "$.success": true, + "$.data.id": "@isNumber" + } + }, + "extract": { + "material_id": "$.data.id", + "material_code": "$.data.code" + } + }, + { + "id": "delete_unused_product", + "name": "사용되지 않는 제품 삭제 (Force Delete 성공)", + "method": "DELETE", + "endpoint": "/items/{{create_test_product.product_id}}?item_type=FG", + "headers": { + "Authorization": "Bearer {{login.token}}" + }, + "expect": { + "status": [200], + "jsonPath": { + "$.success": true + } + } + }, + { + "id": "verify_product_deleted", + "name": "제품 영구 삭제 확인 (404 응답)", + "method": "GET", + "endpoint": "/items/{{create_test_product.product_id}}", + "headers": { + "Authorization": "Bearer {{login.token}}" + }, + "expect": { + "status": [404], + "jsonPath": { + "$.success": false + } + } + }, + { + "id": "delete_unused_material", + "name": "사용되지 않는 자재 삭제 (Force Delete 성공)", + "method": "DELETE", + "endpoint": "/items/{{create_test_material.material_id}}?item_type=RM", + "headers": { + "Authorization": "Bearer {{login.token}}" + }, + "expect": { + "status": [200], + "jsonPath": { + "$.success": true + } + } + }, + { + "id": "verify_material_deleted", + "name": "자재 영구 삭제 확인 (404 응답)", + "method": "GET", + "endpoint": "/items/{{create_test_material.material_id}}?item_type=RM", + "headers": { + "Authorization": "Bearer {{login.token}}" + }, + "expect": { + "status": [404], + "jsonPath": { + "$.success": false + } + } + }, + { + "id": "create_parent_product", + "name": "BOM 상위 제품 생성", + "method": "POST", + "endpoint": "/items", + "headers": { + "Authorization": "Bearer {{login.token}}" + }, + "body": { + "product_type": "FG", + "code": "TEST-BOM-PARENT-{{ts}}", + "name": "BOM 상위 제품", + "unit": "EA", + "is_active": true + }, + "expect": { + "status": [200, 201], + "jsonPath": { + "$.success": true, + "$.data.id": "@isNumber" + } + }, + "extract": { + "parent_id": "$.data.id" + } + }, + { + "id": "create_child_material", + "name": "BOM 구성품용 자재 생성", + "method": "POST", + "endpoint": "/items", + "headers": { + "Authorization": "Bearer {{login.token}}" + }, + "body": { + "product_type": "RM", + "code": "TEST-BOM-CHILD-{{ts}}", + "name": "BOM 구성품 자재", + "unit": "EA", + "is_active": true + }, + "expect": { + "status": [200, 201], + "jsonPath": { + "$.success": true, + "$.data.id": "@isNumber" + } + }, + "extract": { + "child_material_id": "$.data.id", + "child_material_code": "$.data.code" + } + }, + { + "id": "add_bom_component", + "name": "BOM 구성품 추가 (자재를 상위 제품에 연결)", + "method": "POST", + "endpoint": "/items/{{create_parent_product.parent_id}}/bom", + "headers": { + "Authorization": "Bearer {{login.token}}" + }, + "body": { + "items": [ + { + "ref_type": "MATERIAL", + "ref_id": "{{create_child_material.child_material_id}}", + "quantity": 2, + "unit": "EA" + } + ] + }, + "expect": { + "status": [200, 201], + "jsonPath": { + "$.success": true + } + } + }, + { + "id": "delete_used_material", + "name": "사용 중인 자재 삭제 시도 (400 에러 - BOM 구성품)", + "method": "DELETE", + "endpoint": "/items/{{create_child_material.child_material_id}}?item_type=RM", + "headers": { + "Authorization": "Bearer {{login.token}}" + }, + "expect": { + "status": [400], + "jsonPath": { + "$.success": false + } + } + }, + { + "id": "create_batch_products", + "name": "일괄 삭제용 제품 1 생성", + "method": "POST", + "endpoint": "/items", + "headers": { + "Authorization": "Bearer {{login.token}}" + }, + "body": { + "product_type": "FG", + "code": "TEST-BATCH-1-{{ts}}", + "name": "일괄삭제 테스트 1", + "unit": "EA", + "is_active": true + }, + "expect": { + "status": [200, 201], + "jsonPath": { + "$.success": true, + "$.data.id": "@isNumber" + } + }, + "extract": { + "batch_id_1": "$.data.id" + } + }, + { + "id": "create_batch_products_2", + "name": "일괄 삭제용 제품 2 생성", + "method": "POST", + "endpoint": "/items", + "headers": { + "Authorization": "Bearer {{login.token}}" + }, + "body": { + "product_type": "FG", + "code": "TEST-BATCH-2-{{ts}}", + "name": "일괄삭제 테스트 2", + "unit": "EA", + "is_active": true + }, + "expect": { + "status": [200, 201], + "jsonPath": { + "$.success": true, + "$.data.id": "@isNumber" + } + }, + "extract": { + "batch_id_2": "$.data.id" + } + }, + { + "id": "batch_delete_unused", + "name": "사용되지 않는 제품들 일괄 삭제 (성공)", + "method": "DELETE", + "endpoint": "/items/batch", + "headers": { + "Authorization": "Bearer {{login.token}}" + }, + "body": { + "item_type": "FG", + "ids": ["{{create_batch_products.batch_id_1}}", "{{create_batch_products_2.batch_id_2}}"] + }, + "expect": { + "status": [200], + "jsonPath": { + "$.success": true + } + } + }, + { + "id": "create_batch_for_fail", + "name": "일괄 삭제 실패 테스트용 제품 생성", + "method": "POST", + "endpoint": "/items", + "headers": { + "Authorization": "Bearer {{login.token}}" + }, + "body": { + "product_type": "FG", + "code": "TEST-BATCH-FAIL-{{ts}}", + "name": "일괄삭제 실패 테스트", + "unit": "EA", + "is_active": true + }, + "expect": { + "status": [200, 201], + "jsonPath": { + "$.success": true, + "$.data.id": "@isNumber" + } + }, + "extract": { + "batch_fail_id": "$.data.id" + } + }, + { + "id": "batch_delete_with_used", + "name": "사용 중인 자재 일괄 삭제 시도 (400 에러)", + "method": "DELETE", + "endpoint": "/items/batch", + "headers": { + "Authorization": "Bearer {{login.token}}" + }, + "body": { + "item_type": "RM", + "ids": ["{{create_child_material.child_material_id}}"] + }, + "expect": { + "status": [400], + "jsonPath": { + "$.success": false + } + } + }, + { + "id": "cleanup_bom", + "name": "정리: BOM 구성품 전체 삭제 (빈 배열로 교체)", + "method": "POST", + "endpoint": "/items/{{create_parent_product.parent_id}}/bom/replace", + "headers": { + "Authorization": "Bearer {{login.token}}" + }, + "body": { + "items": [] + }, + "expect": { + "status": [200, 201, 404] + } + }, + { + "id": "cleanup_parent", + "name": "정리: 상위 제품 삭제", + "method": "DELETE", + "endpoint": "/items/{{create_parent_product.parent_id}}?item_type=FG", + "headers": { + "Authorization": "Bearer {{login.token}}" + }, + "expect": { + "status": [200, 404] + } + }, + { + "id": "cleanup_child", + "name": "정리: 구성품 자재 삭제 (BOM 해제 후)", + "method": "DELETE", + "endpoint": "/items/{{create_child_material.child_material_id}}?item_type=RM", + "headers": { + "Authorization": "Bearer {{login.token}}" + }, + "expect": { + "status": [200, 404] + } + }, + { + "id": "cleanup_batch_fail", + "name": "정리: 일괄삭제 테스트용 제품 삭제", + "method": "DELETE", + "endpoint": "/items/{{create_batch_for_fail.batch_fail_id}}?item_type=FG", + "headers": { + "Authorization": "Bearer {{login.token}}" + }, + "expect": { + "status": [200, 404] + } + } + ] +} \ No newline at end of file diff --git a/plans/items-table-unification-plan.md b/plans/items-table-unification-plan.md new file mode 100644 index 0000000..244778b --- /dev/null +++ b/plans/items-table-unification-plan.md @@ -0,0 +1,586 @@ +# Items 테이블 통합 마이그레이션 계획 + +## 참조 문서 + +### 필수 확인 + +| 문서 | 경로 | 내용 | +|------|------|------| +| **ItemMaster 연동 설계서** | [specs/item-master-integration.md](../specs/item-master-integration.md) | source_table, EntityRelationship 구조 | +| **DB 스키마** | [specs/database-schema.md](../specs/database-schema.md) | 테이블 구조, Multi-tenant 아키텍처 | + +### 참고 문서 + +| 문서 | 경로 | 내용 | +|------|------|------| +| **품목관리 마이그레이션 가이드** | [projects/mes/ITEM_MANAGEMENT_MIGRATION_GUIDE.md](../projects/mes/ITEM_MANAGEMENT_MIGRATION_GUIDE.md) | 프론트엔드 마이그레이션 | +| **API 품목 분석 요약** | [projects/mes/00_baseline/docs_breakdown/api_item_analysis_summary.md](../projects/mes/00_baseline/docs_breakdown/api_item_analysis_summary.md) | 기존 API 분석, price_histories | +| **Swagger 가이드** | [guides/swagger-guide.md](../guides/swagger-guide.md) | API 문서화 규칙 | + +### 관련 코드 + +| 파일 | 경로 | 역할 | +|------|------|------| +| ItemPage 모델 | `api/app/Models/ItemMaster/ItemPage.php` | source_table 매핑 | +| EntityRelationship 모델 | `api/app/Models/ItemMaster/EntityRelationship.php` | 엔티티 관계 관리 | +| ItemMasterService | `api/app/Services/ItemMaster/ItemMasterService.php` | init API, 메타데이터 조회 | +| ProductService | `api/app/Services/ProductService.php` | 기존 Products API (제거 예정) | +| MaterialService | `api/app/Services/MaterialService.php` | 기존 Materials API (제거 예정) | + +--- + +## 개요 + +### 목적 +`products`/`materials` 테이블을 `items` 테이블로 통합하여: +- BOM 관리 시 `child_item_type` 불필요 (ID만으로 유일 식별) +- 단일 쿼리로 모든 품목 조회 가능 +- Item-Master 시스템과 일관된 구조 + +### 현재 상황 +- **개발 단계**: 미오픈 (레거시 호환 불필요) +- **Item-Master**: 메타데이터 시스템 운영 중 (pages, sections, fields) +- **이전 시도**: 12/11 items 생성 → 12/12 롤백 (정책 정리 필요) + +### 현재 시스템 구조 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Item-Master (메타데이터) │ +├─────────────────────────────────────────────────────────────┤ +│ item_pages (source_table: 'products'|'materials') │ +│ ↓ EntityRelationship │ +│ item_sections → item_fields, item_bom_items │ +└─────────────────────────────────────────────────────────────┘ + ↓ 참조 +┌─────────────────────────────────────────────────────────────┐ +│ 실제 데이터 테이블 │ +├─────────────────────────────────────────────────────────────┤ +│ products (808건) ← ProductController, ProductService │ +│ materials (417건) ← MaterialController, MaterialService │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 목표 구조 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Item-Master (메타데이터) │ +├─────────────────────────────────────────────────────────────┤ +│ item_pages (source_table: 'items') │ +│ ↓ EntityRelationship │ +│ item_sections → item_fields, item_bom_items │ +└─────────────────────────────────────────────────────────────┘ + ↓ 참조 +┌─────────────────────────────────────────────────────────────┐ +│ 통합 데이터 테이블 │ +├─────────────────────────────────────────────────────────────┤ +│ items ← ItemController, ItemService │ +│ item_type: FG, PT, SM, RM, CS │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## Phase 0: 데이터 정규화 + +### 0.1 item_type 표준화 + +개발 중이므로 비표준 데이터는 삭제 처리. 품목관리 완료 후 경동기업 데이터 전체 재세팅 예정. + +**표준 item_type 체계**: + +| 코드 | 설명 | 출처 | +|------|------|------| +| FG | 완제품 (Finished Goods) | products | +| PT | 부품 (Parts) | products | +| SM | 부자재 (Sub-materials) | materials | +| RM | 원자재 (Raw Materials) | materials | +| CS | 소모품 (Consumables) | materials만 | + +**비표준 데이터 삭제**: +```sql +-- products에서 비표준 타입 삭제 (PRODUCT, SUBASSEMBLY, PART, CS) +DELETE FROM products WHERE product_type NOT IN ('FG', 'PT'); + +-- materials는 이미 표준 타입만 사용 (SM, RM, CS) +``` + +### 0.2 BOM 데이터 정리 + +통합 시 문제되는 BOM 데이터 삭제: +```sql +-- 삭제될 products/materials를 참조하는 BOM 항목 제거 +-- (Phase 1 이관 전에 실행) +``` + +### 0.3 체크리스트 + +- [ ] products 비표준 타입 삭제 +- [ ] 관련 BOM 데이터 정리 +- [ ] 삭제 건수 확인 + +--- + +## Phase 1: items 테이블 생성 + 데이터 이관 + +### 1.1 items 테이블 + +```sql +CREATE TABLE items ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + tenant_id BIGINT UNSIGNED NOT NULL, + + -- 기본 정보 + item_type VARCHAR(15) NOT NULL COMMENT 'FG, PT, SM, RM, CS', + code VARCHAR(100) NOT NULL, + name VARCHAR(255) NOT NULL, + unit VARCHAR(20) NULL, + category_id BIGINT UNSIGNED NULL, + + -- BOM (JSON) + bom JSON NULL COMMENT '[{child_item_id, quantity}, ...]', + + -- 상태 + is_active TINYINT(1) DEFAULT 1, + + -- 감사 필드 + created_by BIGINT UNSIGNED NULL, + updated_by BIGINT UNSIGNED NULL, + deleted_by BIGINT UNSIGNED NULL, + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + deleted_at TIMESTAMP NULL, + + -- 인덱스 + INDEX idx_items_tenant_type (tenant_id, item_type), + INDEX idx_items_tenant_code (tenant_id, code), + INDEX idx_items_tenant_category (tenant_id, category_id), + UNIQUE KEY uq_items_tenant_code (tenant_id, code, deleted_at), + + FOREIGN KEY (tenant_id) REFERENCES tenants(id), + FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +``` + +### 1.2 item_details 테이블 (확장 필드) + +```sql +CREATE TABLE item_details ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + item_id BIGINT UNSIGNED NOT NULL, + + -- Products 전용 필드 + is_sellable TINYINT(1) DEFAULT 1, + is_purchasable TINYINT(1) DEFAULT 0, + is_producible TINYINT(1) DEFAULT 0, + safety_stock INT NULL, + lead_time INT NULL, + is_variable_size TINYINT(1) DEFAULT 0, + product_category VARCHAR(50) NULL, + part_type VARCHAR(50) NULL, + + -- Materials 전용 필드 + is_inspection VARCHAR(1) DEFAULT 'N', + + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + + UNIQUE KEY uq_item_details_item_id (item_id), + FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +``` + +### 1.3 item_attributes 테이블 (동적 속성) + +```sql +CREATE TABLE item_attributes ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + item_id BIGINT UNSIGNED NOT NULL, + + attributes JSON NULL, + options JSON NULL, + + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + + UNIQUE KEY uq_item_attributes_item_id (item_id), + FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +``` + +### 1.4 데이터 이관 스크립트 + +```php +// Products → Items +DB::statement(" + INSERT INTO items (tenant_id, item_type, code, name, unit, category_id, bom, + is_active, created_by, updated_by, deleted_by, created_at, updated_at, deleted_at) + SELECT tenant_id, product_type, code, name, unit, category_id, bom, + is_active, created_by, updated_by, deleted_by, created_at, updated_at, deleted_at + FROM products +"); + +// Materials → Items +DB::statement(" + INSERT INTO items (tenant_id, item_type, code, name, unit, category_id, + is_active, created_by, updated_by, deleted_by, created_at, updated_at, deleted_at) + SELECT tenant_id, material_type, material_code, name, unit, category_id, + is_active, created_by, updated_by, deleted_by, created_at, updated_at, deleted_at + FROM materials +"); +``` + +### 1.5 체크리스트 + +- [ ] items 마이그레이션 생성 +- [ ] item_details 마이그레이션 생성 +- [ ] item_attributes 마이그레이션 생성 +- [ ] 데이터 이관 스크립트 실행 +- [ ] 건수 검증 (1,225건) + +--- + +## Phase 2: Item 모델 + Service 생성 + +### 2.1 Item 모델 + +```php +// app/Models/Item.php +class Item extends Model +{ + use BelongsToTenant, ModelTrait, SoftDeletes; + + protected $fillable = [ + 'tenant_id', 'item_type', 'code', 'name', 'unit', + 'category_id', 'bom', 'is_active', + ]; + + protected $casts = [ + 'bom' => 'array', + 'is_active' => 'boolean', + ]; + + // 1:1 관계 + public function details() { return $this->hasOne(ItemDetail::class); } + public function attributes() { return $this->hasOne(ItemAttribute::class); } + + // 타입별 스코프 + public function scopeProducts($q) { + return $q->whereIn('item_type', ['FG', 'PT']); + } + public function scopeMaterials($q) { + return $q->whereIn('item_type', ['SM', 'RM', 'CS']); + } +} +``` + +### 2.2 ItemService + +```php +// app/Services/ItemService.php +class ItemService extends Service +{ + public function index(array $params): LengthAwarePaginator + { + $query = Item::where('tenant_id', $this->tenantId()); + + // item_type 필터 + if ($itemType = $params['item_type'] ?? null) { + $query->where('item_type', strtoupper($itemType)); + } + + // 검색 + if ($search = $params['search'] ?? null) { + $query->where(fn($q) => $q + ->where('code', 'like', "%{$search}%") + ->orWhere('name', 'like', "%{$search}%") + ); + } + + return $query->with(['details', 'attributes'])->paginate($params['per_page'] ?? 15); + } +} +``` + +### 2.3 체크리스트 + +- [ ] Item 모델 생성 +- [ ] ItemDetail 모델 생성 +- [ ] ItemAttribute 모델 생성 +- [ ] ItemService 생성 +- [ ] ItemRequest 생성 + +--- + +## Phase 3: Item-Master 연동 수정 + +### 3.1 ItemPage.source_table 변경 + +```php +// app/Models/ItemMaster/ItemPage.php + +// 기존 +$mapping = [ + 'products' => \App\Models\Product::class, + 'materials' => \App\Models\Material::class, +]; + +// 변경 +$mapping = [ + 'items' => \App\Models\Item::class, +]; +``` + +### 3.2 item_pages 데이터 업데이트 + +```sql +-- source_table 통합 +UPDATE item_pages SET source_table = 'items' WHERE source_table IN ('products', 'materials'); +``` + +### 3.3 체크리스트 + +- [ ] ItemPage 모델 수정 (getTargetModelClass) +- [ ] item_pages.source_table 마이그레이션 +- [ ] ItemMasterService 연동 테스트 + +--- + +## Phase 4: API 통합 + +### 4.1 API 구조 변경 + +``` +기존 (분리): + /api/v1/products → ProductController + /api/v1/products/materials → MaterialController + +통합 후: + /api/v1/items → ItemController + /api/v1/items?item_type=FG → Products 조회 + /api/v1/items?item_type=SM → Materials 조회 +``` + +### 4.2 ItemController + +```php +// app/Http/Controllers/Api/V1/ItemController.php +class ItemController extends Controller +{ + public function __construct(private ItemService $service) {} + + public function index(ItemIndexRequest $request) + { + return ApiResponse::handle(fn() => [ + 'data' => $this->service->index($request->validated()), + ], __('message.fetched')); + } + + public function store(ItemStoreRequest $request) + { + return ApiResponse::handle(fn() => [ + 'data' => $this->service->store($request->validated()), + ], __('message.created')); + } +} +``` + +### 4.3 라우트 + +```php +// routes/api_v1.php +Route::prefix('items')->group(function () { + Route::get('/', [ItemController::class, 'index']); + Route::post('/', [ItemController::class, 'store']); + Route::get('/{id}', [ItemController::class, 'show']); + Route::patch('/{id}', [ItemController::class, 'update']); + Route::delete('/{id}', [ItemController::class, 'destroy']); +}); +``` + +### 4.4 체크리스트 + +- [ ] ItemController 생성 +- [ ] ItemIndexRequest, ItemStoreRequest 등 생성 +- [ ] 라우트 등록 +- [ ] Swagger 문서 작성 +- [ ] 기존 ProductController, MaterialController 제거 + +--- + +## Phase 5: 참조 테이블 마이그레이션 + +### 5.1 변경 대상 + +| 테이블 | 기존 | 변경 | +|--------|------|------| +| product_components | ref_type + ref_id | child_item_id | +| bom_template_items | ref_type + ref_id | item_id | +| orders | product_id | item_id | +| order_items | product_id | item_id | +| material_receipts | material_id | item_id | +| lots | material_id | item_id | +| price_histories | item_type + item_id | item_id | +| item_fields | source_table 'products'\|'materials' | source_table 'items' | + +### 5.2 체크리스트 + +- [ ] 각 참조 테이블 마이그레이션 작성 +- [ ] 관련 모델 관계 업데이트 +- [ ] 데이터 검증 + +--- + +## Phase 6: 정리 + +### 6.1 체크리스트 + +- [ ] CRUD 테스트 (전체 item_type) +- [ ] BOM 계산 테스트 +- [ ] Item-Master 연동 테스트 +- [ ] 참조 무결성 테스트 +- [ ] products 테이블 삭제 +- [ ] materials 테이블 삭제 +- [ ] 기존 Product, Material 모델 삭제 +- [ ] 기존 ProductService, MaterialService 삭제 + +--- + +## 테이블 구조 요약 + +``` +┌─────────────────────────────────────────────────────┐ +│ items (핵심) │ +├─────────────────────────────────────────────────────┤ +│ id, tenant_id, item_type, code, name, unit │ +│ category_id, bom (JSON), is_active │ +│ timestamps + soft deletes │ +└─────────────────────┬───────────────────────────────┘ + │ 1:1 + ┌───────────────┴───────────────┐ + ▼ ▼ +┌─────────────┐ ┌─────────────┐ +│item_details │ │item_attrs │ +├─────────────┤ ├─────────────┤ +│ is_sellable │ │ attributes │ +│ is_purch... │ │ options │ +│ safety_stk │ └─────────────┘ +│ lead_time │ +│ is_inspect │ +└─────────────┘ +``` + +--- + +## BOM 계산 로직 + +### 통합 전 +```php +foreach ($bom as $item) { + if ($item['child_item_type'] === 'product') { + $child = Product::find($item['child_item_id']); + } else { + $child = Material::find($item['child_item_id']); + } +} +``` + +### 통합 후 +```php +$childIds = collect($bom)->pluck('child_item_id'); +$children = Item::whereIn('id', $childIds)->get()->keyBy('id'); +``` + +--- + +## 프론트엔드 전달 사항 + +### API 엔드포인트 변경 + +| 기존 | 통합 | +|------|------| +| `GET /api/v1/products` | `GET /api/v1/items?item_type=FG` | +| `GET /api/v1/products?product_type=PART` | `GET /api/v1/items?item_type=PART` | +| `GET /api/v1/products/materials` | `GET /api/v1/items?item_type=SM` | + +### 응답 필드 변경 + +| 기존 | 통합 | +|------|------| +| `product_type` | `item_type` | +| `material_type` | `item_type` | +| `material_code` | `code` | + +### BOM 요청/응답 변경 + +**요청 (Request)**: +```json +// 기존: BOM 저장 시 ref_type 지정 필요 +{ + "bom": [ + { "ref_type": "PRODUCT", "ref_id": 5, "quantity": 2 }, + { "ref_type": "MATERIAL", "ref_id": 10, "quantity": 1 } + ] +} + +// 통합: item_id만 사용 +{ + "bom": [ + { "child_item_id": 5, "quantity": 2 }, + { "child_item_id": 10, "quantity": 1 } + ] +} +``` + +**응답 (Response)**: +```json +// 기존 +{ "child_item_type": "product", "child_item_id": 5, "quantity": 2 } + +// 통합 +{ "child_item_id": 5, "quantity": 2 } +``` + +**프론트엔드 수정 포인트**: +- BOM 구성품 추가 시 `ref_type` 선택 UI 제거 +- 품목 검색 시 `/api/v1/items` 단일 엔드포인트 사용 +- BOM 저장 payload에서 `ref_type`, `ref_id` → `child_item_id`로 변경 + +--- + +## 일정 + +| Phase | 작업 | 상태 | +|-------|------|------| +| 0 | 데이터 정규화 (비표준 item_type/BOM 삭제) | ⬜ | +| 1 | items 테이블 생성 + 데이터 이관 | ⬜ | +| 2 | Item 모델 + Service 생성 | ⬜ | +| 3 | Item-Master 연동 수정 | ⬜ | +| 4 | API 통합 | ⬜ | +| 5 | 참조 테이블 마이그레이션 | ⬜ | +| 6 | 정리 | ⬜ | + +--- + +## 리스크 + +| 리스크 | 대응 | +|--------|------| +| 데이터 이관 누락 | 이관 전후 건수 검증 | +| Item-Master 연동 오류 | source_table 변경 전 테스트 | +| BOM 순환 참조 | 저장 시 검증 로직 추가 | +| Code 중복 (products↔materials) | 개발 중이므로 품목관리 완료 후 경동기업 데이터 전체 삭제 후 재세팅 예정. 중복 데이터는 삭제 처리 | + +--- + +## 롤백 계획 + +각 Phase는 독립적 마이그레이션으로 구성: +```bash +# Phase 1 롤백 +php artisan migrate:rollback --step=3 + +# 데이터 복구 (products/materials 테이블 유지 상태에서) +# 신규 테이블만 삭제하면 됨 +``` \ No newline at end of file diff --git a/plans/mng-item-field-management-plan.md b/plans/mng-item-field-management-plan.md deleted file mode 100644 index b769486..0000000 --- a/plans/mng-item-field-management-plan.md +++ /dev/null @@ -1,531 +0,0 @@ -# MNG 품목기준 필드 관리 개발 계획 - -> 테넌트별 품목기준관리(ItemMaster) 시스템 필드 시딩 및 커스텀 필드 관리 기능 - -**작성일**: 2025-12-09 -**상태**: 계획 중 -**관련 문서**: -- `docs/specs/item-master-field-integration.md` -- `docs/specs/item-master-field-key-validation.md` -- `docs/specs/ITEM-MASTER-INDEX.md` - ---- - -## 1. 개요 - -### 1.1 목적 -- 테넌트별 품목기준 **시스템 필드(고정 컬럼)** 일괄 등록/삭제 -- 신규 테넌트 생성 시 초기 필드 데이터 자동 시딩 -- 기존 테넌트에 필드 데이터 수동 시딩 -- **커스텀 필드** 추가/삭제 관리 -- 향후 회계, 생산 등 다양한 도메인 확장 지원 - -### 1.2 핵심 개념 - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ 품목기준 필드 구분 │ -├─────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌─────────────────────────────────────┐ │ -│ │ 시스템 필드 (System Fields) │ │ -│ │ ───────────────────────────────── │ │ -│ │ - products 테이블 고정 컬럼 │ │ -│ │ (code, name, unit, is_active...) │ │ -│ │ - materials 테이블 고정 컬럼 │ │ -│ │ (material_code, name, spec...) │ │ -│ │ - storage_type = 'column' │ │ -│ │ - 시딩으로 일괄 등록 │ │ -│ └─────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────┐ │ -│ │ 커스텀 필드 (Custom Fields) │ │ -│ │ ───────────────────────────────── │ │ -│ │ - 테넌트별 추가 필드 │ │ -│ │ - attributes JSON에 저장 │ │ -│ │ - storage_type = 'json' │ │ -│ │ - MNG에서 수동 추가/삭제 │ │ -│ └─────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────┘ -``` - -### 1.3 대상 테이블 (source_table) - -| source_table | 설명 | item_type | -|--------------|------|-----------| -| `products` | 제품 테이블 | FG (완제품), PT (부품) | -| `materials` | 자재 테이블 | SM (부자재), RM (원자재), CS (소모품) | -| `product_components` | BOM 테이블 | - | -| `material_inspections` | 자재 검수 | - | -| `material_receipts` | 자재 입고 | - | - ---- - -## 2. 기능 설계 - -### 2.1 메인 화면: 품목기준 필드 관리 - -**URL**: `GET /item-master/fields` - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ 품목기준 필드 관리 │ -├─────────────────────────────────────────────────────────────────┤ -│ │ -│ 테넌트: [현재 테넌트명] │ -│ │ -│ ┌─────────────────────────────────────────────────────────┐ │ -│ │ 시스템 필드 시딩 │ │ -│ │ ───────────────────────────────────────────────────────│ │ -│ │ │ │ -│ │ 소스 테이블별 상태: │ │ -│ │ ┌────────────────┬──────────┬─────────┬────────────┐ │ │ -│ │ │ 테이블 │ 필드 수 │ 상태 │ 액션 │ │ │ -│ │ ├────────────────┼──────────┼─────────┼────────────┤ │ │ -│ │ │ products │ 12/12 │ ●완료 │ [초기화] │ │ │ -│ │ │ materials │ 0/8 │ ○미등록 │ [시딩] │ │ │ -│ │ │ product_comp.. │ 5/5 │ ●완료 │ [초기화] │ │ │ -│ │ │ material_ins.. │ 0/8 │ ○미등록 │ [시딩] │ │ │ -│ │ │ material_rec.. │ 0/10 │ ○미등록 │ [시딩] │ │ │ -│ │ └────────────────┴──────────┴─────────┴────────────┘ │ │ -│ │ │ │ -│ │ [전체 시딩] [전체 초기화] │ │ -│ │ │ │ -│ └─────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────────┐ │ -│ │ 커스텀 필드 관리 [+ 추가] │ │ -│ │ ───────────────────────────────────────────────────────│ │ -│ │ │ │ -│ │ 필터: [소스 테이블 ▼] [필드 타입 ▼] │ │ -│ │ │ │ -│ │ ┌───┬─────────┬───────────┬────────┬────────┬───────┐│ │ -│ │ │ □ │ 필드키 │ 필드명 │ 타입 │ 소스 │ 액션 ││ │ -│ │ ├───┼─────────┼───────────┼────────┼────────┼───────┤│ │ -│ │ │ □ │ weight │ 무게 │ number │products│ [삭제]││ │ -│ │ │ □ │ grade │ 등급 │dropdown│materials│[삭제]││ │ -│ │ │ □ │ color │ 색상 │ textbox│products│ [삭제]││ │ -│ │ └───┴─────────┴───────────┴────────┴────────┴───────┘│ │ -│ │ │ │ -│ │ [선택 삭제] │ │ -│ │ │ │ -│ └─────────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────┘ -``` - -### 2.2 시스템 필드 시딩 - -**기능**: 특정 source_table의 시스템 필드를 item_fields에 일괄 등록 - -**시딩 데이터 예시** (products): -```php -[ - ['field_key' => 'code', 'field_name' => '품목코드', 'field_type' => 'textbox', 'is_required' => true], - ['field_key' => 'name', 'field_name' => '품목명', 'field_type' => 'textbox', 'is_required' => true], - ['field_key' => 'unit', 'field_name' => '단위', 'field_type' => 'dropdown', 'is_required' => true], - ['field_key' => 'product_type', 'field_name' => '제품유형', 'field_type' => 'dropdown'], - ['field_key' => 'is_sellable', 'field_name' => '판매가능', 'field_type' => 'checkbox'], - ['field_key' => 'is_purchasable', 'field_name' => '구매가능', 'field_type' => 'checkbox'], - ['field_key' => 'is_producible', 'field_name' => '생산가능', 'field_type' => 'checkbox'], - ['field_key' => 'is_active', 'field_name' => '활성화', 'field_type' => 'checkbox'], - // ... -] -``` - -**저장 시 설정**: -```php -[ - 'tenant_id' => $currentTenantId, - 'source_table' => 'products', - 'source_column' => 'code', // field_key와 동일 - 'storage_type' => 'column', // DB 컬럼 직접 저장 - 'is_system' => true, // 시스템 필드 표시 (선택적) -] -``` - -### 2.3 시스템 필드 초기화 - -**기능**: 특정 source_table의 시스템 필드를 삭제하고 다시 시딩 - -**주의사항**: -- 커스텀 필드는 유지 (storage_type = 'json'인 필드) -- 확인 다이얼로그 필수 -- 삭제 전 관련 entity_relationships도 정리 필요 - -### 2.4 커스텀 필드 추가 - -**URL**: `POST /item-master/fields/custom` - -**추가 모달**: -``` -┌─────────────────────────────────────────────┐ -│ 커스텀 필드 추가 [닫기] │ -├─────────────────────────────────────────────┤ -│ │ -│ 소스 테이블: [products ▼] │ -│ │ -│ 필드 키: [_______________] │ -│ * 영문, 숫자, 언더스코어만 허용 │ -│ * 시스템 예약어 사용 불가 │ -│ │ -│ 필드명: [_______________] │ -│ │ -│ 필드 타입: [textbox ▼] │ -│ - textbox, number, dropdown, │ -│ checkbox, date, textarea │ -│ │ -│ 필수 여부: [ ] 필수 │ -│ │ -│ 기본값: [_______________] (선택) │ -│ │ -│ 옵션 (dropdown 선택 시): │ -│ ┌─────────────────────────────────────┐ │ -│ │ 라벨 │ 값 │ │ │ -│ │ [옵션1 ] │ [val1 ] │ [+ 추가] │ │ -│ └─────────────────────────────────────┘ │ -│ │ -│ [취소] [저장] │ -│ │ -└─────────────────────────────────────────────┘ -``` - -**저장 시 설정**: -```php -[ - 'tenant_id' => $currentTenantId, - 'source_table' => 'products', - 'source_column' => null, // 커스텀 필드는 null - 'storage_type' => 'json', // JSON 저장 - 'json_path' => 'attributes.custom_weight', // 저장 경로 - 'is_system' => false, -] -``` - -### 2.5 커스텀 필드 삭제 - -**기능**: 선택한 커스텀 필드 삭제 - -**주의사항**: -- 시스템 필드(storage_type = 'column')는 삭제 불가 -- 이미 데이터가 있는 경우 경고 -- entity_relationships 정리 필요 - ---- - -## 3. 데이터 구조 - -### 3.1 item_fields 테이블 (기존 + 확장) - -```sql -item_fields -├── id -├── tenant_id -├── field_key -- 고유 키 (code, name, custom_weight) -├── field_name -- 표시명 (품목코드, 품목명, 커스텀 무게) -├── field_type -- 필드 타입 (textbox, dropdown...) -├── is_required -- 필수 여부 -├── order_no -- 정렬 순서 -├── default_value -- 기본값 -├── options -- 드롭다운 옵션 JSON -├── properties -- 속성 JSON -├── validation_rules -- 검증 규칙 JSON -├── source_table -- 소스 테이블 (products, materials) -├── source_column -- 소스 컬럼 (시스템 필드만) -├── storage_type -- 저장 방식 (column, json) -├── json_path -- JSON 저장 경로 (커스텀 필드만) -├── is_active -├── created_at / updated_at / deleted_at -``` - -### 3.2 시스템 필드 정의 (SystemFields 상수) - -```php -// app/Constants/SystemFields.php 활용 -// 이미 구현된 상수 클래스에서 시딩 데이터 추출 - -class SystemFields -{ - public const PRODUCTS = [ - 'code', 'name', 'unit', 'product_type', 'category_id', - 'is_sellable', 'is_purchasable', 'is_producible', 'is_active', - 'certification_number', 'certification_date', 'certification_expiry', - // ... - ]; - - public const MATERIALS = [ - 'material_code', 'name', 'item_name', 'specification', - 'unit', 'category_id', 'is_inspection', 'search_tag', - // ... - ]; -} -``` - ---- - -## 4. 파일 구조 - -``` -mng/ -├── app/ -│ ├── Http/ -│ │ └── Controllers/ -│ │ └── ItemFieldController.php # 신규 -│ ├── Models/ -│ │ └── ItemField.php # 신규 (MNG 전용) -│ ├── Services/ -│ │ └── ItemFieldSeedingService.php # 신규: 시딩 로직 -│ └── Constants/ -│ └── SystemFieldDefinitions.php # 신규: 시딩 데이터 정의 -├── resources/ -│ └── views/ -│ └── item-fields/ -│ ├── index.blade.php # 메인 화면 -│ ├── _seeding-section.blade.php # 시딩 섹션 (partial) -│ ├── _custom-fields-section.blade.php # 커스텀 필드 섹션 -│ └── _create-modal.blade.php # 추가 모달 -└── routes/ - └── web.php # 라우트 추가 -``` - ---- - -## 5. 구현 단계 - -### Phase 1: 기반 구조 (0.5일) - -| 작업 | 파일 | 설명 | -|------|------|------| -| ItemField 모델 | `app/Models/ItemField.php` | API DB 참조 모델 | -| 상수 클래스 | `app/Constants/SystemFieldDefinitions.php` | 테이블별 시딩 데이터 | -| 라우트 등록 | `routes/web.php` | CRUD 라우트 | -| 메뉴 추가 | 사이드바 | 품목기준 > 필드 관리 | - -### Phase 2: 시딩 기능 (1일) - -| 작업 | 파일 | 설명 | -|------|------|------| -| 시딩 서비스 | `ItemFieldSeedingService.php` | 시딩/초기화 로직 | -| 메인 화면 | `index.blade.php` | 시딩 상태 표시 | -| 시딩 API | `ItemFieldController.php` | 시딩/초기화 엔드포인트 | -| 확인 다이얼로그 | JS | 초기화 확인 | - -### Phase 3: 커스텀 필드 관리 (1일) - -| 작업 | 파일 | 설명 | -|------|------|------| -| 커스텀 필드 목록 | `index.blade.php` | 목록 표시 | -| 추가 모달 | `_create-modal.blade.php` | 필드 추가 UI | -| 필드 키 검증 | `ItemFieldController.php` | 예약어/중복 체크 | -| 삭제 기능 | `ItemFieldController.php` | 삭제 처리 | - -### Phase 4: 테스트/마무리 (0.5일) - -| 작업 | 설명 | -|------|------| -| 시딩 테스트 | 전체/개별 시딩 | -| 초기화 테스트 | 초기화 후 재시딩 | -| 커스텀 필드 테스트 | 추가/삭제 | -| 예약어 검증 테스트 | 시스템 필드명 입력 시도 | - ---- - -## 6. API 엔드포인트 - -| 메서드 | URL | 설명 | -|--------|-----|------| -| GET | `/item-master/fields` | 필드 관리 메인 화면 | -| GET | `/item-master/fields/status` | 테이블별 시딩 상태 조회 (AJAX) | -| POST | `/item-master/fields/seed` | 시스템 필드 시딩 | -| POST | `/item-master/fields/reset` | 시스템 필드 초기화 | -| GET | `/item-master/fields/custom` | 커스텀 필드 목록 (AJAX) | -| POST | `/item-master/fields/custom` | 커스텀 필드 추가 | -| DELETE | `/item-master/fields/custom/{id}` | 커스텀 필드 삭제 | - ---- - -## 7. 체크리스트 - -### Phase 1: 기반 구조 -- [ ] ItemField 모델 생성 (API DB 참조) -- [ ] SystemFieldDefinitions 상수 클래스 생성 -- [ ] 라우트 등록 -- [ ] 사이드바 메뉴 추가 - -### Phase 2: 시딩 기능 -- [ ] ItemFieldSeedingService 생성 -- [ ] 테이블별 시딩 상태 조회 -- [ ] 단일 테이블 시딩 -- [ ] 전체 테이블 시딩 -- [ ] 시스템 필드 초기화 -- [ ] 확인 다이얼로그 - -### Phase 3: 커스텀 필드 -- [ ] 커스텀 필드 목록 조회 -- [ ] 커스텀 필드 추가 모달 -- [ ] field_key 예약어 검증 연동 -- [ ] 커스텀 필드 삭제 -- [ ] 일괄 삭제 - -### Phase 4: 마무리 -- [ ] 시딩 테스트 -- [ ] 초기화 테스트 -- [ ] 커스텀 필드 테스트 -- [ ] 에러 처리 - ---- - -## 8. SystemFieldDefinitions 상수 클래스 - -```php - 'code', 'field_name' => '품목코드', 'field_type' => 'textbox', 'is_required' => true, 'order_no' => 1], - ['field_key' => 'name', 'field_name' => '품목명', 'field_type' => 'textbox', 'is_required' => true, 'order_no' => 2], - ['field_key' => 'unit', 'field_name' => '단위', 'field_type' => 'dropdown', 'is_required' => true, 'order_no' => 3], - ['field_key' => 'product_type', 'field_name' => '제품유형', 'field_type' => 'dropdown', 'order_no' => 4, 'options' => [ - ['label' => '완제품', 'value' => 'FG'], - ['label' => '부품', 'value' => 'PT'], - ]], - ['field_key' => 'category_id', 'field_name' => '카테고리', 'field_type' => 'dropdown', 'order_no' => 5], - ['field_key' => 'is_sellable', 'field_name' => '판매가능', 'field_type' => 'checkbox', 'order_no' => 6, 'default_value' => 'true'], - ['field_key' => 'is_purchasable', 'field_name' => '구매가능', 'field_type' => 'checkbox', 'order_no' => 7], - ['field_key' => 'is_producible', 'field_name' => '생산가능', 'field_type' => 'checkbox', 'order_no' => 8, 'default_value' => 'true'], - ['field_key' => 'is_active', 'field_name' => '활성화', 'field_type' => 'checkbox', 'order_no' => 9, 'default_value' => 'true'], - ['field_key' => 'certification_number', 'field_name' => '인증번호', 'field_type' => 'textbox', 'order_no' => 10], - ['field_key' => 'certification_date', 'field_name' => '인증일자', 'field_type' => 'date', 'order_no' => 11], - ['field_key' => 'certification_expiry', 'field_name' => '인증만료일', 'field_type' => 'date', 'order_no' => 12], - ]; - - /** - * materials 테이블 시스템 필드 정의 - */ - public const MATERIALS = [ - ['field_key' => 'material_code', 'field_name' => '자재코드', 'field_type' => 'textbox', 'is_required' => true, 'order_no' => 1], - ['field_key' => 'name', 'field_name' => '자재명', 'field_type' => 'textbox', 'is_required' => true, 'order_no' => 2], - ['field_key' => 'item_name', 'field_name' => '품목명', 'field_type' => 'textbox', 'order_no' => 3], - ['field_key' => 'specification', 'field_name' => '규격', 'field_type' => 'textbox', 'order_no' => 4], - ['field_key' => 'unit', 'field_name' => '단위', 'field_type' => 'dropdown', 'is_required' => true, 'order_no' => 5], - ['field_key' => 'category_id', 'field_name' => '카테고리', 'field_type' => 'dropdown', 'order_no' => 6], - ['field_key' => 'is_inspection', 'field_name' => '검수필요', 'field_type' => 'checkbox', 'order_no' => 7], - ['field_key' => 'search_tag', 'field_name' => '검색태그', 'field_type' => 'textarea', 'order_no' => 8], - ]; - - /** - * product_components 테이블 (BOM) 시스템 필드 정의 - */ - public const PRODUCT_COMPONENTS = [ - ['field_key' => 'ref_type', 'field_name' => '참조유형', 'field_type' => 'dropdown', 'order_no' => 1, 'options' => [ - ['label' => '제품', 'value' => 'product'], - ['label' => '자재', 'value' => 'material'], - ]], - ['field_key' => 'ref_id', 'field_name' => '참조품목', 'field_type' => 'dropdown', 'order_no' => 2], - ['field_key' => 'quantity', 'field_name' => '수량', 'field_type' => 'number', 'is_required' => true, 'order_no' => 3], - ['field_key' => 'formula', 'field_name' => '계산공식', 'field_type' => 'textbox', 'order_no' => 4], - ['field_key' => 'note', 'field_name' => '비고', 'field_type' => 'textarea', 'order_no' => 5], - ]; - - /** - * material_inspections 테이블 시스템 필드 정의 - */ - public const MATERIAL_INSPECTIONS = [ - ['field_key' => 'inspection_date', 'field_name' => '검수일', 'field_type' => 'date', 'is_required' => true, 'order_no' => 1], - ['field_key' => 'inspector_id', 'field_name' => '검수자', 'field_type' => 'dropdown', 'order_no' => 2], - ['field_key' => 'status', 'field_name' => '검수상태', 'field_type' => 'dropdown', 'order_no' => 3, 'options' => [ - ['label' => '대기', 'value' => 'pending'], - ['label' => '진행중', 'value' => 'in_progress'], - ['label' => '완료', 'value' => 'completed'], - ['label' => '불합격', 'value' => 'rejected'], - ]], - ['field_key' => 'lot_no', 'field_name' => 'LOT번호', 'field_type' => 'textbox', 'order_no' => 4], - ['field_key' => 'quantity', 'field_name' => '검수수량', 'field_type' => 'number', 'order_no' => 5], - ['field_key' => 'passed_quantity', 'field_name' => '합격수량', 'field_type' => 'number', 'order_no' => 6], - ['field_key' => 'rejected_quantity', 'field_name' => '불합격수량', 'field_type' => 'number', 'order_no' => 7], - ['field_key' => 'note', 'field_name' => '비고', 'field_type' => 'textarea', 'order_no' => 8], - ]; - - /** - * material_receipts 테이블 시스템 필드 정의 - */ - public const MATERIAL_RECEIPTS = [ - ['field_key' => 'receipt_date', 'field_name' => '입고일', 'field_type' => 'date', 'is_required' => true, 'order_no' => 1], - ['field_key' => 'lot_no', 'field_name' => 'LOT번호', 'field_type' => 'textbox', 'order_no' => 2], - ['field_key' => 'quantity', 'field_name' => '입고수량', 'field_type' => 'number', 'is_required' => true, 'order_no' => 3], - ['field_key' => 'unit_price', 'field_name' => '단가', 'field_type' => 'number', 'order_no' => 4], - ['field_key' => 'total_price', 'field_name' => '금액', 'field_type' => 'number', 'order_no' => 5], - ['field_key' => 'supplier_id', 'field_name' => '공급업체', 'field_type' => 'dropdown', 'order_no' => 6], - ['field_key' => 'warehouse_id', 'field_name' => '입고창고', 'field_type' => 'dropdown', 'order_no' => 7], - ['field_key' => 'po_number', 'field_name' => '발주번호', 'field_type' => 'textbox', 'order_no' => 8], - ['field_key' => 'invoice_number', 'field_name' => '송장번호', 'field_type' => 'textbox', 'order_no' => 9], - ['field_key' => 'note', 'field_name' => '비고', 'field_type' => 'textarea', 'order_no' => 10], - ]; - - /** - * 소스 테이블 목록 - */ - public const SOURCE_TABLES = [ - 'products' => '제품', - 'materials' => '자재', - 'product_components' => 'BOM', - 'material_inspections' => '자재검수', - 'material_receipts' => '자재입고', - ]; - - /** - * 소스 테이블별 필드 정의 가져오기 - */ - public static function getFieldsFor(string $sourceTable): array - { - return match ($sourceTable) { - 'products' => self::PRODUCTS, - 'materials' => self::MATERIALS, - 'product_components' => self::PRODUCT_COMPONENTS, - 'material_inspections' => self::MATERIAL_INSPECTIONS, - 'material_receipts' => self::MATERIAL_RECEIPTS, - default => [], - }; - } - - /** - * 전체 테이블 필드 수 조회 - */ - public static function getTotalFieldCount(string $sourceTable): int - { - return count(self::getFieldsFor($sourceTable)); - } -} -``` - ---- - -## 9. 향후 확장 - -### 9.1 신규 도메인 추가 시 -1. `SystemFieldDefinitions`에 상수 추가 -2. `SOURCE_TABLES`에 테이블 추가 -3. MNG에서 해당 테이블 시딩 - -### 9.2 예정 도메인 -- [ ] 회계 (journals, accounts) -- [ ] 생산 (work_orders, production_records) -- [ ] 재고 (inventories, stock_movements) -- [ ] 품질 (quality_controls) - ---- - -## 변경 이력 - -| 날짜 | 내용 | -|------|------| -| 2025-12-09 | 문서 목적 수정: 권한 관리 → 품목기준 필드 시딩/관리 | -| 2025-12-09 | 초안 작성 | \ No newline at end of file From 71b03bcca0b6467009edf5d178dbe9e933aefe14 Mon Sep 17 00:00:00 2001 From: kent Date: Sat, 13 Dec 2025 15:36:26 +0900 Subject: [PATCH 2/2] =?UTF-8?q?docs:=205130=20=E2=86=92=20MNG=20=EC=8B=A4?= =?UTF-8?q?=ED=97=98=EC=8B=A4=20=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EA=B3=84=ED=9A=8D=20=EB=AC=B8=EC=84=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - S, A, M 메뉴 38개 마이그레이션 대상 정리 - 기술 스택 변환 정보 (PHP 8.4, Tailwind+DaisyUI+HTMX) - 참고 문서 목록 추가 (MNG 기술 표준, 레이아웃 패턴 등) - Phase 1: S 메뉴(15개) 마이그레이션 계획 수립 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- plans/5130-to-mng-migration-plan.md | 229 ++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 plans/5130-to-mng-migration-plan.md diff --git a/plans/5130-to-mng-migration-plan.md b/plans/5130-to-mng-migration-plan.md new file mode 100644 index 0000000..53b8c80 --- /dev/null +++ b/plans/5130-to-mng-migration-plan.md @@ -0,0 +1,229 @@ +# 5130 실험실 → MNG 실험실 마이그레이션 계획 + +> **작성일**: 2025-12-13 +> **목표**: 5130 프로젝트의 S, A, M 메뉴를 mng 실험실로 마이그레이션 +> **시작**: S 메뉴부터 + +--- + +## 1. 현황 분석 + +### 1.1 5130 S, A, M 메뉴 구조 (myheader.php) + +5130과 mng의 실험실 메뉴가 **동일한 구조**입니다. 현재 mng에서는 모두 "준비중" 상태이고, 5130에는 실제 구현된 페이지들이 있습니다. + +--- + +## 2. S (Strategy) 메뉴 - 15개 + +| # | 메뉴명 | 5130 경로 | 상태 | +|---|--------|-----------|------| +| 1 | 세무 전략 | `/strategy/index.php` | ⏳ 대기 | +| 2 | 노무 전략 | `/strategy/labor_index.php` | ⏳ 대기 | +| 3 | 채권추심 전략 | `/strategy/debt_index.php` | ⏳ 대기 | +| 4 | 스테이블코인 보고서 | `/stablecoin/index.php` | ⏳ 대기 | +| 5 | MRP 해외사례 | `/stablecoin/overseas_research.php` | ⏳ 대기 | +| 6 | 상담용 챗봇 전략 | `/strategy/chatbot_index.php` | ⏳ 대기 | +| 7 | KoDATA vs NICE API | `/strategy/kodatavsnice_index.php` | ⏳ 대기 | +| 8 | 바로빌 vs 팝빌 API | `/strategy/Decision_Matrix_Barobill_vs_Popbill.php` | ⏳ 대기 | +| 9 | 사내 지식 검색 시스템 | `/strategy/knowledge_search_system.php` | ⏳ 대기 | +| 10 | 챗봇 솔루션 비교 분석 | `/strategy/talkcompare.php` | ⏳ 대기 | +| 11 | RAG 스타트업 현황 | `/strategy/ragsystem.php` | ⏳ 대기 | +| 12 | 더존비즈온 분석 | `/strategy/douzonebizon.php` | ⏳ 대기 | +| 13 | Confluence vs Notion | `/strategy/ConfluencevsNotionAnalysis.php` | ⏳ 대기 | +| 14 | 차세대 QA 솔루션 | `/strategy/testsprite.php` | ⏳ 대기 | +| 15 | SAM 영업전략 | `/strategy/salesstrategy.php` | ⏳ 대기 | + +--- + +## 3. A (AI/Automation) 메뉴 - 12개 + +| # | 메뉴명 | 5130 경로 | 상태 | +|---|--------|-----------|------| +| 1 | 사업자등록증 OCR | `/ocr/index.php` | ⏳ 대기 | +| 2 | 웹 녹음 AI 요약 | `/voice/index.php` | ⏳ 대기 | +| 3 | 회의록 AI 요약 | `/voice_ai/index.php` | ⏳ 대기 | +| 4 | 업무협의록 AI 요약 | `/voice_ai_cnslt/index.php` | ⏳ 대기 | +| 5 | 운영자용 챗봇 | `/chatbot/index.php` | ⏳ 대기 | +| 6 | Vertex RAG 챗봇 | `/chatbot/rag_index.php` | ⏳ 대기 | +| 7 | 테넌트 지식 업로드 | `/chatbot/md_rag/upload.php` | ⏳ 대기 | +| 8 | 테넌트 챗봇 | `/chatbot/md_rag/index.php` | ⏳ 대기 | +| 9 | SAM AI 메뉴 이동 | `/ai_sam/index.php` | ⏳ 대기 | +| 10 | SAM AI 알람음 제작 | `/ai_sound/index.php` | ⏳ 대기 | +| 11 | GPS 출퇴근 관리 | `/geoattendance/index.php` | ⏳ 대기 | +| 12 | 기업개황 조회 | `/opendart/index.php` | ⏳ 대기 | + +--- + +## 4. M (Management) 메뉴 - 11개 + +| # | 메뉴명 | 5130 경로 | 상태 | +|---|--------|-----------|------| +| 1 | 바로빌 테넌트 관리 | `/tenant/index.php` | ⏳ 대기 | +| 2 | 전자세금계산서 전략 | `/strategy/electronicTaxInvoice_index.php` | ⏳ 대기 | +| 3 | 전자세금계산서 | `/etax/index.php` | ⏳ 대기 | +| 4 | 사업자등록번호 진위 확인 | `/tax/invalid_registered.php` | ⏳ 대기 | +| 5 | 영업관리 & 매니저 미팅관리 | `/salesmanagement/index.php` | ⏳ 대기 | +| 6 | 카드 세무항목 매칭 전략 | `/strategy/cardstrategy_index.php` | ⏳ 대기 | +| 7 | 한국 카드사 API 보고서 | `/strategy/KoreaCardApiReport.php` | ⏳ 대기 | +| 8 | 카드 사용내역 수집 후 매칭 | `/ecard/index.php` | ⏳ 대기 | +| 9 | 계좌입출금 내역 조회 API | `/eaccount/index.php` | ⏳ 대기 | +| 10 | 영업관리 시나리오 | `/sales_scenario/index.php` | ⏳ 대기 | +| 11 | 매니저 시나리오 | `/sales_manager_scenario/index.php` | ⏳ 대기 | + +--- + +## 5. 마이그레이션 전략 + +### 5.1 기술 스택 변환 + +| 5130 (레거시) | MNG (신규) | +|---------------|------------| +| PHP 7.3 | PHP 8.4 + Laravel 12 | +| 직접 PDO | Eloquent ORM | +| Bootstrap 5 + jQuery | Blade + Tailwind CSS + DaisyUI + HTMX + Vite | +| 직접 include | Blade 템플릿 | +| 세션 인증 | Laravel Sanctum | +| 단일 테넌트 | Multi-tenant + RBAC | + +> **참고 문서:** +> - PHP 8.4: `docs/architecture/system-overview.md` +> - Frontend 스택: `mng/docs/00_OVERVIEW.md`, `mng/docs/99_TECHNICAL_STANDARDS.md` +> +> **마이그레이션 작업 시 필수 참고:** +> | 문서 | 경로 | 용도 | +> |------|------|------| +> | MNG 기술 표준 | `mng/docs/99_TECHNICAL_STANDARDS.md` | 코드 표준, 네이밍 규칙 | +> | MNG 레이아웃 패턴 | `mng/docs/LAYOUT_PATTERN.md` | Blade 템플릿 구조 | +> | HTMX 패턴 | `mng/docs/HTMX_API_PATTERN.md` | 동적 기능 구현 시 | +> | MNG 핵심 규칙 | `mng/docs/MNG_CRITICAL_RULES.md` | 반드시 준수할 규칙 | +> | 개발 프로세스 | `mng/docs/DEV_PROCESS.md` | 작업 흐름 | +> | 5130 레거시 개요 | `docs/projects/legacy-5130/00_OVERVIEW.md` | 5130 시스템 이해 | + +### 5.2 마이그레이션 방식 + +**페이지 유형별 접근:** + +1. **정적 보고서 페이지** (Strategy 대부분) + - HTML/CSS 변환 → Blade 템플릿 + - 작업량: 낮음 + +2. **API 연동 페이지** (OCR, 챗봇, OpenDart 등) + - API 로직 → Service 클래스 + - 뷰 → Blade 템플릿 + - 작업량: 중간 + +3. **복잡한 기능 페이지** (녹음, RAG, 세금계산서 등) + - 전체 재설계 필요 + - 작업량: 높음 + +--- + +## 6. S 메뉴 마이그레이션 계획 (Phase 1) + +### 6.1 우선순위 분류 + +**1순위 - 정적 보고서 (빠른 마이그레이션 가능)** +- 세무 전략 +- 노무 전략 +- 채권추심 전략 +- 스테이블코인 보고서 +- MRP 해외사례 +- 상담용 챗봇 전략 +- KoDATA vs NICE API +- 바로빌 vs 팝빌 API +- 사내 지식 검색 시스템 +- 챗봇 솔루션 비교 분석 +- RAG 스타트업 현황 +- 더존비즈온 분석 +- Confluence vs Notion +- 차세대 QA 솔루션 +- SAM 영업전략 + +### 6.2 작업 단계 + +``` +Phase 1: S 메뉴 마이그레이션 +├── Step 1: 5130 소스 분석 (각 페이지 구조 파악) +├── Step 2: MNG 라우트 설정 (routes/web.php) +├── Step 3: 컨트롤러 생성 (LabStrategyController) +├── Step 4: Blade 뷰 변환 (resources/views/lab/strategy/) +├── Step 5: sidebar.blade.php 메뉴 링크 연결 +└── Step 6: 테스트 및 검증 +``` + +### 6.3 파일 구조 (MNG) + +``` +mng/ +├── app/Http/Controllers/Lab/ +│ └── StrategyController.php +├── resources/views/lab/ +│ └── strategy/ +│ ├── tax.blade.php # 세무 전략 +│ ├── labor.blade.php # 노무 전략 +│ ├── debt.blade.php # 채권추심 전략 +│ ├── stablecoin.blade.php # 스테이블코인 보고서 +│ ├── mrp-overseas.blade.php # MRP 해외사례 +│ ├── chatbot.blade.php # 상담용 챗봇 전략 +│ ├── kodata-vs-nice.blade.php +│ ├── barobill-vs-popbill.blade.php +│ ├── knowledge-search.blade.php +│ ├── chatbot-compare.blade.php +│ ├── rag-startups.blade.php +│ ├── douzone.blade.php +│ ├── confluence-vs-notion.blade.php +│ ├── qa-solution.blade.php +│ └── sales-strategy.blade.php +└── routes/web.php +``` + +### 6.4 라우트 설계 + +```php +// routes/web.php +Route::prefix('lab')->name('lab.')->middleware(['auth'])->group(function () { + // S. Strategy + Route::prefix('strategy')->name('strategy.')->group(function () { + Route::get('tax', [StrategyController::class, 'tax'])->name('tax'); + Route::get('labor', [StrategyController::class, 'labor'])->name('labor'); + Route::get('debt', [StrategyController::class, 'debt'])->name('debt'); + // ... 나머지 메뉴 + }); +}); +``` + +--- + +## 7. 진행 순서 + +### Week 1: S 메뉴 (15개) +1. 5130 소스 분석 (1일) +2. 라우트 + 컨트롤러 생성 (0.5일) +3. Blade 템플릿 변환 (3일) +4. 사이드바 연결 + 테스트 (0.5일) + +### Week 2: A 메뉴 (12개) +- API 연동 기능 포함으로 작업량 증가 + +### Week 3: M 메뉴 (11개) +- 외부 서비스 연동 (바로빌 등) 포함 + +--- + +## 8. 다음 단계 + +1. ☐ 첫 번째 메뉴 "세무 전략" 5130 소스 분석 +2. ☐ mng 라우트 및 컨트롤러 생성 +3. ☐ Blade 템플릿 변환 +4. ☐ 사이드바 메뉴 링크 연결 +5. ☐ 테스트 + +--- + +**진행할까요?** + +--- + +*작성: Claude Code* +*최종 수정: 2025-12-13* \ No newline at end of file