Files
sam-docs/features/quotes/README.md
권혁성 2bbf220dc8 docs:견적관리 분석 문서 및 INDEX 업데이트
- INDEX.md 업데이트
- 견적관리 URL 마이그레이션 계획 수정
- API 분석 리포트, tenant-id 준수 계획 추가
- 견적관리 기능 문서 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 20:37:04 +09:00

455 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 견적 시스템 분석 문서
> **목적**: 견적 시스템의 비즈니스 로직과 데이터 흐름을 이해하고 검증하기 위한 문서
## 목차
1. [개요](#1-개요)
2. [데이터베이스 구조](#2-데이터베이스-구조)
3. [견적 생성 흐름](#3-견적-생성-흐름)
4. [BOM 계산 로직 (10단계)](#4-bom-계산-로직-10단계)
5. [경동기업 전용 로직](#5-경동기업-전용-로직)
6. [상태 관리](#6-상태-관리)
7. [금액 계산 방식](#7-금액-계산-방식)
8. [관련 파일 목록](#8-관련-파일-목록)
---
## 1. 개요
### 1.1 견적 유형
| 유형 | 코드 | 설명 |
|------|------|------|
| 제조 견적 | `manufacturing` | 스크린/철재 제품 제조 견적 |
| 시공 견적 | `construction` | 현장설명회 기반 시공 견적 |
### 1.2 제품 카테고리
| 카테고리 | 코드 | 설명 |
|----------|------|------|
| 스크린 | `SCREEN` | 방화 스크린 제품 |
| 철재 | `STEEL` | 철재 제품 |
### 1.3 핵심 서비스 클래스
```
QuoteService ← 견적 CRUD, 상태 관리, 수주 전환
├── QuoteNumberService ← 견적번호 생성 (KD-SC-YYMMDD-NN)
├── QuoteCalculationService ← 자동산출 실행, BOM 계산 호출
└── FormulaEvaluatorService ← 수식 평가, 10단계 BOM 계산
└── KyungdongFormulaHandler ← 경동기업(tenant_id=287) 전용 계산
```
---
## 2. 데이터베이스 구조
### 2.1 테이블 관계도
```
┌─────────────────┐
│ quotes │ ← 견적 마스터
├─────────────────┤
│ id │
│ tenant_id │──→ tenants
│ quote_number │
│ quote_type │ ← manufacturing | construction
│ status │ ← pending→draft→finalized→converted
│ client_id │──→ clients
│ item_id │──→ items (완제품)
│ site_briefing_id│──→ site_briefings (시공 견적용)
│ order_id │──→ orders (수주 전환 후)
│ calculation_inputs (JSON) │ ← 자동산출 입력값 + BOM 결과
│ options (JSON) │ ← 세부산출, 비용항목, 할인 정보
└────────┬────────┘
│ 1:N
┌─────────────────┐
│ quote_items │ ← 견적 품목 상세
├─────────────────┤
│ id │
│ quote_id │──→ quotes
│ tenant_id │
│ item_id │──→ items
│ item_code │
│ item_name │
│ calculated_quantity │
│ unit_price │
│ total_price │
│ formula │ ← 수량 계산 수식
│ formula_category│ ← 카테고리 (material/labor/install)
└─────────────────┘
┌─────────────────┐
│ quote_revisions │ ← 수정 이력
├─────────────────┤
│ quote_id │──→ quotes
│ revision_number │
│ previous_data (JSON) │ ← 수정 전 스냅샷
└─────────────────┘
```
### 2.2 quotes 테이블 주요 필드
| 필드명 | 타입 | 설명 |
|--------|------|------|
| `quote_number` | VARCHAR(50) | 견적번호 (예: KD-SC-251204-01) |
| `quote_type` | ENUM | `manufacturing` / `construction` |
| `status` | ENUM | 상태 (pending→draft→finalized→converted) |
| `product_category` | ENUM | `SCREEN` / `STEEL` |
| `open_size_width` | INT | 개구부 폭 (mm) |
| `open_size_height` | INT | 개구부 높이 (mm) |
| `quantity` | INT | 수량 |
| `material_cost` | DECIMAL | 재료비 합계 |
| `labor_cost` | DECIMAL | 노무비 |
| `install_cost` | DECIMAL | 설치비 |
| `subtotal` | DECIMAL | 소계 |
| `discount_rate` | DECIMAL | 할인율 (%) |
| `total_amount` | DECIMAL | 최종 금액 |
| `calculation_inputs` | JSON | 자동산출 입력값 및 BOM 결과 저장 |
| `options` | JSON | 세부산출항목, 비용항목, 할인정보 |
| `is_final` | BOOLEAN | 최종확정 여부 |
### 2.3 calculation_inputs JSON 구조
```json
{
"items": [
{
"floor": "B1",
"code": "A-01",
"openWidth": 3000,
"openHeight": 2500,
"quantity": 2,
"productCategory": "SCREEN",
"productName": "KD-SCREEN-001",
"guideRailType": "wall",
"motorPower": "single"
}
],
"bomResults": [
{
"index": 0,
"finished_goods_code": "KD-SCREEN-001",
"items": [
{
"item_code": "GUIDE-001",
"item_name": "가이드레일",
"quantity": 2.5,
"unit_price": 50000,
"total_price": 125000,
"is_manual": false
}
],
"grand_total": 1250000
}
]
}
```
---
## 3. 견적 생성 흐름
### 3.1 제조 견적 생성 흐름
```
[프론트엔드 - React]
1. 기본정보 입력 (거래처, 현장, 제품카테고리)
2. 위치별 규격 입력 (층/부호, 개구부 크기, 수량)
3. "견적 산출" 버튼 클릭
[API 호출: POST /api/v1/quotes/bom/calculate-bulk]
4. QuoteCalculationService.calculateBomBulk()
├─→ 경동기업(287)? → KyungdongFormulaHandler
└─→ 기타 테넌트 → 표준 BOM 계산
5. 10단계 계산 결과 반환
6. 프론트엔드에서 결과 표시 (세부산출내역)
7. "저장" 또는 "최종확정" 버튼
[API 호출: POST /api/v1/quotes 또는 PUT /api/v1/quotes/{id}]
8. QuoteService.store() / update()
- quotes 테이블에 마스터 정보 저장
- quote_items 테이블에 품목 상세 저장
- calculation_inputs에 입력값 + BOM 결과 저장
```
### 3.2 시공 견적 생성 흐름 (현장설명회 연계)
```
[현장설명회]
1. 현장설명회 참석완료 상태 변경
2. QuoteService.upsertFromSiteBriefing()
3. 견적 자동 생성 (status: pending)
- 거래처, 현장 정보 복사
- 금액 정보는 비어있음
4. 담당자가 견적 편집 화면에서 상세 입력
5. 저장 시 status: pending → draft 변경
```
---
## 4. BOM 계산 로직 (10단계)
### 4.1 계산 단계 개요
| 단계 | 명칭 | 설명 |
|------|------|------|
| 1 | 입력값수집 | W0, H0, QTY, 옵션값 수집 |
| 2 | 완제품선택 | 완제품 코드로 items 테이블 조회 |
| 3 | 변수계산 | W1, H1, AREA, WEIGHT 등 파생 변수 계산 |
| 4 | BOM전개 | 완제품의 BOM 트리 전개 |
| 5 | 단가출처 | 품목별 단가 조회 (prices 테이블) |
| 6 | 수량계산 | 수량 수식 평가 (변수 치환) |
| 7 | 금액계산 | 수량 × 단가 = 금액 |
| 8 | 카테고리그룹화 | item_category 기준 그룹화 |
| 9 | 소계계산 | 카테고리별 소계 |
| 10 | 최종합계 | 전체 합계 계산 |
### 4.2 변수 계산 (Step 3) 상세
```
기본 변수:
- W0: 개구부 폭 (mm) - 사용자 입력
- H0: 개구부 높이 (mm) - 사용자 입력
- QTY: 수량 - 사용자 입력
파생 변수 (스크린):
- W1 = W0 + 140 (제작 폭, 마진 140mm)
- H1 = H0 + 350 (제작 높이, 마진 350mm)
- M = (W1 × H1) / 1,000,000 (면적, ㎡)
- K = M × 2 + (W0 / 1000) × 14.17 (중량, kg)
파생 변수 (철재):
- W1 = W0 + 110 (마진 110mm)
- H1 = H0 + 350
- M = (W1 × H1) / 1,000,000
- K = M × 25 (철재 중량)
```
### 4.3 수량 수식 예시
BOM의 quantity_formula 필드에 저장된 수식:
```
고정값: "1"
변수참조: "QTY"
계산식: "W1 / 1000" → 가이드레일 길이
"CEIL(H1 / 2000)" → 분할 개수
"M * 1.1" → 면적 기반 수량 (여유 10%)
```
---
## 5. 경동기업 전용 로직
### 5.1 적용 조건
```php
// tenant_id = 287 일 때만 적용
private const KYUNGDONG_TENANT_ID = 287;
if ($tenantId === self::KYUNGDONG_TENANT_ID) {
return $this->calculateKyungdongBom(...);
}
```
### 5.2 KyungdongFormulaHandler 주요 기능
| 기능 | 설명 |
|------|------|
| 모터 용량 계산 | 제품타입 × 인치 × 중량 3차원 조건표 |
| 브라켓 크기 결정 | 중량 기준 브라켓 인치 결정 |
| 절곡품 계산 | 10종 절곡품 (케이스, 가이드레일, 하단바 등) |
| 부자재 계산 | 3종 부자재 (볼트, 너트, 패킹 등) |
### 5.3 경동기업 변수 계산
```
기본 변수:
- W0, H0, QTY: 사용자 입력
- bracket_inch: 브라켓 인치 (5", 6", 7")
- product_type: 제품 타입 (screen/steel)
파생 변수:
- W1 = W0 + 140
- H1 = H0 + 350
- AREA = (W0 × (H0 + 550)) / 1,000,000
- WEIGHT = AREA × 2 + (W0 / 1000) × 14.17 (스크린)
= AREA × 25 (철재)
- MOTOR_CAPACITY: 모터 용량 (조건표 조회)
- BRACKET_SIZE: 브라켓 크기 (조건표 조회)
```
---
## 6. 상태 관리
### 6.1 견적 상태 흐름
```
pending ─────→ draft ─────→ finalized ─────→ converted
(견적대기) (작성중) (최종확정) (수주전환)
│ │ │
│ ▼ │
│ sent ────────────→│
│ (발송됨) │
│ │ │
│ ▼ │
│ approved │
│ (승인됨) │
│ │ │
└──────────────┴──────────────┘
rejected
(거절됨)
```
### 6.2 상태별 가능 작업
| 상태 | 수정 | 삭제 | 확정 | 수주전환 |
|------|------|------|------|----------|
| pending | ✓ | ✓ | - | - |
| draft | ✓ | ✓ | ✓ | - |
| sent | ✓ | ✓ | ✓ | - |
| approved | ✓ | ✓ | ✓ | - |
| finalized | - | - | - | ✓ |
| converted | - | - | - | - |
| rejected | ✓ | ✓ | - | - |
---
## 7. 금액 계산 방식
### 7.1 카테고리 기반 단가 계산
CategoryGroup 모델을 사용하여 품목 카테고리별 단가 계산 방식 결정:
| 카테고리 그룹 | 계산 방식 | 수식 |
|--------------|----------|------|
| area_based | 면적 기반 | 단가 × M (면적) |
| weight_based | 중량 기반 | 단가 × K (중량) |
| quantity_based | 수량 기반 | 단가 × 수량 |
### 7.2 총 금액 계산
```
material_cost = SUM(재료비 카테고리 품목의 total_price)
labor_cost = SUM(노무비 카테고리 품목의 total_price)
install_cost = SUM(설치비 카테고리 품목의 total_price)
subtotal = material_cost + labor_cost + install_cost
discount_amount = subtotal × (discount_rate / 100)
total_amount = subtotal - discount_amount
```
### 7.3 단가 조회 우선순위
1. **prices 테이블** (Price::getSalesPriceByItemCode)
2. **items.attributes.salesPrice** (JSON 필드)
3. **기본값 0**
---
## 8. 관련 파일 목록
### 8.1 백엔드 (API)
```
app/Services/Quote/
├── QuoteService.php ← 견적 CRUD, 상태 관리
├── QuoteCalculationService.php ← BOM 계산 진입점
├── QuoteNumberService.php ← 견적번호 생성
├── QuoteDocumentService.php ← 견적서/거래명세서 PDF
├── FormulaEvaluatorService.php ← 수식 평가, 10단계 계산
└── Handlers/
└── KyungdongFormulaHandler.php ← 경동기업 전용
app/Models/Quote/
├── Quote.php ← 견적 마스터 모델
├── QuoteItem.php ← 견적 품목 모델
├── QuoteRevision.php ← 수정 이력 모델
├── QuoteFormula.php ← 수식 정의 모델
├── QuoteFormulaCategory.php
├── QuoteFormulaRange.php
├── QuoteFormulaMapping.php
└── QuoteFormulaItem.php
database/migrations/
├── 2025_12_04_164542_create_quotes_table.php
└── 2025_12_04_133410_create_quote_formula_tables.php
```
### 8.2 프론트엔드 (React)
```
react/src/components/quotes/
├── types.ts ← 타입 정의 (LocationItem, BomCalculationResult)
├── actions.ts ← API 액션 (calculateBomBulk)
├── QuoteFooterBar.tsx ← 하단 버튼바 (견적서보기, 저장, 최종확정)
├── FormulaViewModal.tsx ← 수식 보기 모달 (개발용)
└── ...
```
---
## 9. 검증 체크리스트
### 9.1 데이터 정합성 검증
- [ ] quotes.total_amount = subtotal - discount_amount
- [ ] quotes.subtotal = material_cost + labor_cost + install_cost
- [ ] quote_items의 합계 = quotes의 비용 합계
- [ ] calculation_inputs.bomResults의 grand_total = 품목 합계
### 9.2 상태 전이 검증
- [ ] pending → draft: 첫 수정 시 자동 전환
- [ ] draft → finalized: 확정 버튼 클릭 + total_amount > 0
- [ ] finalized → converted: 수주 전환 시 + order_id 설정
### 9.3 BOM 계산 검증
- [ ] W1 = W0 + 마진값 (SCREEN: 140, STEEL: 110)
- [ ] H1 = H0 + 350
- [ ] 면적(M) = (W1 × H1) / 1,000,000
- [ ] 중량(K) 계산식 제품타입별 확인
- [ ] 수량 수식의 변수 치환 정확성
- [ ] 단가 조회 우선순위 준수
---
*문서 작성일: 2026-01-29*
*작성자: Claude Code*