- 개발팀 전용 폴더 dev/ 생성 (standards, guides, quickstart, changes, deploys, data, history, dev_plans 이동) - 프론트엔드 전용 폴더 frontend/ 생성 (api/ → frontend/api-specs/) - 기획팀 폴더 requests/ 생성 - plans/ → dev/dev_plans/ 이름 변경 - README.md 신규 (사람용 안내), INDEX.md 재작성 (Claude Code용) - resources.md 신규 (노션 링크용, assets/brochure 이관 예정) - CURRENT_WORKS.md 삭제, TODO.md → dev/ 이동 - 전체 참조 경로 업데이트 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
976 lines
36 KiB
Markdown
976 lines
36 KiB
Markdown
# 경동기업 견적 로직 분석 및 구현 계획
|
||
|
||
> **작성일**: 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 분석 진행에 따라 지속 업데이트됩니다.* |