Merge remote-tracking branch 'origin/main'

# Conflicts:
#	plans/items-table-unification-plan.md
#	plans/mng-item-field-management-plan.md
This commit is contained in:
2025-12-16 09:27:37 +09:00
3 changed files with 656 additions and 417 deletions

View File

@@ -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*

View File

@@ -1,87 +1,153 @@
# 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` 테이블 구조로 전환하여:
`products`/`materials` 테이블을 `items` 테이블로 통합하여:
- BOM 관리 시 `child_item_type` 불필요 (ID만으로 유일 식별)
- 단일 쿼리로 모든 품목 조회 가능
- Service/Controller 코드 50% 감소
- 유지보수성 및 확장성 향상
- Item-Master 시스템과 일관된 구조
### 설계 변경 이력
- **2025-12-11 (v1)**: 단일 `items` 테이블에 모든 필드 통합
- **2025-12-11 (v2)**: 4개 테이블로 분리하여 성능 최적화
- `items`: 핵심 필드 (목록 조회, BOM 계산)
- `item_details`: 필수/인덱싱 필드 (검색, 필터링)
- `item_attributes`: 동적 속성 (JSON 기반)
- `item_bending`: 절곡 정보 (별도 관리)
### 현재 상황
- **개발 단계**: 미오픈 (레거시 호환 불필요)
- **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 │
└─────────────────────────────────────────────────────────────┘
```
| 구분 | Products | Materials | 통합 Items |
|------|----------|-----------|------------|
| ID | id (1~808) | id (1~417) | id (새 통합 ID) |
| 타입 | product_type (FG, PT, PRODUCT, SUBASSEMBLY, PART, CS) | material_type (SM, RM, CS) | item_type |
| 코드 | code | material_code | code |
| 이름 | name | name | name |
| 단위 | unit | unit | unit |
| 규격 | - | specification | attributes (JSON) |
| BOM | bom (JSON) | - | bom (JSON) |
| 속성 | attributes, options | attributes, options | attributes, options |
### 목표 구조
#### 데이터 현황 (2025-12-11)
- Products: **808건**
- Materials: **417건**
- 총합: **1,225건**
#### 참조 테이블 현황
| 테이블 | 참조 방식 | 데이터 |
|--------|----------|--------|
| product_components | parent_product_id, ref_type+ref_id | 16건 |
| bom_template_items | ref_type+ref_id | 1건 |
| orders | product_id | 0건 |
| order_items | product_id | 0건 |
| quotes | product_id | 0건 |
| material_receipts | material_id | 0건 |
| lots | material_id | 0건 |
| price_histories | item_type+item_id | 조회 필요 |
```
┌─────────────────────────────────────────────────────────────┐
│ 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 1: 테이블 생성 및 데이터 이관
## Phase 0: 데이터 정규화
### 1.1 items 테이블 (핵심 정보)
### 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 COMMENT '테넌트 ID',
tenant_id BIGINT UNSIGNED NOT NULL,
-- 기본 정보
item_type VARCHAR(15) NOT NULL COMMENT '품목 유형: FG, PT, SM, RM, CS, PRODUCT, SUBASSEMBLY, PART',
code VARCHAR(100) NOT NULL COMMENT '품목 코드',
name VARCHAR(255) NOT NULL COMMENT '품목명',
unit VARCHAR(20) NULL COMMENT '단위',
category_id BIGINT UNSIGNED NULL COMMENT '카테고리 ID',
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
bom JSON NULL COMMENT 'BOM [{child_item_id, quantity}, ...]',
-- BOM (JSON)
bom JSON NULL COMMENT '[{child_item_id, quantity}, ...]',
-- 상태
is_active TINYINT(1) DEFAULT 1 COMMENT '활성 상태',
-- 레거시 참조 (전환기간용)
legacy_table VARCHAR(20) NULL COMMENT '원본 테이블: products | materials',
legacy_id BIGINT UNSIGNED NULL COMMENT '원본 테이블 ID',
is_active TINYINT(1) DEFAULT 1,
-- 감사 필드
created_by BIGINT UNSIGNED NULL COMMENT '생성자',
updated_by BIGINT UNSIGNED NULL COMMENT '수정자',
deleted_by BIGINT UNSIGNED NULL COMMENT '삭제자',
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,
@@ -90,51 +156,39 @@ CREATE TABLE items (
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),
INDEX idx_items_tenant_active (tenant_id, is_active),
INDEX idx_items_legacy (legacy_table, legacy_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
COMMENT='Products + Materials 통합 품목 테이블 (핵심 정보)';
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```
### 1.2 item_details 테이블 (필수/인덱싱 필드)
### 1.2 item_details 테이블 (확장 필드)
```sql
CREATE TABLE item_details (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
item_id BIGINT UNSIGNED NOT NULL COMMENT 'items 테이블 FK',
item_id BIGINT UNSIGNED NOT NULL,
-- Products 전용 (인덱싱/필터링)
is_sellable TINYINT(1) DEFAULT 1 COMMENT '판매 가능',
is_purchasable TINYINT(1) DEFAULT 0 COMMENT '구매 가능',
is_producible TINYINT(1) DEFAULT 0 COMMENT '생산 가능',
safety_stock INT NULL COMMENT '안전 재고',
lead_time INT NULL COMMENT '리드타임 (일)',
is_variable_size TINYINT(1) DEFAULT 0 COMMENT '가변 사이즈 여부',
product_category VARCHAR(50) NULL COMMENT '제품 카테고리',
part_type VARCHAR(50) NULL COMMENT '부품 유형',
-- 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' COMMENT '검사 대상 여부',
-- Materials 전용 필드
is_inspection VARCHAR(1) DEFAULT 'N',
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
-- 인덱스
UNIQUE KEY uq_item_details_item_id (item_id),
INDEX idx_item_details_sellable (is_sellable),
INDEX idx_item_details_purchasable (is_purchasable),
INDEX idx_item_details_producible (is_producible),
INDEX idx_item_details_inspection (is_inspection),
-- 외래키
FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='품목 상세 정보 (필수/인덱싱 필드)';
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```
### 1.3 item_attributes 테이블 (동적 속성)
@@ -142,214 +196,64 @@ COMMENT='품목 상세 정보 (필수/인덱싱 필드)';
```sql
CREATE TABLE item_attributes (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
item_id BIGINT UNSIGNED NOT NULL COMMENT 'items 테이블 FK',
item_id BIGINT UNSIGNED NOT NULL,
-- 동적 속성
attributes JSON NULL COMMENT '동적 속성 (규격 정보 포함)',
options JSON NULL COMMENT '옵션 (파일, 인증, 설명 등)',
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
COMMENT='품목 동적 속성 (JSON 기반)';
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```
### 1.4 item_bending 테이블 (절곡 정보)
```sql
CREATE TABLE item_bending (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
item_id BIGINT UNSIGNED NOT NULL COMMENT 'items 테이블 FK',
-- 절곡 정보
diagram VARCHAR(255) NULL COMMENT '절곡 도면 파일',
details JSON NULL COMMENT '절곡 상세 정보',
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
-- 인덱스
UNIQUE KEY uq_item_bending_item_id (item_id),
-- 외래키
FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='품목 절곡 정보';
```
### 1.5 item_id_mappings 테이블 (전환기간용)
```sql
CREATE TABLE item_id_mappings (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
new_item_id BIGINT UNSIGNED NOT NULL COMMENT '새 items 테이블 ID',
legacy_table VARCHAR(20) NOT NULL COMMENT '원본 테이블: products | materials',
legacy_id BIGINT UNSIGNED NOT NULL COMMENT '원본 테이블 ID',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- 인덱스
UNIQUE KEY uq_legacy (legacy_table, legacy_id),
INDEX idx_new_item (new_item_id),
-- 외래키
FOREIGN KEY (new_item_id) REFERENCES items(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Products/Materials → Items ID 매핑 (전환기간용)';
```
### 1.6 데이터 이관 스크립트
### 1.4 데이터 이관 스크립트
```php
// 1. Products → Items 이관
// 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,
legacy_table, legacy_id
)
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,
'products', id
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
");
// 2. Products → item_details 이관
// Materials → Items
DB::statement("
INSERT INTO item_details (
item_id, is_sellable, is_purchasable, is_producible,
safety_stock, lead_time, is_variable_size,
product_category, part_type, is_inspection,
created_at, updated_at
)
SELECT
i.id, p.is_sellable, p.is_purchasable, p.is_producible,
p.safety_stock, p.lead_time, p.is_variable_size,
p.product_category, p.part_type, 'N',
p.created_at, p.updated_at
FROM products p
JOIN items i ON i.legacy_table = 'products' AND i.legacy_id = p.id
");
// 3. Products → item_attributes 이관
DB::statement("
INSERT INTO item_attributes (
item_id, attributes, options, created_at, updated_at
)
SELECT
i.id, p.attributes, p.options, p.created_at, p.updated_at
FROM products p
JOIN items i ON i.legacy_table = 'products' AND i.legacy_id = p.id
");
// 4. Products → item_bending 이관 (절곡 정보 있는 경우만)
DB::statement("
INSERT INTO item_bending (
item_id, diagram, details, created_at, updated_at
)
SELECT
i.id, p.bending_diagram, p.bending_details, p.created_at, p.updated_at
FROM products p
JOIN items i ON i.legacy_table = 'products' AND i.legacy_id = p.id
WHERE p.bending_diagram IS NOT NULL OR p.bending_details IS NOT NULL
");
// 5. 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,
legacy_table, legacy_id
)
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,
'materials', id
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
");
// 6. Materials → item_details 이관
DB::statement("
INSERT INTO item_details (
item_id, is_sellable, is_purchasable, is_producible,
safety_stock, lead_time, is_variable_size,
product_category, part_type, is_inspection,
created_at, updated_at
)
SELECT
i.id, 0, 1, 0,
NULL, NULL, 0,
NULL, NULL, m.is_inspection,
m.created_at, m.updated_at
FROM materials m
JOIN items i ON i.legacy_table = 'materials' AND i.legacy_id = m.id
");
// 7. Materials → item_attributes 이관
// options에 기존 Materials 전용 필드 포함
DB::statement("
INSERT INTO item_attributes (
item_id, attributes, options, created_at, updated_at
)
SELECT
i.id,
m.attributes,
JSON_OBJECT(
'specification', m.specification,
'item_name', m.item_name,
'search_tag', m.search_tag,
'remarks', m.remarks
),
m.created_at, m.updated_at
FROM materials m
JOIN items i ON i.legacy_table = 'materials' AND i.legacy_id = m.id
");
// 8. ID 매핑 테이블 생성
DB::statement("
INSERT INTO item_id_mappings (new_item_id, legacy_table, legacy_id)
SELECT id, legacy_table, legacy_id
FROM items
WHERE legacy_table IS NOT NULL AND legacy_id IS NOT NULL
");
```
### 1.7 체크리스트
### 1.5 체크리스트
- [ ] items 테이블 마이그레이션 생성
- [ ] item_details 테이블 마이그레이션 생성
- [ ] item_attributes 테이블 마이그레이션 생성
- [ ] item_bending 테이블 마이그레이션 생성
- [ ] item_id_mappings 테이블 마이그레이션 생성
- [ ] Products 데이터 이관 스크립트 실행
- [ ] Materials 데이터 이관 스크립트 실행
- [ ] 데이터 검증 (건수, 필드값 일치)
- [ ] items 마이그레이션 생성
- [ ] item_details 마이그레이션 생성
- [ ] item_attributes 마이그레이션 생성
- [ ] 데이터 이관 스크립트 실행
- [ ] 건수 검증 (1,225건)
---
## Phase 2: Item 모델 Service 생성
## Phase 2: Item 모델 + Service 생성
### 2.1 Item 모델
```php
// app/Models/Items/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', 'created_by', 'updated_by',
'tenant_id', 'item_type', 'code', 'name', 'unit',
'category_id', 'bom', 'is_active',
];
protected $casts = [
@@ -360,51 +264,42 @@ class Item extends Model
// 1:1 관계
public function details() { return $this->hasOne(ItemDetail::class); }
public function attributes() { return $this->hasOne(ItemAttribute::class); }
public function bending() { return $this->hasOne(ItemBending::class); }
// 타입별 스코프
public function scopeType($q, string $type) { return $q->where('item_type', $type); }
public function scopeProducts($q) { return $q->whereIn('item_type', ['FG', 'PT', 'PRODUCT', 'SUBASSEMBLY', 'PART']); }
public function scopeMaterials($q) { return $q->whereIn('item_type', ['SM', 'RM', 'CS']); }
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 관련 모델
### 2.2 ItemService
```php
// app/Models/Items/ItemDetail.php
class ItemDetail extends Model
// app/Services/ItemService.php
class ItemService extends Service
{
protected $fillable = [
'item_id', 'is_sellable', 'is_purchasable', 'is_producible',
'safety_stock', 'lead_time', 'is_variable_size',
'product_category', 'part_type', 'is_inspection',
];
public function index(array $params): LengthAwarePaginator
{
$query = Item::where('tenant_id', $this->tenantId());
protected $casts = [
'is_sellable' => 'boolean',
'is_purchasable' => 'boolean',
'is_producible' => 'boolean',
'is_variable_size' => 'boolean',
];
// item_type 필터
if ($itemType = $params['item_type'] ?? null) {
$query->where('item_type', strtoupper($itemType));
}
public function item() { return $this->belongsTo(Item::class); }
}
// 검색
if ($search = $params['search'] ?? null) {
$query->where(fn($q) => $q
->where('code', 'like', "%{$search}%")
->orWhere('name', 'like', "%{$search}%")
);
}
// app/Models/Items/ItemAttribute.php
class ItemAttribute extends Model
{
protected $fillable = ['item_id', 'attributes', 'options'];
protected $casts = ['attributes' => 'array', 'options' => 'array'];
public function item() { return $this->belongsTo(Item::class); }
}
// app/Models/Items/ItemBending.php
class ItemBending extends Model
{
protected $fillable = ['item_id', 'diagram', 'details'];
protected $casts = ['details' => 'array'];
public function item() { return $this->belongsTo(Item::class); }
return $query->with(['details', 'attributes'])->paginate($params['per_page'] ?? 15);
}
}
```
@@ -413,138 +308,279 @@ class ItemBending extends Model
- [ ] Item 모델 생성
- [ ] ItemDetail 모델 생성
- [ ] ItemAttribute 모델 생성
- [ ] ItemBending 모델 생성
- [ ] UnifiedItemsService 생성
- [ ] ItemRequest (FormRequest) 생성
- [ ] 단위 테스트 작성
- [ ] ItemService 생성
- [ ] ItemRequest 생성
---
## Phase 3: API 엔드포인트 전환
## Phase 3: Item-Master 연동 수정
### 3.1 신규 API (v2)
### 3.1 ItemPage.source_table 변경
| 메서드 | 엔드포인트 | 설명 |
|--------|-----------|------|
| GET | /api/v2/items | 통합 품목 목록 |
| GET | /api/v2/items/{id} | 품목 상세 (with details, attributes, bending) |
| POST | /api/v2/items | 품목 생성 |
| PUT | /api/v2/items/{id} | 품목 수정 |
| DELETE | /api/v2/items/{id} | 품목 삭제 |
| DELETE | /api/v2/items/batch | 일괄 삭제 |
```php
// app/Models/ItemMaster/ItemPage.php
### 3.2 체크리스트
// 기존
$mapping = [
'products' => \App\Models\Product::class,
'materials' => \App\Models\Material::class,
];
- [ ] v2 라우트 정의
- [ ] UnifiedItemsController 생성
- [ ] Swagger 문서 작성 (v2)
- [ ] v1 API 호환 레이어 구현
- [ ] API 테스트 작성
// 변경
$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: 참조 테이블 마이그레이션
## Phase 4: API 통합
### 4.1 참조 테이블 목록
### 4.1 API 구조 변경
| 테이블 | 변경 필요 컬럼 | 작업 |
|--------|--------------|------|
| 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 | 마이그레이션 |
| quotes | product_id → item_id | 마이그레이션 |
| material_receipts | material_id → item_id | 마이그레이션 |
| lots | material_id → item_id | 마이그레이션 |
| price_histories | item_type+item_id → item_id | 마이그레이션 |
```
기존 (분리):
/api/v1/products → ProductController
/api/v1/products/materials → MaterialController
### 4.2 체크리스트
통합 후:
/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 5: 레거시 정리
## Phase 6: 정리
### 5.1 체크리스트
### 6.1 체크리스트
- [ ] 모든 참조 테이블 전환 완료 확인
- [ ] v1 API 사용량 모니터링 (0 확인)
- [ ] items 레거시 컬럼 제거 (legacy_table, legacy_id)
- [ ] item_id_mappings 테이블 제거
- [ ] products 테이블 제
- [ ] materials 테이블 제
- [ ] 최종 테스트
- [ ] 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, legacy_table, legacy_id
│ timestamps + soft deletes
└───────────────────────────┬─────────────────────────────────────┘
│ 1:1
┌───────────────────┼───────────────────┬─────────────────┐
▼ │
┌───────────────┌───────────────┐ ┌───────────────
item_details │item_attributes│ │ item_bending │
├───────────────├───────────────┤ ├───────────────
│ item_id (UK) │ item_id (UK) item_id (UK) │
│ is_sellable │ │ attributes │ diagram │
is_purchasable│ │ options │ details
is_producible │ └───────────────┘ └───────────────┘
safety_stock │
│ lead_time │ ┌───────────────────────────────────┐ │
│ is_variable.. │ │ item_id_mappings │ │
│ product_cat.. │ ├───────────────────────────────────┤ │
│ part_type │ │ new_item_id ──────────────────────┼────────┘
│ is_inspection │ │ legacy_table, legacy_id │
└───────────────┘ └───────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ 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 계산 로직
| 패턴 | 빈도 | JOIN | 쿼리 |
|------|------|------|------|
| 목록 조회 | 80%+ | ❌ | `SELECT * FROM items WHERE ...` |
| 검색 | 빈번 | ❌ | `SELECT * FROM items WHERE code LIKE ...` |
| BOM 계산 | 5% | 자기참조 | `SELECT * FROM items WHERE id IN (...)` |
| 상세 조회 | 15% | ✅ 3-4테이블 | `SELECT * FROM items JOIN item_details ...` |
| 필터 (판매가능) | 간헐 | ✅ 1테이블 | `JOIN item_details WHERE is_sellable = 1` |
### 통합 전
```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');
```
---
## 일정 계획
## 프론트엔드 전달 사항
| Phase | 작업 내용 | 상태 |
|-------|----------|------|
| Phase 1 | 테이블 생성 + 데이터 이관 | ⬜ 대기 |
| Phase 2 | 모델 + Service 생성 | ⬜ 대기 |
| Phase 3 | API 엔드포인트 전환 | ⬜ 대기 |
| Phase 4 | 참조 테이블 마이그레이션 | ⬜ 대기 |
| Phase 5 | 레거시 정리 | ⬜ 대기 |
### 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`로 변경
---
## 리스크 및 대응
## 일정
### 리스크 1: ID 변경으로 인한 기존 연동 깨짐
- **대응**: legacy_table + legacy_id 컬럼으로 매핑 유지
- **전환기간**: v1 API 호환 레이어 제공
| Phase | 작업 | 상태 |
|-------|------|------|
| 0 | 데이터 정규화 (비표준 item_type/BOM 삭제) | ⬜ |
| 1 | items 테이블 생성 + 데이터 이관 | ⬜ |
| 2 | Item 모델 + Service 생성 | ⬜ |
| 3 | Item-Master 연동 수정 | ⬜ |
| 4 | API 통합 | ⬜ |
| 5 | 참조 테이블 마이그레이션 | ⬜ |
| 6 | 정리 | ⬜ |
### 리스크 2: 데이터 이관 중 누락
- **대응**: 이관 전후 건수 검증
- **롤백 계획**: 이관 스크립트 역순 실행
---
### 리스크 3: JOIN 성능
- **분석**: 1:1 JOIN + UNIQUE 인덱스로 10만건도 수 ms 이내
- **모니터링**: 쿼리 성능 측정
## 리스크
| 리스크 | 대응 |
|--------|------|
| 데이터 이관 누락 | 이관 전후 건수 검증 |
| Item-Master 연동 오류 | source_table 변경 전 테스트 |
| BOM 순환 참조 | 저장 시 검증 로직 추가 |
| Code 중복 (products↔materials) | 개발 중이므로 품목관리 완료 후 경동기업 데이터 전체 삭제 후 재세팅 예정. 중복 데이터는 삭제 처리 |
---
## 롤백 계획
각 Phase는 독립적 마이그레이션으로 구성:
```bash
# Phase 1 롤백
php artisan migrate:rollback --step=3
# 데이터 복구 (products/materials 테이블 유지 상태에서)
# 신규 테이블만 삭제하면 됨
```

View File

@@ -4,36 +4,10 @@
**작성일**: 2025-12-09
**상태**: 계획 중
---
## 📋 필수 참조 문서
개발 전 반드시 확인해야 할 문서 목록입니다.
### 🔴 필수 확인
| 문서 | 경로 | 확인 내용 |
|------|------|----------|
| **품목 정책** | `docs/rules/item-policy.md` | item_type 체계, 필드 키 예약어, API 규칙 |
| **DB 스키마** | `docs/specs/database-schema.md` | item_fields, item_pages 테이블 구조 |
| **API 개발 규칙** | `docs/standards/api-rules.md` | Service-First, FormRequest, i18n |
### 🟡 참고 문서
| 문서 | 경로 | 내용 |
|------|------|------|
| **품목 연동 설계** | `docs/specs/item-master-integration.md` | BE-FE 연동 아키텍처 |
| **프론트엔드 가이드** | `docs/front/item-master-guide.md` | 페이지-섹션-필드 구조 |
| **API 플로우 테스트** | `docs/plans/flow-tests/item-master-*.json` | ItemMaster API 테스트 시나리오 |
### 📁 관련 코드 파일
| 파일 | 경로 | 역할 |
|------|------|------|
| SystemFields | `api/app/Constants/SystemFields.php` | 시스템 예약어 상수 |
| ItemTypeSeeder | `api/database/seeders/ItemTypeSeeder.php` | item_type 코드 시딩 |
| ItemFieldService | `api/app/Services/ItemMaster/ItemFieldService.php` | 필드 CRUD 서비스 |
**관련 문서**:
- `docs/specs/item-master-field-integration.md`
- `docs/specs/item-master-field-key-validation.md`
- `docs/specs/ITEM-MASTER-INDEX.md`
---