Files
sam-docs/dev/dev_plans/quote-calculation-api-plan.md
권혁성 db63fcff85 refactor: [docs] 팀별 폴더 구조 재편 (공유/개발/프론트/기획)
- 개발팀 전용 폴더 dev/ 생성 (standards, guides, quickstart, changes, deploys, data, history, dev_plans 이동)
- 프론트엔드 전용 폴더 frontend/ 생성 (api/ → frontend/api-specs/)
- 기획팀 폴더 requests/ 생성
- plans/ → dev/dev_plans/ 이름 변경
- README.md 신규 (사람용 안내), INDEX.md 재작성 (Claude Code용)
- resources.md 신규 (노션 링크용, assets/brochure 이관 예정)
- CURRENT_WORKS.md 삭제, TODO.md → dev/ 이동
- 전체 참조 경로 업데이트

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:46:03 +09:00

26 KiB
Raw Permalink Blame History

견적 산출 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/dev_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/dev_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 스킬로 생성되었습니다.