# Step 1: 데이터 분석 + 전용 테이블 분리

> **프로젝트**: API (`sam/api`)
> **선행 조건**: 없음
> **참조**: `standards/options-column-policy.md`, `rules/item-policy.md`
> **최종수정**: 2026-03-21 (code/lot_no 분리 완료, bending_models lot_no 제거)

---

## 1. 레거시 데이터 매핑

### 1-1. 레거시 데이터 현황 (chandj DB)

**테이블별 건수**:

| 테이블 | 건수 | 설명 |
|--------|------|------|
| `bending` | 265건 (활성) | 개별 절곡품 부품 |
| `guiderail` | 20건 (활성) | 모델별 부품 조합 (7개 모델 + 비인정 1개) |
| `bendingfee` | 82건 | 절곡 단가 |
| `bendingmap` | 9건 | 절곡 매핑 |
| `etcbending` | 2건 | 기타 절곡 |
| `bending_work_log` | 8건 | 작업 로그 |

**bending 분류 분포** (265건):

| 대분류 | 중분류 | 건수 |
|--------|--------|------|
| 스크린 | 가이드레일 | 41 |
| 스크린 | 케이스 | 24 |
| 스크린 | 하단마감재 | 6 |
| 스크린 | 마구리 | 4 |
| 스크린 | L-BAR | 2 |
| 철재 | 케이스 | 136 |
| 철재 | 가이드레일 | 30 |
| 철재 | (미분류) | 9 |
| 철재 | 마구리 | 8 |
| 철재 | 하단마감재 | 5 |

**bending 테이블 전체 컬럼** (25개):

| 컬럼 | 타입 | 설명 | options 키 |
|------|------|------|-----------|
| `num` | int PK | 번호 | `legacy_bending_num` |
| `is_deleted` | tinyint | 삭제여부 | SAM 자체 관리 |
| `item_sep` | varchar(14) | 대분류 (스크린/철재) | `item_sep` |
| `model_UA` | varchar(15) | 인정여부 | `model_UA` |
| `item_bending` | varchar(40) | 중분류 | `item_bending` |
| `itemName` | varchar(50) | 품명 | `item_name` |
| `material` | varchar(25) | 재질 | `material` |
| `parentnum` | varchar(12) | 부모 참조 | `parent_num` |
| `registration_date` | date | 등록일 | `registration_date` |
| `imgdata` | text | 이미지 파일경로 | `image_path` |
| `inputList` | text | 치수 JSON | `bendingData[].input` |
| `bendingrateList` | text | 연신율 JSON | `bendingData[].rate` |
| `sumList` | text | 합계 JSON | `bendingData[].sum` |
| `colorList` | text | 색상마킹 JSON | `bendingData[].color` |
| `AList` | text | A각 JSON | `bendingData[].aAngle` |
| `memo` | text | 비고 | `memo` |
| `update_log` | text | 수정 이력 | SAM 자체 관리 (updated_at) |
| `item_spec` | varchar(50) | 규격 | `item_spec` |
| `widthsum` | int | 폭합계 | 계산값 (bendingData 마지막 sum) |
| `author` | varchar(20) | 작성자 | `author` |
| `search_keyword` | varchar(50) | 검색어 | `search_keyword` |
| `exit_direction` | varchar(20) | 점검구 방향 (케이스) | `exit_direction` |
| `front_bottom_width` | varchar(5) | 전면부 밑 치수 (케이스) | `front_bottom_width` |
| `rail_width` | varchar(5) | 레일폭 | `rail_width` |
| `box_width` | varchar(5) | 케이스 너비 | `box_width` |
| `box_height` | varchar(5) | 케이스 높이 | `box_height` |

**전개도 JSON 현황**: 265건 전부 inputList/bendingrateList/sumList/colorList/imgdata 보유 (크기: 30~50 bytes/필드)

**guiderail 모델 목록** (20건):

