# [ARCHIVED] 경동기업(5130) 레거시 → SAM 전체 데이터 마이그레이션 계획

> ⚠️ **이 문서는 분리되었습니다** (2026-01-28)
>
> 이 통합 문서는 다음 2개 문서로 분리되었습니다:
>
> 1. **📦 품목/단가/BOM**: [`kd-items-migration-plan.md`](./kd-items-migration-plan.md) ← **먼저 작업**
> 2. **📋 입고/재고/주문**: [`kd-orders-migration-plan.md`](./kd-orders-migration-plan.md) ← 품목 완료 후 작업
>
> 아래 내용은 참고용으로 보존됩니다.

---

> **작성일**: 2026-01-28
> **목적**: 경동기업 레거시 시스템(5130/)의 **전체 운영 데이터**를 SAM으로 이관
> **기준 문서**: `5130/` 폴더 분석 결과
> **상태**: ✅ 문서 분리 완료 (2026-01-28)
> **데이터 규모**: ~30,000+ 레코드 (items + prices + receipts + orders)

---

## 🚀 새 세션 시작 가이드 (Quick Start)

### 이 문서만 보고 작업을 재개하려면:

```bash
# 1. Docker 서비스 확인
docker ps | grep sam

# 2. 레거시 DB (chandj) 접속 테스트
docker exec sam-mysql-1 mysql -uroot -proot chandj -e "SELECT COUNT(*) FROM models;"

# 3. 현재 진행 상태 확인
# → 아래 "📍 현재 진행 상태" 섹션 참조

# 4. 다음 작업 시작
# → "📍 현재 진행 상태" > "다음 작업" 참조
```

### 환경 정보

| 항목 | 값 |
|------|-----|
| **프로젝트 루트** | `/Users/kent/Works/@KD_SAM/SAM` |
| **레거시 소스** | `5130/` (프로젝트 루트 직하) |
| **API 프로젝트** | `api/` |
| **Docker 컨테이너** | `sam-mysql-1` |
| **레거시 DB** | `chandj` (MySQL) |
| **SAM DB** | `samdb` (MySQL) ⚠️ |
| **대상 테넌트 ID** | `287` (경동기업) |
| **생성자 사용자 ID** | `1` |

### DB 접속 명령어

```bash
# 레거시 DB (chandj) 접속
docker exec -it sam-mysql-1 mysql -uroot -proot chandj

# SAM DB 접속
docker exec -it sam-mysql-1 mysql -uroot -proot samdb

# 레거시 테이블 목록 확인
docker exec sam-mysql-1 mysql -uroot -proot chandj -e "SHOW TABLES;"

# SAM items 테이블 확인
docker exec sam-mysql-1 mysql -uroot -proot samdb -e "SELECT COUNT(*) FROM items WHERE tenant_id=287;"
```

### 전제 조건 (작업 전 확인)

- [x] Docker 서비스 실행 중
- [x] `sam-mysql-1` 컨테이너 실행 중
- [x] chandj 데이터베이스 접근 가능
- [ ] SAM items 마이그레이션 실행 완료 (`php artisan migrate`)
- [ ] SAM prices 마이그레이션 실행 완료

---

## 📍 현재 진행 상태

| 항목 | 내용 |
|------|------|
| **마지막 완료 작업** | 전체 범위 분석 완료 (KDunitprice 603건, output 24,564건 발견) |
| **다음 작업** | Phase 1.0: KDunitprice → items 마스터 INSERT |
| **진행률** | 2/6 (33%) - 분석 완료, 구현 대기 |
| **마지막 업데이트** | 2026-01-28 |

### 다음 작업 상세

**Phase 1.0: KDunitprice → items (마스터) INSERT** ⭐ 최우선!

1. KDunitprice 데이터 확인:
   ```bash
   docker exec sam-mysql-1 mysql -uroot -proot chandj -e "SELECT COUNT(*), item_div FROM KDunitprice GROUP BY item_div;"
   ```

2. 섹션 5.0의 SQL 쿼리를 SAM DB에서 실행:
   - KDunitprice → items (603건)
   - KDunitprice → prices (603건)

3. 중복 확인 후 추가 items 생성:
   - models, category_l4 중 KDunitprice에 없는 것만 추가

4. ⚠️ 실행 전 사용자 승인 필요

---

## 0. 성공 기준

| 기준 | 목표값 | 확인 방법 |
|------|-------|----------|
| **items 합계** | **~800건** | `SELECT COUNT(*) FROM items WHERE tenant_id=287` |
| items (FG) | ~100건 | `SELECT COUNT(*) FROM items WHERE tenant_id=287 AND item_type='FG'` |
| items (PT) | ~250건 | `SELECT COUNT(*) FROM items WHERE tenant_id=287 AND item_type='PT'` |
| items (SM) | ~300건 | `SELECT COUNT(*) FROM items WHERE tenant_id=287 AND item_type='SM'` |
| items (RM) | ~100건 | `SELECT COUNT(*) FROM items WHERE tenant_id=287 AND item_type='RM'` |
| items (CS) | ~50건 | `SELECT COUNT(*) FROM items WHERE tenant_id=287 AND item_type='CS'` |
| **prices 합계** | **~500건** | `SELECT COUNT(*) FROM prices WHERE tenant_id=287` |
| **BOM 관계** | ~300건 | `SELECT COUNT(*) FROM item_bom_items WHERE tenant_id=287` |
| **입고 기록** | ~2,300건 | `SELECT COUNT(*) FROM item_receipts WHERE tenant_id=287` |
| **주문 기록** | ~24,600건 | `SELECT COUNT(*) FROM orders WHERE tenant_id=287` |
| **로트 기록** | ~200건 | `SELECT COUNT(*) FROM lots WHERE tenant_id=287` |
| code 유일성 | 100% | `SELECT code, COUNT(*) FROM items WHERE tenant_id=287 GROUP BY code HAVING COUNT(*) > 1` (0건) |
| API 테스트 | 100% | `/api/v1/items` 목록 조회 성공 |

---

## 1. 개요

### 1.1 배경

경동기업은 **모델(Model) + 제품(Product)** 분리 구조를 사용하지만, SAM은 **통합 items 테이블** 구조로 기획됨. 레거시 5130/ 폴더의 데이터를 SAM 구조에 맞게 변환하여 이관 필요.

### 1.2 핵심 차이점

```
┌────────────────────────────────────────────────────────────────────────────┐
│  레거시 (chandj)                         →    SAM (samdb)                   │
├────────────────────────────────────────────────────────────────────────────┤
│  📦 품목 마스터                                                             │
│  ─────────────────────────────────────────────────────────────────────────  │
│  KDunitprice (603건, 핵심!)               →    items (마스터, code로 구분)    │
│  models (18건)                           →    items (FG)                    │
│  parts, parts_sub (170건)                →    item_bom_items                │
│  category_l1~l4                          →    items 카테고리 참조            │
│  guiderail, bottombar, bending 등        →    item_details                  │
│                                                                             │
│  💰 단가 정보                                                               │
│  ─────────────────────────────────────────────────────────────────────────  │
│  price_* (10개 테이블)                   →    prices                        │
│  KDunitprice.출고가/입고가               →    prices (기본가)               │
│                                                                             │
│  📥 입고/재고                                                               │
│  ─────────────────────────────────────────────────────────────────────────  │
│  instock (2,286건)                       →    item_receipts + stocks        │
│  lot, lot_sales                          →    lots + lot_sales              │
│                                                                             │
│  📋 주문/출고                                                               │
│  ─────────────────────────────────────────────────────────────────────────  │
│  output (24,564건)                       →    orders + order_items          │
│  output.iList (JSON 파일 참조)           →    orders.options                │
│  estimate                                →    orders (type=견적)            │
└────────────────────────────────────────────────────────────────────────────┘
```

### 1.2.1 중복 제거 전략 ⭐

