2026-01-28 19:22:25 +09:00
|
|
|
|
# 경동기업(5130) 품목/단가 마이그레이션 계획
|
|
|
|
|
|
|
|
|
|
|
|
> **작성일**: 2026-01-28
|
|
|
|
|
|
> **목적**: 경동기업 레거시 시스템(5130/)의 **품목(items), 단가(prices), BOM** 데이터를 SAM으로 이관
|
|
|
|
|
|
> **기준 문서**: `5130/` 폴더 분석 결과
|
|
|
|
|
|
> **상태**: 🔄 분석 완료, 구현 대기
|
|
|
|
|
|
> **데이터 규모**: ~1,500 레코드 (items ~800 + prices ~500 + BOM ~200)
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 🚀 새 세션 시작 가이드 (Quick Start)
|
|
|
|
|
|
|
|
|
|
|
|
### 이 문서만 보고 작업을 재개하려면:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 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 접속 명령어
|
|
|
|
|
|
|
|
|
|
|
|
```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 "SHOW TABLES;"
|
|
|
|
|
|
|
|
|
|
|
|
# SAM items 테이블 확인
|
|
|
|
|
|
docker exec sam-mysql-1 mysql -uroot -proot samdb -e "SELECT COUNT(*) FROM items WHERE tenant_id=287;"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 전제 조건 (작업 전 확인)
|
|
|
|
|
|
|
|
|
|
|
|
- [x] Docker 서비스 실행 중
|
|
|
|
|
|
- [x] `sam-mysql-1` 컨테이너 실행 중
|
|
|
|
|
|
- [x] chandj 데이터베이스 접근 가능
|
|
|
|
|
|
- [ ] SAM items 마이그레이션 실행 완료 (`php artisan migrate`)
|
|
|
|
|
|
- [ ] SAM prices 마이그레이션 실행 완료
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 📍 현재 진행 상태
|
|
|
|
|
|
|
|
|
|
|
|
| 항목 | 내용 |
|
|
|
|
|
|
|------|------|
|
2026-01-28 23:01:40 +09:00
|
|
|
|
| **마지막 완료 작업** | ✅ **정적 데이터 마이그레이션 완료** |
|
|
|
|
|
|
| **다음 작업** | 동적 BOM/견적 로직 구현 → [kd-quote-logic-plan.md](./kd-quote-logic-plan.md) |
|
|
|
|
|
|
| **진행률** | 4/4 (100%) - 정적 데이터 완료 |
|
2026-01-28 19:22:25 +09:00
|
|
|
|
| **마지막 업데이트** | 2026-01-28 |
|
|
|
|
|
|
|
2026-01-28 23:01:40 +09:00
|
|
|
|
> ⚠️ **주의**: 이 문서는 **정적 품목/단가 데이터 이관**만 다룹니다.
|
|
|
|
|
|
> 동적 BOM 계산, 모터/제어기/부자재 자동 추가 등 **견적 로직**은 별도 문서 참조:
|
|
|
|
|
|
> → [kd-quote-logic-plan.md](./kd-quote-logic-plan.md)
|
|
|
|
|
|
|
2026-01-28 21:29:19 +09:00
|
|
|
|
### Phase 1~3 실행 결과 ✅
|
2026-01-28 19:26:30 +09:00
|
|
|
|
|
2026-01-28 20:38:35 +09:00
|
|
|
|
| 소스 | 타입 | 건수 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| KDunitprice | FG/PT/SM/RM/CS | 601건 |
|
|
|
|
|
|
| models | FG | +18건 |
|
|
|
|
|
|
| item_list | PT | +9건 |
|
2026-01-28 21:06:59 +09:00
|
|
|
|
| BDmodels.seconditem | PT (누락 부품) | +6건 |
|
2026-01-28 21:29:19 +09:00
|
|
|
|
| price_motor | SM (누락 품목) | +13건 |
|
|
|
|
|
|
| price_raw_materials | RM (누락 품목) | +4건 |
|
|
|
|
|
|
| **items 합계** | | **651건** |
|
|
|
|
|
|
| **prices 합계** | | **651건** |
|
2026-01-28 21:06:59 +09:00
|
|
|
|
| **BOM 연결** | items.bom JSON | **18건** |
|
2026-01-28 19:22:25 +09:00
|
|
|
|
|
2026-01-28 21:06:59 +09:00
|
|
|
|
**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) 연결
|
2026-01-28 19:26:30 +09:00
|
|
|
|
|
2026-01-28 21:29:19 +09:00
|
|
|
|
**Phase 3 상세:**
|
|
|
|
|
|
- Phase 3.1: price_motor → SM items 13건 추가
|
|
|
|
|
|
- PM-020~PM-032: 제어기 (6P~18P, 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 기존 품목과 명칭 비교로 중복 제외
|
|
|
|
|
|
|
2026-01-28 23:01:40 +09:00
|
|
|
|
### Phase 4 검증 결과 ✅
|
|
|
|
|
|
|
|
|
|
|
|
**로컬 검증 완료 (2026-01-28):**
|
|
|
|
|
|
|
|
|
|
|
|
| 검증 항목 | 기대값 | 실제값 | 상태 |
|
|
|
|
|
|
|-----------|--------|--------|------|
|
|
|
|
|
|
| items 총 건수 | 651건 | 651건 | ✅ |
|
|
|
|
|
|
| prices 총 건수 | 651건 | 651건 | ✅ |
|
|
|
|
|
|
| BOM 연결 | 18건 | 18건 | ✅ |
|
|
|
|
|
|
| code 중복 | 0건 | 0건 | ✅ |
|
2026-01-28 19:22:25 +09:00
|
|
|
|
|
2026-01-28 23:01:40 +09:00
|
|
|
|
**item_type 분포:**
|
|
|
|
|
|
| item_type | 건수 |
|
|
|
|
|
|
|-----------|------|
|
|
|
|
|
|
| FG (완제품) | 470건 |
|
|
|
|
|
|
| PT (부품) | 88건 |
|
|
|
|
|
|
| SM (부자재) | 61건 |
|
|
|
|
|
|
| RM (원자재) | 28건 |
|
|
|
|
|
|
| CS (소모품) | 4건 |
|
2026-01-28 19:22:25 +09:00
|
|
|
|
|
2026-01-28 23:01:40 +09:00
|
|
|
|
### 후속 작업
|
2026-01-28 19:22:25 +09:00
|
|
|
|
|
2026-01-28 23:01:40 +09:00
|
|
|
|
**이 문서 범위 (정적 데이터):**
|
|
|
|
|
|
- ✅ 완료 - 개발서버 배포 대기 중
|
2026-01-28 19:22:25 +09:00
|
|
|
|
|
2026-01-28 23:01:40 +09:00
|
|
|
|
**별도 문서 (동적 로직):**
|
|
|
|
|
|
- → [kd-quote-logic-plan.md](./kd-quote-logic-plan.md)
|
|
|
|
|
|
- 5130 견적 로직 분석
|
|
|
|
|
|
- 동적 BOM 계산 (모터/제어기/부자재)
|
|
|
|
|
|
- 파라미터 기반 절곡품 산출
|
2026-01-28 19:22:25 +09:00
|
|
|
|
|
2026-01-28 23:01:40 +09:00
|
|
|
|
### Seeder 재실행 방법
|
2026-01-28 19:22:25 +09:00
|
|
|
|
|
2026-01-28 23:01:40 +09:00
|
|
|
|
```bash
|
|
|
|
|
|
# Docker 컨테이너 내부에서 실행
|
|
|
|
|
|
docker exec sam-api-1 php artisan db:seed --class="Database\\Seeders\\Kyungdong\\KyungdongItemSeeder"
|
|
|
|
|
|
```
|
2026-01-28 19:22:25 +09:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 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)
|
|
|
|
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
|
-- 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 매핑 ⭐
|
|
|
|
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
|
-- 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 테이블 구조 ⭐ (핵심 마스터)
|
|
|
|
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
|
-- 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 분포 확인 쿼리**:
|
|
|
|
|
|
```sql
|
|
|
|
|
|
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 + 단가)
|
|
|
|
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
|
-- 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):
|
|
|
|
|
|
```json
|
|
|
|
|
|
[
|
|
|
|
|
|
{"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)
|
|
|
|
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
|
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`
|
|
|
|
|
|
|
|
|
|
|
|
**실행 명령어**:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 로컬 실행 (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
|
|
|
|
|
|
<?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에 추가**:
|
|
|
|
|
|
```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에 추가**:
|
|
|
|
|
|
```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
|
|
|
|
|
|
-- ⚠️ 참고용 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
|
|
|
|
|
|
-- ⚠️ 참고용 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
|
|
|
|
|
|
-- ⚠️ 참고용 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
|
|
|
|
|
|
-- ⚠️ 참고용 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
|
|
|
|
|
|
<?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 합계** | - | - | **~700~800건** |
|
|
|
|
|
|
|
|
|
|
|
|
**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. 체크리스트
|
|
|
|
|
|
|
2026-01-28 20:38:35 +09:00
|
|
|
|
### Phase 1: 마스터 데이터 이관 ✅ 완료
|
2026-01-28 19:22:25 +09:00
|
|
|
|
- [x] 레거시 DB 구조 분석 완료
|
|
|
|
|
|
- [x] KDunitprice 테이블 발견 및 분석 (603건, 핵심 마스터)
|
|
|
|
|
|
- [x] 중복 제거 전략 수립 (code 기반, 매핑 테이블 불필요)
|
|
|
|
|
|
- [x] Seeder 기반 마이그레이션 계획 수립
|
|
|
|
|
|
- [x] ~~config/database.php에 'legacy' 연결 추가~~ → 기존 'chandj' 연결 사용
|
|
|
|
|
|
- [x] ~~.env에 LEGACY_DB_* 환경변수 추가~~ → 기존 CHANDJ_DB_* 사용
|
2026-01-28 20:38:35 +09:00
|
|
|
|
- [x] **Phase 1.0**: KDunitprice → items 601건, prices 601건 ✅
|
|
|
|
|
|
- [x] **Phase 1.1**: models → items (FG) 18건 ✅
|
|
|
|
|
|
- [x] **Phase 1.2**: item_list → items (PT) 9건 ✅
|
|
|
|
|
|
- [x] ~~Phase 1.3: category_l4~~ → 스킵 (카테고리 데이터)
|
2026-01-28 21:06:59 +09:00
|
|
|
|
- [x] **Phase 1 결과**: items 628건, prices 628건 ✅
|
2026-01-28 19:22:25 +09:00
|
|
|
|
|
2026-01-28 21:06:59 +09:00
|
|
|
|
### Phase 2: BOM 데이터 이관 ✅ 완료
|
|
|
|
|
|
- [x] BDmodels.seconditem → PT items 누락 부품 6건 추가 ✅
|
|
|
|
|
|
- [x] ~~child_item_id 매핑 테이블 생성~~ → code 기반 직접 조회
|
|
|
|
|
|
- [x] items.bom JSON 생성 (18건 FG ↔ PT 연결) ✅
|
|
|
|
|
|
- [x] **최종 결과**: items 634건, prices 634건, BOM 18건 ✅ (2026-01-28)
|
2026-01-28 19:22:25 +09:00
|
|
|
|
|
2026-01-28 21:29:19 +09:00
|
|
|
|
### Phase 3: 단가 데이터 이관 ✅ 완료
|
2026-01-28 19:22:25 +09:00
|
|
|
|
- [x] 레거시 price_* 테이블 구조 분석 (10개)
|
|
|
|
|
|
- [x] 각 테이블별 JSON 스키마 분석
|
|
|
|
|
|
- [x] SAM prices 테이블 구조 확인
|
|
|
|
|
|
- [x] Legacy → SAM 단가 매핑 전략 수립
|
2026-01-28 21:29:19 +09:00
|
|
|
|
- [x] price_motor → items (SM) 누락 품목 13건 추가 ✅
|
|
|
|
|
|
- [x] price_raw_materials → items (RM) 누락 품목 4건 추가 ✅
|
|
|
|
|
|
- [x] 기타 price_* 테이블 분석 완료 (대부분 계산 참조용, 품목 마스터 아님)
|
|
|
|
|
|
- price_shaft, price_pipe, price_angle, price_bend, price_pole, price_screenplate: 계산 참조용
|
|
|
|
|
|
- 220V/380V 모터: KDunitprice에 "KD모터*Kg단상/삼상"으로 이미 존재
|
|
|
|
|
|
- [x] **사용자 승인**: 완료 (2026-01-28)
|
2026-01-28 19:22:25 +09:00
|
|
|
|
|
2026-01-28 21:39:18 +09:00
|
|
|
|
### Phase 4: 검증 및 배포 ✅ 로컬 검증 완료
|
|
|
|
|
|
- [x] 건수 검증 ✅ (items 651건, prices 651건, BOM 18건)
|
|
|
|
|
|
- [x] 데이터 조회 테스트 ✅ (artisan tinker, MySQL 직접 쿼리)
|
|
|
|
|
|
- [x] code 중복 검증 ✅ (0건)
|
|
|
|
|
|
- [x] Phase 3 추가 품목 확인 ✅ (PM-* 13건, RM-* 4건)
|
2026-01-28 19:22:25 +09:00
|
|
|
|
- [ ] ⚠️ **사용자 승인**: 개발서버 배포
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 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)
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 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 파싱 오류
|
|
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
|
// 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 기반)
|
|
|
|
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
|
-- 이미 존재하는 품목 확인 (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 스킬로 생성되었습니다.*
|