docs: 견적 산출 API 개발 계획 문서 추가
- 견적 산출 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>
This commit is contained in:
620
plans/quote-calculation-api-plan.md
Normal file
620
plans/quote-calculation-api-plan.md
Normal file
@@ -0,0 +1,620 @@
|
|||||||
|
# 견적 산출 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/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)
|
||||||
|
```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 스킬로 생성되었습니다.*
|
||||||
@@ -1,29 +1,40 @@
|
|||||||
# SAM API 페이지네이션 정책
|
# SAM API 페이지네이션 정책
|
||||||
|
|
||||||
**작성일**: 2025-12-09
|
**작성일**: 2025-12-09
|
||||||
|
**수정일**: 2025-12-30
|
||||||
**적용 범위**: 모든 목록 조회 API
|
**적용 범위**: 모든 목록 조회 API
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. 응답 구조
|
## 1. 응답 구조
|
||||||
|
|
||||||
### 1.1 표준 페이지네이션 응답
|
### 1.1 표준 페이지네이션 응답 (Laravel 기본)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"success": true,
|
"success": true,
|
||||||
"message": "조회되었습니다.",
|
"message": "조회되었습니다.",
|
||||||
"data": [
|
"data": {
|
||||||
{ "id": 1, "name": "항목1", ... },
|
|
||||||
{ "id": 2, "name": "항목2", ... }
|
|
||||||
],
|
|
||||||
"pagination": {
|
|
||||||
"current_page": 1,
|
"current_page": 1,
|
||||||
"per_page": 20,
|
"data": [
|
||||||
"total": 100,
|
{ "id": 1, "name": "항목1" },
|
||||||
"last_page": 5,
|
{ "id": 2, "name": "항목2" }
|
||||||
|
],
|
||||||
|
"first_page_url": "http://sam.kr/api/v1/items?page=1",
|
||||||
"from": 1,
|
"from": 1,
|
||||||
"to": 20
|
"last_page": 5,
|
||||||
|
"last_page_url": "http://sam.kr/api/v1/items?page=5",
|
||||||
|
"links": [
|
||||||
|
{ "url": null, "label": "« Previous", "active": false },
|
||||||
|
{ "url": "http://sam.kr/api/v1/items?page=1", "label": "1", "active": true },
|
||||||
|
{ "url": "http://sam.kr/api/v1/items?page=2", "label": "Next »", "active": false }
|
||||||
|
],
|
||||||
|
"next_page_url": "http://sam.kr/api/v1/items?page=2",
|
||||||
|
"path": "http://sam.kr/api/v1/items",
|
||||||
|
"per_page": 20,
|
||||||
|
"prev_page_url": null,
|
||||||
|
"to": 20,
|
||||||
|
"total": 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -34,19 +45,25 @@
|
|||||||
|----|------|------|
|
|----|------|------|
|
||||||
| `success` | boolean | 요청 성공 여부 |
|
| `success` | boolean | 요청 성공 여부 |
|
||||||
| `message` | string | 응답 메시지 (i18n 키) |
|
| `message` | string | 응답 메시지 (i18n 키) |
|
||||||
| `data` | array | **데이터 배열** (바로 접근 가능) |
|
| `data` | object | **페이지네이션 객체** (Laravel 기본) |
|
||||||
| `pagination` | object | **페이지네이션 정보** |
|
|
||||||
|
|
||||||
### 1.3 pagination 객체 필드
|
### 1.3 data 객체 필드
|
||||||
|
|
||||||
| 필드 | 타입 | 설명 |
|
| 필드 | 타입 | 설명 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| `current_page` | int | 현재 페이지 번호 (1부터 시작) |
|
| `current_page` | int | 현재 페이지 번호 (1부터 시작) |
|
||||||
|
| `data` | array | **실제 데이터 배열** |
|
||||||
| `per_page` | int | 페이지당 항목 수 |
|
| `per_page` | int | 페이지당 항목 수 |
|
||||||
| `total` | int | 전체 항목 수 |
|
| `total` | int | 전체 항목 수 |
|
||||||
| `last_page` | int | 마지막 페이지 번호 |
|
| `last_page` | int | 마지막 페이지 번호 |
|
||||||
| `from` | int\|null | 현재 페이지 첫 번째 항목 번호 |
|
| `from` | int\|null | 현재 페이지 첫 번째 항목 번호 |
|
||||||
| `to` | int\|null | 현재 페이지 마지막 항목 번호 |
|
| `to` | int\|null | 현재 페이지 마지막 항목 번호 |
|
||||||
|
| `first_page_url` | string | 첫 페이지 URL |
|
||||||
|
| `last_page_url` | string | 마지막 페이지 URL |
|
||||||
|
| `next_page_url` | string\|null | 다음 페이지 URL |
|
||||||
|
| `prev_page_url` | string\|null | 이전 페이지 URL |
|
||||||
|
| `path` | string | 기본 경로 |
|
||||||
|
| `links` | array | 페이지 링크 배열 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -73,21 +90,11 @@ GET /api/v1/items?page=2&size=50&search=스크린
|
|||||||
### 3.1 Controller 구현
|
### 3.1 Controller 구현
|
||||||
|
|
||||||
```php
|
```php
|
||||||
public function index(Request $request)
|
public function index(IndexRequest $request)
|
||||||
{
|
{
|
||||||
$items = $this->service->getItems($request->all());
|
$items = $this->service->getItems($request->validated());
|
||||||
|
|
||||||
return ApiResponse::success([
|
return ApiResponse::success($items, __('message.fetched'));
|
||||||
'data' => $items->items(),
|
|
||||||
'pagination' => [
|
|
||||||
'current_page' => $items->currentPage(),
|
|
||||||
'per_page' => $items->perPage(),
|
|
||||||
'total' => $items->total(),
|
|
||||||
'last_page' => $items->lastPage(),
|
|
||||||
'from' => $items->firstItem(),
|
|
||||||
'to' => $items->lastItem(),
|
|
||||||
],
|
|
||||||
], __('message.fetched'));
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -105,65 +112,7 @@ public function getItems(array $params): LengthAwarePaginator
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3.3 Helper 함수 (권장)
|
### 3.3 최대 페이지 크기 제한
|
||||||
|
|
||||||
`ApiResponse` 클래스에 페이지네이션 헬퍼 추가:
|
|
||||||
|
|
||||||
```php
|
|
||||||
// app/Helpers/ApiResponse.php
|
|
||||||
|
|
||||||
public static function paginated(LengthAwarePaginator $paginator, string $message = null): JsonResponse
|
|
||||||
{
|
|
||||||
return self::success([
|
|
||||||
'data' => $paginator->items(),
|
|
||||||
'pagination' => [
|
|
||||||
'current_page' => $paginator->currentPage(),
|
|
||||||
'per_page' => $paginator->perPage(),
|
|
||||||
'total' => $paginator->total(),
|
|
||||||
'last_page' => $paginator->lastPage(),
|
|
||||||
'from' => $paginator->firstItem(),
|
|
||||||
'to' => $paginator->lastItem(),
|
|
||||||
],
|
|
||||||
], $message ?? __('message.fetched'));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**사용 예시:**
|
|
||||||
```php
|
|
||||||
public function index(Request $request)
|
|
||||||
{
|
|
||||||
$items = $this->service->getItems($request->all());
|
|
||||||
return ApiResponse::paginated($items);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 설계 원칙
|
|
||||||
|
|
||||||
### 4.1 data는 항상 배열
|
|
||||||
|
|
||||||
```json
|
|
||||||
// ✅ 올바른 형식 - data가 바로 배열
|
|
||||||
{
|
|
||||||
"data": [...]
|
|
||||||
}
|
|
||||||
|
|
||||||
// ❌ 잘못된 형식 - data.data 중첩
|
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"data": [...]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.2 pagination 분리
|
|
||||||
|
|
||||||
- 페이지네이션 정보는 `pagination` 객체로 명확히 분리
|
|
||||||
- Laravel 기본 형식(first_page_url, links 등) 대신 필수 정보만 포함
|
|
||||||
- 응답 경량화 및 프론트엔드 접근 편의성 확보
|
|
||||||
|
|
||||||
### 4.3 최대 페이지 크기 제한
|
|
||||||
|
|
||||||
```php
|
```php
|
||||||
$size = min($params['size'] ?? 20, 100); // 최대 100개
|
$size = min($params['size'] ?? 20, 100); // 최대 100개
|
||||||
@@ -175,27 +124,37 @@ $size = min($params['size'] ?? 20, 100); // 최대 100개
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. 프론트엔드 사용 가이드
|
## 4. 프론트엔드 사용 가이드
|
||||||
|
|
||||||
### 5.1 TypeScript 타입 정의
|
### 4.1 TypeScript 타입 정의
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
interface PaginatedResponse<T> {
|
interface PaginatedResponse<T> {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
message: string;
|
message: string;
|
||||||
data: T[];
|
data: {
|
||||||
pagination: {
|
|
||||||
current_page: number;
|
current_page: number;
|
||||||
per_page: number;
|
data: T[];
|
||||||
total: number;
|
first_page_url: string;
|
||||||
last_page: number;
|
|
||||||
from: number | null;
|
from: number | null;
|
||||||
|
last_page: number;
|
||||||
|
last_page_url: string;
|
||||||
|
links: Array<{
|
||||||
|
url: string | null;
|
||||||
|
label: string;
|
||||||
|
active: boolean;
|
||||||
|
}>;
|
||||||
|
next_page_url: string | null;
|
||||||
|
path: string;
|
||||||
|
per_page: number;
|
||||||
|
prev_page_url: string | null;
|
||||||
to: number | null;
|
to: number | null;
|
||||||
|
total: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5.2 React 사용 예시
|
### 4.2 React 사용 예시
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const fetchItems = async (page: number, size: number) => {
|
const fetchItems = async (page: number, size: number) => {
|
||||||
@@ -203,84 +162,49 @@ const fetchItems = async (page: number, size: number) => {
|
|||||||
params: { page, size }
|
params: { page, size }
|
||||||
});
|
});
|
||||||
|
|
||||||
// 데이터 직접 접근
|
// 데이터 접근
|
||||||
const items = response.data.data;
|
const items = response.data.data.data;
|
||||||
|
|
||||||
// 페이지네이션 정보
|
// 페이지네이션 정보
|
||||||
const { current_page, total, last_page } = response.data.pagination;
|
const { current_page, total, last_page } = response.data.data;
|
||||||
|
|
||||||
return { items, pagination: response.data.pagination };
|
return { items, pagination: response.data.data };
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
### 4.3 간편 접근 패턴
|
||||||
|
|
||||||
## 6. 기존 API 마이그레이션
|
```typescript
|
||||||
|
// 구조 분해로 간결하게 사용
|
||||||
### 6.1 변경 전 (Laravel 기본)
|
const { data: pagination } = response.data;
|
||||||
|
const items = pagination.data;
|
||||||
```json
|
const { current_page, total, last_page, per_page } = pagination;
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"current_page": 1,
|
|
||||||
"data": [...],
|
|
||||||
"first_page_url": "http://...",
|
|
||||||
"from": 1,
|
|
||||||
"last_page": 5,
|
|
||||||
"links": [...],
|
|
||||||
"next_page_url": "http://...",
|
|
||||||
"path": "http://...",
|
|
||||||
"per_page": 20,
|
|
||||||
"prev_page_url": null,
|
|
||||||
"to": 20,
|
|
||||||
"total": 100
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6.2 변경 후 (SAM 표준)
|
---
|
||||||
|
|
||||||
```json
|
## 5. 설계 원칙
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "조회되었습니다.",
|
|
||||||
"data": [...],
|
|
||||||
"pagination": {
|
|
||||||
"current_page": 1,
|
|
||||||
"per_page": 20,
|
|
||||||
"total": 100,
|
|
||||||
"last_page": 5,
|
|
||||||
"from": 1,
|
|
||||||
"to": 20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.3 Breaking Changes
|
### 5.1 Laravel 기본 형식 유지
|
||||||
|
|
||||||
| 항목 | 변경 전 | 변경 후 |
|
- 프레임워크 기본 동작 활용
|
||||||
|------|---------|---------|
|
- 추가 변환 로직 불필요
|
||||||
| 데이터 접근 | `response.data.data` | `response.data` |
|
- 생태계 호환성 유지
|
||||||
| 페이지 정보 | `response.data.current_page` | `response.pagination.current_page` |
|
|
||||||
| URL 정보 | 포함 (first_page_url 등) | 제거 |
|
### 5.2 일관성 우선
|
||||||
| links 배열 | 포함 | 제거 |
|
|
||||||
|
- 기존 API와 동일한 형식 유지
|
||||||
|
- 프론트엔드 코드 재사용
|
||||||
|
- 학습 비용 최소화
|
||||||
|
|
||||||
|
### 5.3 최대 페이지 크기 제한
|
||||||
|
|
||||||
|
- 서버 부하 방지
|
||||||
|
- 응답 시간 최적화
|
||||||
|
- DoS 공격 방어
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. 적용 API 목록
|
## 6. 관련 문서
|
||||||
|
|
||||||
| API | 상태 | 비고 |
|
- [API 개발 규칙](./api-rules.md)
|
||||||
|-----|------|------|
|
|
||||||
| `GET /api/v1/items` | 🔧 예정 | Items API 통합 작업 시 적용 |
|
|
||||||
| `GET /api/v1/products` | ⏳ 대기 | |
|
|
||||||
| `GET /api/v1/materials` | ⏳ 대기 | |
|
|
||||||
| `GET /api/v1/pricing` | ⏳ 대기 | |
|
|
||||||
| `GET /api/v1/employees` | ⏳ 대기 | |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 관련 문서
|
|
||||||
|
|
||||||
- [API 개발 규칙](./api-rules.md)
|
|
||||||
- [Items API 통합 계획](../plans/items-api-unified-plan.md)
|
|
||||||
Reference in New Issue
Block a user