Files
sam-docs/plans/archive/kd-items-migration-plan.md
권혁성 28b69e5449 docs: archive 37개 + COMPLETED 3개 복원 - 향후 docs/ 정식 문서화 시 참조용
- 완료 문서의 상세 내용은 추후 docs/ 구조화 시 정식 문서에 반영 예정
- HISTORY.md는 요약 인덱스로 유지, 개별 파일은 상세 참조용 보관

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 22:32:20 +09:00

52 KiB
Raw Blame History

경동기업(5130) 품목/단가 마이그레이션 계획

작성일: 2026-01-28 목적: 경동기업 레거시 시스템(5130/)의 품목(items), 단가(prices), BOM 데이터를 SAM으로 이관 기준 문서: 5130/ 폴더 분석 결과 상태: 🔄 분석 완료, 구현 대기 데이터 규모: ~1,500 레코드 (items ~800 + prices ~500 + BOM ~200)


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

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

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

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

# 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 마이그레이션 실행 완료

📍 현재 진행 상태

항목 내용
마지막 완료 작업 정적 데이터 마이그레이션 완료
다음 작업 동적 BOM/견적 로직 구현 → kd-quote-logic-plan.md
진행률 4/4 (100%) - 정적 데이터 완료
마지막 업데이트 2026-01-28

⚠️ 주의: 이 문서는 정적 품목/단가 데이터 이관만 다룹니다. 동적 BOM 계산, 모터/제어기/부자재 자동 추가 등 견적 로직은 별도 문서 참조: → kd-quote-logic-plan.md

Phase 1~3 실행 결과

소스 타입 건수
KDunitprice FG/PT/SM/RM/CS 601건
models FG +18건
item_list PT +9건
BDmodels.seconditem PT (누락 부품) +6건
price_motor SM (누락 품목) +13건
price_raw_materials RM (누락 품목) +4건
items 합계 651건
prices 합계 651건
BOM 연결 items.bom JSON 18건

Phase 2 상세:

  • Phase 2.1: BDmodels.seconditem → PT items 6건 추가
    • L-BAR, 보강평철, 케이스, 하단마감재, 가이드레일용 연기차단재, 케이스용 연기차단재
  • Phase 2.2: BDmodels → items.bom JSON 연결 18건
    • FG items (models 기반) ↔ PT items (seconditem) 연결

Phase 3 상세:

  • Phase 3.1: price_motor → SM items 13건 추가
    • PM-020PM-032: 제어기 (6P18P, 20회선~100회선)
    • PM-033~PM-035: 방화/방범 콘트롤박스, 스위치
  • Phase 3.2: price_raw_materials → RM items 4건 추가
    • RM-007: 신설비상문 (3x2 300*200)
    • RM-008~RM-009: 제연커튼 (연기차단원단, 불투명)
    • RM-010~RM-011: 화이바원단, 와이어원단
  • 중복 확인: KDunitprice 기존 품목과 명칭 비교로 중복 제외

Phase 4 검증 결과

로컬 검증 완료 (2026-01-28):

검증 항목 기대값 실제값 상태
items 총 건수 651건 651건
prices 총 건수 651건 651건
BOM 연결 18건 18건
code 중복 0건 0건

item_type 분포:

item_type 건수
FG (완제품) 470건
PT (부품) 88건
SM (부자재) 61건
RM (원자재) 28건
CS (소모품) 4건

후속 작업

이 문서 범위 (정적 데이터):

  • 완료 - 개발서버 배포 대기 중

별도 문서 (동적 로직):

  • kd-quote-logic-plan.md
  • 5130 견적 로직 분석
  • 동적 BOM 계산 (모터/제어기/부자재)
  • 파라미터 기반 절곡품 산출

Seeder 재실행 방법

# Docker 컨테이너 내부에서 실행
docker exec sam-api-1 php artisan db:seed --class="Database\\Seeders\\Kyungdong\\KyungdongItemSeeder"

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
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 (기본가)               │
└────────────────────────────────────────────────────────────────────────────┘