| 모델 | 인정 | 형태 | 레일폭 | 레일길이 | 마감 | 제품 |
|------|------|------|--------|---------|------|------|
| KSS01 | 인정 | 벽면/측면 | 70/120 | 120 | SUS | 스크린 |
| KSS02 | 인정 | 벽면/측면 | 70/120 | 120 | SUS | 스크린 |
| KSE01 | 인정 | 벽면/측면 | 70/120 | 120 | EGI/SUS | 스크린 |
| KWE01 | 인정 | 벽면/측면 | 70/120 | 120 | EGI/SUS | 스크린 |
| KTE01 | 인정 | 벽면/측면 | 75/125 | 130 | EGI/SUS | 철재 |
| KQTS01 | 인정 | 벽면/측면 | 75/125 | 130 | SUS | 철재 |
| KDSS01 | 인정 | 벽면 | 150 | 150 | SUS | 스크린 |
| 스크린비인정 | 비인정 | 벽면 | 70 | 130 | SUS | 스크린 |

**guiderail 테이블 전체 컬럼** (15개):

| 컬럼 | 타입 | 설명 |
|------|------|------|
| `num` | int PK | 번호 |
| `is_deleted` | varchar(2) | 삭제여부 |
| `registration_date` | date | 등록일 |
| `model_UA` | varchar(10) | 인정여부 |
| `check_type` | varchar(20) | 형상 (벽면형/측면형) |
| `model_name` | varchar(15) | 모델명 |
| `author` | varchar(50) | 작성자 |
| `remark` | text | 비고 |
| `update_log` | text | 수정 이력 |
| `rail_width` | varchar(10) | 레일폭 |
| `rail_length` | varchar(10) | 레일길이(높이) |
| `finishing_type` | varchar(10) | 마감 (SUS마감/EGI마감) |
| `bending_components` | mediumtext | 부품 조합 JSON |
| `firstitem` | varchar(15) | 대분류 (스크린/철재) |
| `search_keyword` | varchar(50) | 검색어 |
| `material_summary` | text | 재질별 폭합 |

### 1-1b. 추가 분석 결과 (완료)

#### BendingInfoBuilder 지원 모델

`getMaterialMapping()` (line 924~960)에서 정의:

| 그룹 | 모델 | 마감재 재질 | 본체 재질 |
|------|------|-----------|----------|
| Group 1 (SUS 전용) | KQTS01, KSS01, KSS02 | SUS 1.2T | EGI 1.55T |
| Group 2 (철재) | KTE01 | EGI 1.55T (SUS마감 시 +SUS 1.2T) | EGI 1.55T |
| Group 3 (기타) | KSE01, KWE01 등 | EGI 1.55T (SUS마감 시 +SUS 1.2T) | EGI 1.55T |

**미지원 모델**: KDSS01, 스크린비인정 (guiderail 테이블에는 있지만 Builder에 미등록)

#### 🔴 CRITICAL: BOM 카테고리 분류가 한글 BD 코드 의존

`BendingInfoBuilder.categorizeBomItem()` (line 401~434):

```php
str_starts_with($code, 'BD-가이드레일')  → guideRail
str_starts_with($code, 'BD-케이스')      → shutterBox_case
str_starts_with($code, 'BD-마구리')      → shutterBox_finCover
str_starts_with($code, 'BD-L-BAR')      → detail_lbar
str_starts_with($code, 'BD-보강평철')    → detail_reinforce
```

**→ BD-한글 패턴(58건)을 PREFIX로 변환하면 BOM 분류 로직이 깨짐!**
**→ 결정: 한글 코드는 유지하고, `options`에 속성만 추가하는 방식으로 진행**

#### PrefixResolver PREFIX 규칙

| 카테고리 | partType → prefix | 비고 |
|----------|-------------------|------|
| **가이드레일(벽면)** | finish→RS/RE, body→RM, c_type→RC, d_type→RD, extra→YY, base→XX | KTE body→RT |
| **가이드레일(측면)** | finish→SS/SE, body→SM, c_type→SC, d_type→SD, extra→YY, base→XX | KTE body→ST |
| **하단마감재** | main→BE/BS/TS, lbar→LA, reinforce→HH, extra→YY | 재질 기반 분기 |
| **셔터박스** | front→CF, lintel→CL, inspection→CP, rear_corner→CB, top/fin→XX | 고정 |
| **연기차단재** | smoke→GI | 전용 길이코드(53/54/83/84) |

#### bendingfee (82건) — `price_bend` 테이블