```
┌────────────────────────────────────────────────────────────────────────────┐
│  🎯 핵심: KDunitprice가 마스터, code 필드로 중복 방지                        │
├────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  1️⃣ KDunitprice (603건) → items 먼저 생성                                  │
│     - item_div로 item_type 결정                                            │
│     - code = 품목코드 그대로 사용                                           │
│                                                                             │
│  2️⃣ price_* 테이블 → items 중복 확인 후 prices만 생성                      │
│     - code로 items 조회                                                    │
│     - 존재하면 → prices만 추가 (item_id 연결)                              │
│     - 없으면 → items 생성 후 prices 추가                                   │
│                                                                             │
│  3️⃣ 매핑 테이블 불필요                                                     │
│     - item_id_mappings ❌ (양방향 조회 불필요)                              │
│     - chandj는 손 안댐, samdb에만 밀어 넣음                                 │
│                                                                             │
└────────────────────────────────────────────────────────────────────────────┘
```

### 1.3 SAM items 구조 (Target)

```sql
-- items 테이블 (tenant_id=287 for 경동기업)
CREATE TABLE items (
    id BIGINT PRIMARY KEY,
    tenant_id BIGINT NOT NULL,           -- 287 (경동기업)
    item_type VARCHAR(15) NOT NULL,      -- FG, PT, SM, RM, CS
    code VARCHAR(100) NOT NULL,          -- 품목코드
    name VARCHAR(255) NOT NULL,          -- 품목명
    unit VARCHAR(20),                    -- 단위
    category_id BIGINT,                  -- 카테고리 ID
    bom JSON,                            -- [{child_item_id, quantity}, ...]
    attributes JSON,                     -- 동적 필드 값
    options JSON,                        -- 추가 옵션
    description TEXT,                    -- 설명
    is_active BOOLEAN DEFAULT TRUE,
    created_by, updated_by, deleted_by, timestamps, soft_deletes
);
```

### 1.4 item_type 분류

| SAM item_type | 설명 | 레거시 소스 |
|---------------|------|-------------|
| **FG** | 완제품 (Finished Goods) | KDunitprice[제품], KDunitprice[상품], models |
| **PT** | 부품 (Parts) | KDunitprice[반제품], parts, parts_sub, category_l4 |
| **SM** | 부자재 (Sub-Materials) | KDunitprice[부재료], price_* 테이블들 |
| **RM** | 원자재 (Raw Materials) | KDunitprice[원재료], price_raw_materials |
| **CS** | 소모품 (Consumables) | KDunitprice[무형상품], 기타 |

### 1.4.1 KDunitprice.item_div → item_type 매핑 ⭐

```sql
-- KDunitprice.item_div 값 목록 (603건 중)
-- [제품], [상품], [부재료], [원재료], [반제품], [무형상품]

CASE item_div
    WHEN '[제품]' THEN 'FG'    -- 완제품
    WHEN '[상품]' THEN 'FG'    -- 상품도 완제품으로 분류
    WHEN '[반제품]' THEN 'PT'  -- 반제품 = 부품
    WHEN '[부재료]' THEN 'SM'  -- 부자재
    WHEN '[원재료]' THEN 'RM'  -- 원자재
    WHEN '[무형상품]' THEN 'CS' -- 소모품/무형
    ELSE 'SM'                  -- 기본값
END AS item_type
```

---

## 2. 레거시 DB 구조 분석

### 2.1 핵심 테이블 및 레코드 수 (전체 목록)

#### 📦 품목 마스터 테이블

| 테이블 | 레코드 수 | 역할 | SAM 매핑 |
|--------|----------|------|----------|
| **`KDunitprice`** ⭐ | **603** | **품목 마스터 (핵심!)** | **items (마스터)** |
| `models` | 18 | 모델 마스터 (스크린/철재) | items (FG) |
| `BDmodels` | 59 | 모델별 BOM + 단가 (JSON) | item_bom_items + prices |
| `parts` | 36 | 부품 | item_bom_items |
| `parts_sub` | 134 | 하위 부품 | item_bom_items |
| `category_l1` | 2 | 1단계 카테고리 (스크린/철재) | 참조용 |
| `category_l2` | 14 | 2단계 카테고리 | 참조용 |
| `category_l3` | 24 | 3단계 카테고리 | 참조용 |
| `category_l4` | 37 | 4단계 카테고리 (부품) | items (PT) |
| `item_list` | 5+ | 품목 마스터 | items (PT) |

#### 🔧 제품 상세 테이블

| 테이블 | 레코드 수 | 역할 | SAM 매핑 |
|--------|----------|------|----------|
| `guiderail` | - | 가이드레일 상세 | item_details |
| `bottombar` | - | 하단바 상세 | item_details |
| `shutterbox` | - | 셔터박스 상세 | item_details |
| `bending` | - | 벤딩 상세 | item_details |
| `lift` | - | 리프트 상세 | item_details |

#### 💰 단가 테이블

| 테이블 | 레코드 수 | 역할 | SAM 매핑 |
|--------|----------|------|----------|
| `price_motor` | 2 (JSON) | 모터 단가 | prices |
| `price_shaft` | 2 (JSON) | 감기샤프트 단가 | prices |
| `price_pipe` | 2 (JSON) | 파이프 단가 | prices |
| `price_angle` | 2 (JSON) | 앵글 단가 | prices |
| `price_raw_materials` | 6 (JSON) | 주자재 단가 | prices |
| `price_bend` | 3 (JSON) | 절곡 단가 | prices |
| `price_pole` | 2 (JSON) | 폴 단가 | prices |
| `price_screenplate` | 2 (JSON) | 스크린플레이트 단가 | prices |
| `price_smokeban` | 2 (JSON) | 연기차단 단가 | prices |

#### 📥 입고/재고 테이블

| 테이블 | 레코드 수 | 역할 | SAM 매핑 |
|--------|----------|------|----------|
| **`instock`** ⭐ | **2,286** | 입고 기록 | item_receipts + stocks |
| `lot` | - | 로트 관리 | lots |
| `lot_sales` | - | 로트 소진 | lot_sales |

#### 📋 주문/출고 테이블

| 테이블 | 레코드 수 | 역할 | SAM 매핑 |
|--------|----------|------|----------|
| **`output`** ⭐ | **24,564** | 주문/출고 기록 | orders + order_items |
| `estimate` | - | 견적 | orders (type=견적) |

### 2.2 models 테이블 구조

```sql
-- models: 제품 모델 마스터
model_id INT PRIMARY KEY,
model_name VARCHAR(255),           -- KSS01, KSE01, KWE01 등
major_category ENUM('스크린','철재'),
finishing_type ENUM('SUS마감','EGI마감'),
guiderail_type VARCHAR(20),        -- 벽면형, 측면형, 혼합형
description TEXT,
is_deleted, created_at, updated_at
```

**샘플 데이터**:
- KSS01/스크린/SUS마감/벽면형
- KSS01/스크린/SUS마감/측면형
- KSE01/스크린/EGI마감/벽면형
- KWE01/스크린/SUS마감/벽면형

### 2.3 KDunitprice 테이블 구조 ⭐ (핵심 마스터)

```sql
-- KDunitprice: 품목 마스터 (603건) - 가장 중요한 테이블!
품목코드 VARCHAR(50),          -- items.code (유니크 키!)
품목명 VARCHAR(255),           -- items.name
규격 VARCHAR(100),             -- items.attributes.spec
단위 VARCHAR(20),              -- items.unit
item_div VARCHAR(20),          -- [제품]/[상품]/[부재료]/[원재료]/[반제품]/[무형상품] → item_type
입고가 DECIMAL,                -- prices.purchase_price
출고가 DECIMAL,                -- prices.sales_price
비고 TEXT                      -- items.description
```

