Files
sam-docs/plans/items-migration-kyungdong-plan.md
권혁성 e25a87ed1d docs: 기획 문서 추가
- 문서관리 1단계 변경사항 (20260128_document_management_phase1_1.md)
- FCM 사용자 타겟 알림 계획
- 수입검사 문서 통합 계획
- 품목 마이그레이션 계획 (경동)
- 수주 마이그레이션 계획 (경동)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 01:12:40 +09:00

59 KiB
Raw Blame History

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

⚠️ 이 문서는 분리되었습니다 (2026-01-28)

이 통합 문서는 다음 2개 문서로 분리되었습니다:

  1. 📦 품목/단가/BOM: kd-items-migration-plan.md먼저 작업
  2. 📋 입고/재고/주문: kd-orders-migration-plan.md ← 품목 완료 후 작업

아래 내용은 참고용으로 보존됩니다.


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


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

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

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

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

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

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

환경 정보

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

DB 접속 명령어

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

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

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

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

전제 조건 (작업 전 확인)

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

📍 현재 진행 상태

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

다음 작업 상세

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

  1. KDunitprice 데이터 확인:

    docker exec sam-mysql-1 mysql -uroot -proot chandj -e "SELECT COUNT(*), item_div FROM KDunitprice GROUP BY item_div;"
    
  2. 섹션 5.0의 SQL 쿼리를 SAM DB에서 실행:

    • KDunitprice → items (603건)
    • KDunitprice → prices (603건)
  3. 중복 확인 후 추가 items 생성:

    • models, category_l4 중 KDunitprice에 없는 것만 추가
  4. ⚠️ 실행 전 사용자 승인 필요


0. 성공 기준

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

1. 개요

1.1 배경

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

1.2 핵심 차이점

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

1.2.1 중복 제거 전략

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

1.3 SAM items 구조 (Target)

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

1.4 item_type 분류

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

1.4.1 KDunitprice.item_div → item_type 매핑

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

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

2. 레거시 DB 구조 분석

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

📦 품목 마스터 테이블

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

🔧 제품 상세 테이블

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

💰 단가 테이블

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

📥 입고/재고 테이블

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

📋 주문/출고 테이블

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

2.2 models 테이블 구조

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

샘플 데이터:

  • KSS01/스크린/SUS마감/벽면형
  • KSS01/스크린/SUS마감/측면형
  • KSE01/스크린/EGI마감/벽면형
  • KWE01/스크린/SUS마감/벽면형

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

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

item_div 분포 (예상):

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

2.3.1 output.iList JSON 파일 구조

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

JSON 파일 내용 예시 (5130/output/i_json/22545.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 매핑:

  • inputValueorders.options (JSON)
  • pagesorder_items.options (JSON)
  • approvalorders.approved_by, orders.approved_at
  • beforeWidth/Height, afterWidth/Heightorder_items.options.dimensions

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

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

savejson 예시 (가이드레일 BOM):

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

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

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

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

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

price_motor itemList 예시:

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

2.6 단가 시스템 상세 분석

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

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

2.6.2 공통 테이블 구조

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

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

price_motor (모터/제어기):

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

price_shaft (감기샤프트):

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

price_raw_materials (원자재):

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

price_pipe (파이프):

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

2.6.4 SAM prices 테이블 구조 (Target)

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

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

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

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

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

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

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

2.6.5 Legacy → SAM 단가 매핑 전략

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

2.6.6 items와 prices 관계

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

3. 매핑 설계

3.1 models → items (FG 완제품)

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

코드 생성 규칙:

FG-{model_name}-{guiderail_type}-{finishing_type}
예: FG-KSS01-벽면형-SUS

3.2 BDmodels → items (FG 세부 + BOM)

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

3.3 category_l4 → items (PT 부품)

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

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

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

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

4. 대상 범위

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

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

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

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

4.3 Phase 3: 검증 및 배포

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

4.4 Phase 4: 단가 데이터 이관

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

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

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

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

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

5. SQL 쿼리 (예상)

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

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

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

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

5.0.1 KDunitprice → prices (기본 단가)

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

5.1 models → items (FG)

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

5.2 category_l4 → items (PT)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

$totalItems = 0;
$totalPrices = 0;

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

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

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

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

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

6. 기준 원칙

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

6.1 변경 승인 정책

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

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

7.1 items 테이블 예상

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

item_type별 분포 예상:

item_type 설명 예상 건수
FG 완제품 ~100건
PT 부품 ~250건
SM 부자재 ~300건
RM 원자재 ~100건
CS 소모품 ~50건

7.2 prices 테이블 예상

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

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

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

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

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

7.5 전체 마이그레이션 요약

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

8. 체크리스트

Phase 1: 마스터 데이터 이관

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

Phase 2: BOM 데이터 이관

  • BDmodels.savejson 파싱 로직 작성
  • child_item_id 매핑 테이블 생성
  • items.bom JSON 생성
  • ⚠️ 사용자 승인: BOM 데이터 INSERT 실행

Phase 3: 검증 및 배포

  • 건수 검증
  • API 테스트
  • ⚠️ 사용자 승인: 개발서버 배포

Phase 4: 단가 데이터 이관

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

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

  • instock 테이블 구조 분석
  • instock → item_receipts 매핑 설계
  • 재고 집계 → stocks 매핑 설계
  • lot/lot_sales 구조 분석
  • 마이그레이션 스크립트 작성
  • ⚠️ 사용자 승인: 입고/재고 INSERT 실행

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

  • output 테이블 구조 분석
  • output.iList JSON 파일 구조 분석 (완료)
  • output → orders 매핑 설계
  • JSON → order_items 매핑 설계
  • estimate → orders 매핑 설계
  • 마이그레이션 스크립트 작성 (24,564건)
  • ⚠️ 사용자 승인: 주문/출고 INSERT 실행

9. 참고 문서

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

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

10.1 세션 시작 시 (Load Strategy)

# 1. Docker 확인
docker ps | grep sam

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

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

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

10.2 작업 중 관리

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

10.3 컨텍스트 관리

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

11. 자기완결성 점검 결과

11.1 체크리스트 검증

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

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

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

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

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

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

12. 변경 이력

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

13. 트러블슈팅 가이드

13.1 일반적인 문제

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

13.2 JSON 파싱 오류

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

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

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

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

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

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

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

13.4 output.iList JSON 파일 처리

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

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

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

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