# 경동기업(5130) 입고/재고/주문 마이그레이션 계획

> **작성일**: 2026-01-28
> **목적**: 경동기업 레거시 시스템(5130/)의 **입고(instock), 재고(stocks), 주문(output)** 데이터를 SAM으로 이관
> **기준 문서**: `5130/` 폴더 분석 결과
> **상태**: ⏳ 대기 (품목 마이그레이션 선행 필요)
> **데이터 규모**: ~78,000 레코드 (입고 2,286 + 재고 ~500 + 주문 75,000+)
> **선행 조건**: `kd-items-migration-plan.md` 완료 필수

---

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

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

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

# 2. 선행 조건 확인 (items 마이그레이션 완료 여부)
docker exec sam-mysql-1 mysql -uroot -proot samdb -e "SELECT COUNT(*) FROM items WHERE tenant_id=287;"
# → 최소 600건 이상이어야 함

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

# 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 "SELECT COUNT(*) FROM instock;"

# 주문 기록 확인
docker exec sam-mysql-1 mysql -uroot -proot chandj -e "SELECT COUNT(*) FROM output;"
```

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

- [x] Docker 서비스 실행 중
- [x] `sam-mysql-1` 컨테이너 실행 중
- [x] chandj 데이터베이스 접근 가능
- [ ] **⚠️ 품목 마이그레이션 완료** (`kd-items-migration-plan.md`)
- [ ] SAM orders 마이그레이션 실행 완료 (`php artisan migrate`)
- [ ] SAM item_receipts 마이그레이션 실행 완료

---

## 📍 현재 진행 상태

| 항목 | 내용 |
|------|------|
| **마지막 완료 작업** | 문서 분리 완료 (items + orders 분리) |
| **다음 작업** | ⏳ 품목 마이그레이션 완료 대기 |
| **진행률** | 0/2 (0%) - 대기 중 |
| **마지막 업데이트** | 2026-01-28 |

### 시작 조건

**이 문서의 작업을 시작하기 전:**

1. ✅ `kd-items-migration-plan.md` Phase 1~4 완료
2. ✅ SAM items 테이블에 ~800건 이상 존재
3. ✅ SAM prices 테이블에 ~500건 이상 존재

```sql
-- 시작 조건 확인 쿼리
SELECT
    (SELECT COUNT(*) FROM items WHERE tenant_id=287) AS items_count,
    (SELECT COUNT(*) FROM prices WHERE tenant_id=287) AS prices_count;
-- items_count >= 700, prices_count >= 400 이어야 시작 가능
```

---

## 0. 성공 기준

| 기준 | 목표값 | 확인 방법 |
|------|-------|----------|
| **item_receipts 합계** | **~2,300건** | `SELECT COUNT(*) FROM item_receipts WHERE tenant_id=287` |
| **stocks 합계** | **~500건** | `SELECT COUNT(*) FROM stocks WHERE tenant_id=287` |
| **lots 합계** | **~200건** | `SELECT COUNT(*) FROM lots WHERE tenant_id=287` |
| **lot_sales 합계** | **~300건** | `SELECT COUNT(*) FROM lot_sales WHERE tenant_id=287` |
| **orders 합계** | **~25,000건** | `SELECT COUNT(*) FROM orders WHERE tenant_id=287` |
| **order_items 합계** | **~50,000건** | `SELECT COUNT(*) FROM order_items WHERE tenant_id=287` |
| item_id 연결율 | 100% | `SELECT COUNT(*) FROM item_receipts WHERE item_id IS NULL` (0건) |
| API 테스트 | 100% | `/api/v1/orders` 목록 조회 성공 |

---

## 1. 개요

### 1.1 배경

경동기업 레거시 시스템의 **입고/재고/주문** 데이터를 SAM으로 이관. 이 작업은 **품목(items) 마이그레이션 완료 후** 진행해야 함 (item_id FK 참조 필요).

### 1.2 핵심 차이점

```
┌────────────────────────────────────────────────────────────────────────────┐
│  레거시 (chandj)                         →    SAM (samdb)                   │
├────────────────────────────────────────────────────────────────────────────┤
│  📥 입고/재고                                                               │
│  ─────────────────────────────────────────────────────────────────────────  │
│  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.3 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. 레거시 DB 구조 분석

### 2.1 핵심 테이블 및 레코드 수

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

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

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

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

