Files
sam-docs/plans/items-api-unified-plan.md
hskwon 26aeecd865 docs: Items API 통합 개발 계획 추가
- item_type 명명 체계 통일 계획
- Material CRUD 지원 추가 계획
- Phase 0 Seeder 수정 완료 체크
2025-12-09 20:28:47 +09:00

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`~~