docs: 기획 문서 추가
- 문서관리 1단계 변경사항 (20260128_document_management_phase1_1.md) - FCM 사용자 타겟 알림 계획 - 수입검사 문서 통합 계획 - 품목 마이그레이션 계획 (경동) - 수주 마이그레이션 계획 (경동) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
825
plans/kd-orders-migration-plan.md
Normal file
825
plans/kd-orders-migration-plan.md
Normal file
@@ -0,0 +1,825 @@
|
||||
# 경동기업(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/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 스킬로 생성되었습니다.*
|
||||
Reference in New Issue
Block a user