### 2.2 instock 테이블 구조 ⭐

```sql
-- instock: 입고 기록 (2,286건)
-- ⚠️ 실제 컬럼명 (2026-01-28 확인됨)
num INT PRIMARY KEY,                     -- PK ⭐
is_deleted INT,                          -- 삭제 여부
item_name VARCHAR(255),                  -- 품목명
prodcode VARCHAR(50),                    -- items.code와 매칭 ⭐
iList TEXT,                              -- 관련 정보 (JSON?)
lot_no VARCHAR(100),                     -- 로트번호
lotDone INT,                             -- 로트 완료 여부
inspection_date DATE,                    -- 검수일 (입고일로 사용) ⭐
supplier VARCHAR(255),                   -- 공급업체
specification VARCHAR(255),              -- 규격
unit VARCHAR(20),                        -- 단위
received_qty DECIMAL,                    -- 입고 수량 ⭐
material_no VARCHAR(100),                -- 자재번호
manufacturer VARCHAR(255),               -- 제조사
remarks TEXT,                            -- 비고 ⭐
purchase_price_excl_vat DECIMAL,         -- 단가 (부가세 제외) ⭐
weight_kg DECIMAL,                       -- 중량
searchtag TEXT,                          -- 검색 태그
update_log TEXT                          -- 변경 이력
```

### 2.3 output 테이블 구조 ⭐

```sql
-- output: 주문/출고 기록 (24,564건)
-- ⚠️ 실제 컬럼명 (2026-01-28 확인됨) - 70+ 컬럼 중 주요 컬럼만 표시
num INT PRIMARY KEY,                     -- PK ⭐ (output_id 대신)
secondordnum VARCHAR(50),                -- 2차 주문번호
iList VARCHAR(255),                      -- JSON 파일 경로 (../output/i_json/xxx.json) ⭐
COD VARCHAR(50),                         -- COD 코드
con_num VARCHAR(50),                     -- 계약번호
is_deleted INT,                          -- 삭제 여부
outdate DATE,                            -- 출고일 (order_date 대신) ⭐
indate DATE,                             -- 입고일/등록일
outworkplace VARCHAR(255),               -- 출고처/거래처 ⭐
orderman VARCHAR(100),                   -- 주문자
outputplace VARCHAR(255),                -- 출력처
receiver VARCHAR(100),                   -- 수령자
phone VARCHAR(50),                       -- 전화번호
comment TEXT,                            -- 비고 (memo 대신) ⭐
-- ... 이하 70+ 컬럼 (상세 분석 필요)
-- 참고: 전체 컬럼 목록 확인 필요
-- docker exec sam-mysql-1 mysql -uroot -proot chandj -e "DESCRIBE output;"
```

**output 테이블 전체 컬럼 확인 명령:**
```bash
docker exec sam-mysql-1 mysql -uroot -proot chandj -e "DESCRIBE output;" | head -80
```

---

## 3. SAM 테이블 구조 (Target)

### 3.1 item_receipts 테이블

```sql
CREATE TABLE item_receipts (
    id BIGINT PRIMARY KEY,
    tenant_id BIGINT NOT NULL,              -- 287 (경동기업)
    item_id BIGINT NOT NULL,                -- items.id FK ⭐
    receipt_date DATE NOT NULL,             -- 입고일
    quantity DECIMAL(15,4) NOT NULL,        -- 수량
    unit_price DECIMAL(15,4),               -- 단가
    total_amount DECIMAL(15,4),             -- 금액
    supplier_id BIGINT,                     -- 공급업체 ID
    lot_id BIGINT,                          -- 로트 ID
    note TEXT,
    created_by, updated_by, deleted_by, timestamps, soft_deletes
);
```

### 3.2 stocks 테이블

```sql
CREATE TABLE stocks (
    id BIGINT PRIMARY KEY,
    tenant_id BIGINT NOT NULL,
    item_id BIGINT NOT NULL,                -- items.id FK
    warehouse_id BIGINT,                    -- 창고 ID
    quantity DECIMAL(15,4) NOT NULL,        -- 현재고
    reserved_qty DECIMAL(15,4) DEFAULT 0,   -- 예약수량
    available_qty DECIMAL(15,4),            -- 가용재고
    last_movement_at TIMESTAMP,
    created_by, updated_by, timestamps
);
```

### 3.3 orders 테이블

