# 견적 시스템 분석 문서 > **목적**: 견적 시스템의 비즈니스 로직과 데이터 흐름을 이해하고 검증하기 위한 문서 ## 목차 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*