diff --git a/plans/items-api-unified-plan.md b/plans/items-api-unified-plan.md new file mode 100644 index 0000000..a18f92f --- /dev/null +++ b/plans/items-api-unified-plan.md @@ -0,0 +1,1095 @@ +# 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`~~ \ No newline at end of file