```sql
CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    tenant_id BIGINT NOT NULL,
    order_no VARCHAR(50) NOT NULL,          -- 주문번호
    order_type VARCHAR(20) NOT NULL,        -- 주문/견적
    order_date DATE NOT NULL,
    delivery_date DATE,
    client_id BIGINT,                       -- 거래처 ID
    status VARCHAR(30),                     -- 상태
    total_amount DECIMAL(15,4),
    options JSON,                           -- iList JSON 데이터 ⭐
    approved_by BIGINT,
    approved_at TIMESTAMP,
    note TEXT,
    created_by, updated_by, deleted_by, timestamps, soft_deletes
);
```

### 3.4 order_items 테이블

```sql
CREATE TABLE order_items (
    id BIGINT PRIMARY KEY,
    tenant_id BIGINT NOT NULL,
    order_id BIGINT NOT NULL,               -- orders.id FK
    item_id BIGINT,                         -- items.id FK (nullable - 신규품목 가능)
    seq_no INT NOT NULL,                    -- 순번
    item_code VARCHAR(100),
    item_name VARCHAR(255),
    quantity DECIMAL(15,4) NOT NULL,
    unit_price DECIMAL(15,4),
    amount DECIMAL(15,4),
    options JSON,                           -- pages[n] JSON 데이터 ⭐
    note TEXT,
    created_by, updated_by, timestamps
);
```

---

## 4. 대상 범위

### 4.1 Phase 5: 입고/재고 데이터 이관 ⭐

| # | 작업 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 5.1 | instock 테이블 구조 분석 | ⏳ | 컬럼 확인 필요 |
| 5.2 | instock → item_receipts 매핑 설계 | ⏳ | item_code → item_id |
| 5.3 | instock → item_receipts INSERT | ⏳ | 2,286건 |
| 5.4 | instock 재고 집계 → stocks | ⏳ | 품목별 현재고 |
| 5.5 | lot → lots | ⏳ | 로트 관리 |
| 5.6 | lot_sales → lot_sales | ⏳ | 로트 소진 |
| 5.7 | ⚠️ **사용자 승인**: 입고/재고 INSERT 실행 | ⏳ | |

### 4.2 Phase 6: 주문/출고 데이터 이관 ⭐

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

---

## 5. SQL 쿼리 / 스크립트

### 5.1 instock → item_receipts

```sql
-- 입고 데이터 이관 (prodcode로 item_id 조회)
-- ⚠️ 실제 컬럼명 사용 (2026-01-28 확인됨)
INSERT INTO samdb.item_receipts (
    tenant_id, item_id, receipt_date, quantity,
    unit_price, total_amount, note,
    created_by, created_at, updated_at
)
SELECT
    287 AS tenant_id,
    i.id AS item_id,
    ins.inspection_date AS receipt_date,             -- ⭐ inspection_date 사용
    ins.received_qty AS quantity,                    -- ⭐ received_qty 사용
    ins.purchase_price_excl_vat AS unit_price,       -- ⭐ purchase_price_excl_vat 사용
    (ins.received_qty * COALESCE(ins.purchase_price_excl_vat, 0)) AS total_amount,  -- 계산
    CONCAT_WS(' | ',
        ins.remarks,
        CONCAT('supplier:', ins.supplier),
        CONCAT('manufacturer:', ins.manufacturer),
        CONCAT('material_no:', ins.material_no)
    ) AS note,                                       -- ⭐ remarks + 추가 정보
    1 AS created_by,
    NOW(), NOW()
FROM chandj.instock ins
JOIN samdb.items i ON i.code = ins.prodcode AND i.tenant_id = 287  -- ⭐ prodcode 사용
WHERE ins.is_deleted = 0
  AND ins.prodcode IS NOT NULL AND ins.prodcode != '';

-- 결과 확인
SELECT COUNT(*) FROM samdb.item_receipts WHERE tenant_id = 287;

-- item_id 연결 실패 레코드 확인
SELECT ins.prodcode, ins.item_name, COUNT(*) AS cnt
FROM chandj.instock ins
LEFT JOIN samdb.items i ON i.code = ins.prodcode AND i.tenant_id = 287
WHERE ins.is_deleted = 0 AND i.id IS NULL
GROUP BY ins.prodcode, ins.item_name;
```

### 5.2 재고 집계 → stocks