**item_div 분포 (예상)**:
```sql
SELECT item_div, COUNT(*) FROM KDunitprice GROUP BY item_div;
-- [제품]    ~100건 → FG
-- [상품]    ~50건  → FG
-- [반제품]  ~100건 → PT
-- [부재료]  ~200건 → SM
-- [원재료]  ~100건 → RM
-- [무형상품] ~53건 → CS
```

### 2.3.1 output.iList JSON 파일 구조 ⭐

```sql
-- output 테이블의 iList 컬럼
-- 값: "../output/i_json/22545.json" (파일 경로!)
-- 실제 파일 위치: 5130/output/i_json/{output_id}.json
```

**JSON 파일 내용 예시 (5130/output/i_json/22545.json)**:
```json
{
  "inputValue": [
    "2024-12-03",           // 날짜
    "명보에스티",            // 거래처명
    "KWE01 전체적인 테스트",  // 모델/설명
    // ... 추가 입력값들
  ],
  "beforeWidth": ["8000", "7000"],   // 변경전 폭
  "beforeHeight": ["4000", "3500"],  // 변경전 높이
  "afterWidth": ["8000", "7000"],    // 변경후 폭
  "afterHeight": ["4000", "3500"],   // 변경후 높이
  "pages": [
    {
      "page": "1",
      "inputItems": {
        "openWidth": "8000",
        "openHeight": "4000",
        // ... 기타 치수 정보
      },
      "checkboxData": [...]
    }
  ],
  "approval": {
    "writer": {"name": "개발자", "date": "25/01/02"},
    "approver": {"name": "관리자", "date": "25/01/03"}
  }
}
```

**SAM 매핑**:
- `inputValue` → `orders.options` (JSON)
- `pages` → `order_items.options` (JSON)
- `approval` → `orders.approved_by`, `orders.approved_at`
- `beforeWidth/Height`, `afterWidth/Height` → `order_items.options.dimensions`

### 2.4 BDmodels 테이블 구조 (BOM + 단가)

```sql
-- BDmodels: 모델별 BOM 및 단가 정보
num INT PRIMARY KEY,
major_category VARCHAR(10),        -- 스크린/철재
spec VARCHAR(30),                  -- 규격 (60*40, 120*70 등)
model_name VARCHAR(255),           -- 모델명
finishing_type ENUM('SUS마감','EGI마감'),
check_type VARCHAR(20),            -- 벽면형/측면형/혼합형
seconditem VARCHAR(30),            -- 부품명 (가이드레일, 하단마감재, L-BAR 등)
unitprice TEXT,                    -- 단가 (문자열)
savejson TEXT,                     -- BOM 상세 JSON
description TEXT,
is_deleted, priceDate DATE
```

**savejson 예시** (가이드레일 BOM):
```json
[
  {"col1":"1번(마감재)","col2":"SUS 1.2T","col3":"-3","col4":"203","col5":"206","col6":"51,000","col7":"10,506","col8":"2","col9":"21,012","col10":"삭제"},
  {"col1":"2번(본체)","col2":"EGI 1.55T","col3":"-5","col4":"294","col5":"299","col6":"27,000","col7":"8,073","col8":"1","col9":"8,073","col10":"삭제"},
  {"col1":"3번(벽면형-C)","col2":"EGI 1.55T","col3":"-1","col4":"104","col5":"105","col6":"27,000","col7":"2,835","col8":"1","col9":"2,835","col10":"삭제"},
  {"col1":"4번(벽면형-D)","col2":"EGI 1.55T","col3":"-3","col4":"105","col5":"108","col6":"27,000","col7":"2,916","col8":"1","col9":"2,916","col10":"삭제"}
]
```

### 2.4 카테고리 계층 구조 (4단계)

```
category_l1 (2개)
├── 스크린
│   ├── category_l2 (앵글, 환봉, 각파이프, 감기샤프트, 전동개폐기, 원단, 절곡물)
│   │   ├── category_l3 (받침앵글, 브라켓트, 와이어, 실리카, 마구리, 케이스, 가이드레일, 하단마감재...)
│   │   │   └── category_l4 (점검구양면, 점검구후면, 점검구밑면, 연기차단재, 상부덮개, 마구리, 벽면형, 측면형, 혼합형, L-bar, 하장바, 보강평철, 무게평철...)
│
└── 철재
    ├── category_l2 (환봉, 앵글, 각파이프, 감기샤프트, 전동개폐기, 슬랫, 절곡물)
    │   ├── category_l3 (브라켓트, 받침앵글, 슬랫, 조인트바, 가이드레일, 연동제어기, 모터, 하단마감재, 케이스)
    │   │   └── category_l4 (하부베이스, 매립형, 노출형, 유선, 무선, L-bar, 하장바, 보강평철, 점검구양면, 점검구후면)
```

### 2.5 price_* 테이블 구조 (단가 정보)

```sql
-- 공통 구조 (price_motor, price_shaft, price_pipe, price_raw_materials 등)
num INT PRIMARY KEY,
registedate DATE,                  -- 등록일
itemList TEXT,                     -- JSON 배열 (단가 정보)
is_deleted TINYINT DEFAULT 0,
update_log TEXT,
created_at TIMESTAMP
```

**price_motor itemList 예시**:
```json
[
  {"col1":"220","col2":"150K(S)","col3":"368","col4":"124","col5":"188","col6":"","col7":"680","col8":"6.79","col9":"100.1","col10":"1300","col11":"130,130","col12":"156,156","col13":"285,000","col14":"128,844","col15":"45.2"},
  {"col1":"380","col2":"300K","col3":"420","col4":"180","col5":"188","col6":"","col7":"788","col8":"6.79","col9":"116.1","col10":"1300","col11":"150,930","col12":"181,116","col13":"300,000","col14":"118,884","col15":"39.6"},
  {"col1":"제어기","col2":"노출형","col3":"","col4":"","col5":"300","col6":"","col7":"300","col8":"6.79","col9":"44.2","col10":"1300","col11":"57,460","col12":"68,952","col13":"130000","col14":"61,048","col15":"47"}
]
```

### 2.6 단가 시스템 상세 분석 ⭐

#### 2.6.1 레거시 단가 테이블 전체 목록 (10개)

| 테이블명 | 레코드 수 | 최신 날짜 | 용도 |
|---------|----------|----------|------|
| `price_motor` | 2 | 2024-08-25 | 전동개폐기, 제어기 단가 |
| `price_shaft` | 2 | 2024-08-25 | 감기샤프트 단가 |
| `price_pipe` | 2 | 2024-08-26 | 파이프 단가 |
| `price_angle` | 2 | 2024-08-26 | 앵글 단가 |
| `price_raw_materials` | 6 | 2025-06-18 | 슬랫, 스크린 원자재 단가 |
| `price_bend` | 3 | 2025-03-09 | 절곡 단가 |
| `price_pole` | 2 | 2024-08-26 | 폴 단가 |
| `price_screenplate` | 2 | 2024-08-26 | 스크린플레이트 단가 |
| `price_smokeban` | 2 | 2024-08-26 | 연기차단 단가 |
| `price_etc` | 0 | - | 기타 (개별 컬럼 방식, 비활성) |

#### 2.6.2 공통 테이블 구조

```sql
-- 9개 테이블 공통 구조 (price_etc 제외)
num INT PRIMARY KEY,
registedate DATE,              -- 적용일 (버전 관리 핵심!)
itemList TEXT,                 -- JSON 배열 (단가 정보)
is_deleted TINYINT DEFAULT 0,
update_log TEXT,
searchtag VARCHAR(255),
created_at TIMESTAMP,
memo TEXT
```

#### 2.6.3 각 테이블의 JSON 스키마 분석

**price_motor (모터/제어기)**:
```
col1: 분류 (220/380/제어기/방화/방범)
col2: 용량/타입 (150K, 300K, 노출형, 매립형...)
col3-col10: 치수, 무게, 계산값
col11: 원가 (VAT 제외)
col12: 원가 (VAT 포함)
col13: 판매단가 ⭐
col14: 이익금액
col15: 이익률 (%)
```