1.2.1 중복 제거 전략

┌────────────────────────────────────────────────────────────────────────────┐
│  🎯 핵심: KDunitprice가 마스터, code 필드로 중복 방지                        │
├────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  1⃣ KDunitprice (603건) → items 먼저 생성                                  │
│     - item_div로 item_type 결정                                            │
│     - code = prodcode 그대로 사용 ⭐                                        │
│                                                                             │
│  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 경동기업)
-- ⚠️ 실제 컬럼명 (2026-01-28 확인됨)
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,          -- 품목코드 (← KDunitprice.prodcode)
    name VARCHAR(255) NOT NULL,          -- 품목명 (← KDunitprice.item_name)
    unit VARCHAR(20),                    -- 단위 (← KDunitprice.unit)
    category_id BIGINT,                  -- 카테고리 ID
    process_type VARCHAR(50),            -- 공정 타입
    item_category VARCHAR(50),           -- 품목 분류
    bom JSON,                            -- BOM 정보
    attributes JSON,                     -- 동적 필드 값 (spec 등)
    attributes_archive JSON,             -- 속성 아카이브
    options JSON,                        -- 추가 옵션
    description TEXT,                    -- 설명
    is_active BOOLEAN DEFAULT TRUE,
    created_by BIGINT,
    updated_by BIGINT,
    deleted_by BIGINT,
    created_at TIMESTAMP,
    updated_at TIMESTAMP,
    deleted_at TIMESTAMP                 -- Soft Delete
);

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 매핑
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

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

-- KDunitprice: 품목 마스터 (603건) - 가장 중요한 테이블!
-- ⚠️ 실제 컬럼명 (2026-01-28 확인됨)
num INT PRIMARY KEY,           -- PK
is_deleted INT,                -- 삭제 여부
prodcode VARCHAR(50),          -- items.code (유니크 키!) ⭐
item_name VARCHAR(255),        -- items.name ⭐
item_div VARCHAR(20),          -- [제품]/[상품]/[부재료]/[원재료]/[반제품]/[무형상품] → item_type ⭐
spec VARCHAR(100),             -- items.attributes.spec
unit VARCHAR(20),              -- items.unit
unitprice DECIMAL,             -- prices.sales_price (단일 컬럼, 입고가/출고가 구분 없음!) ⭐
searchtag TEXT,                -- 검색 태그
update_log TEXT                -- 변경 이력

item_div 분포 확인 쿼리:

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

2.3 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":"삭제"}
]

2.4 단가 시스템 상세 분석

2.4.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.4.2 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
);

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 price_* → prices 테이블 (단가 연동)

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

레거시 (price_*) SAM (prices) 비고
registedate effective_from 적용 시작일
itemList.col13 (판매가) sales_price
itemList.col11 (원가) purchase_price
- 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 price_motor → items (SM) + prices ~35건 품목 + 단가
3.2 price_shaft → items (SM) + prices ~15건
3.3 price_pipe → items (SM) + prices ~10건
3.4 price_angle → items (SM) + prices ~10건
3.5 price_raw_materials → items (RM) + prices ~20건
3.6 price_bend → items (SM) + prices ~10건
3.7 price_pole → items (SM) + prices ~5건
3.8 price_screenplate → items (SM) + prices ~5건
3.9 price_smokeban → items (SM) + prices ~5건
3.10 단가 버전 이력 정리 effective_from/to 설정
3.11 ⚠️ 사용자 승인: 단가 INSERT 실행

4.4 Phase 4: 검증 및 배포

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

5. Seeder 파일

5.0 Seeder 구조 및 실행 방법

파일 위치: api/database/seeders/Kyungdong/KyungdongItemSeeder.php

실행 명령어:

