# 견적관리 API 요청서

> **작성일**: 2025-12-04
> **목적**: 견적관리 기능을 위한 백엔드 API 요청
> **참조**: sam-design/QuoteManagement3Write.tsx, QuoteManagement3Detail.tsx

---

## 1. 개요

### 1.1 기능 요약
견적관리 시스템은 다음 기능을 지원해야 합니다:
- 견적 CRUD (등록, 조회, 수정, 삭제)
- 견적 상태 관리 (임시저장 → 확정 → 수주전환)
- 견적 수정 이력 관리 (버전 관리)
- 견적 품목(BOM) 관리
- 자동 견적 산출 (수식 기반 계산) ← **백엔드 구현**

### 1.2 특이사항
- **자동 견적 산출 로직**은 백엔드에서 구현 예정 (수식 계산 엔진)
- 프론트엔드는 입력값을 전달하고 계산 결과를 받아서 표시

---

## 2. 데이터 모델

### 2.1 Quote (견적) - 메인 엔티티

```typescript
interface Quote {
  // === 기본 정보 ===
  id: number;
  tenant_id: number;
  quote_number: string;           // 견적번호 (예: KD-SC-251204-01)
  registration_date: string;      // 등록일 (YYYY-MM-DD)
  receipt_date: string;           // 접수일
  author: string;                 // 작성자

  // === 발주처 정보 ===
  client_id: number | null;       // 거래처 ID (FK)
  client_name: string;            // 거래처명 (직접입력 대응)
  manager: string | null;         // 담당자
  contact: string | null;         // 연락처

  // === 현장 정보 ===
  site_id: number | null;         // 현장 ID (FK, 별도 테이블 필요시)
  site_name: string | null;       // 현장명
  site_code: string | null;       // 현장코드

  // === 제품 정보 ===
  product_category: 'SCREEN' | 'STEEL';  // 제품 카테고리
  product_id: number | null;      // 선택된 제품 ID (품목마스터 FK)
  product_code: string | null;    // 제품코드
  product_name: string | null;    // 제품명

  // === 규격 정보 ===
  open_size_width: number | null;   // 오픈사이즈 폭 (mm)
  open_size_height: number | null;  // 오픈사이즈 높이 (mm)
  quantity: number;                  // 수량 (기본값: 1)
  unit_symbol: string | null;       // 부호
  floors: string | null;            // 층수

  // === 금액 정보 ===
  material_cost: number;          // 재료비 합계
  labor_cost: number;             // 노무비
  install_cost: number;           // 설치비
  subtotal: number;               // 소계
  discount_rate: number;          // 할인율 (%)
  discount_amount: number;        // 할인금액
  total_amount: number;           // 최종 금액

  // === 상태 관리 ===
  status: 'draft' | 'sent' | 'approved' | 'rejected' | 'finalized' | 'converted';
  current_revision: number;       // 현재 수정 차수 (0부터 시작)
  is_final: boolean;              // 최종확정 여부
  finalized_at: string | null;    // 확정일시
  finalized_by: number | null;    // 확정자 ID

  // === 기타 정보 ===
  completion_date: string | null; // 납기일
  remarks: string | null;         // 비고
  memo: string | null;            // 메모
  notes: string | null;           // 특이사항

  // === 시스템 필드 ===
  created_at: string;
  updated_at: string;
  created_by: number | null;
  updated_by: number | null;
  deleted_at: string | null;      // Soft Delete
}
```

### 2.2 QuoteItem (견적 품목) - BOM 계산 결과

```typescript
interface QuoteItem {
  id: number;
  quote_id: number;               // 견적 ID (FK)
  tenant_id: number;

  // === 품목 정보 ===
  item_id: number | null;         // 품목마스터 ID (FK)
  item_code: string;              // 품목코드
  item_name: string;              // 품명
  specification: string | null;   // 규격
  unit: string;                   // 단위

  // === 수량/금액 ===
  base_quantity: number;          // 기본수량
  calculated_quantity: number;    // 계산된 수량
  unit_price: number;             // 단가
  total_price: number;            // 금액 (수량 × 단가)

  // === 수식 정보 ===
  formula: string | null;         // 수식 (예: "W/1000 + 0.1")
  formula_source: string | null;  // 수식 출처 (BOM템플릿, 제품BOM 등)
  formula_category: string | null; // 수식 카테고리
  data_source: string | null;     // 데이터 출처

  // === 기타 ===
  delivery_date: string | null;   // 품목별 납기일
  note: string | null;            // 비고
  sort_order: number;             // 정렬순서

  created_at: string;
  updated_at: string;
}
```

