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