**price_shaft (감기샤프트)**:
```
col1: 품목명 (샤프트(BS))
col2-col5: 규격 (두께, 외경, 두께, 외경)
col6-col10: 길이, 무게, 계산값
col11-col16: 가공비, 원가
col17-col20: 단가 옵션들 (길이별)
```

**price_raw_materials (원자재)**:
```
col1: 분류 (슬랫/스크린)
col2: 종류 (방화/방범/실리카/화이바/조인트바)
col3-col12: 규격, 무게, 계산값
col13: 기준단가
col14: 품목코드
col15: 현재단가 ⭐
```

**price_pipe (파이프)**:
```
col1: 품목 (각파이프)
col2: 길이 (3,000/6,000)
col3: 규격 (50*30, 100*50)
col4: 두께
col5: 수량
col6-col7: 원가
col8: 단가 ⭐
```

#### 2.6.4 SAM prices 테이블 구조 (Target)

```sql
CREATE TABLE prices (
    id BIGINT PRIMARY KEY,
    tenant_id BIGINT NOT NULL,              -- 287 (경동기업)

    -- 품목 연결
    item_type_code VARCHAR(20),             -- FG/PT/SM/RM/CS
    item_id BIGINT,                         -- items.id FK
    client_group_id BIGINT NULL,            -- NULL = 기본가

    -- 원가 정보
    purchase_price DECIMAL(15,4),           -- 매입단가 (원가)
    processing_cost DECIMAL(15,4),          -- 가공비
    loss_rate DECIMAL(5,2),                 -- LOSS율 (%)

    -- 판매가 정보
    margin_rate DECIMAL(5,2),               -- 마진율 (%)
    sales_price DECIMAL(15,4),              -- 판매단가 ⭐
    rounding_rule ENUM('round','ceil','floor'),
    rounding_unit INT DEFAULT 1,            -- 반올림 단위

    -- 메타 정보
    supplier VARCHAR(255),                  -- 공급업체
    effective_from DATE,                    -- 적용 시작일 ⭐
    effective_to DATE NULL,                 -- 적용 종료일
    note TEXT,

    -- 상태 관리
    status ENUM('draft','active','inactive','finalized'),
    is_final BOOLEAN DEFAULT FALSE,

    -- 감사 컬럼
    created_by, updated_by, deleted_by, timestamps, soft_deletes
);
```

#### 2.6.5 Legacy → SAM 단가 매핑 전략

```
┌─────────────────────────────────────────────────────────────────┐
│  단가 마이그레이션 플로우                                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  Legacy (chandj)                    SAM                         │
│  ──────────────                     ───                         │
│                                                                  │
│  1. price_motor.itemList[i]                                      │
│     ├── col1,col2 (전압,용량) ───→ items (SM) 생성               │
│     │   └── code: SM-MOTOR-220-150K                             │
│     │                                                            │
│     └── col11,col13 (원가,판매가) ─→ prices 생성                 │
│         ├── item_id: 위에서 생성된 items.id                      │
│         ├── purchase_price: col11                                │
│         ├── sales_price: col13                                   │
│         └── effective_from: registedate                          │
│                                                                  │
│  2. 날짜별 버전 관리                                              │
│     ├── registedate 2024-08-25 → effective_from                  │
│     └── 다음 레코드 존재 시 → effective_to 설정                   │
│                                                                  │
│  3. 최신 레코드만 active, 나머지는 inactive                       │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘
```

#### 2.6.6 items와 prices 관계

```
items (품목 마스터)                    prices (단가 이력)
┌──────────────────────┐              ┌──────────────────────┐
│ id: 1001             │              │ id: 5001             │
│ code: SM-MOTOR-220-150K │◄────────────│ item_id: 1001        │
│ name: 전동개폐기 220V 150K │          │ sales_price: 285000   │
│ item_type: SM        │              │ effective_from: 2024-08-25 │
│ attributes: {...}    │              │ status: active        │
└──────────────────────┘              └──────────────────────┘
                                              │
                                      ┌──────────────────────┐
                                      │ id: 5002             │
                                      │ item_id: 1001        │
                                      │ sales_price: 270000   │
                                      │ effective_from: 2024-01-01 │
                                      │ effective_to: 2024-08-24   │
                                      │ status: inactive      │
                                      └──────────────────────┘
```

---

## 3. 매핑 설계

### 3.1 models → items (FG 완제품)

| 레거시 (models) | SAM (items) | 비고 |
|----------------|-------------|------|
| model_id | (신규 생성) | |
| model_name | code | KSS01 → FG-KSS01 |
| - | name | 모델명 + 마감타입 + 가이드타입 조합 |
| major_category | attributes.major_category | 스크린/철재 |
| finishing_type | attributes.finishing_type | SUS마감/EGI마감 |
| guiderail_type | attributes.guiderail_type | 벽면형/측면형/혼합형 |
| - | item_type | 'FG' |
| - | tenant_id | 287 |

**코드 생성 규칙**:
```
FG-{model_name}-{guiderail_type}-{finishing_type}
예: FG-KSS01-벽면형-SUS
```

### 3.2 BDmodels → items (FG 세부 + BOM)

| 레거시 (BDmodels) | SAM (items) | 비고 |
|------------------|-------------|------|
| seconditem | code (부품) | 가이드레일 → PT-GR-120x70-SUS-벽면형 |
| savejson | bom | JSON 변환 |
| unitprice | attributes.unit_price | |
| spec | attributes.spec | 120*70 |
| priceDate | attributes.price_date | |

### 3.3 category_l4 → items (PT 부품)

| 레거시 (category_l4) | SAM (items) | 비고 |
|---------------------|-------------|------|
| name | name | 부품명 |
| - | code | PT-L1-L2-L3-{name} 조합 |
| - | item_type | 'PT' |
| parent_id | attributes.parent_category_id | |

### 3.4 price_* → prices 테이블 (단가 연동) ⭐

> **중요**: 단가 데이터는 items.attributes가 아닌 **prices 테이블**에 별도 관리

| 레거시 (price_*) | SAM (prices) | 비고 |
|-----------------|--------------|------|
| registedate | effective_from | 적용 시작일 |
| itemList.col13 (판매가) | sales_price | |
| itemList.col11 (원가) | purchase_price | |
| itemList.col12 (VAT포함) | - | 계산으로 도출 |
| - | item_type_code | FG/PT/SM/RM/CS |
| - | item_id | items.id FK |
| - | client_group_id | NULL (기본가) |
| - | status | 'active' |

---

## 4. 대상 범위

### 4.1 Phase 1: 마스터 데이터 이관

| # | 작업 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| **1.0** | **KDunitprice → items (마스터)** ⭐ | ⏳ | **603건 (최우선!)** |
| 1.1 | models → items (FG) INSERT 쿼리 작성 | ⏳ | 18건 (중복 확인 후) |
| 1.2 | item_list → items (PT) INSERT 쿼리 작성 | ⏳ | 5건+ (중복 확인 후) |
| 1.3 | category_l4 → items (PT) INSERT 쿼리 작성 | ⏳ | 37건 (중복 확인 후) |
| 1.4 | price_motor 파싱 → prices 연결 | ⏳ | code로 items 조회 후 prices 생성 |
| 1.5 | price_shaft 파싱 → prices 연결 | ⏳ | ~15건 |
| 1.6 | price_raw_materials 파싱 → prices 연결 | ⏳ | ~20건 |
| 1.7 | ⚠️ **사용자 승인**: Phase 1 INSERT 실행 | ⏳ | |

### 4.2 Phase 2: BOM 및 상세 데이터 이관

