- 완료된 계획 문서 22개를 plans/archive/로 이동 - tracked 16개 (git mv): bending-lot-pipeline, docs-update, fcm-notification 등 - untracked 6개 (mv): bending-worklog, formula-engine, mng-item 등 - index_plans.md 전면 업데이트 - 진행중 44개 / 완료 37개 현황 반영 - 각 문서별 실제 진행률 기재 (0%~94%) - 카테고리별 재정리 (견적/생산/품목/문서/마이그레이션/시스템/UI) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1078 lines
48 KiB
Markdown
1078 lines
48 KiB
Markdown
# 수식 엔진 실제 데이터 연동 계획
|
||
|
||
> **작성일**: 2026-02-19
|
||
> **목적**: FormulaEvaluatorService의 테스트 데이터(SF-/SM-)를 실제 품목(BD-)으로 재구성
|
||
> **기준 문서**: `docs/features/quotes/README.md`, `docs/rules/item-policy.md`
|
||
> **상태**: ✅ 완료 (Phase 1-3,5 완료 / Phase 4 후순위 보류)
|
||
|
||
---
|
||
|
||
## 📍 현재 진행 상태
|
||
|
||
| 항목 | 내용 |
|
||
|------|------|
|
||
| **마지막 완료 작업** | 문서 최종 업데이트 및 검증 결과 반영 |
|
||
| **다음 작업** | 없음 (Phase 4 Generic 데이터는 후순위 보류) |
|
||
| **진행률** | 4/5 완료 (Phase 1-3,5 ✅ / Phase 4 ⏭️ 후순위) |
|
||
| **마지막 업데이트** | 2026-02-20 17:00 |
|
||
|
||
---
|
||
|
||
## 1. 개요
|
||
|
||
### 1.1 배경
|
||
|
||
수식 엔진(FormulaEvaluatorService)에는 두 가지 실행 경로가 있다:
|
||
- **Generic 경로**: `quote_formula_*` 4개 테이블 기반 (데이터 드리븐)
|
||
- **Kyungdong 경로**: `KyungdongFormulaHandler` 코드 기반 (tenant_id=287 전용)
|
||
|
||
**현재 문제:**
|
||
1. Generic 경로의 `quote_formula_items` (24건)이 모두 삭제된 SF-/SM- 테스트 품목을 참조
|
||
2. `quote_formula_ranges` (12건)도 모두 SF- 코드 반환
|
||
3. `quote_formula_mappings`는 비어있음
|
||
4. Mapping 수식(id:20,21)이 참조하는 product_id 468, 473도 삭제됨
|
||
5. Kyungdong 핸들러는 BD- 품목을 참조하지만, EST- 코드 일부가 items 테이블에 미등록
|
||
6. 핸들러가 `KyungdongFormulaHandler`로 하드코딩 → 업체 추가 시 확장 불가 구조
|
||
|
||
### 1.2 두 경로 비교
|
||
|
||
| 구분 | Generic 경로 | Kyungdong 경로 |
|
||
|------|-------------|---------------|
|
||
| **진입 조건** | 전용 핸들러 없는 tenant | 전용 핸들러 있는 tenant |
|
||
| **BOM 구성** | quote_formula_items + items.bom 전개 | 코드 기반 동적 조립 |
|
||
| **모델 인식** | 없음 (단일 수식 세트) | 모델/마감/타입별 분기 |
|
||
| **아이템 참조** | SF-/SM- (삭제됨) | BD- 동적 코드 조합 + EST- 코드 |
|
||
| **단가 조회** | prices 테이블 + items.attributes | EstimatePriceService |
|
||
| **핸들러 해석** | FormulaHandlerFactory → null → Generic | FormulaHandlerFactory → Tenant{id}/FormulaHandler |
|
||
| **상태** | ⏭️ FG.bom 비어있음 (후순위) | ✅ 정비 완료 |
|
||
|
||
### 1.3 실행 흐름 (MNG → API)
|
||
|
||
#### 현재 (Before)
|
||
```
|
||
FormulaEvaluatorService::calculateBomWithDebug()
|
||
│
|
||
├─ if ($tenantId === 287) ← 하드코딩!
|
||
│ └─ new KyungdongFormulaHandler() ← 직접 생성!
|
||
│
|
||
└─ else → Generic 10단계
|
||
```
|
||
|
||
#### 목표 (After) - Strategy + Factory, Zero Config
|
||
```
|
||
[MNG 품목관리 UI]
|
||
│ 사용자가 FG 선택 + W0/H0/QTY/MP 입력
|
||
▼
|
||
ItemManagementApiController::calculateFormula() (mng, 라인 60-86)
|
||
│ $item->code, {W0, H0, QTY, MP}, session('selected_tenant_id')
|
||
▼
|
||
FormulaApiService::calculateBom() (mng, 라인 24-82)
|
||
│ POST https://nginx/api/v1/quotes/calculate/bom
|
||
│ Headers: X-API-KEY, X-TENANT-ID
|
||
▼
|
||
FormulaEvaluatorService::calculateBomWithDebug() (api, 라인 592-596)
|
||
│
|
||
├─ FormulaHandlerFactory::make($tenantId)
|
||
│ │ class_exists("Handlers\Tenant{$tenantId}\FormulaHandler") ?
|
||
│ │
|
||
│ ├─ 핸들러 존재 → calculateTenantBom($handler, ...)
|
||
│ │ └─ Tenant287/FormulaHandler::calculateDynamicItems()
|
||
│ │ ├─ calculateSteelItems() → BD- 절곡품 (10종)
|
||
│ │ ├─ calculatePartItems() → EST- 부자재 (5종)
|
||
│ │ └─ 모터/제어기/주자재/검사비
|
||
│ │
|
||
│ └─ 핸들러 없음 (null) → 10단계 Generic 계산 (라인 613-791)
|
||
│ └─ quote_formula_* 테이블 (DB 드리븐)
|
||
│
|
||
▼
|
||
[BOM 결과 JSON 반환]
|
||
```
|
||
|
||
#### 핸들러 자동 발견 원리
|
||
```
|
||
FormulaHandlerFactory::make(287)
|
||
→ class_exists("App\Services\Quote\Handlers\Tenant287\FormulaHandler")
|
||
→ YES → new Tenant287\FormulaHandler()
|
||
→ 인터페이스 TenantFormulaHandler 구현 보장
|
||
|
||
FormulaHandlerFactory::make(999)
|
||
→ class_exists("App\Services\Quote\Handlers\Tenant999\FormulaHandler")
|
||
→ NO → return null → Generic DB 경로
|
||
```
|
||
|
||
**업체 추가 시**: `Handlers/Tenant{id}/FormulaHandler.php` 파일 1개만 생성. 설정/매핑 불필요.
|
||
|
||
### 1.4 기준 원칙
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 🎯 핵심 원칙 │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ 1. 업체별 핸들러 구조화 (Tenant{id} 기반 자동 발견, Zero Config) │
|
||
│ 2. 경동(287) 핸들러가 실제 운영 로직 (우선 정비) │
|
||
│ 3. Generic 경로는 핸들러 없는 테넌트용 (DB 드리븐, 후순위) │
|
||
│ 4. 품목 마스터에 실제 품목이 모두 등록되어야 함 │
|
||
│ 5. 수식 데이터는 실제 품목 코드만 참조 │
|
||
│ 6. 기존 테스트 데이터는 삭제하지 않음 (완전 이관 후 별도 삭제) │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 1.5 변경 승인 정책
|
||
|
||
| 분류 | 예시 | 승인 |
|
||
|------|------|------|
|
||
| ✅ 즉시 가능 | items 테이블에 EST- 품목 등록, 핸들러 디렉토리 구조 변경(이동) | 불필요 |
|
||
| ⚠️ 컨펌 필요 | 인터페이스/팩토리 신규 생성, FormulaEvaluatorService 분기 로직 변경, quote_formula_* 데이터 추가 | **필수** |
|
||
| 🔴 금지 | 테이블 스키마 변경, 핸들러 핵심 계산 로직 변경 | 별도 협의 |
|
||
|
||
---
|
||
|
||
## 2. 현황 분석
|
||
|
||
### 2.1 items 테이블 현황 (tenant_id=287)
|
||
|
||
| 코드 접두어 | item_type | 건수 | 설명 | 상태 |
|
||
|------------|-----------|------|------|------|
|
||
| FG- | FG | 18 | 완제품 (7모델 × 타입/마감 조합) | ✅ 정상 |
|
||
| BD- | PT | 58 | 절곡물 (모델별 가이드레일/케이스/마구리 등) | ✅ 정상 |
|
||
| PT- (레거시) | PT | ~650 | 레거시 부품 (5자리 숫자 코드) | ✅ 정상 |
|
||
| RM- | RM | 28 | 원자재 | ✅ 정상 |
|
||
| SM- | SM | 61 | 부자재 (레거시) | ✅ 정상 |
|
||
| CS- | CS | 4 | 소모품 | ✅ 정상 |
|
||
| SF- | - | 0 | 삭제됨 (테스트 데이터) | ❌ 삭제 완료 |
|
||
| EST- | PT | 72 | 부자재 (모터/제어기/샤프트/앵글/파이프/원자재 등) | ✅ 등록 완료 |
|
||
|
||
### 2.2 KyungdongFormulaHandler가 참조하는 미등록 품목
|
||
|
||
> **중요**: 핸들러는 `EST-` 접두어를 사용 (이전 문서의 `ST-`는 오류)
|
||
|
||
#### EST- 코드 (items 미등록, 핸들러가 동적 생성)
|
||
|
||
| 코드 패턴 | 라인 | 메서드 | 용도 | 대안 |
|
||
|-----------|------|--------|------|------|
|
||
| `EST-SMOKE-케이스용` | 519 | calculateSteelItems | 케이스용 연기차단재 | `BD-케이스용 연기차단재` (id:15587) |
|
||
| `EST-SMOKE-레일용` | 557 | calculateSteelItems | 가이드레일용 연기차단재 | `BD-가이드레일용 연기차단재` (id:15572) |
|
||
| `EST-SHAFT-{size}인치-{length}` | 795 | calculatePartItems | 감기샤프트 | 신규 등록 |
|
||
| `EST-PIPE-1.4-{length}` | 854,868 | calculatePartItems | 앵글파이프 | 신규 등록 |
|
||
| `EST-ANGLE-BRACKET-{type}` | 891 | calculatePartItems | 모터받침 앵글 | 신규 등록 |
|
||
| `EST-ANGLE-MAIN-{type}-{size}` | 912 | calculatePartItems | 부자재 앵글 | 신규 등록 |
|
||
| `EST-INSPECTION` | 1010 | calculateDynamicItems | 검사비 | 신규 등록 |
|
||
| `EST-RAW-스크린-{type}` | 1019 | calculateDynamicItems | 스크린 원단 | 신규 등록 |
|
||
| `EST-RAW-슬랫-{type}` | 1025 | calculateDynamicItems | 슬랫 원단 | 신규 등록 |
|
||
| `EST-MOTOR-{voltage}-{capacity}` | 1044 | calculateDynamicItems | 모터 | 신규 등록 |
|
||
| `EST-CTRL-{type}` | 1062 | calculateDynamicItems | 제어기 | 신규 등록 |
|
||
| `EST-CTRL-뒷박스` | 1087 | calculateDynamicItems | 뒷박스 제어기 | 신규 등록 |
|
||
|
||
#### 레거시 숫자 코드 (items 등록됨)
|
||
|
||
| 코드 | 라인 | items.id | items.name | item_type | unit | 용도 |
|
||
|------|------|----------|-----------|-----------|------|------|
|
||
| `00035` | 564 | 14939 | 철재용하장바(SUS)3000 | PT | EA | 하장바 SUS |
|
||
| `00036` | 564 | 14940 | 철재용하장바(SUS1.2T) | SM | M | 하장바 EGI |
|
||
| `00021` | 619 | 14928 | 평철12T | PT | M | 무게평철12T |
|
||
| `90201` | 631 | 15188 | KD환봉(30파이) | PT | EA | 환봉 30파이 (기본) |
|
||
| `90202` | 628 | 15189 | KD환봉 | PT | EA | 환봉 35파이 |
|
||
| `90203` | 629 | 15190 | KD환봉 | PT | EA | 환봉 45파이 |
|
||
| `90204` | 630 | 15191 | KD환봉 | PT | EA | 환봉 50파이 |
|
||
| `00013` | - | 14922 | 점검구3 | PT | EA | 점검구 (핸들러에서 미사용) |
|
||
|
||
### 2.3 quote_formula_* 현황
|
||
|
||
#### quote_formulas (21건, tenant_id=1)
|
||
|
||
| id | type | variable | name | formula | output_type |
|
||
|----|------|----------|------|---------|-------------|
|
||
| 1 | input | PC | 제품 카테고리 | (없음) | variable |
|
||
| 2 | input | W0 | 오픈사이즈 폭 | (없음) | variable |
|
||
| 3 | input | H0 | 오픈사이즈 높이 | (없음) | variable |
|
||
| 4 | input | GT | 가이드레일 설치유형 | (없음) | variable |
|
||
| 5 | input | MP | 모터 전원 | (없음) | variable |
|
||
| 6 | input | CT | 연동제어기 | (없음) | variable |
|
||
| 7 | input | QTY | 수량 | (없음) | variable |
|
||
| 8 | calculation | W1_SCREEN | 제작폭 W1 (스크린) | W0 + 140 | variable |
|
||
| 9 | calculation | W1_STEEL | 제작폭 W1 (철재) | W0 + 110 | variable |
|
||
| 10 | calculation | H1 | 제작높이 H1 | H0 + 350 | variable |
|
||
| 11 | calculation | W | 제작폭 (W) | IF(PC=="스크린", W0+140, W0+110) | variable |
|
||
| 12 | calculation | H | 제작높이 (H) | H0 + 350 | variable |
|
||
| 13 | calculation | M | 면적 (M) | W * H / 1000000 | variable |
|
||
| 14 | calculation | K_SCREEN | 중량 K (스크린) | M * 2 + W0 / 1000 * 14.17 | variable |
|
||
| 15 | calculation | K_STEEL | 중량 K (철재) | M * 25 | variable |
|
||
| 16 | calculation | K | 중량 (K) | IF(PC=="스크린", M*2+W0/1000*14.17, M*25) | variable |
|
||
| 17 | range | MOTOR | 모터 자동선택 | K | item |
|
||
| 18 | range | GUIDE | 가이드레일 자동선택 | H | item |
|
||
| 19 | range | CASE | 케이스 자동선택 | W | item |
|
||
| 20 | mapping | BOM_SCR_001 | FG-SCR-001 BOM 매핑 | (없음) | item |
|
||
| 21 | mapping | BOM_STL_001 | FG-STL-001 BOM 매핑 | (없음) | item |
|
||
|
||
- id 20: product_id=468 (삭제됨)
|
||
- id 21: product_id=473 (삭제됨)
|
||
|
||
#### quote_formula_items (24건) - 전부 삭제된 코드
|
||
|
||
| id | formula_id | item_code | item_name | sort |
|
||
|----|-----------|-----------|-----------|------|
|
||
| 1 | 20 | SF-SCR-F01 | 스크린 원단 | 1 |
|
||
| 2 | 20 | SF-SCR-F02 | 가이드레일 (좌) | 2 |
|
||
| 3 | 20 | SF-SCR-F03 | 가이드레일 (우) | 3 |
|
||
| 4 | 20 | SF-SCR-F04 | 케이스 | 4 |
|
||
| 5 | 20 | SF-SCR-F05 | 하부프레임 | 5 |
|
||
| 6 | 20 | SF-SCR-M01 | 모터 (소형) | 6 |
|
||
| 7 | 20 | SF-SCR-C01 | 제어반 | 7 |
|
||
| 8 | 20 | SF-SCR-S01 | 셋팅박스 | 8 |
|
||
| 9 | 20 | SF-SCR-SW01 | 권선드럼 | 9 |
|
||
| 10 | 20 | SF-SCR-B01 | 브라켓 세트 | 10 |
|
||
| 11 | 20 | SF-SCR-SW01 | 스위치 | 11 |
|
||
| 12 | 20 | SM-B002 | 볼트 M8x25 | 12 |
|
||
| 13 | 20 | SM-N002 | 너트 M8 | 13 |
|
||
| 14 | 20 | SM-W002 | 와셔 M8 | 14 |
|
||
| 15 | 21 | SF-STL-P01 | 도어 패널 | 1 |
|
||
| 16 | 21 | SF-STL-F01 | 문틀 프레임 | 2 |
|
||
| 17 | 21 | SF-STL-G01 | 유리창 | 3 |
|
||
| 18 | 21 | SF-STL-H01 | 힌지 | 4 |
|
||
| 19 | 21 | SF-STL-L01 | 잠금장치 | 5 |
|
||
| 20 | 21 | SF-STL-C01 | 도어클로저 | 6 |
|
||
| 21 | 21 | SF-STL-S01 | 실링재 | 7 |
|
||
| 22 | 21 | SF-STL-PT01 | 파우더 도장 | 8 |
|
||
| 23 | 21 | SM-B002 | 볼트 M8x25 | 9 |
|
||
| 24 | 21 | SM-N002 | 너트 M8 | 10 |
|
||
|
||
#### quote_formula_ranges (12건) - 전부 삭제된 코드
|
||
|
||
| id | formula_id | condition_variable | min | max | result_value |
|
||
|----|-----------|-------------------|-----|-----|--------------|
|
||
| 1 | 17 (MOTOR) | K | 0 | 30 | SF-SCR-M01 |
|
||
| 2 | 17 | K | 30 | 50 | SF-SCR-M02 |
|
||
| 3 | 17 | K | 50 | 80 | SF-SCR-M03 |
|
||
| 4 | 17 | K | 80 | 9999 | SF-SCR-M04 |
|
||
| 5 | 18 (GUIDE) | H | 0 | 2500 | SF-SCR-F02 |
|
||
| 6 | 18 | H | 2500 | 3500 | SF-SCR-F02 |
|
||
| 7 | 18 | H | 3500 | 4500 | SF-SCR-F02 |
|
||
| 8 | 18 | H | 4500 | 9999 | SF-SCR-F02 |
|
||
| 9 | 19 (CASE) | W | 0 | 2000 | SF-SCR-F04 |
|
||
| 10 | 19 | W | 2000 | 3000 | SF-SCR-F04 |
|
||
| 11 | 19 | W | 3000 | 4000 | SF-SCR-F04 |
|
||
| 12 | 19 | W | 4000 | 9999 | SF-SCR-F04 |
|
||
|
||
#### quote_formula_mappings (0건) - 비어있음
|
||
|
||
### 2.4 FG 모델 매트릭스
|
||
|
||
| 모델 | 카테고리 | 마감 | 가이드레일 타입 | BD 부품 수 |
|
||
|------|---------|------|---------------|-----------|
|
||
| KSS01 | 스크린 | SUS | 벽면/측면 | 4 (가이드레일×2, 하단마감재, L-BAR) |
|
||
| KSS02 | 스크린 | SUS | 벽면/측면 | 4 |
|
||
| KSE01 | 스크린 | SUS+EGI | 벽면/측면 | 8 |
|
||
| KWE01 | 스크린 | SUS+EGI | 벽면/측면 | 8 |
|
||
| KQTS01 | 철재 | SUS | 벽면/측면 | 3 (가이드레일×2, 하단마감재) |
|
||
| KTE01 | 철재 | SUS+EGI | 벽면/측면 | 6 |
|
||
| KDSS01 | (FG없음) | SUS | 벽면/측면 | 4 |
|
||
|
||
### 2.5 가이드레일 규격 매핑 (모델별)
|
||
|
||
```
|
||
KSS01/KSS02/KSE01/KWE01 → 벽면: 120*70, 측면: 120*120
|
||
KTE01/KQTS01 → 벽면: 130*75, 측면: 130*125
|
||
KDSS01 → 벽면: 150*150, 측면: 150*212
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 대상 범위
|
||
|
||
### Phase 1: 누락 품목 등록 (items 테이블) ✅ 완료
|
||
|
||
| # | 작업 항목 | 상태 | 비고 |
|
||
|---|----------|:----:|------|
|
||
| 1.1 | EST-SMOKE 코드 → Phase 3.1로 이관 (핸들러 코드 수정) | ⏭️ | Phase 3에서 처리 |
|
||
| 1.2 | EST-MOTOR 품목 등록 (150K~2000K, 전압별) | ✅ | 21건 확인 (220V 8종 + 380V 13종) |
|
||
| 1.3 | EST-CTRL 품목 등록 (제어기 종류별) | ✅ | 20건 확인 (기본3 + 방범9 + 방화4 + 기타4) |
|
||
| 1.4 | EST-SHAFT 품목 등록 (인치×길이별) | ✅ | 16건 확인 (3~12인치) |
|
||
| 1.5 | EST-PIPE 품목 등록 | ✅ | 3건 확인 (1.4T×2 + 2T×1) |
|
||
| 1.6 | EST-ANGLE 품목 등록 | ✅ | 8건 확인 (BRACKET 4 + MAIN 4) |
|
||
| 1.7 | EST-INSPECTION 품목 등록 | ✅ | 1건 확인 |
|
||
| 1.8 | EST-RAW 원자재 품목 등록 | ✅ | 6건 확인 (스크린3 + 슬랫3) |
|
||
|
||
### Phase 2: 핸들러 구조화 (Strategy + Factory, Zero Config) ✅ 완료
|
||
|
||
> **설계 원칙**: tenant_id 기반 자동 발견. 설정/매핑/options 없이 클래스 존재 여부만으로 라우팅.
|
||
|
||
| # | 작업 항목 | 상태 | 비고 |
|
||
|---|----------|:----:|------|
|
||
| 2.1 | `TenantFormulaHandler` 인터페이스 생성 | ✅ | `Contracts/TenantFormulaHandler.php` |
|
||
| 2.2 | `FormulaHandlerFactory` 생성 (class_exists 자동 발견) | ✅ | `FormulaHandlerFactory.php` (35줄) |
|
||
| 2.3 | `KyungdongFormulaHandler` → `Tenant287/FormulaHandler`로 이동 | ✅ | namespace + implements 완료, 원본 삭제 |
|
||
| 2.4 | `FormulaEvaluatorService` 분기 로직 변경 | ✅ | KYUNGDONG_TENANT_ID 상수 제거, Factory::make() 사용 |
|
||
| 2.5 | `calculateKyungdongBom()` → `calculateTenantBom()` 일반화 | ✅ | 메서드명 + 파라미터(handler) + 문자열 일반화 |
|
||
|
||
### Phase 3: 핸들러 아이템 코드 정비 (Tenant287/FormulaHandler) ✅ 완료
|
||
|
||
| # | 작업 항목 | 상태 | 비고 |
|
||
|---|----------|:----:|------|
|
||
| 3.1 | EST-SMOKE 코드 → BD- 코드로 변경 | ✅ | BD-케이스용 연기차단재(id:15587), BD-가이드레일용 연기차단재(id:15572) |
|
||
| 3.2 | 레거시 숫자 코드(00035, 00036 등) 유지 | ✅ | items 테이블에 등록됨, 변경 불필요 |
|
||
| 3.3 | lookupItem 실패 시 Log::warning() 추가 | ✅ | tenant_id, code 포함 경고 로그 |
|
||
| 3.4 | tinker E2E 테스트 통과 | ✅ | 17건, 1,167,934원 (KQTS01-SUS-벽면형) |
|
||
|
||
### Phase 4: Generic 수식 데이터 재구성 (quote_formula_* 테이블) ⏭️ 후순위
|
||
|
||
> **분석 결과**: Generic 경로는 `items.bom` JSON 필드 기반이나, FG 품목의 bom 필드가 비어있음.
|
||
> `quote_formula_*` 테이블은 독립 수식 평가 기능용으로, 메인 BOM 계산 경로에서 직접 사용하지 않음.
|
||
> Tenant 287은 핸들러 경로를 사용하므로 현재 실질적 영향 없음. 다른 테넌트 추가 시 진행.
|
||
|
||
| # | 작업 항목 | 상태 | 비고 |
|
||
|---|----------|:----:|------|
|
||
| 4.1 | 실제 FG 제품용 mapping 수식 신규 생성 | ⏭️ | 다른 테넌트 추가 시 |
|
||
| 4.2 | quote_formula_items에 실제 BD- 코드 BOM 세트 추가 | ⏭️ | FG.bom 필드 구성 선행 필요 |
|
||
| 4.3 | quote_formula_ranges에 실제 BD- 코드 범위 추가 | ⏭️ | |
|
||
| 4.4 | quote_formula_mappings 구성 (FG → BD 모델별 매핑) | ⏭️ | |
|
||
| 4.5 | FormulaEvaluatorService 모델 인식 로직 추가 | ⏭️ | |
|
||
|
||
### Phase 5: 통합 테스트 및 검증 ✅ 완료
|
||
|
||
| # | 작업 항목 | 상태 | 비고 |
|
||
|---|----------|:----:|------|
|
||
| 5.1 | 7모델 전수 BOM 계산 테스트 (벽면형) | ✅ | 7모델 전부 PASS (18건씩, 1.1M~1.3M원) |
|
||
| 5.1b | 측면형 + 대형 규격 테스트 (3000×3000, QTY=2) | ✅ | 3모델 PASS (18건씩, 2.9M~3.2M원) |
|
||
| 5.2 | Factory 엣지 케이스 테스트 | ✅ | tenant 0/-1/999999→null, 287→Handler |
|
||
| 5.3 | SF-/SM- 잔여 참조 점검 (코드 기준) | ✅ | api/Services/Quote/ 내 참조 0건 |
|
||
| 5.4 | React 견적관리 BOM 테스트 | ⏭️ | Phase 4 후순위와 함께 |
|
||
|
||
---
|
||
|
||
## 4. 작업 절차
|
||
|
||
### 4.1 단계별 절차
|
||
|
||
```
|
||
Phase 1: 누락 품목 등록
|
||
├── 1.1 EST-SMOKE → BD- 매핑 (코드만 변경, 품목 신규 등록 불필요)
|
||
├── 1.2~1.8 EST- 품목 등록 (items 테이블 INSERT)
|
||
│ ├── 코드: EST- 접두어 유지 (핸들러 코드와 일치)
|
||
│ ├── item_type: PT, tenant_id: 287
|
||
│ └── options: { lot_managed: false, consumption_method: "none" }
|
||
└── 등록 후 lookupItem() 호출로 매핑 확인
|
||
|
||
Phase 2: 핸들러 구조화 (Strategy + Factory, Zero Config)
|
||
├── 2.1 TenantFormulaHandler 인터페이스 생성
|
||
│ └── Contracts/TenantFormulaHandler.php (신규)
|
||
├── 2.2 FormulaHandlerFactory 생성
|
||
│ └── class_exists("Handlers\Tenant{$tenantId}\FormulaHandler") 자동 발견
|
||
├── 2.3 KyungdongFormulaHandler → Tenant287/FormulaHandler 이동
|
||
│ ├── namespace 변경: Handlers → Handlers\Tenant287
|
||
│ ├── implements TenantFormulaHandler 추가
|
||
│ └── 클래스 docblock에 "경동기업 (tenant_id: 287)" 명시
|
||
├── 2.4 FormulaEvaluatorService 분기 로직 변경
|
||
│ ├── 제거: private const KYUNGDONG_TENANT_ID = 287
|
||
│ ├── 제거: if ($tenantId === self::KYUNGDONG_TENANT_ID)
|
||
│ └── 추가: $handler = FormulaHandlerFactory::make($tenantId)
|
||
└── 2.5 calculateKyungdongBom() → calculateTenantBom($handler, ...) 일반화
|
||
|
||
Phase 3: 핸들러(Tenant287) 아이템 코드 정비
|
||
├── 3.1 EST-SMOKE 코드 변경 (2곳)
|
||
│ ├── 라인 519: 'EST-SMOKE-케이스용' → 'BD-케이스용 연기차단재'
|
||
│ └── 라인 557: 'EST-SMOKE-레일용' → 'BD-가이드레일용 연기차단재'
|
||
├── 3.2 레거시 코드 검토 (00035, 00036, 00021, 90201~90204)
|
||
│ └── 현재 items 테이블에 등록되어 있으므로 동작함. 변경 여부 검토만.
|
||
├── 3.3 lookupItem()에 미등록 품목 경고 로깅 추가
|
||
│ └── 라인 42-48: null 반환 시 Log::warning()
|
||
└── 3.4 MNG 연동 테스트 (https://mng.sam.kr/item-management)
|
||
|
||
Phase 4: Generic 수식 데이터 재구성 (기존 데이터 유지, 실제 데이터 추가)
|
||
├── 4.1 실제 FG 제품용 mapping 수식 신규 생성 (quote_formulas INSERT)
|
||
├── 4.2~4.4 실제 데이터 INSERT (기존 테스트 데이터와 병행)
|
||
│ ├── quote_formula_items: BD-/EST- 코드 기반 BOM 구성
|
||
│ ├── quote_formula_ranges: 실제 규격별 BD- 코드 반환
|
||
│ └── quote_formula_mappings: FG 모델 → BD 부품 매핑
|
||
└── 4.5 FormulaEvaluatorService에 모델 인식 로직 추가
|
||
|
||
Phase 5: 통합 테스트
|
||
├── 5.1 MNG 품목관리 - 7모델 전수 테스트
|
||
├── 5.2 React 견적관리 - BOM 계산 테스트
|
||
├── 5.3 단가 정합성 검증
|
||
└── 5.4 잔여 테스트 데이터 참조 점검
|
||
```
|
||
|
||
### 4.2 EST- 품목 등록 상세
|
||
|
||
#### items INSERT 템플릿
|
||
|
||
```sql
|
||
INSERT INTO items (tenant_id, item_type, code, name, unit, is_active, created_at, updated_at)
|
||
VALUES (287, 'PT', '{code}', '{name}', '{unit}', 1, NOW(), NOW());
|
||
```
|
||
|
||
#### 등록 대상 품목 목록
|
||
|
||
```
|
||
EST-MOTOR-{voltage}-{capacity}: 모터 (전압-용량)
|
||
├── EST-MOTOR-220V-150K 150K 모터 220V
|
||
├── EST-MOTOR-220V-300K 300K 모터 220V
|
||
├── EST-MOTOR-220V-400K 400K 모터 220V
|
||
├── EST-MOTOR-220V-500K 500K 모터 220V
|
||
├── EST-MOTOR-220V-600K 600K 모터 220V
|
||
├── EST-MOTOR-380V-500K 500K 모터 380V
|
||
├── EST-MOTOR-380V-600K 600K 모터 380V
|
||
├── EST-MOTOR-380V-800K 800K 모터 380V
|
||
├── EST-MOTOR-380V-1000K 1000K 모터 380V
|
||
└── item_type: PT, unit: EA
|
||
|
||
EST-CTRL-{type}: 제어기
|
||
├── EST-CTRL-뒷박스 뒷박스 제어기
|
||
├── EST-CTRL-일반 일반 제어기
|
||
├── EST-CTRL-동보 동보 제어기
|
||
├── EST-CTRL-자탈 자탈 제어기
|
||
├── EST-CTRL-셋팅 셋팅 박스
|
||
└── item_type: PT, unit: EA
|
||
|
||
EST-SHAFT-{inch}인치-{length}: 감기샤프트
|
||
├── EST-SHAFT-3인치-300 3인치 300mm
|
||
├── EST-SHAFT-4인치-3000 4인치 3000mm
|
||
├── EST-SHAFT-4인치-4500 4인치 4500mm
|
||
├── EST-SHAFT-4인치-6000 4인치 6000mm
|
||
├── EST-SHAFT-5인치-6000 5인치 6000mm
|
||
├── EST-SHAFT-5인치-7000 5인치 7000mm
|
||
├── EST-SHAFT-5인치-8200 5인치 8200mm
|
||
└── item_type: PT, unit: EA
|
||
|
||
EST-PIPE-1.4-{length}: 앵글파이프
|
||
├── EST-PIPE-1.4-3000 1.4T 3000mm
|
||
├── EST-PIPE-1.4-4500 1.4T 4500mm (핸들러에 없지만 패턴상 추가)
|
||
├── EST-PIPE-1.4-6000 1.4T 6000mm
|
||
└── item_type: PT, unit: EA
|
||
|
||
EST-ANGLE-BRACKET-{type}: 모터받침 앵글
|
||
├── EST-ANGLE-BRACKET-스크린용
|
||
├── EST-ANGLE-BRACKET-철제300K
|
||
├── EST-ANGLE-BRACKET-철제400K
|
||
├── EST-ANGLE-BRACKET-철제500K이상
|
||
└── item_type: PT, unit: EA
|
||
|
||
EST-ANGLE-MAIN-{type}-{size}: 부자재 앵글
|
||
├── EST-ANGLE-MAIN-앵글3T-2.5
|
||
├── EST-ANGLE-MAIN-앵글3T-10
|
||
├── EST-ANGLE-MAIN-앵글4T-2.5
|
||
└── item_type: PT, unit: EA
|
||
|
||
EST-INSPECTION: 검사비
|
||
└── item_type: PT, unit: EA
|
||
|
||
EST-RAW-스크린-{type}: 스크린 원단
|
||
├── EST-RAW-스크린-실리카
|
||
└── item_type: PT, unit: ㎡
|
||
|
||
EST-RAW-슬랫-{type}: 슬랫 원단
|
||
├── EST-RAW-슬랫-방화
|
||
└── item_type: PT, unit: ㎡
|
||
```
|
||
|
||
> **참고**: 핸들러가 동적으로 코드를 조합하므로, 실제 사용되는 코드 조합만 등록.
|
||
> 등록 후 `lookupItem()` 호출 시 item_id/name이 정상 반환되는지 확인.
|
||
|
||
---
|
||
|
||
## 5. 컨펌 대기 목록
|
||
|
||
| # | 항목 | 변경 내용 | 영향 범위 | 상태 |
|
||
|---|------|----------|----------|------|
|
||
| 1 | 핸들러 구조화 | 인터페이스 + 팩토리 신규, 핸들러 이동 | Services/Quote/ 전체 | ✅ 완료 |
|
||
| 2 | FormulaEvaluatorService 분기 변경 | if(287) → Factory::make() | 전체 테넌트 | ✅ 완료 |
|
||
| 3 | EST- 품목 코드 체계 | 72건 이미 등록 확인 | items 테이블 | ✅ 완료 (사전 등록됨) |
|
||
| 4 | EST-SMOKE → BD- 코드 변경 | 핸들러 라인 519, 557 변경 | Tenant287/FormulaHandler | ✅ 완료 |
|
||
| 5 | 레거시 숫자코드 유지 | 00035, 00036 등 유지 결정 | Tenant287/FormulaHandler | ✅ 유지 (items에 등록됨) |
|
||
| 6 | Generic 경로에 모델 인식 추가 | 후순위 보류 (Phase 4) | 핸들러 없는 테넌트 | ⏭️ 후순위 |
|
||
|
||
---
|
||
|
||
## 6. 변경 이력
|
||
|
||
| 날짜 | 항목 | 변경 내용 | 파일 | 승인 |
|
||
|------|------|----------|------|------|
|
||
| 2026-02-19 | - | 문서 초안 작성 | - | - |
|
||
| 2026-02-19 | - | 자기완결성 보완 (부록 추가) | - | - |
|
||
| 2026-02-20 | Phase 1 | EST- 품목 72건 이미 등록 확인 → Phase 1 완료 | items 테이블 | ✅ |
|
||
| 2026-02-20 | Phase 2 | TenantFormulaHandler 인터페이스 + FormulaHandlerFactory 생성 | Contracts/TenantFormulaHandler.php, FormulaHandlerFactory.php | ✅ |
|
||
| 2026-02-20 | Phase 2 | KyungdongFormulaHandler → Tenant287/FormulaHandler 이동 | Handlers/Tenant287/FormulaHandler.php (신규), Handlers/KyungdongFormulaHandler.php (삭제) | ✅ |
|
||
| 2026-02-20 | Phase 2 | FormulaEvaluatorService 분기 로직 변경 (if(287) → Factory::make()) | FormulaEvaluatorService.php | ✅ |
|
||
| 2026-02-20 | Phase 2 | calculateKyungdongBom() → calculateTenantBom() 일반화 | FormulaEvaluatorService.php | ✅ |
|
||
| 2026-02-20 | Phase 3 | EST-SMOKE-케이스용 → BD-케이스용 연기차단재 (id:15587) | Tenant287/FormulaHandler.php | ✅ |
|
||
| 2026-02-20 | Phase 3 | EST-SMOKE-레일용 → BD-가이드레일용 연기차단재 (id:15572) | Tenant287/FormulaHandler.php | ✅ |
|
||
| 2026-02-20 | Phase 3 | lookupItem() 미등록 품목 Log::warning() 추가 | Tenant287/FormulaHandler.php | ✅ |
|
||
| 2026-02-20 | Phase 4 | Generic 경로 분석 → items.bom 기반, FG.bom 비어있음 → 후순위 결정 | - | ⏭️ |
|
||
| 2026-02-20 | Phase 5 | 벽부형 7모델 + 측면형 3모델 tinker 통합 테스트 PASS | - | ✅ |
|
||
| 2026-02-20 | Phase 5 | Factory 엣지케이스 + SF-/SM- 잔존 참조 점검 완료 | - | ✅ |
|
||
| 2026-02-20 | - | 문서 최종 업데이트 (검증결과, 변경이력, 상태 반영) | formula-engine-real-data-plan.md | ✅ |
|
||
|
||
---
|
||
|
||
## 7. 참고 문서
|
||
|
||
- **견적 시스템**: `docs/features/quotes/README.md`
|
||
- **품목 정책**: `docs/rules/item-policy.md`
|
||
- **DB 스키마**: `docs/specs/database-schema.md`
|
||
- **빠른 시작**: `docs/quickstart/quick-start.md`
|
||
|
||
---
|
||
|
||
## 8. 관련 파일 및 코드 위치
|
||
|
||
### 8.1 API (api/) - 핵심 코드 위치
|
||
|
||
| 파일 | 메서드 | 라인 | 역할 |
|
||
|------|--------|------|------|
|
||
| `Services/Quote/FormulaEvaluatorService.php` | `calculateBomWithDebug()` | 592-596 | 메인 엔트리 |
|
||
| 같은 파일 | (경동 분기 if문) | 609-611 | **Phase 2에서 Factory로 교체** |
|
||
| 같은 파일 | `calculateKyungdongBom()` | 1574-1881 | **Phase 2에서 calculateTenantBom()으로 일반화** |
|
||
| 같은 파일 | `KYUNGDONG_TENANT_ID` | 35 | **Phase 2에서 제거** |
|
||
| 같은 파일 | `expandBomWithFormulas()` | 1261-1333 | items.bom 재귀 전개 (Generic, 유지) |
|
||
| 같은 파일 | `calculateCategoryPrice()` | 812-862 | 카테고리 그룹 기반 단가 (유지) |
|
||
| 같은 파일 | `getItemPrice()` | 1066-1097 | 단가 조회 (유지) |
|
||
| **신규** `Contracts/TenantFormulaHandler.php` | - | - | **Phase 2에서 생성** |
|
||
| **신규** `FormulaHandlerFactory.php` | `make()` | - | **Phase 2에서 생성** |
|
||
| `Handlers/KyungdongFormulaHandler.php` | - | - | **→ `Handlers/Tenant287/FormulaHandler.php`로 이동** |
|
||
| `Handlers/Tenant287/FormulaHandler.php` | `calculateDynamicItems()` | 963 | **메인 엔트리** (이동 후) |
|
||
| 같은 파일 | `calculateSteelItems()` | 448 | 절곡품 10종 계산 |
|
||
| 같은 파일 | `calculatePartItems()` | 778 | 부자재 5종 계산 |
|
||
| 같은 파일 | `lookupItem()` | 35-49 | 품목 코드 → id/name 조회 (캐싱) |
|
||
| 같은 파일 | `withItemMapping()` | 72-87 | 아이템에 item_code/item_id 매핑 |
|
||
| 같은 파일 | `getGuideRailSpecs()` | 666-672 | 모델별 가이드레일 규격 매핑 |
|
||
| 같은 파일 | `calculateGuideRails()` | 675-730 | 가이드레일 타입별 계산 |
|
||
| `Services/Quote/EstimatePriceService.php` | (전체) | - | 단가 조회 서비스 (유지) |
|
||
| `Services/FormulaApiService.php` | `calculateBom()` | - | API 서버 호출 래퍼 (유지) |
|
||
|
||
### 8.2 MNG (mng/)
|
||
|
||
| 파일 | 메서드 | 라인 | 역할 |
|
||
|------|--------|------|------|
|
||
| `Controllers/Api/Admin/ItemManagementApiController.php` | `calculateFormula()` | 60-86 | 수식 BOM 계산 API |
|
||
| `Services/FormulaApiService.php` | `calculateBom()` | 24-82 | POST /api/v1/quotes/calculate/bom |
|
||
| `Services/ItemManagementService.php` | `getBomTree()` | - | BOM 트리 조회 (items.bom) |
|
||
| `views/item-management/index.blade.php` | JS `calculateFormula()` | - | 프론트 수식 계산 호출 |
|
||
|
||
### 8.3 DB 테이블 스키마
|
||
|
||
#### items 테이블
|
||
|
||
| 컬럼 | 타입 | NULL | 설명 |
|
||
|------|------|------|------|
|
||
| id | bigint unsigned | NO | PK |
|
||
| tenant_id | bigint unsigned | NO | 테넌트 |
|
||
| item_type | varchar(15) | NO | FG/PT/SM/RM/CS |
|
||
| code | varchar(100) | NO | 품목 코드 |
|
||
| name | varchar(255) | NO | 품목명 |
|
||
| unit | varchar(20) | YES | 단위 (EA/M/㎡) |
|
||
| category_id | bigint unsigned | YES | 카테고리 FK |
|
||
| process_type | varchar(20) | YES | 공정 유형 |
|
||
| item_category | varchar(50) | YES | 품목 카테고리 |
|
||
| bom | json | YES | BOM JSON (FG는 현재 NULL) |
|
||
| attributes | json | YES | 동적 속성 |
|
||
| options | json | YES | 관리 옵션 |
|
||
| is_active | tinyint(1) | NO | 활성 (기본 1) |
|
||
|
||
#### quote_formula_items 테이블
|
||
|
||
| 컬럼 | 타입 | NULL | 설명 |
|
||
|------|------|------|------|
|
||
| id | bigint unsigned | NO | PK |
|
||
| formula_id | bigint unsigned | NO | quote_formulas FK |
|
||
| item_code | varchar(50) | NO | 품목 코드 |
|
||
| item_name | varchar(200) | NO | 품목명 |
|
||
| specification | varchar(100) | YES | 규격 |
|
||
| unit | varchar(20) | NO | 단위 |
|
||
| quantity_formula | varchar(500) | NO | 수량 수식 |
|
||
| unit_price_formula | varchar(500) | YES | 단가 수식 |
|
||
| sort_order | int unsigned | NO | 정렬 |
|
||
|
||
#### quote_formula_ranges 테이블
|
||
|
||
| 컬럼 | 타입 | NULL | 설명 |
|
||
|------|------|------|------|
|
||
| id | bigint unsigned | NO | PK |
|
||
| formula_id | bigint unsigned | NO | quote_formulas FK |
|
||
| min_value | decimal(15,4) | NO | 최소값 |
|
||
| max_value | decimal(15,4) | NO | 최대값 |
|
||
| condition_variable | varchar(50) | NO | 조건 변수 (K/H/W) |
|
||
| result_value | varchar(500) | NO | 결과값 (품목 코드) |
|
||
| result_type | enum('fixed','formula') | NO | 결과 유형 |
|
||
| sort_order | int unsigned | NO | 정렬 |
|
||
|
||
#### quote_formula_mappings 테이블
|
||
|
||
| 컬럼 | 타입 | NULL | 설명 |
|
||
|------|------|------|------|
|
||
| id | bigint unsigned | NO | PK |
|
||
| formula_id | bigint unsigned | NO | quote_formulas FK |
|
||
| source_variable | varchar(50) | NO | 원본 변수 |
|
||
| source_value | varchar(200) | NO | 원본 값 |
|
||
| result_value | varchar(500) | NO | 결과값 |
|
||
| result_type | enum('fixed','formula') | NO | 결과 유형 |
|
||
| sort_order | int unsigned | NO | 정렬 |
|
||
|
||
#### quote_formulas 테이블
|
||
|
||
| 컬럼 | 타입 | NULL | 설명 |
|
||
|------|------|------|------|
|
||
| id | bigint unsigned | NO | PK |
|
||
| tenant_id | bigint unsigned | NO | 테넌트 |
|
||
| category_id | bigint unsigned | NO | 카테고리 FK |
|
||
| product_id | bigint unsigned | YES | 매핑 대상 제품 FK |
|
||
| name | varchar(200) | NO | 수식명 |
|
||
| variable | varchar(50) | NO | 변수명 |
|
||
| type | enum('input','calculation','range','mapping') | NO | 유형 |
|
||
| formula | text | YES | 수식 표현식 |
|
||
| output_type | enum('variable','item') | NO | 출력 유형 |
|
||
| sort_order | int unsigned | NO | 정렬 |
|
||
| is_active | tinyint(1) | NO | 활성 |
|
||
|
||
---
|
||
|
||
## 9. 검증 결과
|
||
|
||
### 9.1 테스트 케이스 (tinker 수동 실행)
|
||
|
||
#### 벽부형 7모델 (W0=2000, H0=2500, QTY=1)
|
||
|
||
| 모델 | FG 코드 | BOM 항목수 | 총 금액 | 상태 |
|
||
|------|---------|----------|--------|------|
|
||
| KQTS01 | FG-KQTS01-벽면형-SUS | 18건 | 1,167,934원 | ✅ |
|
||
| KSS01 | FG-KSS01-벽면형-SUS | 18건 | ~1.1M원 | ✅ |
|
||
| KSS02 | FG-KSS02-벽면형-SUS | 18건 | ~1.1M원 | ✅ |
|
||
| KSE01 | FG-KSE01-벽면형-SUS | 18건 | ~1.2M원 | ✅ |
|
||
| KSE01-EGI | FG-KSE01-벽면형-EGI | 18건 | ~1.2M원 | ✅ |
|
||
| KWE01 | FG-KWE01-벽면형-SUS | 18건 | ~1.2M원 | ✅ |
|
||
| KTE01 | FG-KTE01-벽면형-SUS | 18건 | ~1.3M원 | ✅ |
|
||
|
||
#### 측면형 + 대형 규격 (W0=4000, H0=5000, QTY=2)
|
||
|
||
| 모델 | FG 코드 | BOM 항목수 | 총 금액 | 상태 |
|
||
|------|---------|----------|--------|------|
|
||
| KQTS01 | FG-KQTS01-측면형-SUS | 18건 | ~2.9M원 | ✅ |
|
||
| KSE01 | FG-KSE01-측면형-SUS | 18건 | ~3.1M원 | ✅ |
|
||
| KTE01-EGI | FG-KTE01-측면형-EGI | 18건 | ~3.2M원 | ✅ |
|
||
|
||
#### Factory 엣지 케이스
|
||
|
||
| tenant_id | 예상 | 실제 | 상태 |
|
||
|-----------|------|------|------|
|
||
| 287 | Tenant287\FormulaHandler 인스턴스 | ✅ 정상 반환 | ✅ |
|
||
| 0 | null | null | ✅ |
|
||
| -1 | null | null | ✅ |
|
||
| 999999 | null | null | ✅ |
|
||
|
||
#### SF-/SM- 잔존 참조 점검
|
||
|
||
| 검색 범위 | 패턴 | 결과 | 상태 |
|
||
|-----------|------|------|------|
|
||
| api/app/Services/Quote/ | SF- / SM- 코드 참조 | 0건 | ✅ |
|
||
|
||
### 9.2 성공 기준
|
||
|
||
| 기준 | 달성 | 비고 |
|
||
|------|------|------|
|
||
| FormulaHandlerFactory::make(287)이 Tenant287 핸들러 반환 | ✅ | 자동 발견 정상 동작 |
|
||
| FormulaHandlerFactory::make(999)이 null 반환 → Generic 경로 | ✅ | 미등록 테넌트 정상 |
|
||
| tinker에서 FG 선택 시 BOM 계산 성공 | ✅ | 벽부 7모델 + 측면 3모델 전수 PASS |
|
||
| BOM 결과의 모든 item_code가 items에 존재 | ✅ | BD- 코드 정상 매핑 (lookupItem null 없음) |
|
||
| React 견적관리 BOM 벌크 계산 정상 | ⏭️ | Phase 4 후순위와 함께 |
|
||
| SF-/SM- 코드 참조 잔존 없음 | ✅ | api/Services/Quote/ 내 0건 확인 |
|
||
|
||
---
|
||
|
||
## 부록 A. FG 품목 전체 목록 (18건)
|
||
|
||
| id | code | model | guiderail | finishing | major_category | legacy_model_id |
|
||
|----|------|-------|-----------|-----------|---------------|-----------------|
|
||
| 15515 | FG-KSS01-벽면형-SUS | KSS01 | 벽면형 | SUS마감 | 스크린 | 12 |
|
||
| 15516 | FG-KSS01-측면형-SUS | KSS01 | 측면형 | SUS마감 | 스크린 | 13 |
|
||
| 15517 | FG-KSE01-벽면형-SUS | KSE01 | 벽면형 | SUS마감 | 스크린 | 14 |
|
||
| 15518 | FG-KSE01-벽면형-EGI | KSE01 | 벽면형 | EGI마감 | 스크린 | 15 |
|
||
| 15519 | FG-KSE01-측면형-SUS | KSE01 | 측면형 | SUS마감 | 스크린 | 16 |
|
||
| 15520 | FG-KSE01-측면형-EGI | KSE01 | 측면형 | EGI마감 | 스크린 | 17 |
|
||
| 15521 | FG-KWE01-벽면형-SUS | KWE01 | 벽면형 | SUS마감 | 스크린 | 18 |
|
||
| 15522 | FG-KWE01-벽면형-EGI | KWE01 | 벽면형 | EGI마감 | 스크린 | 19 |
|
||
| 15523 | FG-KWE01-측면형-SUS | KWE01 | 측면형 | SUS마감 | 스크린 | 20 |
|
||
| 15524 | FG-KWE01-측면형-EGI | KWE01 | 측면형 | EGI마감 | 스크린 | 21 |
|
||
| 15525 | FG-KQTS01-벽면형-SUS | KQTS01 | 벽면형 | SUS마감 | 철재 | 22 |
|
||
| 15526 | FG-KQTS01-측면형-SUS | KQTS01 | 측면형 | SUS마감 | 철재 | 23 |
|
||
| 15527 | FG-KTE01-측면형-SUS | KTE01 | 측면형 | SUS마감 | 철재 | 24 |
|
||
| 15528 | FG-KTE01-벽면형-SUS | KTE01 | 벽면형 | SUS마감 | 철재 | 25 |
|
||
| 15529 | FG-KTE01-측면형-EGI | KTE01 | 측면형 | EGI마감 | 철재 | 26 |
|
||
| 15530 | FG-KTE01-벽면형-EGI | KTE01 | 벽면형 | EGI마감 | 철재 | 27 |
|
||
| 15531 | FG-KSS02-측면형-SUS | KSS02 | 측면형 | SUS마감 | 스크린 | 28 |
|
||
| 15532 | FG-KSS02-벽면형-SUS | KSS02 | 벽면형 | SUS마감 | 스크린 | 29 |
|
||
|
||
---
|
||
|
||
## 부록 B. BD- 품목 전체 목록 (58건, 모두 item_type=PT)
|
||
|
||
### 가이드레일 (17건)
|
||
|
||
| id | code | name |
|
||
|----|------|------|
|
||
| 15589 | BD-가이드레일-KDSS01-SUS-150*150 | 가이드레일 KDSS01 SUS 150*150 |
|
||
| 15590 | BD-가이드레일-KDSS01-SUS-150*212 | 가이드레일 KDSS01 SUS 150*212 |
|
||
| 15592 | BD-가이드레일-KQTS01-SUS-130*125 | 가이드레일 KQTS01 SUS 130*125 |
|
||
| 15593 | BD-가이드레일-KQTS01-SUS-130*75 | 가이드레일 KQTS01 SUS 130*75 |
|
||
| 15596 | BD-가이드레일-KSE01-SUS-120*120 | 가이드레일 KSE01 SUS 120*120 |
|
||
| 15597 | BD-가이드레일-KSE01-SUS-120*70 | 가이드레일 KSE01 SUS 120*70 |
|
||
| 15598 | BD-가이드레일-KSE01-EGI-120*120 | 가이드레일 KSE01 EGI 120*120 |
|
||
| 15599 | BD-가이드레일-KSE01-EGI-120*70 | 가이드레일 KSE01 EGI 120*70 |
|
||
| 15603 | BD-가이드레일-KSS01-SUS-120*120 | 가이드레일 KSS01 SUS 120*120 |
|
||
| 15604 | BD-가이드레일-KSS01-SUS-120*70 | 가이드레일 KSS01 SUS 120*70 |
|
||
| 15607 | BD-가이드레일-KSS02-SUS-120*120 | 가이드레일 KSS02 SUS 120*120 |
|
||
| 15608 | BD-가이드레일-KSS02-SUS-120*70 | 가이드레일 KSS02 SUS 120*70 |
|
||
| 15610 | BD-가이드레일-KTE01-SUS-130*125 | 가이드레일 KTE01 SUS 130*125 |
|
||
| 15611 | BD-가이드레일-KTE01-SUS-130*75 | 가이드레일 KTE01 SUS 130*75 |
|
||
| 15612 | BD-가이드레일-KTE01-EGI-130*125 | 가이드레일 KTE01 EGI 130*125 |
|
||
| 15613 | BD-가이드레일-KTE01-EGI-130*75 | 가이드레일 KTE01 EGI 130*75 |
|
||
| 15617 | BD-가이드레일-KWE01-SUS-120*120 | 가이드레일 KWE01 SUS 120*120 |
|
||
| 15618 | BD-가이드레일-KWE01-SUS-120*70 | 가이드레일 KWE01 SUS 120*70 |
|
||
| 15619 | BD-가이드레일-KWE01-EGI-120*120 | 가이드레일 KWE01 EGI 120*120 |
|
||
| 15620 | BD-가이드레일-KWE01-EGI-120*70 | 가이드레일 KWE01 EGI 120*70 |
|
||
|
||
### 하단마감재 (10건)
|
||
|
||
| id | code | name |
|
||
|----|------|------|
|
||
| 15591 | BD-하단마감재-KDSS01-SUS-140*78 | 하단마감재 KDSS01 SUS 140*78 |
|
||
| 15594 | BD-하단마감재-KQTS01-SUS-60*30 | 하단마감재 KQTS01 SUS 60*30 |
|
||
| 15600 | BD-하단마감재-KSE01-SUS-64*43 | 하단마감재 KSE01 SUS 64*43 |
|
||
| 15601 | BD-하단마감재-KSE01-EGI-60*40 | 하단마감재 KSE01 EGI 60*40 |
|
||
| 15605 | BD-하단마감재-KSS01-SUS-60*40 | 하단마감재 KSS01 SUS 60*40 |
|
||
| 15609 | BD-하단마감재-KSS02-SUS-60*40 | 하단마감재 KSS02 SUS 60*40 |
|
||
| 15614 | BD-하단마감재-KTE01-SUS-64*34 | 하단마감재 KTE01 SUS 64*34 |
|
||
| 15615 | BD-하단마감재-KTE01-EGI-60*30 | 하단마감재 KTE01 EGI 60*30 |
|
||
| 15621 | BD-하단마감재-KWE01-SUS-64*43 | 하단마감재 KWE01 SUS 64*43 |
|
||
| 15622 | BD-하단마감재-KWE01-EGI-60*40 | 하단마감재 KWE01 EGI 60*40 |
|
||
|
||
### L-BAR (5건)
|
||
|
||
| id | code | name |
|
||
|----|------|------|
|
||
| 15588 | BD-L-BAR-KDSS01-17*100 | L-BAR KDSS01 17*100 |
|
||
| 15595 | BD-L-BAR-KSE01-17*60 | L-BAR KSE01 17*60 |
|
||
| 15602 | BD-L-BAR-KSS01-17*60 | L-BAR KSS01 17*60 |
|
||
| 15606 | BD-L-BAR-KSS02-17*60 | L-BAR KSS02 17*60 |
|
||
| 15616 | BD-L-BAR-KWE01-17*60 | L-BAR KWE01 17*60 |
|
||
|
||
### 케이스 (11건)
|
||
|
||
| id | code | name |
|
||
|----|------|------|
|
||
| 15577 | BD-케이스-500*350 | 케이스 500*350 |
|
||
| 15578 | BD-케이스-500*380 | 케이스 500*380 |
|
||
| 15579 | BD-케이스-600*500 | 케이스 600*500 |
|
||
| 15580 | BD-케이스-600*550 | 케이스 600*550 |
|
||
| 15581 | BD-케이스-650*500 | 케이스 650*500 |
|
||
| 15582 | BD-케이스-650*550 | 케이스 650*550 |
|
||
| 15583 | BD-케이스-700*550 | 케이스 700*550 |
|
||
| 15584 | BD-케이스-700*600 | 케이스 700*600 |
|
||
| 15585 | BD-케이스-780*600 | 케이스 780*600 |
|
||
| 15586 | BD-케이스-780*650 | 케이스 780*650 |
|
||
| 15587 | BD-케이스용 연기차단재 | 케이스용 연기차단재 |
|
||
|
||
### 마구리 (10건)
|
||
|
||
| id | code | name |
|
||
|----|------|------|
|
||
| 15565 | BD-마구리-505*355 | 마구리 505*355 |
|
||
| 15566 | BD-마구리-505*385 | 마구리 505*385 |
|
||
| 15567 | BD-마구리-605*555 | 마구리 605*555 |
|
||
| 15568 | BD-마구리-655*555 | 마구리 655*555 |
|
||
| 15569 | BD-마구리-705*605 | 마구리 705*605 |
|
||
| 15570 | BD-마구리-785*685 | 마구리 785*685 |
|
||
| 15573 | BD-마구리-655*505 | 마구리 655*505 |
|
||
| 15574 | BD-마구리-705*555 | 마구리 705*555 |
|
||
| 15575 | BD-마구리-785*605 | 마구리 785*605 |
|
||
| 15576 | BD-마구리-785*655 | 마구리 785*655 |
|
||
|
||
### 기타 (5건)
|
||
|
||
| id | code | name |
|
||
|----|------|------|
|
||
| 15571 | BD-보강평철-50 | 보강평철 50 |
|
||
| 15572 | BD-가이드레일용 연기차단재 | 가이드레일용 연기차단재 |
|
||
|
||
---
|
||
|
||
## 부록 C. 코드 변경 포인트
|
||
|
||
### C.1 EST-SMOKE → BD- 변경 (Phase 3.1)
|
||
|
||
**파일**: `api/app/Services/Quote/Handlers/Tenant287/FormulaHandler.php` (이동 후)
|
||
|
||
```
|
||
라인 519: 'EST-SMOKE-케이스용' → 'BD-케이스용 연기차단재' (id: 15587)
|
||
라인 557: 'EST-SMOKE-레일용' → 'BD-가이드레일용 연기차단재' (id: 15572)
|
||
```
|
||
|
||
### C.2 레거시 숫자 코드 매핑 (Phase 3.2 검토 대상)
|
||
|
||
| 라인 | 현재 코드 | items.id | items.name | 비고 |
|
||
|------|----------|----------|-----------|------|
|
||
| 564 | 00035 | 14939 | 철재용하장바(SUS)3000 | 하장바 SUS |
|
||
| 564 | 00036 | 14940 | 철재용하장바(SUS1.2T) | 하장바 EGI (SM타입) |
|
||
| 619 | 00021 | 14928 | 평철12T | 무게평철12T |
|
||
| 631 | 90201 | 15188 | KD환봉(30파이) | 환봉 기본 |
|
||
| 628 | 90202 | 15189 | KD환봉 | 환봉 35파이 |
|
||
| 629 | 90203 | 15190 | KD환봉 | 환봉 45파이 |
|
||
| 630 | 90204 | 15191 | KD환봉 | 환봉 50파이 |
|
||
|
||
> 모두 items 테이블에 존재하므로 lookupItem() 정상 동작.
|
||
> 변경 여부는 코드 가독성 차원에서 검토 (기능적 문제 없음).
|
||
|
||
### C.3 lookupItem 로깅 추가 (Phase 3.3)
|
||
|
||
**파일**: `api/app/Services/Quote/Handlers/Tenant287/FormulaHandler.php`
|
||
**위치**: 라인 42-48 `lookupItem()` 메서드
|
||
|
||
```php
|
||
// 변경 전 (라인 46)
|
||
$cache[$code] = ['id' => $item?->id, 'name' => $item?->name];
|
||
|
||
// 변경 후
|
||
$cache[$code] = ['id' => $item?->id, 'name' => $item?->name];
|
||
if (!$item) {
|
||
\Log::warning("[Tenant287\FormulaHandler] 미등록 품목: {$code}");
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 부록 D. calculateDynamicItems 입력 파라미터
|
||
|
||
KyungdongFormulaHandler의 메인 엔트리 `calculateDynamicItems()` (라인 963)가 수신하는 파라미터:
|
||
|
||
```php
|
||
$inputs = [
|
||
// 기본 치수
|
||
'W0' => float, // 폭 (mm)
|
||
'H0' => float, // 높이 (mm)
|
||
'QTY' => int, // 수량
|
||
|
||
// 제품 정보
|
||
'product_type' => string, // 'screen' | 'slat' | 'steel'
|
||
'model_name' => string, // 'KSS01' | 'KSE01' | ...
|
||
'finishing_type' => string, // 'SUS마감' | 'EGI마감' (→ 내부에서 '마감' 제거)
|
||
|
||
// 가이드레일
|
||
'guide_type' => string, // '벽면형' | '측면형' | '혼합형'
|
||
|
||
// 케이스
|
||
'case_spec' => string, // '500*380' 등
|
||
|
||
// 모터/제어기
|
||
'bracket_inch' => string, // '4' | '5' | '6' | '8'
|
||
'motor_power' => string, // 'single' | 'three'
|
||
'controller_type' => string, // '일반' | '동보' | '자탈' 등
|
||
|
||
// 기타 (선택)
|
||
'weight_plate_qty' => int,
|
||
'round_bar_qty' => int,
|
||
'round_bar_phi' => int, // 30 | 35 | 45 | 50
|
||
];
|
||
```
|
||
|
||
**반환값** (아이템 배열):
|
||
|
||
```php
|
||
[
|
||
[
|
||
'category' => string, // 'steel' | 'parts' | 'inspection' | 'material' | 'motor' | 'controller'
|
||
'item_name' => string,
|
||
'item_code' => string, // EST-*, BD-*, 또는 레거시 숫자코드
|
||
'item_id' => int|null, // items.id (lookupItem 결과)
|
||
'specification' => string,
|
||
'unit' => string, // 'EA' | 'm' | '㎡'
|
||
'quantity' => float,
|
||
'unit_price' => float,
|
||
'total_price' => float,
|
||
],
|
||
// ...
|
||
]
|
||
```
|
||
|
||
---
|
||
|
||
## 부록 E. 핸들러 구조화 설계 (Phase 2 상세)
|
||
|
||
### E.1 디렉토리 구조 (Before → After)
|
||
|
||
```
|
||
Before:
|
||
api/app/Services/Quote/
|
||
├── FormulaEvaluatorService.php ← if (287) 하드코딩
|
||
├── EstimatePriceService.php
|
||
└── Handlers/
|
||
└── KyungdongFormulaHandler.php ← 독립 클래스, 인터페이스 없음
|
||
|
||
After:
|
||
api/app/Services/Quote/
|
||
├── FormulaEvaluatorService.php ← Factory::make($tenantId) 사용
|
||
├── FormulaHandlerFactory.php ← 신규: 자동 발견 팩토리
|
||
├── EstimatePriceService.php
|
||
├── Contracts/
|
||
│ └── TenantFormulaHandler.php ← 신규: 인터페이스
|
||
└── Handlers/
|
||
└── Tenant287/ ← 경동기업 (tenant_id: 287)
|
||
└── FormulaHandler.php ← KyungdongFormulaHandler 이동
|
||
└── Tenant{N}/ ← 향후 업체 추가 시
|
||
└── FormulaHandler.php
|
||
```
|
||
|
||
### E.2 인터페이스 설계
|
||
|
||
```php
|
||
// api/app/Services/Quote/Contracts/TenantFormulaHandler.php
|
||
namespace App\Services\Quote\Contracts;
|
||
|
||
interface TenantFormulaHandler
|
||
{
|
||
/**
|
||
* 동적 BOM 항목 계산 (메인 엔트리)
|
||
*/
|
||
public function calculateDynamicItems(array $inputs): array;
|
||
|
||
/**
|
||
* 모터 용량 계산
|
||
*/
|
||
public function calculateMotorCapacity(string $productType, float $weight, string $bracketInch): string;
|
||
|
||
/**
|
||
* 브라켓 사이즈 계산
|
||
*/
|
||
public function calculateBracketSize(float $weight, ?string $bracketInch = null): string;
|
||
}
|
||
```
|
||
|
||
### E.3 팩토리 설계
|
||
|
||
```php
|
||
// api/app/Services/Quote/FormulaHandlerFactory.php
|
||
namespace App\Services\Quote;
|
||
|
||
use App\Services\Quote\Contracts\TenantFormulaHandler;
|
||
|
||
class FormulaHandlerFactory
|
||
{
|
||
/**
|
||
* tenant_id로 핸들러 자동 발견.
|
||
* Handlers/Tenant{id}/FormulaHandler.php가 존재하면 인스턴스 반환.
|
||
* 없으면 null → Generic DB 경로.
|
||
*/
|
||
public static function make(int $tenantId): ?TenantFormulaHandler
|
||
{
|
||
$class = "App\\Services\\Quote\\Handlers\\Tenant{$tenantId}\\FormulaHandler";
|
||
|
||
if (!class_exists($class)) {
|
||
return null;
|
||
}
|
||
|
||
$handler = new $class();
|
||
|
||
if (!$handler instanceof TenantFormulaHandler) {
|
||
throw new \RuntimeException(
|
||
"Tenant{$tenantId} FormulaHandler must implement TenantFormulaHandler"
|
||
);
|
||
}
|
||
|
||
return $handler;
|
||
}
|
||
}
|
||
```
|
||
|
||
### E.4 핸들러 이동 (Tenant287)
|
||
|
||
```php
|
||
// api/app/Services/Quote/Handlers/Tenant287/FormulaHandler.php
|
||
namespace App\Services\Quote\Handlers\Tenant287;
|
||
|
||
use App\Services\Quote\Contracts\TenantFormulaHandler;
|
||
use App\Services\Quote\EstimatePriceService;
|
||
|
||
/**
|
||
* 경동기업 수식 핸들러 (tenant_id: 287)
|
||
*
|
||
* 방화셔터/스크린/철재 제품의 BOM 동적 계산.
|
||
* KyungdongFormulaHandler에서 이동됨.
|
||
*/
|
||
class FormulaHandler implements TenantFormulaHandler
|
||
{
|
||
private const TENANT_ID = 287;
|
||
|
||
// ... 기존 KyungdongFormulaHandler 코드 그대로 유지
|
||
}
|
||
```
|
||
|
||
### E.5 FormulaEvaluatorService 변경 포인트
|
||
|
||
```php
|
||
// 변경 전 (라인 35)
|
||
private const KYUNGDONG_TENANT_ID = 287;
|
||
|
||
// 변경 전 (라인 609-611)
|
||
if ($tenantId === self::KYUNGDONG_TENANT_ID) {
|
||
return $this->calculateKyungdongBom($finishedGoodsCode, $inputVariables, $tenantId);
|
||
}
|
||
|
||
// ─────────────────────────────────────────
|
||
|
||
// 변경 후 (라인 35 제거)
|
||
// KYUNGDONG_TENANT_ID 상수 제거
|
||
|
||
// 변경 후 (라인 609-611)
|
||
$handler = FormulaHandlerFactory::make($tenantId);
|
||
if ($handler) {
|
||
return $this->calculateTenantBom($handler, $finishedGoodsCode, $inputVariables, $tenantId);
|
||
}
|
||
// else → 기존 Generic 10단계 그대로 실행
|
||
|
||
// calculateKyungdongBom() → calculateTenantBom() 리네이밍
|
||
// $handler 파라미터 추가, 내부의 new KyungdongFormulaHandler() 제거
|
||
```
|
||
|
||
### E.6 향후 업체 추가 절차
|
||
|
||
```
|
||
1. Handlers/Tenant{id}/FormulaHandler.php 파일 1개 생성
|
||
2. implements TenantFormulaHandler
|
||
3. 끝. (설정 파일, DB 옵션, 매핑 테이블 변경 없음)
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 자기완결성 점검 결과
|
||
|
||
### 10.1 체크리스트 검증
|
||
|
||
| # | 검증 항목 | 상태 | 비고 |
|
||
|---|----------|:----:|------|
|
||
| 1 | 작업 목적이 명확한가? | ✅ | 1.1 배경 |
|
||
| 2 | 성공 기준이 정의되어 있는가? | ✅ | 9.2 |
|
||
| 3 | 작업 범위가 구체적인가? | ✅ | 4 Phase + 부록 |
|
||
| 4 | 의존성이 명시되어 있는가? | ✅ | Phase 순서 = 의존성 |
|
||
| 5 | 참고 파일 경로 + 라인번호가 정확한가? | ✅ | 섹션 8 + 부록 C/E |
|
||
| 6 | 단계별 절차가 실행 가능한가? | ✅ | 4.1 + 4.2 (SQL), 부록 E (코드 설계) |
|
||
| 7 | 검증 방법이 명시되어 있는가? | ✅ | 섹션 9 |
|
||
| 8 | 모호한 표현이 없는가? | ✅ | 구체적 코드/건수/라인번호 |
|
||
|
||
### 10.2 새 세션 시뮬레이션 테스트
|
||
|
||
| 질문 | 답변 가능 | 참조 섹션 |
|
||
|------|:--------:|----------|
|
||
| Q1. 이 작업의 목적은 무엇인가? | ✅ | 1.1 배경 |
|
||
| Q2. 어디서부터 시작해야 하는가? | ✅ | 3 Phase 1, 4.1 단계별 절차 |
|
||
| Q3. 어떤 파일의 몇 번째 줄을 수정해야 하는가? | ✅ | 8.1 코드 위치, 부록 C/E |
|
||
| Q4. 어떤 품목을 등록해야 하는가? | ✅ | 4.2 등록 상세, 부록 A/B |
|
||
| Q5. 작업 완료 확인 방법은? | ✅ | 9. 검증 결과 |
|
||
| Q6. 핸들러가 어떤 파라미터를 받는가? | ✅ | 부록 D |
|
||
| Q7. DB INSERT 어떻게 하는가? | ✅ | 4.2 SQL 템플릿 |
|
||
| Q8. 기존 데이터 건드려도 되는가? | ✅ | 1.4 원칙 6번 (삭제 금지) |
|
||
| Q9. 핸들러 구조는 어떻게 만드는가? | ✅ | 부록 E (인터페이스/팩토리/이동 상세) |
|
||
| Q10. 향후 업체 추가 시 절차는? | ✅ | 부록 E.6 (파일 1개 생성, 끝) |
|
||
|
||
**결과**: 10/10 통과 → ✅ 자기완결성 확보
|
||
|
||
---
|
||
|
||
*이 문서는 /sc:plan 스킬로 생성되었습니다.*
|