수식 엔진 실제 데이터 연동 계획
작성일: 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 전용)
현재 문제:
- Generic 경로의
quote_formula_items (24건)이 모두 삭제된 SF-/SM- 테스트 품목을 참조
quote_formula_ranges (12건)도 모두 SF- 코드 반환
quote_formula_mappings는 비어있음
- Mapping 수식(id:20,21)이 참조하는 product_id 468, 473도 삭제됨
- Kyungdong 핸들러는 BD- 품목을 참조하지만, EST- 코드 일부가 items 테이블에 미등록
- 핸들러가
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)
목표 (After) - Strategy + Factory, Zero Config
핸들러 자동 발견 원리
업체 추가 시: Handlers/Tenant{id}/FormulaHandler.php 파일 1개만 생성. 설정/매핑 불필요.
1.4 기준 원칙
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=="스크린", M2+W0/100014.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 가이드레일 규격 매핑 (모델별)
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 단계별 절차
4.2 EST- 품목 등록 상세
items INSERT 템플릿
등록 대상 품목 목록
참고: 핸들러가 동적으로 코드를 조합하므로, 실제 사용되는 코드 조합만 등록.
등록 후 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 (이동 후)
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() 메서드
부록 D. calculateDynamicItems 입력 파라미터
KyungdongFormulaHandler의 메인 엔트리 calculateDynamicItems() (라인 963)가 수신하는 파라미터:
반환값 (아이템 배열):
부록 E. 핸들러 구조화 설계 (Phase 2 상세)
E.1 디렉토리 구조 (Before → After)
E.2 인터페이스 설계
E.3 팩토리 설계
E.4 핸들러 이동 (Tenant287)
E.5 FormulaEvaluatorService 변경 포인트
E.6 향후 업체 추가 절차
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 스킬로 생성되었습니다.