### 2.3 QuoteRevision (견적 수정 이력)

```typescript
interface QuoteRevision {
  id: number;
  quote_id: number;               // 견적 ID (FK)
  tenant_id: number;

  revision_number: number;        // 수정 차수 (1, 2, 3...)
  revision_date: string;          // 수정일
  revision_by: number;            // 수정자 ID
  revision_by_name: string;       // 수정자 이름
  revision_reason: string | null; // 수정 사유

  // 이전 버전 데이터 (JSON)
  previous_data: object;          // 수정 전 견적 전체 데이터 (스냅샷)

  created_at: string;
}
```

---

## 3. API 엔드포인트

### 3.1 견적 CRUD

| Method | Endpoint | 설명 | 비고 |
|--------|----------|------|------|
| `GET` | `/api/v1/quotes` | 목록 조회 | 페이지네이션, 필터, 검색 |
| `GET` | `/api/v1/quotes/{id}` | 단건 조회 | 품목(items), 이력(revisions) 포함 |
| `POST` | `/api/v1/quotes` | 생성 | 품목 배열 포함 |
| `PUT` | `/api/v1/quotes/{id}` | 수정 | 수정이력 자동 생성 |
| `DELETE` | `/api/v1/quotes/{id}` | 삭제 | Soft Delete |
| `DELETE` | `/api/v1/quotes` | 일괄 삭제 | `ids[]` 파라미터 |

### 3.2 견적 상태 관리

| Method | Endpoint | 설명 | 비고 |
|--------|----------|------|------|
| `PATCH` | `/api/v1/quotes/{id}/finalize` | 최종확정 | status → 'finalized', is_final → true |
| `PATCH` | `/api/v1/quotes/{id}/convert-to-order` | 수주전환 | status → 'converted', 수주 데이터 생성 |
| `PATCH` | `/api/v1/quotes/{id}/cancel-finalize` | 확정취소 | is_final → false (조건부) |

### 3.3 자동 견적 산출 (핵심 기능)

| Method | Endpoint | 설명 | 비고 |
|--------|----------|------|------|
| `POST` | `/api/v1/quotes/calculate` | 자동 산출 | **수식 계산 엔진** |
| `POST` | `/api/v1/quotes/{id}/recalculate` | 재계산 | 기존 견적 재산출 |

### 3.4 견적 문서 출력

| Method | Endpoint | 설명 | 비고 |
|--------|----------|------|------|
| `GET` | `/api/v1/quotes/{id}/document/quote` | 견적서 PDF | |
| `GET` | `/api/v1/quotes/{id}/document/calculation` | 산출내역서 PDF | |
| `GET` | `/api/v1/quotes/{id}/document/purchase-order` | 발주서 PDF | |

### 3.5 문서 발송 API ⭐ 신규 요청

| Method | Endpoint | 설명 | 비고 |
|--------|----------|------|------|
| `POST` | `/api/v1/quotes/{id}/send/email` | 이메일 발송 | 첨부파일 포함 |
| `POST` | `/api/v1/quotes/{id}/send/fax` | 팩스 발송 | 팩스 서비스 연동 |
| `POST` | `/api/v1/quotes/{id}/send/kakao` | 카카오톡 발송 | 알림톡/친구톡 |

### 3.6 견적번호 생성

| Method | Endpoint | 설명 | 비고 |
|--------|----------|------|------|
| `GET` | `/api/v1/quotes/generate-number` | 견적번호 생성 | `?category=SCREEN` |

---

## 4. 상세 API 명세

### 4.1 목록 조회 `GET /api/v1/quotes`

**Query Parameters:**
```
page: number (default: 1)
size: number (default: 20)
q: string (검색어 - 견적번호, 발주처, 담당자, 현장명)
status: string (상태 필터)
product_category: string (제품 카테고리)
client_id: number (발주처 ID)
date_from: string (등록일 시작)
date_to: string (등록일 종료)
sort_by: string (정렬 컬럼)
sort_order: 'asc' | 'desc'
```