```sql
-- 입고 데이터 기반 현재고 집계
INSERT INTO samdb.stocks (
    tenant_id, item_id, quantity, available_qty,
    last_movement_at, created_by, created_at, updated_at
)
SELECT
    287 AS tenant_id,
    ir.item_id,
    SUM(ir.quantity) AS quantity,
    SUM(ir.quantity) AS available_qty,
    MAX(ir.receipt_date) AS last_movement_at,
    1 AS created_by,
    NOW(), NOW()
FROM samdb.item_receipts ir
WHERE ir.tenant_id = 287
GROUP BY ir.item_id;
```

### 5.3 output → orders + order_items [PHP 스크립트]

```php
<?php
/**
 * output → orders + order_items 마이그레이션 * ⚠️ 실제 컬럼명 사용 (2026-01-28 확인됨)
 *
 * 1단계: output 레코드 → orders 헤더 생성
 * 2단계: iList JSON 파일 파싱 → order_items 생성
 */

$tenantId = 287;
$userId = 1;
$basePath = '/Users/kent/Works/@KD_SAM/SAM/5130';

// output 레코드 조회 (실제 컬럼명 사용)
$stmt = $pdo->query("
    SELECT num, secondordnum, iList, COD, con_num,
           outdate, indate, outworkplace, orderman,
           outputplace, receiver, phone, comment
    FROM output
    WHERE is_deleted = 0
    ORDER BY num
");
$outputs = $stmt->fetchAll(PDO::FETCH_ASSOC);

$orderCount = 0;
$itemCount = 0;

foreach ($outputs as $output) {
    // 1단계: orders INSERT
    // ⭐ num을 사용 (output_id 대신)
    $orderNo = 'ORD-' . str_pad($output['num'], 8, '0', STR_PAD_LEFT);

    // iList JSON 파일 읽기
    $iListPath = $output['iList'];  // "../output/i_json/22545.json"
    if (empty($iListPath)) {
        continue;  // iList 없으면 스킵
    }

    $jsonFile = str_replace('../', '', $iListPath);
    $fullPath = $basePath . '/' . $jsonFile;

    $options = null;
    $approvedBy = null;
    $approvedAt = null;
    $jsonContent = null;

    if (file_exists($fullPath)) {
        $jsonContent = json_decode(file_get_contents($fullPath), true);

        // options에 전체 JSON 저장
        $options = json_encode([
            'inputValue' => $jsonContent['inputValue'] ?? [],
            'beforeWidth' => $jsonContent['beforeWidth'] ?? [],
            'beforeHeight' => $jsonContent['beforeHeight'] ?? [],
            'afterWidth' => $jsonContent['afterWidth'] ?? [],
            'afterHeight' => $jsonContent['afterHeight'] ?? [],
        ]);

        // 승인 정보 추출
        if (isset($jsonContent['approval']['approver'])) {
            $approver = $jsonContent['approval']['approver'];
            // approver.name으로 사용자 ID 조회 필요
            $approvedAt = $approver['date'] ?? null;
        }
    }

    $orderStmt = $pdo->prepare("
        INSERT INTO orders (
            tenant_id, order_no, order_type, order_date, delivery_date,
            status, total_amount, options, approved_at, note,
            created_by, created_at, updated_at
        ) VALUES (?, ?, 'order', ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
    ");
    $orderStmt->execute([
        $tenantId,
        $orderNo,
        $output['outdate'],           // ⭐ outdate 사용 (order_date 대신)
        $output['indate'],            // ⭐ indate 사용 (delivery_date 대신?)
        'completed',                  // 상태 - output 테이블에서 확인 필요
        0,                            // total_amount - output 테이블에서 확인 필요
        $options,
        $approvedAt,
        $output['comment'],           // ⭐ comment 사용 (memo 대신)
        $userId,
    ]);
    $orderId = $pdo->lastInsertId();
    $orderCount++;

    // 2단계: order_items INSERT (pages 배열 처리)
    if ($jsonContent && isset($jsonContent['pages']) && is_array($jsonContent['pages'])) {
        foreach ($jsonContent['pages'] as $seqNo => $page) {
            $itemOptions = json_encode([
                'inputItems' => $page['inputItems'] ?? [],
                'checkboxData' => $page['checkboxData'] ?? [],
            ]);

            $itemStmt = $pdo->prepare("
                INSERT INTO order_items (
                    tenant_id, order_id, seq_no, item_code, item_name,
                    quantity, options,
                    created_by, created_at, updated_at
                ) VALUES (?, ?, ?, ?, ?, 1, ?, ?, NOW(), NOW())
            ");
            $itemStmt->execute([
                $tenantId,
                $orderId,
                $seqNo + 1,
                null,  // item_code - JSON에서 추출 필요
                $output['outworkplace'] ?? '',  // ⭐ outworkplace 사용 (거래처명)
                $itemOptions,
                $userId
            ]);
            $itemCount++;
        }
    }

    if ($orderCount % 1000 === 0) {
        echo "진행중: {$orderCount} orders, {$itemCount} items\n";
    }
}

echo "완료: {$orderCount} orders, {$itemCount} items\n";
```

