Files
sam-docs/plans/quote-calculation-api-plan.md
kent 3ba4f87566 docs: 견적 산출 API 개발 계획 문서 추가
- 견적 산출 API 개발 계획 문서 신규 작성 (quote-calculation-api-plan.md)
- MNG FormulaEvaluatorService 핵심 로직 상세 명세 포함
- 10단계 BOM 계산 프로세스, CategoryGroup 단가 계산 방식 등
- React 견적등록 화면 연동을 위한 API 명세 정의
- 페이지네이션 정책 문서 업데이트 (Laravel 기본 응답 구조 반영)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 22:55:09 +09:00

620 lines
26 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

# 견적 산출 API 개발 계획
> **작성일**: 2025-12-30
> **목적**: 견적 산출 API 개발 및 React 견적등록 화면 연동
> **참조 로직**: `mng/app/Services/Quote/FormulaEvaluatorService.php` (코드 복사/재구현)
> **상태**: 🔄 진행중 (Serena ID: quote-calc-api-state)
---
## 📍 현재 진행 상태
| 항목 | 내용 |
|------|------|
| **마지막 완료 작업** | 분석 및 계획 수립 |
| **다음 작업** | Phase 1.1 API 계산 로직 구현 |
| **진행률** | 0/12 (0%) |
| **마지막 업데이트** | 2025-12-30 20:00 |
---
## 0. 로컬 개발 환경
### 도메인 구성
| 서비스 | 도메인 | 설명 |
|--------|--------|------|
| React (프론트엔드) | `http://dev.sam.kr` | 사용자 화면 |
| API (백엔드) | `http://api.sam.kr` | REST API 서버 |
| MNG (운영관리자) | `http://mng.sam.kr` | 관리자 패널 |
### 테스트 대상 테넌트
| 항목 | 값 | 비고 |
|------|-----|------|
| **Tenant ID** | 287 | 프론트_테스트회사 |
| **테스트 User ID** | 33 | 홍킬동 (hhhhhh@example.com) |
---
## 1. 작업 규칙
### 1.0 아키텍처 원칙 (필수)
> **React는 오직 `api.sam.kr` (api 프로젝트)만 호출한다**
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ react/ │ ───► │ api/ │ │ mng/ │
│ dev.sam.kr │ │ api.sam.kr │ │ mng.sam.kr │
│ (프론트엔드) │ │ (REST API) │ │ (관리자패널) │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ ✅ 호출 허용 │ │
└────────────────────┘ │
❌ 절대 호출 금지 ─────────────────────────┘
```
**규칙:**
- React에서 mng API 직접 호출 **절대 금지**
- 필요한 API가 api 프로젝트에 없으면 **api에 새로 개발**
- mng의 모델/로직은 **참조만** (코드 복사 또는 재구현)
- **MNG와 API는 동일한 DB 사용** (데이터 복제 불필요)
### 1.1 작업 진행 정책
> **단위 작업 → 검수 → 승인 → 문서 업데이트 → 커밋** 순서로 진행
```
┌─────────────────────────────────────────────────────────────────┐
│ 📋 작업 흐름 (페이지 단위) │
├─────────────────────────────────────────────────────────────────┤
│ 1⃣ 작업 시작: 대상 기능 구현 │
│ 2⃣ 작업 완료: 코드 수정 완료 후 사용자에게 검수 요청 │
│ 3⃣ 검수: 사용자가 기능 확인 (브라우저 테스트) │
│ 4⃣ [승인] 문서 업데이트: 이 문서의 상태 갱신 │
│ 5⃣ [승인] 커밋: Git 커밋 생성 │
│ 6⃣ 다음 작업으로 이동 │
└─────────────────────────────────────────────────────────────────┘
```
**⚠️ 중요 규칙:**
- 각 단계에서 `[승인]` 표시된 작업은 **사용자 승인 후** 진행
---
## 2. 개요
### 2.1 배경
MNG 시뮬레이터(`mng.sam.kr/quote-formulas/simulator`)의 견적 산출 로직이 정상 작동함을 확인함.
이 로직을 **API 프로젝트에 재구현**하여 React 견적등록 화면(`dev.sam.kr/sales/quote-management/new`)에서 사용.
**현재 상태:**
- MNG: `FormulaEvaluatorService` - DB 기반 정확한 계산 (**참조용**)
- API: `QuoteCalculationService` - 존재하지만 로직 미완성
- React: `handleAutoCalculate()` - 토스트 메시지만 표시 (API 미연동)
**목표:**
```
React 입력 → API 계산 → 결과 반환 → React 표시
```
### 2.2 기준 원칙
```
┌─────────────────────────────────────────────────────────────────┐
│ 🎯 핵심 원칙 │
├─────────────────────────────────────────────────────────────────┤
│ 1. MNG FormulaEvaluatorService 로직을 API에 재구현 │
│ 2. DB 기반 동적 계산 (하드코딩 금지) │
│ 3. 단가는 DB에서 조회 (localStorage 사용 금지) │
│ 4. 카테고리별 계산 방식도 DB에서 (CategoryGroup 활용) │
│ 5. MNG 직접 호출 절대 금지 │
└─────────────────────────────────────────────────────────────────┘
```
### 1.3 변경 승인 정책
| 분류 | 예시 | 승인 |
|------|------|------|
| ✅ 즉시 가능 | React UI 연동, 파라미터 추가 | 불필요 |
| ⚠️ 컨펌 필요 | API 계산 로직 변경, 새 엔드포인트 | **필수** |
| 🔴 금지 | DB 스키마 변경, 기존 API 삭제 | 별도 협의 |
### 1.4 준수 규칙
- `docs/quickstart/quick-start.md` - 빠른 시작 가이드
- `docs/standards/quality-checklist.md` - 품질 체크리스트
- `docs/guides/swagger-guide.md` - Swagger 문서 가이드
- `api/CLAUDE.md` - API 개발 규칙
---
## 2. 대상 범위
### 2.1 Phase 1: API 계산 로직 구현
| # | 작업 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 1.1 | QuoteCalculationService MNG 로직 동기화 | ⏳ | FormulaEvaluatorService 기반 |
| 1.2 | 입력 변수 처리 (W0, H0, GT, MP, CT 등) | ⏳ | React QuoteItem 매핑 |
| 1.3 | 수식 평가 로직 구현 | ⏳ | evaluate, evaluateRange |
| 1.4 | 품목 가격 계산 로직 구현 | ⏳ | area_based, weight_based 구분 |
### 2.2 Phase 2: API 엔드포인트 정비
| # | 작업 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 2.1 | POST /api/v1/quotes/calculate 엔드포인트 | ⏳ | 기존 존재, 로직 수정 |
| 2.2 | QuoteCalculateRequest 유효성 검증 | ⏳ | FormRequest 수정 |
| 2.3 | Swagger 문서 업데이트 | ⏳ | 요청/응답 스키마 |
### 2.3 Phase 3: React 연동
| # | 작업 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 3.1 | handleAutoCalculate API 호출 구현 | ⏳ | quoteApi.calculate() 사용 |
| 3.2 | 계산 결과 UI 표시 | ⏳ | 품목별 단가/금액 |
| 3.3 | 에러 처리 및 로딩 상태 | ⏳ | UX 개선 |
| 3.4 | 계산 결과로 QuoteItem 자동 생성 | ⏳ | items 배열 업데이트 |
---
## 3. MNG 핵심 로직 상세 (API 재구현 기준)
> **중요**: 이 섹션은 API에서 재구현해야 할 MNG 로직의 완전한 명세입니다.
> 새 세션에서 이 문서만 보고 작업할 수 있도록 상세히 기술합니다.
### 3.1 핵심 서비스 구조
```
┌──────────────────────────────────────────────────────────────────────────┐
│ FormulaEvaluatorService 핵심 메서드 │
├──────────────────────────────────────────────────────────────────────────┤
│ 📥 입력 처리 │
│ ├── validateFormula() - 수식 문법 검증 │
│ └── resetVariables() - 변수 초기화 │
│ │
│ 🔢 수식 평가 │
│ ├── evaluate() - 단일 수식 평가 (변수 치환 + 함수 처리) │
│ ├── evaluateRange() - 범위 조건별 수식 평가 │
│ └── evaluateMapping() - 매핑값 기반 수식 평가 │
│ │
│ 📊 BOM 기반 계산 (핵심) │
│ ├── calculateBomWithDebug() - 10단계 디버그 포함 전체 계산 │
│ ├── expandBomWithFormulas() - BOM 트리 전개 │
│ └── calculateCategoryPrice() - 카테고리별 단가 계산 │
│ │
│ 💰 가격 조회 │
│ ├── getItemPrice() - 품목 단가 조회 (Price 모델 → Fallback) │
│ └── getItemCategory() - 품목 카테고리 조회 │
└──────────────────────────────────────────────────────────────────────────┘
```
### 3.2 사용 DB 테이블
| 테이블명 | 용도 | 주요 컬럼 |
|---------|------|----------|
| `items` | 품목 마스터 | code, name, item_type, item_category, process_type, bom(JSON), unit |
| `prices` | 단가 정보 | tenant_id, item_code, sales_price |
| `category_groups` | 카테고리별 계산 방식 | code, categories(JSON), multiplier_variable |
| `quote_formulas` | 수식 정의 | variable, formula, type, output_type |
| `quote_formula_ranges` | 범위별 조건 | formula_id, condition_variable, min, max, result_value |
| `quote_formula_mappings` | 매핑 정의 | formula_id, source_variable, source_value, result_value |
### 3.3 입력 변수 (React → API)
| 변수명 | 의미 | 타입 | 예시 |
|--------|------|------|------|
| `W0` | 오픈사이즈 가로 (mm) | number | 3000 |
| `H0` | 오픈사이즈 세로 (mm) | number | 2500 |
| `QTY` | 수량 | number | 1 |
| `PC` | 제품 카테고리 | string | "SCREEN", "STEEL" |
| `GT` | 가이드레일 설치유형 | string | "wall", "ceiling" |
| `MP` | 모터 전원 | string | "single", "three" |
| `CT` | 연동제어기 | string | "basic", "advanced" |
| `WS` | 마구리 날개치수 | number | 50 |
| `INSP` | 검사비 | number | 50000 |
### 3.4 계산 변수 (자동 산출)
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 변수 계산 로직 (제품 카테고리별 마진값) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ if (PC === 'STEEL') { │
│ marginW = 110; // 철재 마진 │
│ marginH = 350; │
│ K = M × 25; // 철재 중량 │
│ } else { │
│ marginW = 140; // 스크린 기본 마진 │
│ marginH = 350; │
│ K = M × 2 + (W0 / 1000) × 14.17; // 스크린 중량 │
│ } │
│ │
│ W1 = W0 + marginW; // 마진 포함 폭 │
│ H1 = H0 + marginH; // 마진 포함 높이 │
│ M = (W1 × H1) / 1,000,000; // 면적 (㎡) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
### 3.5 CategoryGroup 단가 계산 방식
```php
// category_groups 테이블 구조
// code: 'area_based' | 'weight_based' | 'quantity_based'
// multiplier_variable: 'M' (면적) | 'K' (중량) | null (수량)
// categories: JSON 배열 ['원단', '패널', '도장'] 등
// 단가 계산 로직
if (multiplier_variable === 'M') {
// 면적 기반: 기본단가 × M (면적 ㎡)
final_price = base_price × M;
note = "면적단가 (xxx원/㎡ × x.xx㎡)";
} else if (multiplier_variable === 'K') {
// 중량 기반: 기본단가 × K (중량 kg)
final_price = base_price × K;
note = "중량단가 (xxx원/kg × x.xxkg)";
} else {
// 수량 기반: 기본단가 × 수량
final_price = base_price × quantity;
note = "수량단가";
}
```
### 3.6 10단계 BOM 계산 프로세스
```
┌─────────────────────────────────────────────────────────────────────────┐
│ calculateBomWithDebug() 10단계 프로세스 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Step 1. 입력값 수집 │
│ W0, H0, QTY, PC, GT, MP, CT, WS, INSP + 완제품코드 │
│ │
│ Step 2. 완제품 선택 │
│ items 테이블에서 FG(완제품) 조회 │
│ → code, name, item_category, bom(JSON) 확인 │
│ │
│ Step 3. 변수 계산 │
│ W1, H1, M, K 계산 (3.4 참조) │
│ │
│ Step 4. BOM 전개 │
│ 완제품의 bom JSON → 자식 품목 목록 생성 │
│ 재귀적으로 반제품(SF, PT) 하위 BOM 포함 │
│ │
│ Step 5. 단가 출처 결정 │
│ 각 품목의 item_category → CategoryGroup 매칭 │
│ → multiplier_variable 결정 (M/K/null) │
│ │
│ Step 6. 수량 수식 평가 │
│ BOM의 quantityFormula 평가 (예: "M", "W0/1000", "1") │
│ │
│ Step 7. 금액 계산 │
│ 면적/중량 기반: final_price = base_price × M|K │
│ 수량 기반: total_price = quantity × unit_price │
│ │
│ Step 8. 공정별 그룹화 │
│ items.process_type으로 그룹화 │
│ screen, bending, steel, electric, assembly, other │
│ │
│ Step 9. 소계 계산 │
│ 공정별 subtotal 합산 │
│ │
│ Step 10. 최종 합계 │
│ grand_total = sum(all item total_price) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
### 3.7 수식 평가 함수 (evaluate)
```php
// 지원 함수 목록
$supportedFunctions = ['SUM', 'ROUND', 'CEIL', 'FLOOR', 'ABS', 'MIN', 'MAX', 'IF', 'AND', 'OR', 'NOT'];
// 평가 과정
1. substituteVariables() - 변수명 값으로 치환
: "W0 + 140" "3000 + 140"
2. processFunctions() - 함수 처리
- ROUND(value, decimals) round(value, decimals)
- SUM(a, b, c) a + b + c
- IF(condition, true_val, false_val) 조건 평가 결과 반환
- MIN/MAX(a, b, ...) 최소/최대값
- ABS/CEIL/FLOOR(value) 수학 함수
3. calculateExpression() - 최종 계산
- 안전한 수식 평가 (숫자, 연산자, 괄호만 허용)
- eval() 사용 (프로덕션에서는 expression-language 라이브러리 권장)
```
### 3.8 단가 조회 우선순위
```php
function getItemPrice(string $itemCode): float
{
// 1차: prices 테이블에서 판매단가 조회
$price = Price::getSalesPriceByItemCode($tenantId, $itemCode);
if ($price > 0) return $price;
// 2차 Fallback: items.attributes.salesPrice에서 조회
$item = DB::table('items')
->where('tenant_id', $tenantId)
->where('code', $itemCode)
->first();
if ($item && $item->attributes) {
$attributes = json_decode($item->attributes, true);
return (float) ($attributes['salesPrice'] ?? 0);
}
return 0;
}
```
### 3.9 BOM JSON 구조
```json
// items.bom 필드 (완제품/반제품)
[
{
"child_item_id": 123, // 또는
"item_code": "PT-001", // childItemCode
"quantity": 1, // 또는
"quantityFormula": "M" // 수식 (면적 기반)
},
{
"childItemCode": "RM-002",
"quantityFormula": "W0/1000", // 폭 기반 수량
"quantity": 1
}
]
```
### 3.10 API 응답 구조 (목표)
```json
{
"success": true,
"data": {
"finished_goods": {
"code": "FG-SCR-001",
"name": "스크린셔터 3000x2500",
"item_category": "SCREEN"
},
"variables": {
"W0": 3000, "H0": 2500,
"W1": 3140, "H1": 2850,
"M": 8.949, "K": 60.598
},
"items": [
{
"item_code": "RM-FABRIC-01",
"item_name": "스크린 원단",
"item_category": "원단",
"quantity": 8.949,
"unit_price": 213465,
"total_price": 1910203,
"calculation_note": "면적단가 (213,465원/㎡ × 8.949㎡)",
"category_group": "area_based"
}
],
"grouped_items": {
"screen": {
"name": "스크린 공정",
"items": [...],
"subtotal": 1910203
}
},
"subtotals": {
"screen": { "count": 3, "subtotal": 1910203 },
"electric": { "count": 2, "subtotal": 500000 }
},
"grand_total": 2410203
}
}
```
---
## 4. 현재 시스템 분석
### 4.1 MNG FormulaEvaluatorService 핵심 기능
```php
// 1. 수식 검증
validateFormula(string $formula): array
// 2. 수식 평가 (변수 치환 + 함수 처리)
evaluate(string $formula, array $variables): mixed
// 3. 범위 기반 수식 평가 (조건에 따른 결과)
evaluateRange(QuoteFormula $formula, array $variables): mixed
// 4. 전체 수식 실행 (카테고리별 순서대로)
executeAll(Collection $formulasByCategory, array $inputVariables): array
// 5. BOM 기반 계산 (디버깅 포함)
calculateBomWithDebug(string $fgCode, array $inputVars, int $tenantId): array
// 지원 함수: SUM, ROUND, CEIL, FLOOR, ABS, MIN, MAX, IF, AND, OR, NOT
```
### 3.2 React QuoteItem 인터페이스
```typescript
interface QuoteItem {
id: string;
floor: string; // 층수
code: string; // 부호
productCategory: string; // PC (제품 카테고리)
productName: string; // 제품명
openWidth: string; // W0 (오픈사이즈 가로)
openHeight: string; // H0 (오픈사이즈 세로)
guideRailType: string; // GT (가이드레일 설치유형)
motorPower: string; // MP (모터 전원)
controller: string; // CT (연동제어기)
quantity: number; // QTY (수량)
wingSize: string; // WS (마구리 날개치수)
inspectionFee: number; // INSP (검사비)
unitPrice?: number; // 단가 (계산 결과)
totalAmount?: number; // 합계 (계산 결과)
}
```
### 3.3 API 요청/응답 구조 (목표)
**요청:**
```json
{
"items": [
{
"product_code": "SCR-001",
"W0": 3000,
"H0": 2500,
"QTY": 1,
"GT": "벽면형",
"MP": "220V",
"CT": "단독",
"WS": 50,
"INSP": 50000
}
]
}
```
**응답:**
```json
{
"success": true,
"data": {
"items": [
{
"product_code": "SCR-001",
"inputs": { "W0": 3000, "H0": 2500, ... },
"outputs": {
"W1": 3140,
"H1": 2850,
"M": 8.95,
"K": 0
},
"bom_items": [
{
"item_code": "SF-SCR-F01",
"item_name": "스크린 원단",
"quantity": 8.95,
"unit_price": 213465,
"total_price": 1910486
}
],
"costs": {
"material_cost": 1910486,
"labor_cost": 0,
"install_cost": 50000,
"subtotal": 1960486
}
}
],
"summary": {
"total_material": 1910486,
"total_labor": 0,
"total_install": 50000,
"grand_total": 1960486
}
}
}
```
---
## 4. 상세 작업 내용
### 4.1 Phase 1: API 계산 로직 구현
#### 1.1 QuoteCalculationService MNG 로직 동기화
**현재 상태:**
- `api/app/Services/Quote/QuoteCalculationService.php` 존재
- MNG `FormulaEvaluatorService`와 동기화 안됨
**목표 상태:**
- MNG 로직을 API로 완전 이전
- DB 기반 수식/단가 조회
- CategoryGroup 활용한 계산 방식 결정
**수정 사항:**
- [ ] ✅ FormulaEvaluatorService 메서드 이전
- [ ] ✅ DB 연결 (quote_formulas, items, category_groups 테이블)
- [ ] ⚠️ 계산 로직 구현 (컨펌 필요)
---
## 5. 컨펌 대기 목록
| # | 항목 | 변경 내용 | 영향 범위 | 상태 |
|---|------|----------|----------|------|
| 1 | API 계산 로직 | MNG FormulaEvaluatorService 기반 구현 | api | ⚠️ 컨펌 필요 |
| 2 | DB 연결 방식 | MNG DB 직접 조회 vs API DB 복제 | api, database | ⚠️ 컨펌 필요 |
---
## 6. 변경 이력
| 날짜 | 항목 | 변경 내용 | 파일 | 승인 |
|------|------|----------|------|------|
| 2025-12-30 | 초안 | 계획 문서 작성 | - | - |
| 2025-12-30 | MNG 로직 상세 | 섹션 3 추가: API 재구현을 위한 MNG 핵심 로직 상세 명세 | docs/plans/quote-calculation-api-plan.md | - |
---
## 7. 참고 문서
- **MNG 시뮬레이터**: `mng/resources/views/quote-formulas/simulator.blade.php`
- **MNG 계산 로직**: `mng/app/Services/Quote/FormulaEvaluatorService.php`
- **API 컨트롤러**: `api/app/Http/Controllers/Api/V1/QuoteController.php`
- **React 컴포넌트**: `react/src/components/quotes/QuoteRegistration.tsx`
- **기존 시뮬레이터 계획**: `docs/plans/simulator-ui-enhancement-plan.md`
---
## 8. 세션 및 메모리 관리 정책
### 8.1 세션 시작 시 (Load Strategy)
```javascript
read_memory("quote-calc-api-state") // 1. 상태 파악
read_memory("quote-calc-api-snapshot") // 2. 사고 흐름 복구
```
### 8.2 Serena 메모리 구조
- `quote-calc-api-state`: { phase, progress, next_step, last_decision }
- `quote-calc-api-snapshot`: 현재까지의 논의 및 코드 변경점 요약
---
## 9. 검증 결과
> 작업 완료 후 이 섹션에 검증 결과 추가
### 9.1 테스트 케이스
| 입력값 | 예상 결과 | 실제 결과 | 상태 |
|--------|----------|----------|------|
| W0=3000, H0=2500, QTY=1 | MNG와 동일 | - | ⏳ |
| W0=1200, H0=2400, QTY=10 | MNG와 동일 | - | ⏳ |
### 9.2 성공 기준 달성 현황
| 기준 | 달성 | 비고 |
|------|------|------|
| API 계산 로직 MNG와 동일 | ⏳ | FormulaEvaluatorService 기반 |
| React에서 API 호출 성공 | ⏳ | handleAutoCalculate 연동 |
| 계산 결과 UI 표시 | ⏳ | 품목/단가/금액 표시 |
---
*이 문서는 /sc:plan 스킬로 생성되었습니다.*