Files
sam-react-prod/claudedocs/sales/[API-2025-12-04] quote-api-request.md
byeongcheolryu 751e65f59b fix: 품목관리 수정 기능 버그 수정 및 Sales 페이지 추가
## 품목관리 수정 버그 수정
- FG(제품) 수정 시 품목명 반영 안되는 문제 해결
  - productName → name 필드 매핑 추가
  - FG 품목코드 = 품목명 동기화 로직 추가
- Materials(SM, RM, CS) 수정페이지 진입 오류 해결
- UNIQUE 제약조건 위반 오류 해결

## Sales 페이지
- 거래처관리 (client-management-sales-admin) 페이지 구현
- 견적관리 (quote-management) 페이지 구현
- 관련 컴포넌트 및 훅 추가

## 기타
- 회원가입 페이지 차단 처리
- 디버깅용 콘솔 로그 추가 (PUT 요청/응답 확인용)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 20:52:42 +09:00

676 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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-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. **알림**: 견적 발송 시 이메일/카카오톡 연동 계획?