| # | 작업 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 2.1 | BDmodels.savejson → item_bom_items | ⏳ | 59건 |
| 2.2 | parts → item_bom_items | ⏳ | 36건 |
| 2.3 | parts_sub → item_bom_items | ⏳ | 134건 |
| 2.4 | guiderail/bottombar/bending 등 → item_details | ⏳ | 제품 상세 |
| 2.5 | parent_item_id, child_item_id 매핑 | ⏳ | code 기반 조회 |

### 4.3 Phase 3: 검증 및 배포

| # | 작업 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 3.1 | 로컬 테스트 | ⏳ | |
| 3.2 | API 테스트 | ⏳ | |
| 3.3 | 개발서버 배포 | ⏳ | ⚠️ 컨펌 필요 |

### 4.4 Phase 4: 단가 데이터 이관 ⭐

| # | 작업 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 4.1 | price_motor → items (SM) + prices | ⏳ | ~35건 품목 + 단가 |
| 4.2 | price_shaft → items (SM) + prices | ⏳ | ~15건 |
| 4.3 | price_pipe → items (SM) + prices | ⏳ | ~10건 |
| 4.4 | price_angle → items (SM) + prices | ⏳ | ~10건 |
| 4.5 | price_raw_materials → items (RM) + prices | ⏳ | ~20건 |
| 4.6 | price_bend → items (SM) + prices | ⏳ | ~10건 |
| 4.7 | price_pole → items (SM) + prices | ⏳ | ~5건 |
| 4.8 | price_screenplate → items (SM) + prices | ⏳ | ~5건 |
| 4.9 | price_smokeban → items (SM) + prices | ⏳ | ~5건 |
| 4.10 | 단가 버전 이력 정리 | ⏳ | effective_from/to 설정 |
| 4.11 | ⚠️ **사용자 승인**: 단가 INSERT 실행 | ⏳ | |

### 4.5 Phase 5: 입고/재고 데이터 이관 ⭐ (신규)

| # | 작업 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 5.1 | instock → item_receipts | ⏳ | 2,286건 |
| 5.2 | instock 재고 계산 → stocks | ⏳ | 현재고 집계 |
| 5.3 | lot → lots | ⏳ | 로트 관리 |
| 5.4 | lot_sales → lot_sales | ⏳ | 로트 소진 |
| 5.5 | ⚠️ **사용자 승인**: 입고/재고 INSERT 실행 | ⏳ | |

### 4.6 Phase 6: 주문/출고 데이터 이관 ⭐ (신규)

| # | 작업 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 6.1 | output → orders 헤더 | ⏳ | 24,564건 |
| 6.2 | output.iList JSON 파일 파싱 | ⏳ | 파일 경로 → JSON 읽기 |
| 6.3 | JSON → order_items 생성 | ⏳ | pages 배열 처리 |
| 6.4 | JSON.approval → orders 승인 정보 | ⏳ | approved_by, approved_at |
| 6.5 | estimate → orders (type=견적) | ⏳ | 견적 데이터 |
| 6.6 | ⚠️ **사용자 승인**: 주문/출고 INSERT 실행 | ⏳ | |

---

## 5. SQL 쿼리 (예상)

### 5.0 KDunitprice → items (마스터) ⭐ 최우선!

```sql
-- KDunitprice: 품목 마스터 (603건) → SAM items
-- ⚠️ 이 쿼리를 가장 먼저 실행하여 items 마스터 생성

INSERT INTO samdb.items (
    tenant_id, item_type, code, name, unit,
    attributes, description, is_active,
    created_by, created_at, updated_at
)
SELECT
    287 AS tenant_id,
    -- item_div → item_type 매핑
    CASE item_div
        WHEN '[제품]' THEN 'FG'
        WHEN '[상품]' THEN 'FG'
        WHEN '[반제품]' THEN 'PT'
        WHEN '[부재료]' THEN 'SM'
        WHEN '[원재료]' THEN 'RM'
        WHEN '[무형상품]' THEN 'CS'
        ELSE 'SM'
    END AS item_type,
    품목코드 AS code,                    -- 유니크 키!
    품목명 AS name,
    단위 AS unit,
    JSON_OBJECT(
        'spec', 규격,
        'item_div', item_div,
        'legacy_source', 'KDunitprice'
    ) AS attributes,
    비고 AS description,
    1 AS is_active,
    1 AS created_by,
    NOW(), NOW()
FROM chandj.KDunitprice
WHERE 품목코드 IS NOT NULL AND 품목코드 != '';

-- 결과 확인
SELECT item_type, COUNT(*)
FROM samdb.items
WHERE tenant_id = 287
GROUP BY item_type;
```

### 5.0.1 KDunitprice → prices (기본 단가)

```sql
-- KDunitprice의 입고가/출고가 → prices 테이블
INSERT INTO samdb.prices (
    tenant_id, item_type_code, item_id, client_group_id,
    purchase_price, sales_price,
    effective_from, status,
    created_by, created_at, updated_at
)
SELECT
    287 AS tenant_id,
    i.item_type AS item_type_code,
    i.id AS item_id,
    NULL AS client_group_id,           -- 기본가
    COALESCE(k.입고가, 0) AS purchase_price,
    COALESCE(k.출고가, 0) AS sales_price,
    CURDATE() AS effective_from,       -- 적용일
    'active' AS status,
    1 AS created_by,
    NOW(), NOW()
FROM chandj.KDunitprice k
JOIN samdb.items i ON i.code = k.품목코드 AND i.tenant_id = 287
WHERE k.품목코드 IS NOT NULL AND k.품목코드 != '';
```

### 5.1 models → items (FG)

```sql
-- 레거시 chandj.models → SAM items (FG)
INSERT INTO samdb.items (
    tenant_id, item_type, code, name, unit,
    attributes, is_active, created_by, created_at, updated_at
)
SELECT
    287 AS tenant_id,
    'FG' AS item_type,
    CONCAT('FG-', model_name, '-',
           COALESCE(guiderail_type, 'STD'), '-',
           CASE finishing_type
               WHEN 'SUS마감' THEN 'SUS'
               WHEN 'EGI마감' THEN 'EGI'
               ELSE 'STD'
           END
    ) AS code,
    CONCAT(model_name, ' ', major_category, ' ', finishing_type, ' ', COALESCE(guiderail_type, '')) AS name,
    'EA' AS unit,
    JSON_OBJECT(
        'major_category', major_category,
        'finishing_type', finishing_type,
        'guiderail_type', guiderail_type,
        'legacy_model_id', model_id
    ) AS attributes,
    CASE WHEN is_deleted = 0 THEN 1 ELSE 0 END AS is_active,
    1 AS created_by,
    created_at,
    updated_at
FROM chandj.models
WHERE is_deleted = 0;
```

### 5.2 category_l4 → items (PT)

```sql
-- 레거시 4단계 카테고리 → SAM items (PT)
INSERT INTO samdb.items (
    tenant_id, item_type, code, name, unit,
    attributes, is_active, created_by, created_at, updated_at
)
SELECT
    287 AS tenant_id,
    'PT' AS item_type,
    CONCAT('PT-', l1.name, '-', l2.name, '-', l3.name, '-', l4.name) AS code,
    l4.name AS name,
    'EA' AS unit,
    JSON_OBJECT(
        'category_l1', l1.name,
        'category_l2', l2.name,
        'category_l3', l3.name,
        'category_l4', l4.name,
        'legacy_l4_id', l4.id
    ) AS attributes,
    1 AS is_active,
    1 AS created_by,
    NOW(), NOW()
FROM chandj.category_l4 l4
JOIN chandj.category_l3 l3 ON l4.parent_id = l3.id
JOIN chandj.category_l2 l2 ON l3.parent_id = l2.id
JOIN chandj.category_l1 l1 ON l2.parent_id = l1.id;
```

### 5.3 price_motor → items (SM) + prices [PHP 스크립트]

