# 경동기업 견적 로직 분석 및 구현 계획 > **작성일**: 2026-01-28 > **목적**: 5130 레거시 견적 시스템 분석 → SAM 동적 BOM/견적 로직 구현 > **선행 작업**: [kd-items-migration-plan.md](./kd-items-migration-plan.md) (정적 품목/단가 완료) > **상태**: 🔄 Phase 0 진행중 --- ## 🚀 Quick Start ### 이 문서의 목적 정적 품목 데이터는 이관 완료 (items 651건, prices 651건). 이제 **동적으로 BOM을 계산하고 견적을 산출하는 로직**을 5130에서 분석하여 SAM에 구현. ### 환경 정보 | 항목 | 값 | |------|-----| | 레거시 소스 | `5130/` (프로젝트 루트) | | 대상 테넌트 | 287 (경동기업) | | 관련 SAM 페이지 | https://dev.sam.kr/sales/quote-management/new | --- ## 📍 현재 진행 상태 | 항목 | 내용 | |------|------| | **현재 단계** | Phase 4: SAM 구현 완료 ✅ | | **다음 작업** | Phase 5: 통합 테스트 및 프론트엔드 연동 | | **진행률** | 4/5 (100%) - Phase 0~4 완료 | | **마지막 업데이트** | 2026-01-29 | ### Phase 4.8 테스트 결과 (2026-01-29) ``` 📊 테스트 입력값 - W0: 3000mm, H0: 2500mm, QTY: 1 - 철재형, 5인치 브라켓, 매립형 제어기 - KSS01 모델, SUS 마감 📦 계산된 항목 (16개) 1. 주자재(스크린) → 228,750원 2. 모터 400K → 150,000원 3. 제어기 매립형 → 45,000원 4. 케이스 → 45,000원 5. 케이스용 연기차단재 → 10,500원 6. 케이스 마구리 → 10,000원 7. 가이드레일 → 73,200원 8. 레일용 연기차단재 → 15,250원 9. 하장바 → 24,000원 10. L바 → 13,500원 11. 보강평철 → 9,000원 12. 무게평철12T → 24,000원 13. 환봉 → 8,000원 14. 감기샤프트 5인치 → 65,000원 15. 각파이프 → 12,000원 16. 앵글 앵글3T → 18,000원 💰 합계: 751,200원 ✅ ``` --- ## 1. 배경 및 문제 정의 ### 1.1 현재 상황 **SAM 견적 화면에서 FG-KSS01-벽면형-SUS 선택 시:** - 현재: 3개 항목만 표시 (가이드레일, 하단마감재, L-BAR) - 기대: 본체, 절곡품, 모터/제어기, 부자재 등 전체 BOM ### 1.2 레거시 DB 구조 (분석 완료) ``` models (모델 마스터) └─ parts (대분류 부품: 가이드레일, 하단마감재) └─ parts_sub (세부 절곡품: 1번마감제, 2번본체, 3번-C, 4번-D...) ``` **KSS01 벽면형 예시:** | 대분류 (parts) | 세부품 (parts_sub) | 재질 | 수량 | |---------------|-------------------|------|------| | 가이드레일 | 1번(마감제) | SUS 1.2T | 1 | | | 2번(본체) | EGI 1.55T | 2 | | | 3번(벽면형-C) | EGI 1.55T | 1 | | | 4번(벽면형-D) | EGI 1.55T | 1 | | 하단마감재 | 1번(하장바) | SUS 1.5T | 1 | ### 1.3 동적 항목 (5130 분석 필요) | 항목 | 설명 | 레거시 소스 | |------|------|-------------| | 모터 | W0, H0 기반 용량 자동 계산 | 5130 로직 분석 필요 | | 제어기 | 모터 사양에 따라 연동 | 5130 로직 분석 필요 | | 부자재 | 모델/규격별 자동 추가 | 5130 로직 분석 필요 | | 절곡품 수량 | 파라미터 기반 동적 계산 | 5130 로직 분석 필요 | --- ## 2. 분석 대상 ### 2.1 5130 디렉토리 구조 (분석 완료) ``` 5130/ ├── estimate/ # 견적 관련 (핵심 분석 대상) │ ├── README.md # 시스템 문서 │ ├── estimate.php # 스크린 견적 메인 페이지 │ ├── slat.php # 철재 견적 메인 페이지 │ ├── get_screen_amount.php # 스크린 금액 계산 엔진 ⭐ │ ├── get_slat_amount.php # 철재 금액 계산 엔진 ⭐ │ ├── fetch_unitprice.php # 단가 조회 유틸리티 ⭐ │ ├── write_form.php # 견적서 양식 생성 │ └── common/ │ └── calculation.js # 프론트엔드 계산 로직 ├── output/ # 출력/리포트 ├── dbeditor/ # DB 관리 └── [기타 모듈]/ ``` ### 2.2 분석 우선순위 | 순위 | 대상 | 목적 | |------|------|------| | 1 | 견적 생성 로직 | BOM 자동 구성 방식 파악 | | 2 | 모터 계산 로직 | W0/H0 → 모터 용량 공식 | | 3 | 절곡품 계산 로직 | 파라미터 → 수량/단가 공식 | | 4 | 부자재 추가 로직 | 모델별 자동 추가 규칙 | | 5 | 가격 산출 로직 | 최종 견적 금액 계산 | --- ## 3. 작업 계획 ### Phase 0: 5130 탐색 및 구조 파악 ✅ - [x] 5130/ 디렉토리 구조 분석 - [x] 견적 관련 파일 식별 (estimate/, output/) - [x] 주요 함수/클래스 목록화 (아래 섹션 4.3 참조) ### Phase 1: 견적 생성 로직 분석 🔄 - [x] 모델 선택 → BOM 구성 흐름 파악 - [x] 동적 항목 추가 조건 분석 (체크박스 기반) - [x] DB 조회 패턴 파악 (BDmodels, price_* 테이블) - [ ] 세부 계산 로직 문서화 ### Phase 2: 계산 공식 추출 ✅ - [x] 모터 용량 계산 공식 (`calculateMotorSpec` 분석 완료) - [x] 절곡품 수량/단가 계산 공식 (섹션 4.12 참조) - [x] 부자재 자동 추가 규칙 (섹션 4.13 참조) ### Phase 3: SAM 설계 ✅ - [x] 기존 견적 시스템 분석 (QuoteCalculationService, FormulaEvaluatorService) - [x] 5130 로직 통합 설계 → 하이브리드 접근 결정 (섹션 10.1) - [x] API 엔드포인트 확장 설계 → 기존 엔드포인트 활용 - [x] DB 스키마 변경 필요 여부 → kd_price_tables 신규 테이블 (옵션) ### Phase 4: SAM 구현 🔄 - [x] 4.1 KyungdongFormulaHandler 클래스 생성 (경동 전용) ✅ - [x] 4.2 FormulaEvaluatorService 확장 (tenant 분기) ✅ - [x] 4.3 모터 용량 계산 구현 ✅ - [x] 4.4 kd_price_tables 마이그레이션 + Model 생성 ✅ - [x] 4.5 price_* 테이블 조회 로직 구현 (KdPriceTable 연동) ✅ - [x] 4.6 단가 데이터 마이그레이션 (Seeder) ✅ - [ ] 4.7 절곡품 계산 구현 (10종) - [ ] 4.8 API 테스트 및 검증 ### Phase 5: 검증 - [ ] 레거시 vs SAM 결과 비교 - [ ] 사용자 테스트 - [ ] 배포 --- ## 4. 레거시 분석 기록 ### 4.1 분석된 테이블 | 테이블 | 용도 | 분석 상태 | |--------|------|-----------| | models | 모델 마스터 | ✅ 완료 | | parts | 대분류 부품 | ✅ 완료 | | parts_sub | 세부 절곡품 | ✅ 완료 | | BDmodels | BOM + 단가 JSON | ✅ 완료 | | price_motor | 모터 단가 | ✅ 완료 | | price_shaft | 샤프트 계산 참조 | ✅ 완료 | | price_pipe | 파이프 계산 참조 | ✅ 완료 | | price_raw_materials | 원자재 단가 | ✅ 완료 | ### 4.2 분석된 5130 코드 | 파일/모듈 | 내용 | 분석 상태 | |-----------|------|-----------| | estimate/README.md | 시스템 문서 | ✅ 완료 | | estimate/estimate.php | 스크린 견적 메인 | ✅ 완료 | | estimate/get_screen_amount.php | 스크린 금액 계산 엔진 | ✅ 완료 | | estimate/get_slat_amount.php | 철재 금액 계산 엔진 | ✅ 완료 | | estimate/fetch_unitprice.php | 단가 조회 유틸리티 | ✅ 완료 | | estimate/common/calculation.js | 프론트엔드 계산 | ✅ 완료 | ### 4.3 핵심 함수 목록 #### 금액 계산 함수 | 함수명 | 파일 | 역할 | |--------|------|------| | `calculateScreenAmount()` | get_screen_amount.php | 스크린 견적 총액 계산 | | `calculateSlatAmount()` | get_slat_amount.php | 철재 견적 총액 계산 | | `calculateGuideRailPrice()` | get_screen_amount.php | 가이드레일 단가 계산 | | `calculateShaftPrice()` | get_screen_amount.php | 감기샤프트 단가 계산 | #### 단가 조회 함수 (fetch_unitprice.php) | 함수명 | 역할 | 참조 테이블 | |--------|------|-------------| | `searchBracketSize()` | 모터 중량 → 브라켓 크기 | - | | `calculateMotorSpec()` | 중량/인치 → 모터 용량 (150K~1000K) | - | | `getPriceForMotor()` | 모터 용량 → 단가 조회 | price_motor | | `calculateControllerSpec()` | 제어기 타입 → 단가 조회 | price_motor | | `calculatePipe()` | 파이프 규격 → 단가 조회 | price_pipe | | `calculateShaft()` | 샤프트 규격 → 단가 조회 | price_shaft | | `calculateAngle()` | 앵글 규격 → 단가 조회 | price_angle | | `slatPrice()` | 원자재 → 단가 조회 | price_raw_materials | ### 4.4 모터 용량 계산 공식 (추출 완료) ``` 모터 용량 = f(제품타입, 중량, 브라켓인치) ┌──────────┬─────────┬──────────────────────────────────┐ │ 제품타입 │ 인치 │ 중량 범위 → 용량 │ ├──────────┼─────────┼──────────────────────────────────┤ │ 스크린 │ 4" │ ≤150kg → 150K │ │ │ │ 150~300kg → 300K │ │ │ │ 300~400kg → 400K │ ├──────────┼─────────┼──────────────────────────────────┤ │ 스크린 │ 5" │ ≤123kg → 150K │ │ │ │ 123~246kg → 300K │ │ │ │ 246~327kg → 400K │ │ │ │ 327~500kg → 500K │ │ │ │ 500~600kg → 600K │ ├──────────┼─────────┼──────────────────────────────────┤ │ 스크린 │ 6" │ ≤104kg → 150K │ │ │ │ 104~208kg → 300K │ │ │ │ 208~300kg → 400K │ │ │ │ 300~424kg → 500K │ │ │ │ 424~508kg → 600K │ ├──────────┼─────────┼──────────────────────────────────┤ │ 철재 │ 4" │ ≤300kg → 300K │ │ │ │ 300~400kg → 400K │ ├──────────┼─────────┼──────────────────────────────────┤ │ 철재 │ 5" │ ≤246kg → 300K │ │ │ │ 246~327kg → 400K │ │ │ │ 327~500kg → 500K │ │ │ │ 500~600kg → 600K │ ├──────────┼─────────┼──────────────────────────────────┤ │ 철재 │ 6" │ ≤208kg → 300K │ │ │ │ 208~277kg → 400K │ │ │ │ 277~424kg → 500K │ │ │ │ 424~508kg → 600K │ │ │ │ 508~800kg → 800K │ │ │ │ 800~1000kg → 1000K │ ├──────────┼─────────┼──────────────────────────────────┤ │ 철재 │ 8" │ ≤324kg → 500K │ │ │ │ 324~388kg → 600K │ │ │ │ 388~611kg → 800K │ │ │ │ 611~1000kg → 1000K │ └──────────┴─────────┴──────────────────────────────────┘ ``` ### 4.5 견적 항목 구성 (스크린) 체크박스 옵션에 따라 동적으로 항목 포함/제외: | 체크박스 | 포함 항목 | |---------|----------| | `slatcheck` (주자재) | 주자재(스크린), 환봉 | | `steel` (절곡) | 케이스, 가이드레일, 하장바, L바, 보강평철, 연기차단재(케이스/레일), 케이스 마구리 | | `motor` (모터) | 모터 (경동견적가포함일 때만) | | `partscheck` (부자재) | 감기샤프트, 무게평철12T, 각파이프, 앵글 | | `warranty` (보증) | (금액 조정에 영향) | ### 4.6 가격 산출 흐름 ``` 1. 검사비 (고정) 2. 주자재 = 원자재단가 × 면적(W×H/1000000) 3. 모터 = 용량별 단가표 조회 4. 제어기 = 매립/노출/뒷박스 × 수량 5. 케이스 = 규격별 단가 × 길이(m) 6. 가이드레일 = 모델|마감재|규격별 단가 × 길이(m) × 2 7. 하장바/L바 = 단가 × 길이(m) 8. 샤프트 = 규격별(3",4",5") × 길이별(3000~8200) 단가표 9. 파이프 = 두께(1.4) × 길이(3000/6000) 단가표 10. 앵글 = 타입(3T/4T) × 두께(2.5) × 수량 ``` --- ## 5. 기술적 고려사항 ### 5.1 SAM 아키텍처 준수 ```php // Service-First 패턴 class QuoteBomService extends Service { public function calculateDynamicBom(int $modelId, array $parameters): array { // 1. 정적 BOM 조회 (items.bom) // 2. 파라미터 기반 동적 항목 계산 // 3. 모터/제어기 자동 추가 // 4. 부자재 자동 추가 // 5. 단가 계산 } } ``` ### 5.2 API 설계 (예상) ``` POST /api/v1/quotes/calculate-bom Request: { "model_id": 13147, // FG-KSS01-벽면형-SUS "parameters": { "W0": 3000, // 폭 "H0": 2000, // 높이 "installation_type": "벽면형", "power_source": "220V" } } Response: { "static_bom": [...], // 기존 items.bom "dynamic_items": [...], // 모터, 제어기, 부자재 "calculated_values": { "motor_capacity": "150K", "total_area": 6.0, "estimated_weight": 45.5 }, "pricing": {...} } ``` --- ## 6. 관련 문서 - [kd-items-migration-plan.md](./kd-items-migration-plan.md) - 정적 품목/단가 이관 (완료) - [kd-orders-migration-plan.md](./kd-orders-migration-plan.md) - 입고/재고/주문 이관 - SAM API Rules - api/CLAUDE.md --- ## 7. 변경 이력 | 날짜 | 항목 | 변경 내용 | |------|------|----------| | 2026-01-28 | 문서 생성 | 초기 계획 수립, 레거시 DB 분석 결과 반영 | | 2026-01-28 | Phase 0 완료 | 5130 estimate 디렉토리 분석 완료, 핵심 함수 목록화 | | 2026-01-28 | Phase 1-2 진행 | 모터 용량 계산 공식 추출, 가격 산출 흐름 문서화 | | 2026-01-28 | Phase 2 계속 | 브라켓 크기 공식, BDmodels 구조, SAM 매핑 전략 추가 | | 2026-01-28 | Phase 3 시작 | 기존 SAM 견적 시스템 분석, 5130 통합 설계 문서화 | | 2026-01-29 | 설계 결정 | 체크박스 방식 → "전체계산 → 개별제거" 방식으로 변경 | | 2026-01-29 | Phase 2 완료 | 절곡품/부자재/주자재 계산 공식 추출 완료 (4.12~4.15) | | 2026-01-29 | Phase 4 설계 | 하이브리드 접근 결정 (범용 + 경동전용 Handler), 구현 계획 수립 | | 2026-01-29 | Phase 4.1 완료 | KyungdongFormulaHandler 기본 구조 생성 (모터/브라켓 계산) | | 2026-01-29 | Phase 4.2 완료 | FormulaEvaluatorService 확장 (tenant_id=287 분기 처리) | | 2026-01-29 | Phase 4.3~4.5 완료 | kd_price_tables 마이그레이션, KdPriceTable 모델, Seeder, 단가 조회 연동 | | 2026-01-29 | Phase 4.6 완료 | 부자재 계산 (3종: 샤프트, 파이프, 앵글) 구현 | --- ### 4.7 브라켓 크기 결정 공식 (추출 완료) ``` searchBracketSize(중량, 인치) → 브라켓크기 ┌──────────┬──────────────────────────────────┐ │ 모터용량 │ 브라켓 사이즈 │ ├──────────┼──────────────────────────────────┤ │ 300K │ 530*320 │ │ 400K │ 530*320 │ │ 500K │ 600*350 │ │ 600K │ 600*350 │ │ 800K │ 690*390 │ │ 1000K │ 690*390 │ └──────────┴──────────────────────────────────┘ [중량만으로 판단 (인치 없을 때)] - ≤300kg → 300K - ≤400kg → 400K - ≤500kg → 500K - ≤600kg → 600K - ≤800kg → 800K - ≤1000kg → 1000K ``` ### 4.8 견적 입력 컬럼 매핑 (스크린) | 컬럼 | 필드명 | 설명 | |------|--------|------| | col4 | 모델코드 | KSS01, KWS01 등 | | col5 | 제목 | 현장명 | | col6 | 가이드레일타입 | 벽면형, 측면형, 혼합형 | | col7 | 마감재질 | SUS, EGI 등 | | col10 | 폭(W) | mm 단위 | | col11 | 높이(H) | mm 단위 | | col14 | 수량 | 대수 | | col15~17 | 제어기 | 매립형/노출형/뒷박스 수량 | | col18_brand | 모터업체 | 경동(견적가포함) 등 | | col19 | 모터용량 | 150K~1000K | | col22 | 앵글사이즈 | 모터받침용 | | col23 | 가이드레일길이 | mm 단위 | | col31~35 | 연기차단재 | 각 규격별 수량 | | col36 | 케이스규격 | 또는 col36_custom | | col37 | 케이스길이 | mm 단위 | | col45 | 마구리규격 | | | col48 | 하장바길이 | mm 단위 | | col49~50 | 하장바 | 수량 관련 | | col51 | L바길이 | mm 단위 | | col52~53 | L바 | 수량 관련 | | col54 | 보강평철길이 | mm 단위 | | col55~56 | 보강평철 | 수량 관련 | | col57 | 무게평철 | 수량 | | col59~65 | 샤프트 | 규격별(3"/4"/5") × 길이별 | | col68~69 | 각파이프 | 3000/6000 수량 | | col70 | 환봉 | 수량 | | col71 | 앵글 | 수량 | ### 4.9 단가 테이블 JSON 구조 **price_shaft (샤프트)** - col4: 사이즈 (3, 4, 5인치) - col10: 길이 (m 단위, 예: 3.0 = 3000mm) - col19: 판매가 **price_pipe (각파이프)** - col2: 길이 (3000, 6000) - col4: 두께 (1.4) - col8: 판매가 **price_angle (앵글)** - col2: 타입 (스크린용, 철재용) - col3: 브라켓크기 (530*320, 600*350, 690*390) - col4: 앵글타입 (앵글3T, 앵글4T) - col10: 두께 (2.5) - col19: 판매가 **price_motor (모터/제어기)** - col2: 용량/타입 (150K, 300K, 매립형, 노출형, 뒷박스) - col13: 판매가 **price_raw_materials (원자재)** - col2: 원자재명 (스크린, 슬랫, 조인트바 등) - col13: 판매가 ### 4.10 BDmodels 테이블 구조 (절곡품 단가) **컬럼 구조:** | 컬럼 | 설명 | 예시 | |------|------|------| | model_name | 모델코드 | KSS01, KWS01, KDSS01 | | seconditem | 부품분류 | 케이스, 가이드레일, 하단마감재, L-BAR | | spec | 규격 | 120*70, 650*550 | | finishing_type | 마감재질 | SUS, EGI | | unitprice | 단가 | 원/m 또는 원/개 | **seconditem 종류:** - 케이스: 케이스박스 (규격별 단가) - 가이드레일: 레일 (모델+마감+규격별 단가) - 하단마감재: 하장바 (모델+마감별 단가) - L-BAR: L바 (모델별 단가) - 보강평철: 평철 (공통 단가) - 마구리: 케이스 마감재 (규격별 단가) - 케이스용 연기차단재: (공통 단가) - 가이드레일용 연기차단재: (공통 단가) **단가 조회 키 패턴:** ```php // 가이드레일: 모델코드|마감재질|규격 $key = "KSS01|SUS|120*70"; $price = $guidrailPrices[$key]; // 케이스: 규격만 $price = $shutterBoxprices["650*550"]; // 하단마감재: 모델코드 + 마감재질 매칭 if ($prodcode == $modelCode && $finishing == $load_finishingType) { $price = $bottomBarPrices; } ``` ### 4.11 SAM 매핑 전략 **현재 SAM items 테이블의 BOM 구조:** ```json // items.bom (JSON) [ {"child_item_id": 123, "quantity": 1}, // 가이드레일 {"child_item_id": 456, "quantity": 1}, // 하단마감재 {"child_item_id": 789, "quantity": 1} // L-BAR ] ``` **문제점:** - 정적 BOM만 저장 (동적 계산 불가) - 모터/제어기/부자재 누락 - 파라미터(W, H, 수량) 기반 수량 계산 없음 **해결 방안 (Phase 3 설계 시 반영):** ```php // 1. 정적 BOM + 동적 계산 분리 class QuoteBomService { public function calculate(int $modelId, array $params): array { // 1. 정적 BOM 조회 (items.bom) $staticBom = $this->getStaticBom($modelId); // 2. 동적 항목 계산 $dynamicItems = $this->calculateDynamicItems($params); // 3. 단가 적용 return $this->applyPricing($staticBom, $dynamicItems, $params); } private function calculateDynamicItems(array $params): array { $items = []; // 모터 (체크박스 옵션) if ($params['motor_check']) { $motorCapacity = $this->calculateMotorCapacity( $params['weight'], $params['bracket_inch'] ); $items['motor'] = $this->getMotorPrice($motorCapacity); } // 제어기 $items['controller'] = $this->calculateController($params); // 샤프트/파이프/앵글 (부자재) if ($params['parts_check']) { $items['shaft'] = $this->calculateShaft($params); $items['pipe'] = $this->calculatePipe($params); $items['angle'] = $this->calculateAngle($params); } return $items; } } ``` ### 4.12 절곡품 계산 공식 (steel 체크박스) **절곡품 = BDmodels 테이블 조회 (seconditem별 단가)** | 품목 | 조회 키 | 계산식 | 비고 | |------|--------|--------|------| | 케이스 | `seconditem='케이스', spec=규격` | 단가/1000 × 길이(mm) × 수량 | 기본단가 500*380 기준 면적비 계산 | | 케이스용 연기차단재 | `seconditem='케이스용 연기차단재'` | 단가 × 길이(m) × 수량 | | | 케이스 마구리 | `seconditem='마구리', spec=규격` | 단가 × 수량 | col45 규격 | | 가이드레일 | `model_name\|finishing_type\|spec` | 단가 × 길이(m) × 수량 | 벽면/측면 ×2, 혼합 각1 | | 레일용 연기차단재 | `seconditem='가이드레일용 연기차단재'` | 단가 × 길이(m) × 2 × 수량 | | | 하장바 | `model_name, seconditem='하단마감재', finishing_type` | 단가 × 길이(m) × 수량 | col48 길이 | | L바 | `model_name, seconditem='L-BAR'` | 단가 × 길이(m) × 수량 | col51 길이 | | 보강평철 | `seconditem='보강평철'` | 단가 × 길이(m) × 수량 | col54 길이 | | 무게평철12T | 고정 12,000원 | 12,000 × col57(수량) | | | 환봉 | 고정 2,000원 | 2,000 × col70(수량) | | **가이드레일 타입별 처리:** ``` 벽면형(120*70) → baseKey|120*70 × 2개 측면형(120*100) → baseKey|120*100 × 2개 혼합형(120*70+120*100) → baseKey|120*70 + baseKey|120*100 (각 1개) ``` ### 4.13 부자재 계산 공식 (partscheck 체크박스) **부자재 = price_* 테이블 조회 (JSON itemList)** | 품목 | 테이블 | 조회 조건 | 계산식 | |------|--------|----------|--------| | 감기샤프트 | price_shaft | col4=사이즈, col10=길이(m) | col19(판매가) × 수량 | | 각파이프 | price_pipe | col4=두께(1.4), col2=길이 | col8(판매가) × 수량 | | 앵글 | price_angle | col2='앵글3T', col10=두께(2.5) | col19(판매가) × 수량 | **샤프트 규격별 컬럼 매핑:** ``` col59 → 3" × 300mm (사실상 미사용) col60 → 4" × 3000mm col61 → 4" × 4500mm col62 → 4" × 6000mm col63 → 5" × 6000mm col64 → 5" × 7000mm col65 → 5" × 8200mm ``` **각파이프 컬럼 매핑:** ``` col68 → 1.4T × 3000mm 수량 col69 → 1.4T × 6000mm 수량 ``` ### 4.14 모터 받침용 앵글 (특수 조건) ``` 조건: col22(앵글사이즈) 값이 있고, (slatcheck만 체크 AND motor/steel/partscheck 모두 미체크) 가 아닐 때 계산: calculateAngle(수량, itemList, '스크린용') × 수량 × 4 ``` ### 4.15 주자재 계산 공식 (slatcheck 체크박스) ``` 스크린 가격 = 원자재단가 × 면적(㎡) 면적 = W × (H + 550) / 1,000,000 ↑ 550mm는 스크린 기본 여유분 (350 + 200 추가) 원자재단가: price_raw_materials 테이블에서 col2='실리카' 조회 → col13(판매가) ``` --- ## 8. 다음 작업 (TODO) ### 즉시 필요 1. ~~**브라켓 크기 결정 공식**~~ ✅ 완료 2. ~~**BDmodels 조회 패턴**~~ ✅ 완료 3. **절곡품 수량 계산 공식** - parts_sub 기반 동적 수량 결정 로직 (선택적) ### SAM 구현 시 고려사항 1. **전체 계산 → 개별 제거 방식**: 5130의 체크박스 방식 대신, 전체 BOM 계산 후 불필요 항목 제거 2. **단가 테이블 통합**: price_motor, price_shaft, price_pipe 등 → SAM prices 테이블과 연동 3. **BOM 동적 생성 API**: 파라미터(W0, H0) 입력 → 전체 견적 항목 반환 → UI에서 개별 제거 --- ## 9. Phase 3: SAM 설계 ### 9.1 기존 SAM 견적 시스템 분석 #### 현재 구조 ``` api/app/Services/Quote/ ├── QuoteCalculationService.php # 견적 계산 메인 서비스 ├── FormulaEvaluatorService.php # 수식 평가 엔진 ├── QuoteService.php # 견적 CRUD └── Requests/ └── QuoteBomCalculateRequest.php # BOM 계산 입력값 검증 ``` #### QuoteCalculationService 핵심 메서드 | 메서드 | 역할 | 입력 | 출력 | |--------|------|------|------| | `calculate()` | 일반 견적 계산 | inputs, productCategory, productId | items, costs, errors | | `calculateBom()` | BOM 기반 견적 | finishedGoodsCode, inputs, debug | finished_goods, items, grand_total | | `calculateBomBulk()` | 다건 BOM 계산 | inputItems[], debug | summary, items[] | | `preview()` | 견적 미리보기 | inputs, productCategory, productId | (calculate와 동일) | | `recalculate()` | 기존 견적 재계산 | Quote | (calculate와 동일) | #### FormulaEvaluatorService 10단계 BOM 계산 ``` Step 1: 입력값 수집 (W0, H0, QTY, PC, GT, MP, CT, WS, INSP) Step 2: 완제품 선택 (finishedGoodsCode → Item 조회) Step 3: 변수 계산 (수식 기반 중간값) Step 4: BOM 전개 (items.bom JSON → 구성품 목록) Step 5: 단가 출처 결정 (prices 테이블 or 수식 계산) Step 6: 수량 수식 평가 (formula → 실제 수량) Step 7: 단가 계산 (unit_price × quantity) Step 8: 공정별 그룹화 (category 기준) Step 9: 소계 계산 (그룹별 합계) Step 10: 최종 합계 (grand_total) ``` #### 현재 입력 파라미터 (QuoteBomCalculateRequest) | 파라미터 | 설명 | 필수 | |---------|------|------| | W0 | 개구부 폭(mm) | ✅ | | H0 | 개구부 높이(mm) | ✅ | | QTY | 수량 | ✅ | | PC | 제품코드 | ✅ | | GT | 가이드타입 | ❌ | | MP | 모터파워 | ❌ | | CT | 제어타입 | ❌ | | WS | 와이어사이드 | ❌ | | INSP | 검사비 | ❌ | ### 9.2 5130 로직 통합 설계 #### 5130 vs SAM 비교 | 항목 | 5130 | SAM (현재) | SAM (목표) | |------|------|-----------|-----------| | 모터 계산 | `calculateMotorSpec()` | 없음 (수동 입력) | 자동 계산 | | 브라켓 크기 | `searchBracketSize()` | 없음 | 자동 계산 | | 항목 선택 | 체크박스 (사전 선택) | 없음 | **전체계산 → 개별제거** | | 절곡품 단가 | BDmodels 테이블 | prices 테이블 | prices + 범위 조회 | | 부자재 | 파라미터 기반 동적 추가 | 정적 BOM | 동적 계산 | #### 항목 선택 방식 변경 (중요) **5130 방식 (체크박스):** ``` ☑ 주자재 ☑ 절곡 ☐ 모터 ☑ 부자재 → 계산 ``` **SAM 방식 (전체계산 → 개별제거):** ``` 전체 BOM 계산 → 견적 라인 표시 → 불필요 항목 제거/수량 조정 ┌─────────────────────────────────────────────────┐ │ 품목명 │ 수량 │ 단가 │ 금액 │ ⊘ │ ├─────────────────────────────────────────────────┤ │ 스크린원단 │ 6㎡ │ 15,000 │ 90,000 │ │ │ 가이드레일 │ 4m │ 12,000 │ 48,000 │ │ │ 모터 300K │ 1 │ 85,000 │ 85,000 │ ✕ │ ← 제거 가능 │ 제어기 매립형 │ 1 │ 25,000 │ 25,000 │ ✕ │ ← 제거 가능 │ 감기샤프트 4" │ 1 │ 35,000 │ 35,000 │ │ └─────────────────────────────────────────────────┘ ``` **장점:** - 더 직관적인 UX (전체를 보고 판단) - 개별 품목 단위 제어 가능 (카테고리 단위보다 유연) - SAM 기존 견적 라인 구조와 호환 #### 확장 필요 항목 **1. 입력 파라미터 추가** ```php // QuoteBomCalculateRequest 확장 'bracket_inch' => 'nullable|string|in:4,5,6,8', // 브라켓 인치 'estimated_weight' => 'nullable|numeric', // 예상 중량 'guide_rail_type' => 'nullable|string', // 가이드레일 타입 'finishing_type' => 'nullable|string', // 마감재질 // 체크박스 옵션은 제거 (전체 계산 후 개별 제거 방식) ``` **2. FormulaEvaluatorService 확장** ```php // 5130 계산 함수 추가 private function calculateMotorCapacity(string $productType, float $weight, string $bracketInch): string { // 4.4 모터 용량 계산 공식 구현 } private function calculateBracketSize(float $weight, ?string $bracketInch = null): string { // 4.7 브라켓 크기 결정 공식 구현 } private function calculateDynamicItems(array $params): array { // 체크박스 옵션에 따른 동적 항목 생성 } ``` **3. 단가 조회 확장** ```php // 범위 기반 단가 조회 (price_shaft, price_pipe 등) private function getPriceByRange(string $priceTable, array $conditions): ?float { // JSON 데이터에서 범위 조건으로 단가 조회 } ``` ### 9.3 API 엔드포인트 설계 #### 기존 엔드포인트 (유지) ``` POST /api/v1/quotes/calculate-bom POST /api/v1/quotes/calculate-bom-bulk GET /api/v1/quotes/input-schema ``` #### 신규 엔드포인트 (추가) ``` POST /api/v1/quotes/calculate-motor - 입력: weight, bracket_inch, product_type - 출력: motor_capacity, bracket_size POST /api/v1/quotes/calculate-dynamic-items - 입력: model_id, W0, H0, options (체크박스) - 출력: dynamic_items[] (모터, 제어기, 부자재) GET /api/v1/quotes/price-tables/{table} - 테이블: motor, shaft, pipe, angle, raw_materials - 출력: 단가표 데이터 (프론트엔드 참조용) ``` ### 9.4 DB 스키마 변경 (최소화) **변경 불필요:** - items, prices 테이블: 기존 구조 활용 - quote_formulas: 기존 수식 시스템 활용 **검토 필요:** - `items.metadata` JSON 필드에 5130 특수 정보 저장 가능 - `quote_formula_ranges`: 범위 기반 단가 조회에 활용 **대안:** - 5130 price_* 테이블 데이터를 `quote_formula_ranges`로 마이그레이션 - 또는 별도 `kd_price_tables` 테이블 생성 (tenant_id=287 전용) ### 9.5 구현 우선순위 | 순위 | 항목 | 난이도 | 의존성 | |------|------|--------|--------| | 1 | 모터 용량 계산 함수 | 낮음 | 없음 | | 2 | 브라켓 크기 계산 함수 | 낮음 | 없음 | | 3 | 체크박스 옵션 → 동적 항목 | 중간 | 1, 2 | | 4 | 범위 기반 단가 조회 | 중간 | 없음 | | 5 | API 엔드포인트 추가 | 낮음 | 1-4 | | 6 | 프론트엔드 연동 | 중간 | 5 | --- ## 10. Phase 4: 구현 상세 계획 ### 10.1 아키텍처 결정: 하이브리드 접근 **배경:** - 5130 경동 로직은 3차원 조건, 외부 테이블 조회 등 복잡 - 현재 SAM quote_formulas 시스템으로 표현 불가 - 범용으로 만들면 다른 테넌트에 불필요한 복잡성 **결정:** ``` [범용 레이어] - quote_formulas 테이블 ├── 단순 계산, 1차원 범위, 단순 매핑 └── 기본 테넌트들이 사용 [테넌트 전용 레이어] - 전용 Handler 클래스 ├── tenant_id = 287 (경동기업) │ └── KyungdongFormulaHandler.php └── tenant_id = 기타 → 기본 수식 시스템 ``` ### 10.2 파일 구조 ``` api/app/Services/Quote/ ├── QuoteCalculationService.php # 기존 (수정) ├── FormulaEvaluatorService.php # 기존 (확장) └── Handlers/ └── KyungdongFormulaHandler.php # 신규 (경동 전용) api/database/seeders/Kyungdong/ ├── KyungdongItemSeeder.php # 기존 (품목/단가) └── KyungdongPriceTableSeeder.php # 신규 (price_* 데이터) ``` ### 10.3 KyungdongFormulaHandler 설계 ```php namespace App\Services\Quote\Handlers; class KyungdongFormulaHandler { // 모터 용량 계산 (3차원 조건) public function calculateMotorCapacity( string $productType, // screen, steel float $weight, string $bracketInch // 4, 5, 6, 8 ): string; // 150K, 300K, ... // 브라켓 크기 결정 public function calculateBracketSize( float $weight, ?string $bracketInch = null ): string; // 530*320, 600*350, 690*390 // 절곡품 계산 (10종) public function calculateSteelItems(array $params): array; // 부자재 계산 (3종) public function calculatePartItems(array $params): array; // 주자재 계산 (스크린) public function calculateScreenPrice( float $width, float $height ): float; // BDmodels 단가 조회 private function getBDModelPrice( string $modelName, string $secondItem, ?string $finishingType = null, ?string $spec = null ): float; // price_* 테이블 조회 private function getPriceFromTable( string $tableName, array $conditions ): float; } ``` ### 10.4 FormulaEvaluatorService 확장 ```php // FormulaEvaluatorService.php public function calculateBomWithDebug( string $finishedGoodsCode, array $inputs, int $tenantId ): array { // 테넌트별 분기 if ($tenantId === 287) { return $this->calculateKyungdongBom($finishedGoodsCode, $inputs); } // 기본 로직 (기존 코드) return $this->calculateDefaultBom($finishedGoodsCode, $inputs); } private function calculateKyungdongBom( string $finishedGoodsCode, array $inputs ): array { $handler = new KyungdongFormulaHandler(); // 1. 기본 BOM 전개 (items.bom) $staticBom = $this->getStaticBom($finishedGoodsCode); // 2. 동적 항목 계산 $dynamicItems = $handler->calculateDynamicItems($inputs); // 3. 전체 항목 병합 return $this->mergeAndCalculatePrices($staticBom, $dynamicItems, $inputs); } ``` ### 10.5 단가 데이터 마이그레이션 **옵션 A: 기존 prices 테이블 활용** - items에 품목 추가, prices에 단가 추가 - 장점: 기존 구조 활용 - 단점: 복잡한 조회 조건 표현 어려움 **옵션 B: 전용 테이블 생성 (권장)** ```sql -- 경동 전용 단가 테이블 CREATE TABLE kd_price_tables ( id BIGINT PRIMARY KEY, tenant_id BIGINT DEFAULT 287, table_name VARCHAR(50), -- motor, shaft, pipe, angle, bdmodels item_data JSON, -- 원본 JSON 데이터 created_at TIMESTAMP, updated_at TIMESTAMP ); ``` ### 10.6 구현 순서 | 순서 | 작업 | 상태 | |------|------|------| | 1 | KyungdongFormulaHandler 기본 구조 | ✅ 완료 | | 2 | 모터/브라켓 계산 메서드 | ✅ 완료 | | 3 | kd_price_tables 마이그레이션 | ✅ 완료 | | 4 | KdPriceTable 모델 생성 | ✅ 완료 | | 5 | KdPriceTableSeeder 생성 | ✅ 완료 | | 6 | 단가 조회 메서드 (KdPriceTable 연동) | ✅ 완료 | | 7 | 부자재 계산 (3종) | ✅ 완료 | | 8 | 절곡품 계산 (10종) | ⏳ 대기 | | 9 | FormulaEvaluatorService 연동 | ✅ 완료 | | 10 | API 테스트 및 검증 | ⏳ 대기 | | 8 | API 테스트 | | --- *이 문서는 5130 분석 진행에 따라 지속 업데이트됩니다.*