# 로컬 실행 (tenant_id=287만 삭제 후 INSERT)
cd /Users/kent/Works/@KD_SAM/SAM/api
php artisan db:seed --class=Database\\Seeders\\Kyungdong\\KyungdongItemSeeder

# 개발서버 실행 (TRUNCATE 후 INSERT) - ⚠️ 컨펌 필요
php artisan db:seed --class=Database\\Seeders\\Kyungdong\\KyungdongItemSeeder --env=development

환경별 삭제 전략:

환경 삭제 방식 비고
로컬 (local) DELETE WHERE tenant_id=287 다른 테넌트 데이터 보존
개발 (development) TRUNCATE 전체 초기화

5.1 KyungdongItemSeeder.php (전체 코드)

<?php

namespace Database\Seeders\Kyungdong;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\App;

class KyungdongItemSeeder extends Seeder
{
    private const TENANT_ID = 287;  // 경동기업
    private const USER_ID = 1;

    /**
     * 경동기업 품목/단가 마이그레이션 Seeder
     *
     * 소스: chandj.KDunitprice (603건)
     * 타겟: samdb.items, samdb.prices
     */
    public function run(): void
    {
        $this->command->info('🚀 경동기업 품목/단가 마이그레이션 시작...');

        // 1. 기존 데이터 삭제
        $this->cleanupExistingData();

        // 2. KDunitprice → items
        $itemCount = $this->migrateItems();

        // 3. KDunitprice → prices
        $priceCount = $this->migratePrices();

        $this->command->info("✅ 완료: items {$itemCount}건, prices {$priceCount}건");
    }

    /**
     * 기존 데이터 삭제
     */
    private function cleanupExistingData(): void
    {
        if (App::environment('local')) {
            // 로컬: tenant_id=287만 삭제
            $this->command->info('   🧹 로컬 환경: tenant_id=287 데이터 삭제...');
            DB::table('prices')->where('tenant_id', self::TENANT_ID)->delete();
            DB::table('items')->where('tenant_id', self::TENANT_ID)->delete();
        } else {
            // 개발/운영: TRUNCATE (⚠️ 주의)
            $this->command->info('   🧹 개발 환경: TRUNCATE...');
            DB::statement('SET FOREIGN_KEY_CHECKS=0');
            DB::table('prices')->truncate();
            DB::table('items')->truncate();
            DB::statement('SET FOREIGN_KEY_CHECKS=1');
        }
    }

    /**
     * KDunitprice → items 마이그레이션
     */
    private function migrateItems(): int
    {
        $this->command->info('   📦 KDunitprice → items 마이그레이션...');

        // chandj.KDunitprice에서 데이터 조회
        $kdItems = DB::connection('legacy')  // config/database.php에 'legacy' 연결 필요
            ->table('KDunitprice')
            ->where('is_deleted', 0)
            ->whereNotNull('prodcode')
            ->where('prodcode', '!=', '')
            ->get();

        $items = [];
        $now = now();

        foreach ($kdItems as $kd) {
            $items[] = [
                'tenant_id' => self::TENANT_ID,
                'item_type' => $this->mapItemType($kd->item_div),
                'code' => $kd->prodcode,
                'name' => $kd->item_name,
                'unit' => $kd->unit,
                'attributes' => json_encode([
                    'spec' => $kd->spec,
                    'item_div' => $kd->item_div,
                    'legacy_source' => 'KDunitprice',
                    'legacy_num' => $kd->num,
                ]),
                'is_active' => true,
                'created_by' => self::USER_ID,
                'updated_by' => self::USER_ID,
                'created_at' => $now,
                'updated_at' => $now,
            ];

            // 500건씩 배치 INSERT
            if (count($items) >= 500) {
                DB::table('items')->insert($items);
                $items = [];
            }
        }

        // 남은 데이터 INSERT
        if (!empty($items)) {
            DB::table('items')->insert($items);
        }

        return $kdItems->count();
    }