- **용도**: 절곡 BOM 단가 (모델별 단가 JSON)
- **구조**: `itemList` (JSON), `registedate`, `is_deleted`
- **사용법**: 최신 1건만 사용 (`ORDER BY num DESC LIMIT 1`)
- **관련 테이블**: `BDmodels` (모델별 조회)
- **SAM 연동**: 1차 제외 (단가 관리는 별도 Phase)

#### bendingmap (9건)

- **용도**: 절곡품 그룹화/매핑 (생산 현장용)
- **컬럼**: prodcode, railtype, boxsize, boxexit, frontbottom, railwidth, search_tag
- **SAM 연동**: 1차 제외 (그룹화는 options.item_bending으로 대체)

### 1-2. SAM 데이터 현황

#### ✅ 현재 (전용 테이블 분리 후 — 2026-03-19)

| 테이블 | 건수 | 소스 | 상태 |
|--------|------|------|------|
| **bending_items** | **266건** | chandj.bending 직접 이관 | ✅ 전건 bending_data JSON 포함 |
| **bending_models** | **62건** | guiderail 21 + shutterbox 30 + bottombar 11 | ✅ 이관 완료 |
| items (BENDING) | 215건 | 기존 재고/BOM용 — 무변경 | ✅ 독립 유지 |
| bending_item_mappings | — | **DROP 완료** | ✅ bending_items.code에 흡수 |

| 검증 항목 | 결과 |
|----------|------|
| 하장바 검색 | **10건** (chandj 유효건 일치) ✅ |
| bending_data JSON | **266건 전건** 포함 ✅ |
| 이미지 R2 | ⬜ 미업로드 (레거시 파일 docker 복사 후 커맨드 필요) |

#### 참고: 이전 items 방식 (170건, 폐기됨)

| 패턴 | 건수 | 예시 |
|------|------|------|
| A) `BD-PREFIX-LEN` | 112 | BD-RS-30, BD-CF-35 |
| B) `BD-L-BAR-모델-규격` | 5 | BD-L-BAR-KSS01-17*60 |
| C) `BD-가이드레일-모델-재질-규격` | 21 | BD-가이드레일-KSS01-SUS-120*70 |
| D) `BD-마구리-규격` | 10 | BD-마구리-655*505 |
| E) `BD-케이스-규격` | 11 | BD-케이스-650*550 |
| F) `BD-하단마감재-모델-재질-규격` | 10 | BD-하단마감재-KSS01-SUS-60*40 |
| G) `BD-보강평철-규격` | 1 | BD-보강평철-50 |

> items 테이블의 BD-* 215건은 **재고/BOM용으로 무변경 유지**. 기초관리는 bending_items 전용 테이블 사용.

### 1-3. 매핑 관계

```
레거시 chandj.bending (부품 단위)        SAM (테이블 분리 후)
─────────────────────────               ─────────────────────
num:100 마감재 SUS 120*70          →    bending_items (기초관리, 정규 컬럼)
                                        legacy_bending_id=100으로 추적

SAM items (재고/BOM용)                  역할 분리
─────────────────────
BD-RS-30 (길이별 재고)              →    items 테이블 무변경 (BendingInfoBuilder 참조)
BD-가이드레일-KSS01-SUS-120*70     →    items 테이블 무변경 (categorizeBomItem 참조)
```

**최종 결정사항**:
- ~~items.options 방식~~ → **bending_items 전용 테이블 분리** (검색 불가 문제 해결)
- items 테이블의 BD-* 코드는 **무변경 유지** (BendingInfoBuilder/PrefixResolver 의존)
- chandj.bending 266건 → bending_items 직접 이관 (`bending:clean-reimport`)

### 🔴 chandj 265건 vs SAM 데이터 관계 (테이블 분리 후)

```
[테이블 분리 후 — 현재 구조]

chandj.bending 265건 → bending_items 266건 (1:1 직접 이관 + 1건 추가)
                        ├── 정규 컬럼: item_name, item_sep, material 등 (인덱스)
                        ├── bending_data: JSON [{no, input, rate, sum, color, aAngle}]
                        └── legacy_bending_id: chandj.bending.num 참조

items (BENDING) 215건 → 무변경 유지 (재고/BOM/작업지시서 전용)
                        ├── BD-PREFIX-LEN 112건 (길이별 확장)
                        └── BD-한글 58건 (BendingInfoBuilder 의존)
```