---

## 6. 기준 원칙

```
┌─────────────────────────────────────────────────────────────────────────┐
│  🎯 핵심 원칙                                                            │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  📦 데이터 전략                                                          │
│  ─────────────────────────────────────────────────────────────────────   │
│  - item_code → item_id 변환 (items 테이블 참조)                          │
│  - JSON 파일은 options 컬럼에 통째로 저장 (파싱 + 원본 보존)              │
│  - 재고는 입고 기록 집계로 계산                                          │
│                                                                          │
│  ⚠️ 선행 조건                                                            │
│  ─────────────────────────────────────────────────────────────────────   │
│  - 반드시 items 마이그레이션 완료 후 진행                                 │
│  - item_code가 없는 레코드는 스킵하고 로그 기록                          │
│                                                                          │
│  ✅ 필수 사항                                                            │
│  ─────────────────────────────────────────────────────────────────────   │
│  - 전체 이관 (instock 2,286건, output 24,564건)                         │
│  - JSON 파일 파싱 (5130/output/i_json/*.json)                           │
│  - 로컬 검증 완료 후 개발서버 배포                                       │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘
```

### 6.1 변경 승인 정책

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

---

## 7. 데이터 규모 예상

### 7.1 입고/재고 테이블 예상

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

### 7.2 주문/출고 테이블 예상

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

### 7.3 전체 마이그레이션 요약 (이 문서 범위)

| SAM 테이블 | 예상 건수 | 비고 |
|------------|----------|------|
| item_receipts | ~2,300 | 입고 기록 |
| stocks | ~500 | 현재고 |
| lots | ~200 | 로트 |
| lot_sales | ~300 | 로트 소진 |
| orders | ~25,000 | 주문 헤더 |
| order_items | ~50,000 | 주문 상세 |
| **총계** | **~78,000건** | |

---

## 8. 체크리스트

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

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

---

## 9. 참고 문서

- **레거시 소스**: `5130/` 폴더
- **JSON 파일 경로**: `5130/output/i_json/*.json`
- **선행 문서**: `docs/dev_plans/kd-items-migration-plan.md` (품목/단가 마이그레이션)
- **SAM orders 마이그레이션**: `api/database/migrations/*_create_orders_table.php`
- **SAM item_receipts 마이그레이션**: `api/database/migrations/*_create_item_receipts_table.php`
- **DummyDataSeeder**: `api/database/seeders/DummyDataSeeder.php` (TENANT_ID=287, USER_ID=1)

---

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

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

# 2. 선행 조건 확인
docker exec sam-mysql-1 mysql -uroot -proot samdb -e "SELECT COUNT(*) FROM items WHERE tenant_id=287;"
# → 최소 600건 이상이어야 시작 가능

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

### 10.2 작업 중 관리

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

---

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

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