    /**
     * KDunitprice → prices 마이그레이션
     */
    private function migratePrices(): int
    {
        $this->command->info('   💰 KDunitprice → prices 마이그레이션...');

        // items와 KDunitprice 조인하여 prices 생성
        $count = DB::statement("
            INSERT INTO prices (
                tenant_id, item_type_code, item_id, client_group_id,
                purchase_price, sales_price,
                effective_from, status,
                created_by, updated_by, created_at, updated_at
            )
            SELECT
                ? AS tenant_id,
                i.item_type AS item_type_code,
                i.id AS item_id,
                NULL AS client_group_id,
                0 AS purchase_price,
                COALESCE(k.unitprice, 0) AS sales_price,
                CURDATE() AS effective_from,
                'active' AS status,
                ? AS created_by,
                ? AS updated_by,
                NOW(), NOW()
            FROM items i
            JOIN " . config('database.connections.legacy.database') . ".KDunitprice k
                ON k.prodcode = i.code
            WHERE i.tenant_id = ?
              AND k.is_deleted = 0
              AND k.prodcode IS NOT NULL
              AND k.prodcode != ''
        ", [self::TENANT_ID, self::USER_ID, self::USER_ID, self::TENANT_ID]);

        return DB::table('prices')->where('tenant_id', self::TENANT_ID)->count();
    }

    /**
     * item_div → item_type 매핑
     */
    private function mapItemType(?string $itemDiv): string
    {
        return match ($itemDiv) {
            '[제품]', '[상품]' => 'FG',
            '[반제품]' => 'PT',
            '[부재료]' => 'SM',
            '[원재료]' => 'RM',
            '[무형상품]' => 'CS',
            default => 'SM',
        };
    }
}

5.2 Legacy DB 연결 설정

config/database.php에 추가:

'connections' => [
    // ... 기존 연결들

    'legacy' => [
        'driver' => 'mysql',
        'host' => env('LEGACY_DB_HOST', '127.0.0.1'),
        'port' => env('LEGACY_DB_PORT', '3306'),
        'database' => env('LEGACY_DB_DATABASE', 'chandj'),
        'username' => env('LEGACY_DB_USERNAME', 'root'),
        'password' => env('LEGACY_DB_PASSWORD', 'root'),
        'charset' => 'utf8mb4',
        'collation' => 'utf8mb4_unicode_ci',
    ],
],

.env에 추가:

LEGACY_DB_HOST=127.0.0.1
LEGACY_DB_PORT=3306
LEGACY_DB_DATABASE=chandj
LEGACY_DB_USERNAME=root
LEGACY_DB_PASSWORD=root

5.3 참고: SQL 쿼리 (직접 실행용)

5.3.1 KDunitprice → items (마스터)

-- ⚠️ 참고용 SQL (Seeder 사용 권장)
-- KDunitprice: 품목 마스터 (603건) → SAM 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,
    prodcode AS code,                    -- 유니크 키! ⭐
    item_name AS name,                   -- ⭐
    unit AS unit,
    JSON_OBJECT(
        'spec', spec,                    -- ⭐
        'item_div', item_div,
        'legacy_source', 'KDunitprice',
        'legacy_num', num
    ) AS attributes,
    NULL AS description,                 -- 비고 컬럼 없음
    1 AS is_active,
    1 AS created_by,
    NOW(), NOW()
FROM chandj.KDunitprice
WHERE is_deleted = 0
  AND prodcode IS NOT NULL AND prodcode != '';

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

5.3.2 KDunitprice → prices (기본 단가)

-- ⚠️ 참고용 SQL (Seeder 사용 권장)
-- unitprice 단일 컬럼 → sales_price, purchase_price는 0
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,           -- 기본가
    0 AS purchase_price,               -- 입고가 컬럼 없음, 0으로 설정
    COALESCE(k.unitprice, 0) AS sales_price,  -- ⭐ unitprice 사용
    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.prodcode AND i.tenant_id = 287  -- ⭐ prodcode 사용
WHERE k.is_deleted = 0
  AND k.prodcode IS NOT NULL AND k.prodcode != '';

5.4 models → items (FG) - 추가 SQL 참고용

-- ⚠️ 참고용 SQL (Seeder 확장 시 사용)
-- 레거시 chandj.models → SAM items (FG)
-- KDunitprice에 없는 것만 추가 (중복 확인 필요)
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.5 category_l4 → items (PT) - 추가 SQL 참고용

-- ⚠️ 참고용 SQL (Seeder 확장 시 사용)
-- 레거시 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.6 price_motor → items (SM) + prices - PHP 스크립트 참고용

<?php
/**
 * ⚠️ 참고용 스크립트 (Seeder 확장 시 활용)
 * 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";
}

6. 기준 원칙

┌─────────────────────────────────────────────────────────────────────────┐
│  🎯 핵심 원칙                                                            │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  📦 데이터 전략                                                          │
│  ─────────────────────────────────────────────────────────────────────   │
│  - KDunitprice(603건)가 품목 마스터 → items 최우선 생성                  │
│  - code 필드로 중복 방지 (ON DUPLICATE KEY UPDATE)                       │
│  - BOM은 item_bom_items 테이블 사용 (items.bom JSON ❌)                  │
│  - 단가 정보는 prices 테이블에 별도 저장 (items.attributes ❌)            │
│                                                                          │
│  ❌ 불필요한 것                                                          │
│  ─────────────────────────────────────────────────────────────────────   │
│  - item_id_mappings 테이블 (양방향 조회 불필요)                          │
│  - chandj 수정 (손 안댐, samdb에만 밀어 넣음)                            │
│  - 레거시 소스 확인 (마이그레이션 후 검증만)                             │
│                                                                          │
│  ✅ 필수 사항                                                            │
│  ─────────────────────────────────────────────────────────────────────   │
│  - 경동기업 기준으로 맞춤 (이미 사용중인 시스템)                          │
│  - 전체 이관 (items + prices + BOM)                                     │
│  - 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건 (중복 제외)

8. 체크리스트

Phase 1: 마스터 데이터 이관 완료

  • 레거시 DB 구조 분석 완료
  • KDunitprice 테이블 발견 및 분석 (603건, 핵심 마스터)
  • 중복 제거 전략 수립 (code 기반, 매핑 테이블 불필요)
  • Seeder 기반 마이그레이션 계획 수립
  • config/database.php에 'legacy' 연결 추가 → 기존 'chandj' 연결 사용
  • .env에 LEGACY_DB_* 환경변수 추가 → 기존 CHANDJ_DB_* 사용
  • Phase 1.0: KDunitprice → items 601건, prices 601건
  • Phase 1.1: models → items (FG) 18건
  • Phase 1.2: item_list → items (PT) 9건
  • Phase 1.3: category_l4 → 스킵 (카테고리 데이터)
  • Phase 1 결과: items 628건, prices 628건

Phase 2: BOM 데이터 이관 완료

  • BDmodels.seconditem → PT items 누락 부품 6건 추가
  • child_item_id 매핑 테이블 생성 → code 기반 직접 조회
  • items.bom JSON 생성 (18건 FG ↔ PT 연결)
  • 최종 결과: items 634건, prices 634건, BOM 18건 (2026-01-28)

Phase 3: 단가 데이터 이관 완료

  • 레거시 price_* 테이블 구조 분석 (10개)
  • 각 테이블별 JSON 스키마 분석
  • SAM prices 테이블 구조 확인
  • Legacy → SAM 단가 매핑 전략 수립
  • price_motor → items (SM) 누락 품목 13건 추가
  • price_raw_materials → items (RM) 누락 품목 4건 추가
  • 기타 price_* 테이블 분석 완료 (대부분 계산 참조용, 품목 마스터 아님)
    • price_shaft, price_pipe, price_angle, price_bend, price_pole, price_screenplate: 계산 참조용
    • 220V/380V 모터: KDunitprice에 "KD모터*Kg단상/삼상"으로 이미 존재
  • 사용자 승인: 완료 (2026-01-28)

Phase 4: 검증 및 배포 로컬 검증 완료

  • 건수 검증 (items 651건, prices 651건, BOM 18건)
  • 데이터 조회 테스트 (artisan tinker, MySQL 직접 쿼리)
  • code 중복 검증 (0건)
  • Phase 3 추가 품목 확인 (PM-* 13건, RM-* 4건)
  • ⚠️ 사용자 승인: 개발서버 배포

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
  • 연관 문서: docs/plans/kd-orders-migration-plan.md (입고/재고/주문 마이그레이션)

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 실행 "12. 변경 이력" 추가
스키마 변경 관련 섹션 업데이트 + 변경 이력 추가
오류 발생 체크리스트에 메모 추가

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) 품목/단가 이관            │
│                                                                          │
│  📊 데이터 규모 (총 ~1,500건):                                          │
│     - items: ~800건 (KDunitprice 603 + 추가)                            │
│     - prices: ~500건                                                    │
│     - item_bom_items: ~200건                                           │
│                                                                          │
│  🔑 핵심 상수:                                                           │
│     - tenant_id = 287 (경동기업)                                        │
│     - user_id = 1 (생성자)                                              │
│     - Docker: sam-mysql-1                                               │
│     - 레거시 DB: chandj / SAM DB: samdb ⚠️                              │
│                                                                          │
│  ⭐ KDunitprice 실제 컬럼명 (2026-01-28 확인):                           │
│     - prodcode (품목코드) → items.code                                  │
│     - item_name (품목명) → items.name                                   │
│     - spec (규격) → items.attributes.spec                               │
│     - unit (단위) → items.unit                                          │
│     - item_div ([제품] 등) → items.item_type                            │
│     - unitprice (단가, 단일 컬럼!) → prices.sales_price                 │
│                                                                          │
│  ⭐ 마이그레이션 순서 (Seeder 기반):                                      │
│     1. config/database.php에 'legacy' 연결 추가                         │
│     2. .env에 LEGACY_DB_* 환경변수 추가                                 │
│     3. KyungdongItemSeeder.php 파일 생성 ← 최우선!                      │
│     4. Seeder 실행 (items 603건 + prices 603건)                         │
│     5. 추가 items/BOM은 확장 Seeder로 처리                               │
│                                                                          │
│  📍 현재 상태: Phase 1 대기 (Seeder 파일 생성 및 실행)                   │
│                                                                          │
│  ❌ 불필요한 것: item_id_mappings (양방향 조회 불필요, chandj 손 안댐)    │
│                                                                          │
│  ⚠️ 주의: 모든 INSERT 실행 전 사용자 승인 필요                           │
│                                                                          │
│  📎 연관 문서: docs/plans/kd-orders-migration-plan.md (입고/재고/주문)   │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

12. 변경 이력

날짜 항목 변경 내용 파일 승인
2026-01-28 문서 분리 items-migration-kyungdong-plan.md에서 품목/단가 부분 분리 - -
2026-01-28 문서 생성 kd-items-migration-plan.md 신규 생성 - -
2026-01-28 컬럼명 수정 실제 DB 컬럼명으로 업데이트 (품목코드→prodcode, 품목명→item_name 등) - -
2026-01-28 Seeder 전환 SQL → Seeder 방식으로 전환, 섹션 5.0~5.6 구조 정리 - -

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 전체 덤프 확인

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.prodcode, '모터 150K' AS price_item
FROM chandj.KDunitprice k
WHERE k.item_name LIKE '%모터%150K%';
-- → KDunitprice가 마스터, price_*는 가격만 추가

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