**Response:**
```json
{
  "success": true,
  "data": {
    "current_page": 1,
    "data": [
      {
        "id": 1,
        "quote_number": "KD-SC-251204-01",
        "registration_date": "2025-12-04",
        "client_name": "ABC건설",
        "site_name": "강남 오피스텔 현장",
        "product_category": "SCREEN",
        "product_name": "전동스크린 A형",
        "quantity": 10,
        "total_amount": 15000000,
        "status": "draft",
        "current_revision": 0,
        "is_final": false,
        "created_at": "2025-12-04T10:00:00Z"
      }
    ],
    "last_page": 5,
    "per_page": 20,
    "total": 100
  }
}
```

### 4.2 단건 조회 `GET /api/v1/quotes/{id}`

**Response:**
```json
{
  "success": true,
  "data": {
    "id": 1,
    "quote_number": "KD-SC-251204-01",
    "registration_date": "2025-12-04",
    "receipt_date": "2025-12-04",
    "author": "김철수",

    "client_id": 10,
    "client_name": "ABC건설",
    "manager": "이영희",
    "contact": "010-1234-5678",

    "site_id": 5,
    "site_name": "강남 오피스텔 현장",
    "site_code": "PJ-20251204-01",

    "product_category": "SCREEN",
    "product_id": 100,
    "product_code": "SCR-001",
    "product_name": "전동스크린 A형",

    "open_size_width": 2000,
    "open_size_height": 3000,
    "quantity": 10,
    "unit_symbol": "A",
    "floors": "3층",

    "material_cost": 12000000,
    "labor_cost": 1500000,
    "install_cost": 1500000,
    "subtotal": 15000000,
    "discount_rate": 0,
    "discount_amount": 0,
    "total_amount": 15000000,

    "status": "draft",
    "current_revision": 2,
    "is_final": false,

    "completion_date": "2025-12-31",
    "remarks": "급하게 진행 필요",
    "memo": "",
    "notes": "",

    "items": [
      {
        "id": 1,
        "item_code": "SCR-MOTOR-001",
        "item_name": "스크린 모터",
        "specification": "220V, 1/4HP",
        "unit": "EA",
        "base_quantity": 1,
        "calculated_quantity": 10,
        "unit_price": 150000,
        "total_price": 1500000,
        "formula": "Q",
        "formula_source": "제품BOM",
        "sort_order": 1
      }
    ],

    "revisions": [
      {
        "revision_number": 2,
        "revision_date": "2025-12-04",
        "revision_by_name": "김철수",
        "revision_reason": "고객 요청으로 수량 변경"
      },
      {
        "revision_number": 1,
        "revision_date": "2025-12-03",
        "revision_by_name": "김철수",
        "revision_reason": "단가 조정"
      }
    ],

    "created_at": "2025-12-04T10:00:00Z",
    "updated_at": "2025-12-04T15:30:00Z"
  }
}
```

### 4.3 생성 `POST /api/v1/quotes`

**Request Body:**
```json
{
  "registration_date": "2025-12-04",
  "receipt_date": "2025-12-04",

  "client_id": 10,
  "client_name": "ABC건설",
  "manager": "이영희",
  "contact": "010-1234-5678",

  "site_id": 5,
  "site_name": "강남 오피스텔 현장",
  "site_code": "PJ-20251204-01",

  "product_category": "SCREEN",
  "product_id": 100,

  "open_size_width": 2000,
  "open_size_height": 3000,
  "quantity": 10,
  "unit_symbol": "A",
  "floors": "3층",

  "completion_date": "2025-12-31",
  "remarks": "급하게 진행 필요",

  "items": [
    {
      "item_id": 50,
      "item_code": "SCR-MOTOR-001",
      "item_name": "스크린 모터",
      "unit": "EA",
      "base_quantity": 1,
      "calculated_quantity": 10,
      "unit_price": 150000,
      "total_price": 1500000,
      "formula": "Q",
      "sort_order": 1
    }
  ]
}
```

### 4.4 자동 산출 `POST /api/v1/quotes/calculate` ⭐ 핵심

**Request Body:**
```json
{
  "product_id": 100,
  "product_category": "SCREEN",
  "open_size_width": 2000,
  "open_size_height": 3000,
  "quantity": 10,
  "floors": "3층",
  "unit_symbol": "A",

  "options": {
    "guide_rail_install_type": "벽부형",
    "motor_power": "1/4HP",
    "controller": "표준형",
    "edge_wing_size": 50,
    "inspection_fee": 0
  }
}
```

