diff --git a/plans/quote-calculation-api-plan.md b/plans/quote-calculation-api-plan.md new file mode 100644 index 0000000..9bb1acf --- /dev/null +++ b/plans/quote-calculation-api-plan.md @@ -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 스킬로 생성되었습니다.* \ No newline at end of file diff --git a/standards/pagination-policy.md b/standards/pagination-policy.md index ce94cae..0974687 100644 --- a/standards/pagination-policy.md +++ b/standards/pagination-policy.md @@ -1,29 +1,40 @@ # SAM API 페이지네이션 정책 **작성일**: 2025-12-09 +**수정일**: 2025-12-30 **적용 범위**: 모든 목록 조회 API --- ## 1. 응답 구조 -### 1.1 표준 페이지네이션 응답 +### 1.1 표준 페이지네이션 응답 (Laravel 기본) ```json { "success": true, "message": "조회되었습니다.", - "data": [ - { "id": 1, "name": "항목1", ... }, - { "id": 2, "name": "항목2", ... } - ], - "pagination": { + "data": { "current_page": 1, - "per_page": 20, - "total": 100, - "last_page": 5, + "data": [ + { "id": 1, "name": "항목1" }, + { "id": 2, "name": "항목2" } + ], + "first_page_url": "http://sam.kr/api/v1/items?page=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 | 요청 성공 여부 | | `message` | string | 응답 메시지 (i18n 키) | -| `data` | array | **데이터 배열** (바로 접근 가능) | -| `pagination` | object | **페이지네이션 정보** | +| `data` | object | **페이지네이션 객체** (Laravel 기본) | -### 1.3 pagination 객체 필드 +### 1.3 data 객체 필드 | 필드 | 타입 | 설명 | |------|------|------| | `current_page` | int | 현재 페이지 번호 (1부터 시작) | +| `data` | array | **실제 데이터 배열** | | `per_page` | int | 페이지당 항목 수 | | `total` | int | 전체 항목 수 | | `last_page` | int | 마지막 페이지 번호 | | `from` | 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 구현 ```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([ - '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')); + return ApiResponse::success($items, __('message.fetched')); } ``` @@ -105,65 +112,7 @@ public function getItems(array $params): LengthAwarePaginator } ``` -### 3.3 Helper 함수 (권장) - -`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 최대 페이지 크기 제한 +### 3.3 최대 페이지 크기 제한 ```php $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 interface PaginatedResponse { success: boolean; message: string; - data: T[]; - pagination: { + data: { current_page: number; - per_page: number; - total: number; - last_page: number; + data: T[]; + first_page_url: string; 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; + total: number; }; } ``` -### 5.2 React 사용 예시 +### 4.2 React 사용 예시 ```typescript const fetchItems = async (page: number, size: number) => { @@ -203,84 +162,49 @@ const fetchItems = async (page: number, size: number) => { 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 마이그레이션 - -### 6.1 변경 전 (Laravel 기본) - -```json -{ - "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 - } -} +```typescript +// 구조 분해로 간결하게 사용 +const { data: pagination } = response.data; +const items = pagination.data; +const { current_page, total, last_page, per_page } = pagination; ``` -### 6.2 변경 후 (SAM 표준) +--- -```json -{ - "success": true, - "message": "조회되었습니다.", - "data": [...], - "pagination": { - "current_page": 1, - "per_page": 20, - "total": 100, - "last_page": 5, - "from": 1, - "to": 20 - } -} -``` +## 5. 설계 원칙 -### 6.3 Breaking Changes +### 5.1 Laravel 기본 형식 유지 -| 항목 | 변경 전 | 변경 후 | -|------|---------|---------| -| 데이터 접근 | `response.data.data` | `response.data` | -| 페이지 정보 | `response.data.current_page` | `response.pagination.current_page` | -| URL 정보 | 포함 (first_page_url 등) | 제거 | -| links 배열 | 포함 | 제거 | +- 프레임워크 기본 동작 활용 +- 추가 변환 로직 불필요 +- 생태계 호환성 유지 + +### 5.2 일관성 우선 + +- 기존 API와 동일한 형식 유지 +- 프론트엔드 코드 재사용 +- 학습 비용 최소화 + +### 5.3 최대 페이지 크기 제한 + +- 서버 부하 방지 +- 응답 시간 최적화 +- DoS 공격 방어 --- -## 7. 적용 API 목록 +## 6. 관련 문서 -| API | 상태 | 비고 | -|-----|------|------| -| `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) \ No newline at end of file +- [API 개발 규칙](./api-rules.md) \ No newline at end of file