| 테이블 | 역할 | 건수 |
|--------|------|------|
| bending_items | 기초관리 마스터 (품명/재질/전개도) | 266건 |
| bending_models | 절곡품 모델 (부품 조합) | 62건 |
| items (BENDING) | 재고/BOM/작업지시서 | 215건 (무변경) |

**이전 문제 해결**:
- ~~chandj 265건 vs SAM 170건 불일치~~ → bending_items 266건으로 전건 이관 완료
- ~~options.bendingData 누락~~ → bending_items.bending_data JSON 전건 포함
- ~~하장바 검색 2건~~ → bending_items.item_name 정규 컬럼으로 10건 정상 검색

---

### 🔴 코드 체계 변경 불가 사유

**BD 코드(절곡 부품)는 변경 금지:**

`BendingInfoBuilder.categorizeBomItem()` (line 401~434)에서 코드 접두사 기반으로 BOM 카테고리 분류:
```php
str_starts_with($code, 'BD-가이드레일')  → guideRail
str_starts_with($code, 'BD-케이스')      → shutterBox_case
str_starts_with($code, 'BD-마구리')      → shutterBox_finCover
str_starts_with($code, 'BD-L-BAR')      → detail_lbar
str_starts_with($code, 'BD-보강평철')    → detail_reinforce
```
BD-PREFIX-LEN 코드(BD-RS-30 등)도 `PrefixResolver` + LOT 재고에서 참조.
→ **견적→BOM→작업지시 전체 흐름이 깨지므로 코드 변경 불가**

**모델 코드(GR/SB/BB)는 변경 가능** — 신규 생성이라 참조 없음:

| 코드 | 카테고리 | 변경 가능 | 이유 |
|------|---------|:---:|------|
| `BD-*` (170건) | BENDING | ❌ | BendingInfoBuilder + PrefixResolver + LOT 의존 |
| `GR-*` (20건) | GUIDERAIL_MODEL | ✅ | 신규, 참조 없음 |
| `SB-*` (30건) | SHUTTERBOX_MODEL | ✅ | 신규, 참조 없음 |
| `BB-*` (10건) | BOTTOMBAR_MODEL | ✅ | 신규, 참조 없음 |

---

## 2. 전용 테이블 구조 (items.options → bending_items 정규 컬럼)

### 2-1. bending_items 스키마 (최종)

```sql
bending_items
├── id, tenant_id
├── code              -- LOT: {제품Code}{종류Code}{YYMMDD} (예: RM260319)
├── legacy_code       -- 이전 BD-LEGACY-* / BD-{품명}-* 코드
├── legacy_bending_id -- chandj.bending.num 참조
├── item_name         -- 품명 (정규 컬럼, 인덱스) ← 검색 문제 해결 핵심
├── item_sep          -- 대분류: 스크린/철재 (인덱스)
├── item_bending      -- 중분류: 가이드레일/케이스/하단마감재 (인덱스)
├── material          -- 재질: SUS 1.2T / EGI 1.55T (인덱스)
├── item_spec         -- 규격: 120*70
├── model_name        -- 소속 모델: KSS01 (인덱스)
├── model_UA          -- 인정여부: 인정/비인정
├── rail_width        -- 레일폭
├── exit_direction    -- 출구방향 (케이스 전용)
├── box_width/height  -- 박스 크기 (케이스 전용)
├── front_bottom      -- 전면밑 (케이스 전용)
├── inspection_door   -- 점검구 (케이스 전용)
├── bending_data      -- JSON [{no, input, rate, sum, color, aAngle}] ← 전개도
├── length_code       -- 원자재 길이코드 (24,30,35,40 등)
├── length_mm         -- 원자재 길이(mm)
├── options           -- 비정형만: memo, author, search_keyword 등
└── is_active, created_by, timestamps, soft_delete
```

> **변경 경위**: items.options JSON → 정규 컬럼 승격. 인덱싱/검색/정렬 가능해짐.

### 2-2. artisan command (✅ 전체 실행 완료)

```bash
# 기초관리 클린 재이관 (chandj.bending 266건 + bending_data JSON + 이미지)
php artisan bending:clean-reimport --legacy-img-path=/tmp/bending_img    # ✅ 완료

# 절곡품 모델 이관 (62건 + 조립도 + component 이미지 복사)
php artisan bending:model-import --legacy-path=/tmp/legacy_5130          # ✅ 완료
```