```php
<?php
/**
 * price_motor JSON → items + prices 마이그레이션
 *
 * 1단계: items 테이블에 품목 생성 (SM 타입)
 * 2단계: prices 테이블에 단가 연결
 */

$tenantId = 287;  // 경동기업
$userId = 1;

// 모든 price_motor 레코드 조회 (버전별 단가 이력)
$stmt = $pdo->query("
    SELECT num, registedate, itemList
    FROM price_motor
    WHERE is_deleted = 0
    ORDER BY registedate DESC
");
$priceRecords = $stmt->fetchAll(PDO::FETCH_ASSOC);

// 최신 단가의 itemList 파싱 → items 생성
$latestRecord = $priceRecords[0];
$itemList = json_decode($latestRecord['itemList'], true);

foreach ($itemList as $idx => $item) {
    $voltage = $item['col1'];       // 220, 380, 제어기, 방화, 방범
    $capacity = $item['col2'];      // 150K(S), 300K, 노출형, 매립형...
    $purchasePrice = (float)str_replace(',', '', $item['col11'] ?? '0');
    $salesPrice = (float)str_replace(',', '', $item['col13'] ?? '0');

    // 품목 코드 생성
    $code = "SM-MOTOR-" . preg_replace('/[^A-Za-z0-9가-힣]/', '', $voltage)
          . "-" . preg_replace('/[^A-Za-z0-9가-힣()]/', '', $capacity);

    // 품목명 생성
    if (in_array($voltage, ['220', '380'])) {
        $name = "전동개폐기 {$voltage}V {$capacity}";
        $itemType = 'SM';
    } elseif ($voltage === '제어기') {
        $name = "연동제어기 {$capacity}";
        $itemType = 'SM';
    } else {
        $name = "{$voltage} {$capacity}";
        $itemType = 'SM';
    }

    // 1단계: items INSERT
    $itemStmt = $pdo->prepare("
        INSERT INTO items (
            tenant_id, item_type, code, name, unit,
            attributes, is_active, created_by, created_at, updated_at
        ) VALUES (?, ?, ?, ?, 'EA', ?, 1, ?, NOW(), NOW())
        ON DUPLICATE KEY UPDATE name = VALUES(name)
    ");
    $attributes = json_encode([
        'voltage' => $voltage,
        'capacity' => $capacity,
        'legacy_source' => 'price_motor',
        'legacy_col_index' => $idx
    ]);
    $itemStmt->execute([$tenantId, $itemType, $code, $name, $attributes, $userId]);
    $itemId = $pdo->lastInsertId();

    // 2단계: prices INSERT (모든 버전)
    foreach ($priceRecords as $priceIdx => $priceRecord) {
        $priceItemList = json_decode($priceRecord['itemList'], true);
        if (!isset($priceItemList[$idx])) continue;

        $priceItem = $priceItemList[$idx];
        $pPrice = (float)str_replace(',', '', $priceItem['col11'] ?? '0');
        $sPrice = (float)str_replace(',', '', $priceItem['col13'] ?? '0');
        $effectiveFrom = $priceRecord['registedate'];

        // 다음 레코드가 있으면 effective_to 설정
        $effectiveTo = isset($priceRecords[$priceIdx + 1])
            ? date('Y-m-d', strtotime($effectiveFrom . ' -1 day'))
            : null;

        $status = ($priceIdx === 0) ? 'active' : 'inactive';

        $priceStmt = $pdo->prepare("
            INSERT INTO prices (
                tenant_id, item_type_code, item_id, client_group_id,
                purchase_price, sales_price, effective_from, effective_to,
                status, created_by, created_at, updated_at
            ) VALUES (?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, NOW(), NOW())
        ");
        $priceStmt->execute([
            $tenantId, $itemType, $itemId,
            $pPrice, $sPrice, $effectiveFrom, $effectiveTo,
            $status, $userId
        ]);
    }

    echo "✓ {$code} - items + prices 생성 완료\n";
}
```

### 5.4 단가 마이그레이션 요약 스크립트

```php
<?php
/**
 * 전체 price_* 테이블 마이그레이션 통합 스크립트
 */

$priceTables = [
    'price_motor' => ['item_type' => 'SM', 'prefix' => 'MOTOR'],
    'price_shaft' => ['item_type' => 'SM', 'prefix' => 'SHAFT'],
    'price_pipe' => ['item_type' => 'SM', 'prefix' => 'PIPE'],
    'price_angle' => ['item_type' => 'SM', 'prefix' => 'ANGLE'],
    'price_raw_materials' => ['item_type' => 'RM', 'prefix' => 'RAW'],
    'price_bend' => ['item_type' => 'SM', 'prefix' => 'BEND'],
    'price_pole' => ['item_type' => 'SM', 'prefix' => 'POLE'],
    'price_screenplate' => ['item_type' => 'SM', 'prefix' => 'SCREEN'],
    'price_smokeban' => ['item_type' => 'SM', 'prefix' => 'SMOKE'],
];

$totalItems = 0;
$totalPrices = 0;

foreach ($priceTables as $table => $config) {
    echo "\n📦 Processing: {$table}\n";

    // 각 테이블별 JSON 스키마에 맞는 파싱 로직 호출
    list($itemCount, $priceCount) = migratePrice($table, $config);

    $totalItems += $itemCount;
    $totalPrices += $priceCount;

    echo "   → items: {$itemCount}, prices: {$priceCount}\n";
}

echo "\n✅ 마이그레이션 완료!\n";
echo "   총 items: {$totalItems}\n";
echo "   총 prices: {$totalPrices}\n";
```

---

## 6. 기준 원칙

```
┌─────────────────────────────────────────────────────────────────────────┐
│  🎯 핵심 원칙                                                            │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  📦 데이터 전략                                                          │
│  ─────────────────────────────────────────────────────────────────────   │
│  - KDunitprice(603건)가 품목 마스터 → items 최우선 생성                  │
│  - code 필드로 중복 방지 (ON DUPLICATE KEY UPDATE)                       │
│  - BOM은 item_bom_items 테이블 사용 (items.bom JSON ❌)                  │
│  - 단가 정보는 prices 테이블에 별도 저장 (items.attributes ❌)            │
│                                                                          │
│  ❌ 불필요한 것                                                          │
│  ─────────────────────────────────────────────────────────────────────   │
│  - item_id_mappings 테이블 (양방향 조회 불필요)                          │
│  - chandj 수정 (손 안댐, samdb에만 밀어 넣음)                            │
│  - 레거시 소스 확인 (마이그레이션 후 검증만)                             │
│                                                                          │
│  ✅ 필수 사항                                                            │
│  ─────────────────────────────────────────────────────────────────────   │
│  - 경동기업 기준으로 맞춤 (이미 사용중인 시스템)                          │
│  - 전체 이관 (instock 2,286건, output 24,564건 포함)                     │
│  - SQL 쿼리 + PHP 스크립트 혼용 (JSON 파싱 필요)                         │
│  - 로컬 검증 완료 후 개발서버 배포                                       │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘
```

### 6.1 변경 승인 정책

| 분류 | 예시 | 승인 |
|------|------|------|
| ✅ 즉시 가능 | SELECT 쿼리, 분석, 매핑 설계 | 불필요 |
| ⚠️ 컨펌 필요 | INSERT 실행, TRUNCATE, 개발서버 배포 | **필수** |
| 🔴 금지 | 운영서버 직접 작업 | 별도 협의 |

---

## 7. 데이터 규모 예상 (전체 마이그레이션)

### 7.1 items 테이블 예상

| 소스 | 레코드 수 | SAM item_type | 예상 items 건수 |
|------|----------|---------------|----------------|
| **KDunitprice** ⭐ | **603** | FG/PT/SM/RM/CS | **~603 (마스터)** |
| models | 18 | FG | ~0 (중복 제외) |
| category_l4 | 37 | PT | ~20 (일부 신규) |
| item_list | 5 | PT | ~0 (중복 제외) |
| price_* 테이블 | ~130 항목 | SM/RM | ~100 (신규만) |
| **items 합계** | - | - | **~700~800건** |

