# 경동기업(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 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/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/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 스킬로 생성되었습니다.*