- 개발팀 전용 폴더 dev/ 생성 (standards, guides, quickstart, changes, deploys, data, history, dev_plans 이동) - 프론트엔드 전용 폴더 frontend/ 생성 (api/ → frontend/api-specs/) - 기획팀 폴더 requests/ 생성 - plans/ → dev/dev_plans/ 이름 변경 - README.md 신규 (사람용 안내), INDEX.md 재작성 (Claude Code용) - resources.md 신규 (노션 링크용, assets/brochure 이관 예정) - CURRENT_WORKS.md 삭제, TODO.md → dev/ 이동 - 전체 참조 경로 업데이트 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
825 lines
34 KiB
Markdown
825 lines
34 KiB
Markdown
# 경동기업(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 스킬로 생성되었습니다.* |