**item_type별 분포 예상**:
| item_type | 설명 | 예상 건수 |
|-----------|------|----------|
| FG | 완제품 | ~100건 |
| PT | 부품 | ~250건 |
| SM | 부자재 | ~300건 |
| RM | 원자재 | ~100건 |
| CS | 소모품 | ~50건 |

### 7.2 prices 테이블 예상 ⭐

| 소스 | 버전 수 | 품목당 단가 | 예상 prices 건수 |
|------|--------|------------|-----------------|
| KDunitprice | 1 | 603 | ~603 |
| price_motor | 2 | 35 | ~70 |
| price_shaft | 2 | 15 | ~30 |
| price_pipe | 2 | 10 | ~20 |
| price_angle | 2 | 10 | ~20 |
| price_raw_materials | 6 | 20 | ~120 |
| price_bend | 3 | 10 | ~30 |
| 기타 price_* | 2 | 15 | ~30 |
| **prices 합계** | - | - | **~500건** (중복 제외)

### 7.3 입고/재고 테이블 예상 ⭐ (신규)

| 소스 | 레코드 수 | SAM 테이블 | 예상 건수 |
|------|----------|------------|----------|
| instock | 2,286 | item_receipts | ~2,286 |
| instock (집계) | - | stocks | ~500 (품목별 현재고) |
| lot | - | lots | ~200 |
| lot_sales | - | lot_sales | ~300 |
| **합계** | - | - | **~3,300건** |

### 7.4 주문/출고 테이블 예상 ⭐ (신규)

| 소스 | 레코드 수 | SAM 테이블 | 예상 건수 |
|------|----------|------------|----------|
| output | 24,564 | orders | ~24,564 |
| output.iList (JSON) | ~24,564 파일 | order_items | ~50,000 (주문당 2건 평균) |
| estimate | - | orders (type=견적) | ~500 |
| **합계** | - | - | **~75,000건** |

### 7.5 전체 마이그레이션 요약

| SAM 테이블 | 예상 건수 | 비고 |
|------------|----------|------|
| items | ~800 | 품목 마스터 |
| item_bom_items | ~300 | BOM 관계 |
| item_details | ~200 | 제품 상세 |
| prices | ~500 | 단가 정보 |
| item_receipts | ~2,300 | 입고 기록 |
| stocks | ~500 | 현재고 |
| lots | ~200 | 로트 |
| lot_sales | ~300 | 로트 소진 |
| orders | ~25,000 | 주문 헤더 |
| order_items | ~50,000 | 주문 상세 |
| **총계** | **~80,000건** | |

---

## 8. 체크리스트

### Phase 1: 마스터 데이터 이관
- [x] 레거시 DB 구조 분석 완료
- [x] KDunitprice 테이블 발견 및 분석 (603건, 핵심 마스터)
- [x] 중복 제거 전략 수립 (code 기반, 매핑 테이블 불필요)
- [ ] **KDunitprice → items 마이그레이션 스크립트 작성** ⭐
- [ ] models → items (FG) INSERT 쿼리 작성 (중복 확인)
- [ ] category_l4 → items (PT) INSERT 쿼리 작성 (중복 확인)
- [ ] ⚠️ **사용자 승인**: 로컬 INSERT 실행

### Phase 2: BOM 데이터 이관
- [ ] BDmodels.savejson 파싱 로직 작성
- [ ] child_item_id 매핑 테이블 생성
- [ ] items.bom JSON 생성
- [ ] ⚠️ **사용자 승인**: BOM 데이터 INSERT 실행

### Phase 3: 검증 및 배포
- [ ] 건수 검증
- [ ] API 테스트
- [ ] ⚠️ **사용자 승인**: 개발서버 배포

### Phase 4: 단가 데이터 이관 ⭐
- [x] 레거시 price_* 테이블 구조 분석 (10개)
- [x] 각 테이블별 JSON 스키마 분석
- [x] SAM prices 테이블 구조 확인
- [x] Legacy → SAM 단가 매핑 전략 수립
- [ ] price_motor → prices 연결 스크립트 작성
- [ ] price_shaft → prices 연결 스크립트 작성
- [ ] price_pipe → prices 연결 스크립트 작성
- [ ] price_angle → prices 연결 스크립트 작성
- [ ] price_raw_materials → prices 연결 스크립트 작성
- [ ] 기타 price_* 테이블 처리
- [ ] 단가 버전 이력 정리 (effective_from/to)
- [ ] ⚠️ **사용자 승인**: 단가 INSERT 실행

### Phase 5: 입고/재고 데이터 이관 ⭐ (신규)
- [ ] instock 테이블 구조 분석
- [ ] instock → item_receipts 매핑 설계
- [ ] 재고 집계 → stocks 매핑 설계
- [ ] lot/lot_sales 구조 분석
- [ ] 마이그레이션 스크립트 작성
- [ ] ⚠️ **사용자 승인**: 입고/재고 INSERT 실행

### Phase 6: 주문/출고 데이터 이관 ⭐ (신규)
- [ ] output 테이블 구조 분석
- [ ] output.iList JSON 파일 구조 분석 (완료)
- [ ] output → orders 매핑 설계
- [ ] JSON → order_items 매핑 설계
- [ ] estimate → orders 매핑 설계
- [ ] 마이그레이션 스크립트 작성 (24,564건)
- [ ] ⚠️ **사용자 승인**: 주문/출고 INSERT 실행

---

## 9. 참고 문서

- **레거시 소스**: `5130/` 폴더
- **SAM items 마이그레이션**: `api/database/migrations/2025_12_13_152507_create_items_table.php`
- **SAM prices 마이그레이션**: `api/database/migrations/2025_12_08_154633_create_prices_table.php`
- **SAM price_revisions 마이그레이션**: `api/database/migrations/2025_12_08_154634_create_price_revisions_table.php`
- **품목 분석**: `docs/data/analysis/item-db-analysis.md`
- **DummyItemSeeder**: `api/database/seeders/Dummy/DummyItemSeeder.php`
- **DummyDataSeeder**: `api/database/seeders/DummyDataSeeder.php` (TENANT_ID=287, USER_ID=1 상수 참조)
- **prices item_type_code 마이그레이션**: `api/database/migrations/2025_12_21_165524_update_prices_item_type_code_to_actual_item_type.php`

---

## 10. 세션 및 메모리 관리 정책

### 10.1 세션 시작 시 (Load Strategy)
```bash
# 1. Docker 확인
docker ps | grep sam

# 2. DB 접속 테스트
docker exec sam-mysql-1 mysql -uroot -proot chandj -e "SELECT COUNT(*) FROM KDunitprice;"
docker exec sam-mysql-1 mysql -uroot -proot samdb -e "SELECT COUNT(*) FROM items WHERE tenant_id=287;"

# 3. 현재 진행 상태 확인
# → 이 문서의 "📍 현재 진행 상태" 섹션 참조

# 4. 마이그레이션 상태 확인 (API 프로젝트)
cd /Users/kent/Works/@KD_SAM/SAM/api && php artisan migrate:status
```

### 10.2 작업 중 관리

| 작업 완료 시 | 조치 |
|-------------|------|
| Phase 완료 | "📍 현재 진행 상태" 업데이트 |
| INSERT 실행 | "10. 변경 이력" 추가 |
| 스키마 변경 | 관련 섹션 업데이트 + 변경 이력 추가 |
| 오류 발생 | 체크리스트에 메모 추가 |

### 10.3 컨텍스트 관리

| 컨텍스트 잔량 | 조치 |
|--------------|------|
| **30% 이하** | 현재 작업 중단점 문서에 기록 |
| **20% 이하** | "📍 현재 진행 상태" 최종 업데이트 |
| **10% 이하** | 세션 정리 및 다음 세션 가이드 작성 |