**Response:**
```json
{
  "success": true,
  "data": {
    "product_id": 100,
    "product_name": "전동스크린 A형",
    "product_category": "SCREEN",

    "open_size": {
      "width": 2000,
      "height": 3000
    },
    "quantity": 10,

    "items": [
      {
        "item_id": 50,
        "item_code": "SCR-MOTOR-001",
        "item_name": "스크린 모터",
        "specification": "220V, 1/4HP",
        "unit": "EA",
        "base_quantity": 1,
        "calculated_quantity": 10,
        "unit_price": 150000,
        "total_price": 1500000,
        "formula": "Q",
        "formula_result": "10 × 1 = 10",
        "formula_source": "제품BOM: 전동스크린 A형",
        "data_source": "품목마스터 [SCR-MOTOR-001]"
      },
      {
        "item_id": 51,
        "item_code": "SCR-RAIL-001",
        "item_name": "가이드레일",
        "specification": "알루미늄",
        "unit": "M",
        "base_quantity": 1,
        "calculated_quantity": 60,
        "unit_price": 15000,
        "total_price": 900000,
        "formula": "H/1000 × 2 × Q",
        "formula_result": "(3000/1000) × 2 × 10 = 60",
        "formula_source": "BOM템플릿: 스크린_표준",
        "data_source": "품목마스터 [SCR-RAIL-001]"
      }
    ],

    "summary": {
      "material_cost": 12000000,
      "labor_cost": 1500000,
      "install_cost": 1500000,
      "subtotal": 15000000,
      "total_amount": 15000000
    },

    "calculation_info": {
      "bom_template_used": "스크린_표준",
      "formula_variables": {
        "W": 2000,
        "H": 3000,
        "Q": 10
      },
      "calculated_at": "2025-12-04T10:00:00Z"
    }
  }
}
```

---

## 5. 수식 계산 엔진 (백엔드 구현 요청)

### 5.1 수식 변수

| 변수 | 설명 | 예시 |
|------|------|------|
| `W` | 오픈사이즈 폭 (mm) | 2000 |
| `H` | 오픈사이즈 높이 (mm) | 3000 |
| `Q` | 수량 | 10 |

### 5.2 수식 예시

```
수량 그대로: Q
높이 기반: H/1000
폭+높이: (W + H) / 1000
가이드레일: H/1000 × 2 × Q
스크린원단: (W/1000 + 0.1) × (H/1000 + 0.3) × Q
```

### 5.3 반올림 규칙

| 규칙 | 설명 |
|------|------|
| `ceil` | 올림 |
| `floor` | 내림 |
| `round` | 반올림 |

### 5.4 BOM 템플릿 연동

- 제품별 BOM 템플릿에서 수식 조회
- 템플릿이 없으면 품목마스터 BOM 사용
- 수식 + 단가로 자동 금액 계산

---

## 6. 상태 흐름도

```
[신규등록]
    ↓
[draft] 임시저장
    ↓ (최종확정)
[finalized] 확정
    ↓ (수주전환)
[converted] 수주전환
```

### 6.1 상태별 제약

| 상태 | 수정 가능 | 삭제 가능 | 비고 |
|------|----------|----------|------|
| `draft` | O | O | 자유롭게 수정 |
| `sent` | O | O | 발송 후 수정 가능 (이력 기록) |
| `finalized` | X | X | 확정 후 수정 불가 |
| `converted` | X | X | 수주전환 후 불변 |

---

## 7. 프론트엔드 구현 현황 (2025-12-04 업데이트)

### 7.1 구현 완료된 파일

| 파일 | 설명 | 상태 |
|------|------|------|
| `quote-management/page.tsx` | 견적 목록 페이지 | ✅ 완료 (샘플 데이터) |
| `quote-management/new/page.tsx` | 견적 등록 페이지 | ✅ 완료 |
| `quote-management/[id]/page.tsx` | 견적 상세 페이지 | ✅ 완료 |
| `quote-management/[id]/edit/page.tsx` | 견적 수정 페이지 | ✅ 완료 |
| `components/quotes/QuoteRegistration.tsx` | 견적 등록/수정 컴포넌트 | ✅ 완료 |
| `components/quotes/QuoteDocument.tsx` | 견적서 문서 컴포넌트 | ✅ 완료 |
| `components/quotes/QuoteCalculationReport.tsx` | 산출내역서 문서 컴포넌트 | ✅ 완료 |
| `components/quotes/PurchaseOrderDocument.tsx` | 발주서 문서 컴포넌트 | ✅ 완료 |

### 7.2 UI 기능 구현 현황

