1095 lines
29 KiB
Markdown
1095 lines
29 KiB
Markdown
# Items API 통합 개발 계획
|
|
|
|
> Items API 명명 체계 통일 + Material CRUD 지원 추가
|
|
|
|
**작성일**: 2025-12-09
|
|
**상태**: 진행 예정
|
|
|
|
---
|
|
|
|
## 1. 개요
|
|
|
|
### 1.1 목표
|
|
|
|
1. **명명 체계 통일**: `item_type` 파라미터의 이중 의미 충돌 해소
|
|
2. **Material CRUD 지원**: 수정/삭제/일괄삭제/코드조회 기능 추가
|
|
|
|
### 1.2 현재 문제점
|
|
|
|
**item_type 이중 의미 충돌:**
|
|
```
|
|
❌ 동일한 이름(item_type)이 두 가지 다른 의미로 사용됨
|
|
|
|
[의미 1] 품목 유형 (올바른 사용)
|
|
├─ item_pages.item_type = 'FG' | 'PT' | 'SM' | 'RM' | 'CS'
|
|
├─ common_codes (code_group='item_type')
|
|
└─ ItemsService.createItem() - product_type 파라미터
|
|
|
|
[의미 2] 테이블 구분 (잘못된 사용)
|
|
├─ ItemsService.getItem() - item_type = 'PRODUCT' | 'MATERIAL'
|
|
├─ ItemsController.show() - item_type 파라미터
|
|
└─ Swagger ItemsApi - item_type 값 정의
|
|
```
|
|
|
|
**Material CRUD 미지원:**
|
|
|
|
| 메서드 | 경로 | Product | Material | 비고 |
|
|
|--------|------|:-------:|:--------:|------|
|
|
| GET | `/api/v1/items` | ✅ | ✅ | 통합 조회 정상 |
|
|
| POST | `/api/v1/items` | ✅ | ✅ | 생성 정상 |
|
|
| GET | `/api/v1/items/code/{code}` | ✅ | ❌ | Product만 지원 |
|
|
| GET | `/api/v1/items/{id}` | ✅ | ✅ | item_type으로 분기 |
|
|
| PUT | `/api/v1/items/{id}` | ✅ | ❌ | **수정 필요** |
|
|
| DELETE | `/api/v1/items/{id}` | ✅ | ❌ | **수정 필요** |
|
|
| DELETE | `/api/v1/items/batch` | ✅ | ❌ | **수정 필요** |
|
|
|
|
---
|
|
|
|
## 2. 설계 결정 사항
|
|
|
|
### 2.1 명명 규칙
|
|
|
|
| 용어 | 필드명 | 값 | 설명 |
|
|
|------|--------|-----|------|
|
|
| **품목 유형** | `item_type` | FG, PT, SM, RM, CS | 비즈니스 분류 (API 파라미터) |
|
|
| **저장 테이블** | `source_table` | products, materials | 물리적 저장소 (내부 자동 매핑) |
|
|
| **참조 타입** | `ref_type` | PRODUCT, MATERIAL | 폴리모픽 관계용 (기존 유지) |
|
|
|
|
### 2.2 품목 유형 → 테이블 매핑
|
|
|
|
```
|
|
item_type → source_table 자동 매핑
|
|
|
|
FG (완제품) ─┐
|
|
PT (반제품) ─┴─→ source_table = 'products'
|
|
|
|
SM (부자재) ─┐
|
|
RM (원자재) ─┼─→ source_table = 'materials'
|
|
CS (소모품) ─┘
|
|
```
|
|
|
|
### 2.3 테넌트별 품목 유형 관리
|
|
|
|
- 품목 유형은 테넌트(업체)별로 커스터마이징 가능
|
|
- **`attributes` JSON 필드 활용** (스키마 변경 없음)
|
|
- 캐시를 통한 성능 최적화
|
|
|
|
```json
|
|
// common_codes 레코드 예시
|
|
{
|
|
"code_group": "item_type",
|
|
"code": "FG",
|
|
"name": "완제품",
|
|
"attributes": {
|
|
"source_table": "products"
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2.4 API 파라미터 변경 (Breaking Change)
|
|
|
|
```
|
|
Before: GET /api/v1/items/{id}?item_type=PRODUCT
|
|
After: GET /api/v1/items/{id}?item_type=FG
|
|
|
|
Before: DELETE /api/v1/items/{id} (item_type 없음)
|
|
After: DELETE /api/v1/items/{id}?item_type=RM
|
|
```
|
|
|
|
### 2.5 기존 시스템 호환성 (변경 없음)
|
|
|
|
| 시스템 | 필드 | 값 | 상태 |
|
|
|--------|------|-----|------|
|
|
| Prices API | `item_type_code` | PRODUCT, MATERIAL | ✅ 유지 |
|
|
| BOM API | `ref_type` | PRODUCT, MATERIAL | ✅ 유지 |
|
|
| ItemMaster API | `item_type` | FG, PT, SM, RM, CS | ✅ 유지 |
|
|
|
|
---
|
|
|
|
## 3. 영향 범위
|
|
|
|
### 3.1 수정 파일
|
|
|
|
| 파일 | 경로 | 변경 내용 |
|
|
|------|------|----------|
|
|
| ItemTypeSeeder | `database/seeders/ItemTypeSeeder.php` | attributes.source_table 추가 |
|
|
| ItemTypeHelper | `app/Helpers/ItemTypeHelper.php` | **신규 생성** |
|
|
| ItemStoreRequest | `app/Http/Requests/Item/ItemStoreRequest.php` | Material 필드 추가 |
|
|
| ItemUpdateRequest | `app/Http/Requests/Item/ItemUpdateRequest.php` | item_type 필수화 + Material 필드 |
|
|
| ItemBatchDeleteRequest | `app/Http/Requests/Item/ItemBatchDeleteRequest.php` | item_type 필드 추가 |
|
|
| ItemsService | `app/Services/ItemsService.php` | Material CRUD 메서드 추가 |
|
|
| ItemsController | `app/Http/Controllers/Api/V1/ItemsController.php` | item_type 파라미터 처리 |
|
|
| ItemsApi | `app/Swagger/v1/ItemsApi.php` | item_type 값 변경 + 스키마 보완 |
|
|
|
|
### 3.2 작업 완료 후 예상 상태
|
|
|
|
| 품목 유형 | 조회 | 생성 | 수정 | 삭제 | 일괄삭제 | 코드조회 |
|
|
|----------|:----:|:----:|:----:|:----:|:--------:|:--------:|
|
|
| 제품(FG) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
| 반제품(PT) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
| 부자재(SM) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
| 원자재(RM) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
| 소모품(CS) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
|
|
---
|
|
|
|
## 4. 작업 계획
|
|
|
|
### Phase 0: Seeder 수정 (5분)
|
|
|
|
**파일**: `database/seeders/ItemTypeSeeder.php`
|
|
|
|
**작업 내용**:
|
|
- attributes.source_table 추가
|
|
|
|
**수정 후 코드**:
|
|
```php
|
|
$itemTypes = [
|
|
['code' => 'FG', 'name' => '완제품', 'attributes' => ['source_table' => 'products']],
|
|
['code' => 'PT', 'name' => '반제품', 'attributes' => ['source_table' => 'products']],
|
|
['code' => 'SM', 'name' => '부자재', 'attributes' => ['source_table' => 'materials']],
|
|
['code' => 'RM', 'name' => '원자재', 'attributes' => ['source_table' => 'materials']],
|
|
['code' => 'CS', 'name' => '소모품', 'attributes' => ['source_table' => 'materials']],
|
|
];
|
|
```
|
|
|
|
**체크리스트**:
|
|
- [x] ItemTypeSeeder.php 수정 ✅ (2025-12-09)
|
|
- [x] `php artisan db:seed --class=ItemTypeSeeder` 실행 ✅
|
|
- [x] DB 데이터 확인 ✅ (5개 item_type 코드 시딩 완료)
|
|
|
|
---
|
|
|
|
### Phase 1: ItemTypeHelper 생성 (10분)
|
|
|
|
**파일**: `app/Helpers/ItemTypeHelper.php` (신규)
|
|
|
|
**작업 내용**:
|
|
- 테넌트별 품목 유형 조회 (캐시 적용)
|
|
- isMaterial(), isProduct(), getSourceTable() 메서드
|
|
- 캐시 무효화 메서드
|
|
|
|
**체크리스트**:
|
|
- [ ] `app/Helpers/` 디렉토리 생성 (없으면)
|
|
- [ ] ItemTypeHelper.php 생성
|
|
- [ ] composer.json autoload 확인 (PSR-4로 자동 로드됨)
|
|
|
|
---
|
|
|
|
### Phase 2: FormRequest 수정 (10분)
|
|
|
|
**파일 3개**:
|
|
|
|
#### 2-1. ItemStoreRequest.php
|
|
```php
|
|
// Material 전용 필드 추가
|
|
'specification' => 'nullable|string|max:255',
|
|
'remarks' => 'nullable|string',
|
|
'item_name' => 'nullable|string|max:255',
|
|
'search_tag' => 'nullable|string|max:255',
|
|
'is_inspection' => 'nullable|string|in:Y,N',
|
|
'options' => 'nullable|array',
|
|
'material_code' => 'nullable|string|max:50',
|
|
```
|
|
|
|
#### 2-2. ItemUpdateRequest.php
|
|
```php
|
|
// item_type 필수화 + Material 필드 추가
|
|
'item_type' => 'required|string|in:FG,PT,SM,RM,CS',
|
|
'material_code' => 'sometimes|string|max:50',
|
|
'specification' => 'nullable|string|max:255',
|
|
'remarks' => 'nullable|string',
|
|
'item_name' => 'nullable|string|max:255',
|
|
'search_tag' => 'nullable|string|max:255',
|
|
'is_inspection' => 'nullable|string|in:Y,N',
|
|
'options' => 'nullable|array',
|
|
```
|
|
|
|
#### 2-3. ItemBatchDeleteRequest.php
|
|
```php
|
|
// item_type 필드 추가
|
|
'item_type' => 'required|string|in:FG,PT,SM,RM,CS',
|
|
```
|
|
|
|
**체크리스트**:
|
|
- [ ] ItemStoreRequest.php 수정
|
|
- [ ] ItemUpdateRequest.php 수정
|
|
- [ ] ItemBatchDeleteRequest.php 수정
|
|
|
|
---
|
|
|
|
### Phase 3: ItemsService 수정 (20분)
|
|
|
|
**파일**: `app/Services/ItemsService.php`
|
|
|
|
**작업 내용**:
|
|
|
|
1. **import 추가**: `use App\Helpers\ItemTypeHelper;`
|
|
|
|
2. **getItems() 수정**: 목록 조회 시 `specification` 추가 + `attributes` 플랫 전개
|
|
```php
|
|
// Product 쿼리 select에 추가
|
|
DB::raw('NULL as specification'), // Product는 specification 컬럼 없음
|
|
'attributes',
|
|
|
|
// Material 쿼리 select에 추가
|
|
'specification',
|
|
'attributes',
|
|
|
|
// 조회 후 attributes 플랫 전개 (후처리)
|
|
$items->getCollection()->transform(function ($item) {
|
|
$data = $item->toArray();
|
|
$attributes = $data['attributes'] ?? [];
|
|
unset($data['attributes']);
|
|
return array_merge($data, $attributes);
|
|
});
|
|
```
|
|
|
|
3. **getItem() 수정**: item_type 파라미터를 FG/PT/SM/RM/CS로 받도록 변경
|
|
```php
|
|
// Before: getItem('PRODUCT', $id, ...)
|
|
// After: getItem($id, 'FG', ...)
|
|
```
|
|
|
|
4. **updateItem() 수정**: Material 분기 추가
|
|
```php
|
|
public function updateItem(int $id, array $data): Product|Material
|
|
{
|
|
$itemType = strtoupper($data['item_type'] ?? 'FG');
|
|
if (ItemTypeHelper::isMaterial($itemType, $this->tenantId())) {
|
|
return $this->updateMaterial($id, $data);
|
|
}
|
|
return $this->updateProduct($id, $data);
|
|
}
|
|
```
|
|
|
|
4. **updateMaterial() 추가**: Material 수정 로직
|
|
|
|
5. **deleteItem() 수정**: item_type 파라미터 추가
|
|
```php
|
|
public function deleteItem(int $id, string $itemType = 'FG'): void
|
|
```
|
|
|
|
6. **deleteMaterial() 추가**: Material 삭제 로직
|
|
|
|
7. **batchDeleteItems() 수정**: item_type 지원
|
|
|
|
8. **getItemByCode() 수정**: Product 없으면 Material에서 조회
|
|
|
|
9. **checkMaterialUsageInBom() 추가**: BOM 사용 여부 체크
|
|
|
|
**체크리스트**:
|
|
- [ ] ItemTypeHelper import 추가
|
|
- [ ] getItems() specification/attributes 추가 + 플랫 전개 후처리
|
|
- [ ] getItem() 시그니처 및 로직 변경
|
|
- [ ] updateItem() Material 분기 추가
|
|
- [ ] updateMaterial() 메서드 추가
|
|
- [ ] deleteItem() item_type 파라미터 추가
|
|
- [ ] deleteMaterial() 메서드 추가
|
|
- [ ] batchDeleteItems() Material 지원
|
|
- [ ] getItemByCode() Material 지원
|
|
- [ ] checkMaterialUsageInBom() 메서드 추가
|
|
|
|
---
|
|
|
|
### Phase 4: ItemsController 수정 (5분)
|
|
|
|
**파일**: `app/Http/Controllers/Api/V1/ItemsController.php`
|
|
|
|
**작업 내용**:
|
|
|
|
1. **show() 수정**: 기본값 'PRODUCT' → 'FG'
|
|
```php
|
|
$itemType = strtoupper($request->input('item_type', 'FG'));
|
|
```
|
|
|
|
2. **destroy() 수정**: item_type 파라미터 추가
|
|
```php
|
|
public function destroy(Request $request, int $id)
|
|
{
|
|
$itemType = strtoupper($request->input('item_type', 'FG'));
|
|
return ApiResponse::handle(function () use ($id, $itemType) {
|
|
$this->service->deleteItem($id, $itemType);
|
|
return 'success';
|
|
}, __('message.item.deleted'));
|
|
}
|
|
```
|
|
|
|
3. **batchDestroy() 수정**: item_type 처리
|
|
```php
|
|
$itemType = strtoupper($request->validated()['item_type'] ?? 'FG');
|
|
$this->service->batchDeleteItems($request->validated()['ids'], $itemType);
|
|
```
|
|
|
|
**체크리스트**:
|
|
- [ ] show() 기본값 변경
|
|
- [ ] destroy() item_type 파라미터 추가
|
|
- [ ] batchDestroy() item_type 처리
|
|
|
|
---
|
|
|
|
### Phase 5: Swagger 문서 수정 (15분)
|
|
|
|
**파일**: `app/Swagger/v1/ItemsApi.php`
|
|
|
|
**작업 내용**:
|
|
|
|
1. **Item 스키마 보완**:
|
|
- `item_type` (FG|PT|SM|RM|CS) - 품목 유형
|
|
- Material 전용 필드 추가
|
|
|
|
2. **ItemCreateRequest 스키마 보완**:
|
|
- Material 전용 필드 추가
|
|
|
|
3. **ItemUpdateRequest 스키마 보완**:
|
|
- `item_type` 필수 (FG|PT|SM|RM|CS)
|
|
- Material 전용 필드 추가
|
|
|
|
4. **ItemBatchDeleteRequest 스키마 보완**:
|
|
- `item_type` 필드 추가
|
|
|
|
5. **show 엔드포인트**:
|
|
- item_type 값 변경 (common_codes): PRODUCT|MATERIAL → FG|PT|SM|RM|CS
|
|
|
|
6. **destroy 엔드포인트**:
|
|
- item_type 쿼리 파라미터 추가
|
|
|
|
7. **batchDestroy 엔드포인트**:
|
|
- Request에 item_type 필드 추가
|
|
|
|
8. **showByCode 엔드포인트**:
|
|
- Product/Material 모두 지원 설명 추가
|
|
|
|
**체크리스트**:
|
|
- [ ] Item 스키마 보완
|
|
- [ ] ItemCreateRequest 스키마 보완
|
|
- [ ] ItemUpdateRequest 스키마 보완
|
|
- [ ] ItemBatchDeleteRequest 스키마 보완
|
|
- [ ] show 엔드포인트 item_type 값 변경
|
|
- [ ] destroy 엔드포인트 item_type 파라미터 추가
|
|
- [ ] batchDestroy 엔드포인트 스키마 수정
|
|
- [ ] showByCode 엔드포인트 설명 보완
|
|
|
|
---
|
|
|
|
### Phase 6: 검증 (10분)
|
|
|
|
**작업 내용**:
|
|
|
|
1. **Pint 코드 포맷팅**
|
|
```bash
|
|
cd api && ./vendor/bin/pint
|
|
```
|
|
|
|
2. **Swagger 생성 테스트**
|
|
```bash
|
|
cd api && php artisan l5-swagger:generate
|
|
```
|
|
|
|
3. **API 테스트** (Swagger UI)
|
|
- Product CRUD 정상 동작 확인
|
|
- Material CRUD 정상 동작 확인
|
|
- 코드 중복 체크 동작 확인
|
|
- BOM 사용 중 삭제 방지 확인
|
|
|
|
**체크리스트**:
|
|
- [ ] Pint 실행 및 오류 수정
|
|
- [ ] Swagger 생성 성공
|
|
- [ ] Product CRUD 테스트
|
|
- [ ] Material CRUD 테스트
|
|
|
|
---
|
|
|
|
## 5. 예상 시간
|
|
|
|
| Phase | 작업 | 예상 시간 |
|
|
|-------|------|----------|
|
|
| 0 | Seeder 수정 | 5분 |
|
|
| 1 | ItemTypeHelper 생성 | 10분 |
|
|
| 2 | FormRequest 수정 (3개) | 10분 |
|
|
| 3 | ItemsService 수정 | 20분 |
|
|
| 4 | ItemsController 수정 | 5분 |
|
|
| 5 | Swagger 문서 수정 | 15분 |
|
|
| 6 | 검증 | 10분 |
|
|
| **총계** | | **약 75분** |
|
|
|
|
---
|
|
|
|
## 6. 아키텍처 다이어그램
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ API Request │
|
|
│ item_type = 'FG' | 'SM' | ... │
|
|
└─────────────────────────┬───────────────────────────────────┘
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ ItemTypeHelper │
|
|
│ ┌─────────────────────────────────────────────────────┐ │
|
|
│ │ getTypesByTenant($tenantId) │ │
|
|
│ │ → Cache::remember("item_types:tenant:{id}") │ │
|
|
│ │ → CommonCode::where('code_group', 'item_type') │ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
│ ┌─────────────────────────────────────────────────────┐ │
|
|
│ │ isMaterial($itemType, $tenantId) │ │
|
|
│ │ isProduct($itemType, $tenantId) │ │
|
|
│ │ getSourceTable($itemType, $tenantId) │ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
└─────────────────────────┬───────────────────────────────────┘
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ ItemsService │
|
|
│ if (ItemTypeHelper::isMaterial($itemType, $tenantId)) { │
|
|
│ → materials 테이블 │
|
|
│ } else { │
|
|
│ → products 테이블 │
|
|
│ } │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 7. 변경 이력
|
|
|
|
| 날짜 | 내용 |
|
|
|------|------|
|
|
| 2025-12-09 | 통합 문서 작성 (items-naming-convention.md + items-api-modification-plan.md 병합) |
|
|
|
|
---
|
|
|
|
## 8. API별 Request/Response 예시
|
|
|
|
### 8.1 품목 목록 조회 (GET /api/v1/items)
|
|
|
|
> 🔧 **변경**: `specification` 추가 + `attributes` 플랫 전개 + 페이지네이션 구조 변경
|
|
|
|
**Request:**
|
|
```http
|
|
GET /api/v1/items?type=FG,PT&search=스크린&page=1&size=20
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "조회되었습니다.",
|
|
"data": [
|
|
{
|
|
"id": 1,
|
|
"item_type": "FG",
|
|
"code": "P-001",
|
|
"name": "스크린 제품 A",
|
|
"specification": null,
|
|
"unit": "EA",
|
|
"category_id": 1,
|
|
"color": "white",
|
|
"size": "100x200",
|
|
"created_at": "2025-12-01 10:00:00",
|
|
"deleted_at": null
|
|
},
|
|
{
|
|
"id": 5,
|
|
"item_type": "RM",
|
|
"code": "M-001",
|
|
"name": "스크린 원단",
|
|
"specification": "1.2T x 1219 x 2438",
|
|
"unit": "M",
|
|
"category_id": 2,
|
|
"thickness": "1.2T",
|
|
"width": "1219",
|
|
"created_at": "2025-12-01 11:00:00",
|
|
"deleted_at": null
|
|
}
|
|
],
|
|
"pagination": {
|
|
"current_page": 1,
|
|
"per_page": 20,
|
|
"total": 2,
|
|
"last_page": 1,
|
|
"from": 1,
|
|
"to": 2
|
|
}
|
|
}
|
|
```
|
|
|
|
**응답 구조:**
|
|
|
|
| 키 | 타입 | 설명 |
|
|
|----|------|------|
|
|
| `success` | boolean | 성공 여부 |
|
|
| `message` | string | 메시지 |
|
|
| `data` | array | **품목 데이터 배열** (바로 접근 가능) |
|
|
| `pagination` | object | **페이지네이션 정보** |
|
|
|
|
**data[] 필드 설명:**
|
|
|
|
| 필드 | Product | Material | 설명 |
|
|
|------|---------|----------|------|
|
|
| `id` | ✅ | ✅ | 품목 ID |
|
|
| `item_type` | FG/PT | SM/RM/CS | 품목 유형 |
|
|
| `code` | ✅ | ✅ | 품목 코드 |
|
|
| `name` | ✅ | ✅ | 품목명 |
|
|
| `specification` | null | ✅ | 규격 (Material만 컬럼 존재) |
|
|
| `unit` | ✅ | ✅ | 단위 |
|
|
| `category_id` | ✅ | ✅ | 카테고리 ID |
|
|
| `{attr_key}` | ✅ | ✅ | **attributes JSON 플랫 전개** (동적 필드) |
|
|
| `created_at` | ✅ | ✅ | 생성일 |
|
|
| `deleted_at` | ✅ | ✅ | 삭제일 (soft delete) |
|
|
|
|
**pagination 필드 설명:**
|
|
|
|
| 필드 | 타입 | 설명 |
|
|
|------|------|------|
|
|
| `current_page` | int | 현재 페이지 |
|
|
| `per_page` | int | 페이지당 항목 수 |
|
|
| `total` | int | 전체 항목 수 |
|
|
| `last_page` | int | 마지막 페이지 |
|
|
| `from` | int | 현재 페이지 시작 번호 |
|
|
| `to` | int | 현재 페이지 끝 번호 |
|
|
|
|
**⚠️ 구현 (ItemsService.getItems()):**
|
|
```php
|
|
// 1. attributes 플랫 전개
|
|
$items->getCollection()->transform(function ($item) {
|
|
$data = $item->toArray();
|
|
$attributes = $data['attributes'] ?? [];
|
|
unset($data['attributes']);
|
|
return array_merge($data, $attributes);
|
|
});
|
|
|
|
// 2. 페이지네이션 구조 변환 (Controller에서)
|
|
return [
|
|
'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(),
|
|
],
|
|
];
|
|
```
|
|
|
|
---
|
|
|
|
### 8.2 품목 생성 (POST /api/v1/items)
|
|
|
|
> 🔧 **변경**: Request/Response 모두 플랫 구조 (품목기준관리 필드 기반)
|
|
|
|
#### Product 생성 (FG/PT)
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"code": "P-002",
|
|
"name": "스크린 제품 B",
|
|
"product_type": "FG",
|
|
"unit": "EA",
|
|
"category_id": 1,
|
|
"description": "완제품 설명",
|
|
"is_sellable": true,
|
|
"is_purchasable": false,
|
|
"is_producible": true,
|
|
"safety_stock": 10,
|
|
"lead_time": 7,
|
|
"color": "white",
|
|
"size": "100x200"
|
|
}
|
|
```
|
|
|
|
> **참고**: 모든 필드는 플랫하게 전송. 백엔드에서 품목기준관리(item_fields) 정의에 따라 고정 컬럼과 attributes JSON 저장을 자동 분리.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "품목이 등록되었습니다.",
|
|
"data": {
|
|
"id": 2,
|
|
"tenant_id": 1,
|
|
"code": "P-002",
|
|
"name": "스크린 제품 B",
|
|
"product_type": "FG",
|
|
"unit": "EA",
|
|
"category_id": 1,
|
|
"description": "완제품 설명",
|
|
"is_sellable": true,
|
|
"is_purchasable": false,
|
|
"is_producible": true,
|
|
"is_active": true,
|
|
"safety_stock": 10,
|
|
"lead_time": 7,
|
|
"color": "white",
|
|
"size": "100x200",
|
|
"created_at": "2025-12-09 10:00:00",
|
|
"updated_at": "2025-12-09 10:00:00"
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Material 생성 (SM/RM/CS)
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"code": "M-002",
|
|
"name": "철판 1.2T",
|
|
"product_type": "RM",
|
|
"unit": "EA",
|
|
"category_id": 2,
|
|
"item_name": "철판",
|
|
"specification": "1.2T x 1219 x 2438",
|
|
"is_inspection": "Y",
|
|
"search_tag": "철판,원자재,1.2T",
|
|
"remarks": "포스코산",
|
|
"thickness": "1.2T",
|
|
"width": "1219"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "품목이 등록되었습니다.",
|
|
"data": {
|
|
"id": 10,
|
|
"tenant_id": 1,
|
|
"material_code": "M-002",
|
|
"name": "철판 1.2T",
|
|
"material_type": "RM",
|
|
"unit": "EA",
|
|
"category_id": 2,
|
|
"item_name": "철판",
|
|
"specification": "1.2T x 1219 x 2438",
|
|
"is_inspection": "Y",
|
|
"search_tag": "철판,원자재,1.2T",
|
|
"remarks": "포스코산",
|
|
"thickness": "1.2T",
|
|
"width": "1219",
|
|
"is_active": true,
|
|
"created_at": "2025-12-09 10:05:00",
|
|
"updated_at": "2025-12-09 10:05:00"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 8.3 품목 상세 조회 - ID (GET /api/v1/items/{id})
|
|
|
|
> 🔧 **변경**: `item_type` 파라미터 값 변경 + `attributes` 플랫 전개
|
|
|
|
#### Before (현재)
|
|
```http
|
|
GET /api/v1/items/1?item_type=PRODUCT&include_price=true
|
|
GET /api/v1/items/10?item_type=MATERIAL
|
|
```
|
|
|
|
#### After (변경 후)
|
|
```http
|
|
GET /api/v1/items/1?item_type=FG&include_price=true
|
|
GET /api/v1/items/10?item_type=RM
|
|
```
|
|
|
|
**Response (Product):**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "조회되었습니다.",
|
|
"data": {
|
|
"id": 1,
|
|
"item_type": "FG",
|
|
"code": "P-001",
|
|
"name": "스크린 제품 A",
|
|
"unit": "EA",
|
|
"category_id": 1,
|
|
"category": {
|
|
"id": 1,
|
|
"name": "완제품"
|
|
},
|
|
"description": "완제품 설명",
|
|
"is_sellable": true,
|
|
"is_purchasable": false,
|
|
"is_producible": true,
|
|
"is_active": true,
|
|
"safety_stock": 10,
|
|
"lead_time": 7,
|
|
"color": "white",
|
|
"size": "100x200",
|
|
"prices": {
|
|
"sale": {
|
|
"price": 150000,
|
|
"currency": "KRW",
|
|
"effective_from": "2025-01-01"
|
|
},
|
|
"purchase": null
|
|
},
|
|
"created_at": "2025-12-01 10:00:00",
|
|
"updated_at": "2025-12-01 10:00:00"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response (Material):**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "조회되었습니다.",
|
|
"data": {
|
|
"id": 10,
|
|
"item_type": "RM",
|
|
"code": "M-002",
|
|
"name": "철판 1.2T",
|
|
"unit": "EA",
|
|
"category_id": 2,
|
|
"item_name": "철판",
|
|
"specification": "1.2T x 1219 x 2438",
|
|
"is_inspection": "Y",
|
|
"search_tag": "철판,원자재,1.2T",
|
|
"remarks": "포스코산",
|
|
"thickness": "1.2T",
|
|
"width": "1219",
|
|
"is_active": true,
|
|
"created_at": "2025-12-09 10:05:00",
|
|
"updated_at": "2025-12-09 10:05:00"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 8.4 품목 상세 조회 - 코드 (GET /api/v1/items/code/{code})
|
|
|
|
> 🔧 **변경**: Material 코드 조회 지원 추가 + `attributes` 플랫 전개
|
|
|
|
#### Before (현재)
|
|
```http
|
|
GET /api/v1/items/code/P-001?include_bom=true # ✅ Product만 지원
|
|
GET /api/v1/items/code/M-002 # ❌ 404 에러
|
|
```
|
|
|
|
#### After (변경 후)
|
|
```http
|
|
GET /api/v1/items/code/P-001?include_bom=true # ✅ Product 조회
|
|
GET /api/v1/items/code/M-002 # ✅ Material 조회
|
|
```
|
|
|
|
**Response (Product):**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "품목을 조회했습니다.",
|
|
"data": {
|
|
"id": 1,
|
|
"item_type": "FG",
|
|
"code": "P-001",
|
|
"name": "스크린 제품 A",
|
|
"unit": "EA",
|
|
"category_id": 1,
|
|
"category": {
|
|
"id": 1,
|
|
"name": "완제품"
|
|
},
|
|
"description": "완제품 설명",
|
|
"is_sellable": true,
|
|
"is_purchasable": false,
|
|
"is_producible": true,
|
|
"is_active": true,
|
|
"safety_stock": 10,
|
|
"lead_time": 7,
|
|
"color": "white",
|
|
"size": "100x200",
|
|
"component_lines": [
|
|
{
|
|
"id": 1,
|
|
"child_product": {
|
|
"id": 2,
|
|
"code": "PT-001",
|
|
"name": "스크린 본체",
|
|
"unit": "EA"
|
|
},
|
|
"quantity": 1
|
|
}
|
|
],
|
|
"created_at": "2025-12-01 10:00:00",
|
|
"updated_at": "2025-12-01 10:00:00"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response (Material):**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "품목을 조회했습니다.",
|
|
"data": {
|
|
"id": 10,
|
|
"item_type": "RM",
|
|
"code": "M-002",
|
|
"name": "철판 1.2T",
|
|
"unit": "EA",
|
|
"category_id": 2,
|
|
"item_name": "철판",
|
|
"specification": "1.2T x 1219 x 2438",
|
|
"is_inspection": "Y",
|
|
"search_tag": "철판,원자재,1.2T",
|
|
"remarks": "포스코산",
|
|
"thickness": "1.2T",
|
|
"width": "1219",
|
|
"is_active": true,
|
|
"created_at": "2025-12-09 10:05:00",
|
|
"updated_at": "2025-12-09 10:05:00"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 8.5 품목 수정 (PUT /api/v1/items/{id})
|
|
|
|
> 🔧 **변경**: `item_type` 필수화 + Material 수정 지원 + `attributes` 플랫 전개
|
|
|
|
#### Before (현재)
|
|
```http
|
|
PUT /api/v1/items/1
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"name": "스크린 제품 A (수정)" # ✅ Product만 지원
|
|
}
|
|
```
|
|
|
|
#### After (변경 후)
|
|
|
|
**Product 수정:**
|
|
```http
|
|
PUT /api/v1/items/1
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"item_type": "FG",
|
|
"name": "스크린 제품 A (수정)",
|
|
"description": "수정된 설명",
|
|
"safety_stock": 20,
|
|
"color": "black",
|
|
"size": "150x300"
|
|
}
|
|
```
|
|
|
|
> **참고**: 모든 필드는 플랫하게 전송. 백엔드에서 품목기준관리(item_fields) 정의에 따라 고정 컬럼과 attributes JSON 저장을 자동 분리.
|
|
|
|
**Response (Product):**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "품목이 수정되었습니다.",
|
|
"data": {
|
|
"id": 1,
|
|
"item_type": "FG",
|
|
"code": "P-001",
|
|
"name": "스크린 제품 A (수정)",
|
|
"unit": "EA",
|
|
"category_id": 1,
|
|
"description": "수정된 설명",
|
|
"is_sellable": true,
|
|
"is_purchasable": false,
|
|
"is_producible": true,
|
|
"is_active": true,
|
|
"safety_stock": 20,
|
|
"lead_time": 7,
|
|
"color": "black",
|
|
"size": "150x300",
|
|
"created_at": "2025-12-01 10:00:00",
|
|
"updated_at": "2025-12-09 11:00:00"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Material 수정:**
|
|
```http
|
|
PUT /api/v1/items/10
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"item_type": "RM",
|
|
"name": "철판 1.5T",
|
|
"specification": "1.5T x 1219 x 2438",
|
|
"remarks": "포스코산 (규격 변경)",
|
|
"thickness": "1.5T",
|
|
"width": "1219"
|
|
}
|
|
```
|
|
|
|
**Response (Material):**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "품목이 수정되었습니다.",
|
|
"data": {
|
|
"id": 10,
|
|
"item_type": "RM",
|
|
"code": "M-002",
|
|
"name": "철판 1.5T",
|
|
"unit": "EA",
|
|
"category_id": 2,
|
|
"item_name": "철판",
|
|
"specification": "1.5T x 1219 x 2438",
|
|
"is_inspection": "Y",
|
|
"search_tag": "철판,원자재,1.5T",
|
|
"remarks": "포스코산 (규격 변경)",
|
|
"thickness": "1.5T",
|
|
"width": "1219",
|
|
"is_active": true,
|
|
"created_at": "2025-12-09 10:05:00",
|
|
"updated_at": "2025-12-09 11:05:00"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 8.6 품목 삭제 (DELETE /api/v1/items/{id})
|
|
|
|
> 🔧 **변경**: `item_type` 쿼리 파라미터 추가 + Material 삭제 지원
|
|
|
|
#### Before (현재)
|
|
```http
|
|
DELETE /api/v1/items/1 # ✅ Product만 삭제
|
|
DELETE /api/v1/items/10 # ❌ Material은 404 에러
|
|
```
|
|
|
|
#### After (변경 후)
|
|
```http
|
|
DELETE /api/v1/items/1?item_type=FG # ✅ Product 삭제
|
|
DELETE /api/v1/items/10?item_type=RM # ✅ Material 삭제
|
|
```
|
|
|
|
**Response (성공):**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "품목이 삭제되었습니다.",
|
|
"data": "success"
|
|
}
|
|
```
|
|
|
|
**Response (BOM 사용 중 - 삭제 불가):**
|
|
```json
|
|
{
|
|
"success": false,
|
|
"message": "다른 BOM의 구성품으로 사용 중입니다. (3건)",
|
|
"error": {
|
|
"code": "ITEM_IN_USE",
|
|
"count": 3
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 8.7 품목 일괄 삭제 (DELETE /api/v1/items/batch)
|
|
|
|
> 🔧 **변경**: `item_type` 필드 추가 + Material 일괄 삭제 지원
|
|
|
|
#### Before (현재)
|
|
```http
|
|
DELETE /api/v1/items/batch
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"ids": [1, 2, 3] # ✅ Product만 삭제
|
|
}
|
|
```
|
|
|
|
#### After (변경 후)
|
|
|
|
**Product 일괄 삭제:**
|
|
```http
|
|
DELETE /api/v1/items/batch
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"item_type": "FG",
|
|
"ids": [1, 2, 3]
|
|
}
|
|
```
|
|
|
|
**Material 일괄 삭제:**
|
|
```http
|
|
DELETE /api/v1/items/batch
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"item_type": "RM",
|
|
"ids": [10, 11, 12]
|
|
}
|
|
```
|
|
|
|
**Response (성공):**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "품목이 일괄 삭제되었습니다.",
|
|
"data": "success"
|
|
}
|
|
```
|
|
|
|
**Response (일부 BOM 사용 중):**
|
|
```json
|
|
{
|
|
"success": false,
|
|
"message": "일부 품목이 BOM 구성품으로 사용 중입니다. (2건)",
|
|
"error": {
|
|
"code": "ITEMS_IN_USE",
|
|
"count": 2
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 8.8 에러 응답 예시
|
|
|
|
**404 Not Found:**
|
|
```json
|
|
{
|
|
"success": false,
|
|
"message": "품목을 찾을 수 없습니다.",
|
|
"error": {
|
|
"code": "NOT_FOUND"
|
|
}
|
|
}
|
|
```
|
|
|
|
**400 Bad Request (코드 중복):**
|
|
```json
|
|
{
|
|
"success": false,
|
|
"message": "이미 사용 중인 품목코드입니다.",
|
|
"error": {
|
|
"code": "DUPLICATE_CODE"
|
|
}
|
|
}
|
|
```
|
|
|
|
**422 Validation Error:**
|
|
```json
|
|
{
|
|
"success": false,
|
|
"message": "입력값이 올바르지 않습니다.",
|
|
"errors": {
|
|
"item_type": ["품목 유형은 필수입니다."],
|
|
"name": ["품목명은 255자 이내로 입력하세요."]
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 9. 관련 문서 (삭제 완료)
|
|
|
|
아래 문서들은 이 통합 문서로 병합되어 삭제되었습니다:
|
|
- ~~`docs/plans/items-naming-convention.md`~~
|
|
- ~~`docs/plans/items-api-modification-plan.md`~~ |