**폐기된 커맨드** (구 items 방식):
```bash
# php artisan bending:fill-options           ← 구 items.options 보강 (대체됨)
# php artisan bending:import-legacy          ← 구 items 방식 이관 (대체됨)
# php artisan guiderail:import-legacy        ← bending:model-import로 통합
# php artisan bending-product:import-legacy  ← bending:model-import로 통합
```

---

## 3. 회귀 테스트

| 테스트 | 확인 내용 | 판정 기준 | 상태 |
|--------|----------|----------|:---:|
| BendingInfoBuilder | 견적 BOM 계산 결과 | 변경 전/후 동일 | ⚠️ BD 코드 무변경이므로 영향 없음 |
| PrefixResolver | BD 코드 자동 결정 | 변경 전/후 동일 | ⚠️ 무변경 |
| 작업지시서 절곡 섹션 | GuideRailSection 렌더링 | 정상 표시 | ⚠️ 무변경 |
| CRUD 테스트 | 기초관리/모델 전체 | 생성/조회/수정/삭제 | ✅ 검증 완료 |
| 테넌트 격리 | 287 vs 1 데이터 분리 | 각 테넌트 독립 조회 | ✅ 검증 완료 |
| 이미지 표시 | R2→API→MNG 프록시 | 정상 표시 | ✅ 검증 완료 |

---

## 4. 산출물

- [x] bending_items 전용 테이블 (266건, 정규 컬럼 + bending_data JSON)
- [x] bending_models 전용 테이블 (62건, components JSON)
- [x] bending_item_mappings DROP (bending_items.code에 흡수)
- [x] artisan command 2개 (bending:clean-reimport, bending:model-import)
- [x] CRUD 검증 완료
- [x] 운영 서버(114.203.209.83) 데이터 복사 + 마이그레이션 이력 등록
- [ ] 이미지 R2 업로드 (레거시 파일 docker 복사 후 커맨드 재실행 필요)




{
  "lot_no_code_table": [
    {
      "제품": "가이드레일(벽면형)",
      "제품Code": "R",
      "종류": [
        { "종류명": "본체",       "Code": "M" },
        { "종류명": "본체(철재)", "Code": "T" },
        { "종류명": "C형",        "Code": "C" },
        { "종류명": "D형",        "Code": "D" },
        { "종류명": "SUS 마감재", "Code": "S" }
      ]
    },
    {
      "제품": "가이드레일(측면형)",
      "제품Code": "S",
      "종류": [
        { "종류명": "본체디딤",    "Code": "M" },
        { "종류명": "본체(철재)", "Code": "T" },
        { "종류명": "C형",         "Code": "C" },
        { "종류명": "D형",         "Code": "D" },
        { "종류명": "SUS 마감재①", "Code": "S" },
        { "종류명": "SUS 마감재②", "Code": "U" }
      ]
    },
    {
      "제품": "케이스",
      "제품Code": "C",
      "종류": [
        { "종류명": "전면부",   "Code": "F" },
        { "종류명": "점검구",   "Code": "P" },
        { "종류명": "린텔부",   "Code": "L" },
        { "종류명": "후면코너부", "Code": "B" }
      ]
    },
    {
      "제품": "하단마감재(스크린)",
      "제품Code": "B",
      "종류": [
        { "종류명": "SUS", "Code": "S" },
        { "종류명": "EGI", "Code": "E" }
      ]
    },
    {
      "제품": "하단마감재(철재)",
      "제품Code": "T",
      "종류": [
        { "종류명": "SUS", "Code": "S" },
        { "종류명": "EGI", "Code": "E" }
      ]
    },
    {
      "제품": "L-Bar",
      "제품Code": "L",
      "종류": [
        { "종류명": "스크린용", "Code": "A" }
      ]
    },
    {
      "제품": "연기차단재",
      "제품Code": "G",
      "종류": [
        { "종류명": "화이바원단(W50)", "Code": "I" },
        { "종류명": "화이바원단(W80)", "Code": "I" }
      ]
    }
  ],
  "비고": {
    "년월일": "Code 참조"
  }
}

