# 견적 시스템 분석 문서

> **목적**: 견적 시스템의 비즈니스 로직과 데이터 흐름을 이해하고 검증하기 위한 문서

## 목차

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*