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:
@@ -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<T> {
|
||||
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)
|
||||
- [API 개발 규칙](./api-rules.md)
|
||||
Reference in New Issue
Block a user