---

## 11. 자기완결성 점검 결과

### 11.1 체크리스트 검증

| # | 검증 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 1 | 작업 목적이 명확한가? | ✅ | 섹션 1.1 배경 |
| 2 | 성공 기준이 정의되어 있는가? | ✅ | 섹션 0 성공 기준 |
| 3 | 작업 범위가 구체적인가? | ✅ | 섹션 4 대상 범위 |
| 4 | 의존성이 명시되어 있는가? | ✅ | Quick Start 전제 조건 |
| 5 | 참고 파일 경로가 정확한가? | ✅ | 섹션 9 참고 문서 |
| 6 | 단계별 절차가 실행 가능한가? | ✅ | 섹션 5 SQL 쿼리 |
| 7 | 검증 방법이 명시되어 있는가? | ✅ | 섹션 0 확인 방법 SQL |
| 8 | 모호한 표현이 없는가? | ✅ | 구체적 건수, 테이블명, 컬럼 명시 |

### 11.2 새 세션 시뮬레이션 테스트

| 질문 | 답변 가능 | 참조 섹션 |
|------|:--------:|----------|
| Q1. 이 작업의 목적은 무엇인가? | ✅ | 1.1 배경, 문서 헤더 |
| Q2. 어디서부터 시작해야 하는가? | ✅ | 📍 현재 진행 상태 → 다음 작업 상세 |
| Q3. 어떤 파일을 수정해야 하는가? | ✅ | 9. 참고 문서 |
| Q4. 작업 완료 확인 방법은? | ✅ | 0. 성공 기준 |
| Q5. 막혔을 때 참고 문서는? | ✅ | 9. 참고 문서, Quick Start DB 접속 명령어 |

**결과**: 5/5 통과 → ✅ 자기완결성 확보

### 11.3 핵심 정보 요약 (새 세션용)

```
┌─────────────────────────────────────────────────────────────────────────┐
│  📋 핵심 정보 요약                                                       │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  🎯 목표: 경동기업 레거시(chandj) → SAM(samdb) 전체 데이터 이관          │
│                                                                          │
│  📊 데이터 규모 (총 ~80,000건):                                          │
│     - items: ~800건 (KDunitprice 603 + 추가)                            │
│     - prices: ~500건                                                    │
│     - item_receipts: ~2,300건 (입고)                                    │
│     - orders + order_items: ~75,000건 (주문)                            │
│                                                                          │
│  🔑 핵심 상수:                                                           │
│     - tenant_id = 287 (경동기업)                                        │
│     - user_id = 1 (생성자)                                              │
│     - Docker: sam-mysql-1                                               │
│     - 레거시 DB: chandj / SAM DB: samdb ⚠️                              │
│                                                                          │
│  ⭐ 마이그레이션 순서:                                                    │
│     1. KDunitprice → items (마스터, 603건) ← 최우선!                    │
│     2. code 기반 중복 확인 후 추가 items 생성                            │
│     3. prices 연결 (item_id 참조)                                       │
│     4. BOM, 입고, 주문 순서대로 진행                                     │
│                                                                          │
│  📍 현재 상태: Phase 1 대기 (KDunitprice → items 마스터 INSERT)          │
│                                                                          │
│  ❌ 불필요한 것: item_id_mappings (양방향 조회 불필요, chandj 손 안댐)    │
│                                                                          │
│  ⚠️ 주의: 모든 INSERT 실행 전 사용자 승인 필요                           │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘
```

---

## 12. 변경 이력

| 날짜 | 항목 | 변경 내용 | 파일 | 승인 |
|------|------|----------|------|------|
| 2026-01-28 | 문서 재작성 | 레거시 5130/ 분석 기반으로 완전 재작성 | - | - |
| 2026-01-28 | 단가 시스템 추가 | price_* 테이블 분석, SAM prices 매핑 전략 | - | - |
| 2026-01-28 | 자기완결성 보완 | Quick Start, 성공 기준, 세션 관리, 자기완결성 점검 섹션 추가 | - | - |
| 2026-01-28 | **전체 범위 확장** | KDunitprice(603건) 발견, Phase 5/6 추가, ~80,000건 전체 이관 | - | - |
| 2026-01-28 | 중복 제거 전략 | code 기반 단순화, item_id_mappings 제거 | - | - |
| 2026-01-28 | DB 이름 수정 | sam → samdb 수정 | - | - |
| 2026-01-28 | output.iList | JSON 파일 구조 분석 및 문서화 | - | - |

---

## 13. 트러블슈팅 가이드

### 13.1 일반적인 문제

| 문제 | 원인 | 해결책 |
|------|------|--------|
| Docker 컨테이너 없음 | Docker 미실행 | `docker-compose up -d` 실행 |
| DB 접속 실패 | 컨테이너명 변경 | `docker ps`로 정확한 컨테이너명 확인 |
| chandj DB 없음 | 레거시 DB 미설정 | Docker 볼륨 확인 또는 덤프 복원 |
| tenant_id 287 없음 | 경동기업 테넌트 미생성 | SAM에서 테넌트 생성 필요 |
| items 테이블 없음 | 마이그레이션 미실행 | `php artisan migrate` 실행 |
| **SAM DB 이름 오류** | `sam` 대신 `samdb` | 모든 쿼리에서 `samdb` 사용 확인 |
| KDunitprice 테이블 없음 | 레거시 덤프 불완전 | chandj 전체 덤프 확인 |
| output.iList 파일 없음 | JSON 파일 경로 오류 | `5130/output/i_json/` 폴더 확인 |

### 13.2 JSON 파싱 오류

```php
// price_* 테이블의 itemList 파싱 시 주의사항
$itemList = json_decode($record['itemList'], true);

// 빈 값 또는 잘못된 JSON 처리
if (empty($itemList) || !is_array($itemList)) {
    // 스킵하고 로그 기록
    error_log("Invalid itemList in {$table} num={$record['num']}");
    continue;
}

// 숫자 형식 변환 (콤마 제거)
$price = (float)str_replace(',', '', $item['col13'] ?? '0');
```

### 13.3 중복 코드 처리 (code 기반)

```sql
-- 이미 존재하는 품목 확인 (code 유일성 검사)
SELECT code, COUNT(*) AS cnt
FROM samdb.items
WHERE tenant_id=287
GROUP BY code
HAVING cnt > 1;

-- INSERT 시 ON DUPLICATE KEY UPDATE 사용
-- ⚠️ items 테이블에 (tenant_id, code) UNIQUE 인덱스 필요
INSERT INTO samdb.items (...) VALUES (...)
ON DUPLICATE KEY UPDATE name = VALUES(name), updated_at = NOW();

-- KDunitprice와 price_* 중복 확인
SELECT k.품목코드, '모터 150K' AS price_item
FROM chandj.KDunitprice k
WHERE k.품목명 LIKE '%모터%150K%';
-- → KDunitprice가 마스터, price_*는 가격만 추가
```

### 13.4 output.iList JSON 파일 처리

```php
// output.iList 값 예시: "../output/i_json/22545.json"
$iListPath = $output['iList'];  // "../output/i_json/22545.json"

// 실제 파일 경로로 변환
$basePath = '/Users/kent/Works/@KD_SAM/SAM/5130';
$jsonFile = str_replace('../', '', $iListPath);
$fullPath = $basePath . '/' . $jsonFile;

// JSON 파일 읽기
if (file_exists($fullPath)) {
    $jsonContent = json_decode(file_get_contents($fullPath), true);
    // $jsonContent['inputValue'], $jsonContent['pages'] 등 사용
} else {
    // 파일 없음 - 로그 기록 후 스킵
    error_log("JSON file not found: {$fullPath}");
}
```

---

*이 문서는 /sc:plan 스킬로 생성되었습니다.*