| 기능 | 상태 | 비고 |
|------|------|------|
| 견적 목록 조회 | ✅ UI 완료 | 샘플 데이터, API 연동 필요 |
| 견적 검색/필터 | ✅ UI 완료 | 로컬 필터링, API 연동 필요 |
| 견적 등록 폼 | ✅ UI 완료 | API 연동 필요 |
| 견적 상세 페이지 | ✅ UI 완료 | API 연동 필요 |
| 견적 수정 폼 | ✅ UI 완료 | API 연동 필요 |
| 견적 삭제 | ✅ UI 완료 | 로컬 상태, API 연동 필요 |
| 견적 일괄 삭제 | ✅ UI 완료 | 로컬 상태, API 연동 필요 |
| 자동 견적 산출 | ⏳ 버튼만 | 백엔드 수식 엔진 필요 |
| 발주처 선택 | ⏳ 샘플 데이터 | `/api/v1/clients` 연동 필요 |
| 현장 선택 | ⏳ 샘플 데이터 | 발주처 연동 후 현장 API 필요 |
| 제품 선택 | ⏳ 샘플 데이터 | `/api/v1/item-masters` 연동 필요 |
| **견적서 모달** | ✅ UI 완료 | PDF/이메일/팩스/카톡 버튼, **발송 API 필요** |
| **산출내역서 모달** | ✅ UI 완료 | PDF/이메일/팩스/카톡 버튼, **발송 API 필요** |
| **발주서 모달** | ✅ UI 완료 | PDF/이메일/팩스/카톡 버튼, **발송 API 필요** |
| 최종확정 버튼 | ✅ UI 완료 | API 연동 필요 |

### 7.3 견적 등록/수정 폼 필드 (구현 완료)

**기본 정보 섹션:**
- 등록일 (readonly, 오늘 날짜)
- 작성자 (readonly, 로그인 사용자)
- 발주처 선택 * (필수)
- 현장명 (발주처 선택 시 연동)
- 발주 담당자
- 연락처
- 납기일
- 비고

**자동 견적 산출 섹션 (동적 항목):**
- 층수
- 부호
- 제품 카테고리 (PC) *
- 제품명 *
- 오픈사이즈 (W0) *
- 오픈사이즈 (H0) *
- 가이드레일 설치 유형 (GT) *
- 모터 전원 (MP) *
- 연동제어기 (CT) *
- 수량 (QTY) *
- 마구리 날개치수 (WS)
- 검사비 (INSP)

**기능:**
- 견적 항목 추가/복사/삭제
- 자동 견적 산출 버튼
- 샘플 데이터 생성 버튼

### 7.4 다음 단계 (API 연동)

```typescript
// useQuoteList 훅 (목록)
const {
  quotes,
  pagination,
  isLoading,
  fetchQuotes,
  deleteQuote,
  bulkDelete
} = useQuoteList();

// useQuote 훅 (단건 CRUD)
const {
  quote,
  isLoading,
  fetchQuote,
  createQuote,
  updateQuote,
  finalizeQuote,
  convertToOrder
} = useQuote();

// useQuoteCalculation 훅 (자동 산출)
const {
  calculationResult,
  isCalculating,
  calculate,
  recalculate
} = useQuoteCalculation();
```

---

## 8. 관련 참조

### 8.1 거래처 API 연동
- 발주처 선택 시 `/api/v1/clients` 연동
- 직접입력 시 자동 등록 가능

### 8.2 현장 API (추후)
- 현장 선택 시 `/api/v1/sites` 연동 (별도 API 필요시)

### 8.3 품목마스터 연동
- 제품 선택 시 `/api/v1/item-masters` 연동
- BOM 조회 시 품목마스터 BOM 활용

---

## 9. 요청 우선순위

| 순위 | API | 설명 |
|------|-----|------|
| P1 | 견적 CRUD | 기본 목록/등록/수정/삭제 |
| P1 | 자동 산출 | 수식 계산 엔진 (핵심) |
| P1 | 견적번호 생성 | 자동 채번 |
| P2 | 상태 관리 | 확정/수주전환 |
| P2 | 수정 이력 | 버전 관리 |
| P3 | 문서 출력 | PDF 생성 |

---

## 10. 질문사항

1. **현장(Site) 테이블**: 별도 테이블로 관리할지? (거래처 하위 개념)
2. **수식 계산**: BOM 템플릿 테이블 구조는?
3. **문서 출력**: PDF 라이브러리 선정 (TCPDF, Dompdf 등)
4. **알림**: 견적 발송 시 이메일/카카오톡 연동 계획?
