- 견적 산출 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>
26 KiB
26 KiB
견적 산출 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 단가 계산 방식
// 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)
// 지원 함수 목록
$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 단가 조회 우선순위
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 구조
// 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 응답 구조 (목표)
{
"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 핵심 기능
// 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 인터페이스
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 요청/응답 구조 (목표)
요청:
{
"items": [
{
"product_code": "SCR-001",
"W0": 3000,
"H0": 2500,
"QTY": 1,
"GT": "벽면형",
"MP": "220V",
"CT": "단독",
"WS": 50,
"INSP": 50000
}
]
}
응답:
{
"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)
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 스킬로 생성되었습니다.