```
┌─────────────────────────────────────────────────────────────────────────┐
│  📋 핵심 정보 요약                                                       │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  🎯 목표: 경동기업 레거시(chandj) → SAM(samdb) 입고/재고/주문 이관       │
│                                                                          │
│  📊 데이터 규모 (총 ~78,000건):                                          │
│     - item_receipts: ~2,300건 (입고)                                    │
│     - stocks: ~500건 (현재고)                                           │
│     - orders: ~25,000건 (주문 헤더)                                     │
│     - order_items: ~50,000건 (주문 상세)                                │
│                                                                          │
│  🔑 핵심 상수:                                                           │
│     - tenant_id = 287 (경동기업)                                        │
│     - user_id = 1 (생성자)                                              │
│     - Docker: sam-mysql-1                                               │
│     - 레거시 DB: chandj / SAM DB: samdb ⚠️                              │
│     - JSON 파일: 5130/output/i_json/*.json                              │
│                                                                          │
│  ⭐ instock 실제 컬럼명 (2026-01-28 확인):                               │
│     - prodcode (품목코드) → items.code 매칭용                            │
│     - item_name (품목명)                                                │
│     - received_qty (입고수량)                                           │
│     - purchase_price_excl_vat (단가)                                    │
│     - inspection_date (입고일)                                          │
│     - remarks (비고)                                                    │
│                                                                          │
│  ⭐ output 실제 컬럼명 (2026-01-28 확인):                                │
│     - num (PK, output_id 대신)                                          │
│     - outdate (출고일, order_date 대신)                                 │
│     - iList (JSON 파일 경로)                                            │
│     - outworkplace (거래처)                                             │
│     - comment (비고, memo 대신)                                         │
│                                                                          │
│  ⚠️ 선행 조건:                                                           │
│     - kd-items-migration-plan.md 완료 필수!                             │
│     - SAM items 테이블에 ~800건 이상 존재해야 함                         │
│                                                                          │
│  ⭐ 마이그레이션 순서:                                                    │
│     1. instock → item_receipts (2,286건)                                │
│     2. 재고 집계 → stocks (~500건)                                      │
│     3. output → orders + order_items (24,564건 + ~50,000건)             │
│                                                                          │
│  📍 현재 상태: ⏳ 대기 (품목 마이그레이션 완료 대기)                       │
│                                                                          │
│  📎 선행 문서: docs/dev_plans/kd-items-migration-plan.md (품목/단가)         │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘
```

---

## 12. 변경 이력

| 날짜 | 항목 | 변경 내용 | 파일 | 승인 |
|------|------|----------|------|------|
| 2026-01-28 | 문서 분리 | items-migration-kyungdong-plan.md에서 입고/재고/주문 부분 분리 | - | - |
| 2026-01-28 | 문서 생성 | kd-orders-migration-plan.md 신규 생성 | - | - |
| 2026-01-28 | 컬럼명 수정 | 실제 DB 컬럼명으로 업데이트 (item_code→prodcode, output_id→num 등) | - | - |

---

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

### 13.1 일반적인 문제

| 문제 | 원인 | 해결책 |
|------|------|--------|
| item_id 연결 실패 | items 마이그레이션 미완료 | `kd-items-migration-plan.md` 먼저 완료 |
| JSON 파일 없음 | 파일 경로 오류 | `5130/output/i_json/` 폴더 확인 |
| 대량 INSERT 느림 | 단건 INSERT | 배치 INSERT (1000건씩) 사용 |
| 외래키 오류 | item_id 없음 | item_code → item_id 매핑 확인 |

### 13.2 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}");
}
```

### 13.3 prodcode → item_id 매칭 실패

```sql
-- 매칭 실패 레코드 확인 (⭐ prodcode 사용)
SELECT ins.prodcode, ins.item_name, COUNT(*) AS cnt
FROM chandj.instock ins
LEFT JOIN samdb.items i ON i.code = ins.prodcode AND i.tenant_id = 287
WHERE ins.is_deleted = 0 AND i.id IS NULL
GROUP BY ins.prodcode, ins.item_name;

-- 해결 방법:
-- 1. 매칭 실패한 prodcode를 items 테이블에 추가
-- 2. 또는 스킵하고 로그 기록

-- items에 없는 품목 신규 생성 쿼리 (필요시)
INSERT INTO samdb.items (tenant_id, item_type, code, name, unit, attributes, is_active, created_by, created_at, updated_at)
SELECT DISTINCT
    287 AS tenant_id,
    'SM' AS item_type,  -- 기본값: 부자재
    ins.prodcode AS code,
    ins.item_name AS name,
    ins.unit AS unit,
    JSON_OBJECT('legacy_source', 'instock', 'specification', ins.specification) AS attributes,
    1 AS is_active,
    1 AS created_by,
    NOW(), NOW()
FROM chandj.instock ins
LEFT JOIN samdb.items i ON i.code = ins.prodcode AND i.tenant_id = 287
WHERE ins.is_deleted = 0
  AND ins.prodcode IS NOT NULL AND ins.prodcode != ''
  AND i.id IS NULL;
```

---

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