docs: API 문서 및 테스트 시나리오 추가
- front/item-master-items-api.md - Items API 프론트엔드 연동 문서 - guides/menu-delete-verification-queries.md - 메뉴 삭제 검증 쿼리 가이드 - plans/flow-tests/item-delete-force-delete.json - 품목 삭제 테스트 시나리오
This commit is contained in:
763
front/item-master-items-api.md
Normal file
763
front/item-master-items-api.md
Normal file
@@ -0,0 +1,763 @@
|
|||||||
|
# 품목기준관리(ItemMaster) & 품목관리(Items) API 문서
|
||||||
|
|
||||||
|
> 프론트엔드 개발자를 위한 API 스펙 문서
|
||||||
|
> 작성일: 2025-12-10
|
||||||
|
|
||||||
|
## 목차
|
||||||
|
1. [개요](#개요)
|
||||||
|
2. [품목관리 (Items) API](#품목관리-items-api)
|
||||||
|
3. [품목기준관리 (ItemMaster) API](#품목기준관리-itemmaster-api)
|
||||||
|
4. [공통 응답 형식](#공통-응답-형식)
|
||||||
|
5. [에러 처리](#에러-처리)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 개요
|
||||||
|
|
||||||
|
### 품목관리 (Items) vs 품목기준관리 (ItemMaster)
|
||||||
|
|
||||||
|
| 구분 | 품목관리 (Items) | 품목기준관리 (ItemMaster) |
|
||||||
|
|------|------------------|---------------------------|
|
||||||
|
| **역할** | 실제 품목 데이터 CRUD | 품목 입력 화면/폼 구성 관리 |
|
||||||
|
| **대상 데이터** | products, materials 테이블 | item_pages, item_sections, item_fields 테이블 |
|
||||||
|
| **사용자** | 품목 등록/수정하는 일반 사용자 | 화면 구성을 설정하는 관리자 |
|
||||||
|
| **비유** | 엑셀 데이터 | 엑셀 양식(템플릿) |
|
||||||
|
|
||||||
|
### 품목 유형 코드 (item_type / product_type)
|
||||||
|
|
||||||
|
| 코드 | 설명 | 저장 테이블 |
|
||||||
|
|------|------|-------------|
|
||||||
|
| `FG` | 완제품 (Finished Goods) | products |
|
||||||
|
| `PT` | 반제품/부품 (Part) | products |
|
||||||
|
| `SM` | 반자재 (Semi-Material) | materials |
|
||||||
|
| `RM` | 원자재 (Raw Material) | materials |
|
||||||
|
| `CS` | 소모품 (Consumables) | materials |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 품목관리 (Items) API
|
||||||
|
|
||||||
|
실제 품목 데이터를 조회/생성/수정/삭제하는 API입니다.
|
||||||
|
|
||||||
|
### 1. 통합 품목 목록 조회
|
||||||
|
|
||||||
|
**역할**: products와 materials를 통합하여 조회 (UNION 방식)
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/items
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Request Parameters (Query)
|
||||||
|
|
||||||
|
| 파라미터 | 타입 | 필수 | 설명 |
|
||||||
|
|----------|------|------|------|
|
||||||
|
| `type` | string | N | 품목 유형 필터 (쉼표 구분). 기본값: `FG,PT,SM,RM,CS` |
|
||||||
|
| `search` 또는 `q` | string | N | 검색어 (코드, 이름, 태그) |
|
||||||
|
| `category_id` | integer | N | 카테고리 ID 필터 |
|
||||||
|
| `size` | integer | N | 페이지당 항목 수. 기본값: 20 |
|
||||||
|
| `page` | integer | N | 페이지 번호. 기본값: 1 |
|
||||||
|
| `include_deleted` | boolean | N | 삭제된 항목 포함 여부. 기본값: false |
|
||||||
|
|
||||||
|
#### Request 예시
|
||||||
|
```http
|
||||||
|
GET /api/v1/items?type=FG,PT&search=모터&size=10&page=1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "조회되었습니다.",
|
||||||
|
"data": {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"item_type": "FG",
|
||||||
|
"code": "P-001",
|
||||||
|
"name": "완제품A",
|
||||||
|
"specification": null,
|
||||||
|
"unit": "EA",
|
||||||
|
"category_id": 5,
|
||||||
|
"type_code": "FG",
|
||||||
|
"created_at": "2025-01-01T00:00:00.000000Z",
|
||||||
|
"deleted_at": null,
|
||||||
|
"safety_stock": 100,
|
||||||
|
"lead_time": 7
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"current_page": 1,
|
||||||
|
"per_page": 20,
|
||||||
|
"total": 150,
|
||||||
|
"last_page": 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Response 필드 설명
|
||||||
|
|
||||||
|
| 필드 | 설명 |
|
||||||
|
|------|------|
|
||||||
|
| `id` | 품목 고유 ID (테이블별 독립) |
|
||||||
|
| `item_type` | 품목 유형 코드 |
|
||||||
|
| `code` | 품목 코드 |
|
||||||
|
| `name` | 품목명 |
|
||||||
|
| `specification` | 규격 (materials만 해당) |
|
||||||
|
| `unit` | 단위 (EA, KG, M 등) |
|
||||||
|
| `category_id` | 카테고리 ID |
|
||||||
|
| `type_code` | 품목 유형 코드 (item_type과 동일) |
|
||||||
|
| `created_at` | 생성일시 |
|
||||||
|
| `deleted_at` | 삭제일시 (Soft Delete) |
|
||||||
|
| `safety_stock` | 안전재고 (attributes에서 플랫 전개) |
|
||||||
|
| `lead_time` | 리드타임 (attributes에서 플랫 전개) |
|
||||||
|
|
||||||
|
> **Note**: `attributes` JSON 필드는 자동으로 최상위로 플랫 전개되어 반환됩니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. 단일 품목 조회 (ID 기반)
|
||||||
|
|
||||||
|
**역할**: 특정 ID의 품목 상세 정보 조회 (가격 정보 옵션)
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/items/{id}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Path Parameters
|
||||||
|
|
||||||
|
| 파라미터 | 타입 | 필수 | 설명 |
|
||||||
|
|----------|------|------|------|
|
||||||
|
| `id` | integer | Y | 품목 ID |
|
||||||
|
|
||||||
|
#### Query Parameters
|
||||||
|
|
||||||
|
| 파라미터 | 타입 | 필수 | 설명 |
|
||||||
|
|----------|------|------|------|
|
||||||
|
| `item_type` | string | N | 품목 유형 코드. 기본값: `FG` |
|
||||||
|
| `include_price` | boolean | N | 가격 정보 포함 여부. 기본값: false |
|
||||||
|
| `client_id` | integer | N | 고객 ID (가격 조회 시) |
|
||||||
|
| `price_date` | string | N | 가격 기준일 (YYYY-MM-DD) |
|
||||||
|
|
||||||
|
#### Request 예시
|
||||||
|
```http
|
||||||
|
GET /api/v1/items/123?item_type=SM&include_price=true&client_id=5
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "조회되었습니다.",
|
||||||
|
"data": {
|
||||||
|
"id": 123,
|
||||||
|
"item_type": "SM",
|
||||||
|
"code": "M-001",
|
||||||
|
"name": "반자재A",
|
||||||
|
"specification": "10mm x 20mm",
|
||||||
|
"unit": "EA",
|
||||||
|
"category_id": 10,
|
||||||
|
"category": {
|
||||||
|
"id": 10,
|
||||||
|
"name": "철강류"
|
||||||
|
},
|
||||||
|
"type_code": "SM",
|
||||||
|
"prices": {
|
||||||
|
"sale": {
|
||||||
|
"unit_price": 15000,
|
||||||
|
"currency": "KRW",
|
||||||
|
"effective_from": "2025-01-01"
|
||||||
|
},
|
||||||
|
"purchase": {
|
||||||
|
"unit_price": 10000,
|
||||||
|
"currency": "KRW",
|
||||||
|
"effective_from": "2025-01-01"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. 단일 품목 조회 (코드 기반)
|
||||||
|
|
||||||
|
**역할**: 품목 코드로 상세 정보 조회 (Product → Material 순서로 검색)
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/items/code/{code}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Path Parameters
|
||||||
|
|
||||||
|
| 파라미터 | 타입 | 필수 | 설명 |
|
||||||
|
|----------|------|------|------|
|
||||||
|
| `code` | string | Y | 품목 코드 |
|
||||||
|
|
||||||
|
#### Query Parameters
|
||||||
|
|
||||||
|
| 파라미터 | 타입 | 필수 | 설명 |
|
||||||
|
|----------|------|------|------|
|
||||||
|
| `include_bom` | boolean | N | BOM 정보 포함 여부. 기본값: false |
|
||||||
|
|
||||||
|
#### Request 예시
|
||||||
|
```http
|
||||||
|
GET /api/v1/items/code/P-001?include_bom=true
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. 품목 생성
|
||||||
|
|
||||||
|
**역할**: 새 품목 등록 (product_type에 따라 products 또는 materials에 저장)
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v1/items
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Request Body
|
||||||
|
|
||||||
|
| 필드 | 타입 | 필수 | 설명 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `code` | string | Y | 품목 코드 (최대 50자, 중복 시 자동 증가) |
|
||||||
|
| `name` | string | Y | 품목명 (최대 255자) |
|
||||||
|
| `product_type` | string | Y | 품목 유형: `FG`, `PT`, `SM`, `RM`, `CS` |
|
||||||
|
| `unit` | string | Y | 단위 (최대 20자) |
|
||||||
|
| `category_id` | integer | N | 카테고리 ID |
|
||||||
|
| `description` | string | N | 설명 |
|
||||||
|
| `is_sellable` | boolean | N | 판매 가능 여부. 기본값: true |
|
||||||
|
| `is_purchasable` | boolean | N | 구매 가능 여부. 기본값: false |
|
||||||
|
| `is_producible` | boolean | N | 생산 가능 여부. 기본값: false |
|
||||||
|
| `safety_stock` | integer | N | 안전재고 (0 이상) |
|
||||||
|
| `lead_time` | integer | N | 리드타임 (0 이상) |
|
||||||
|
| `is_variable_size` | boolean | N | 가변 사이즈 여부 |
|
||||||
|
| `product_category` | string | N | 제품 분류 (최대 20자) |
|
||||||
|
| `part_type` | string | N | 부품 유형 (최대 20자) |
|
||||||
|
| `attributes` | object | N | 동적 필드 (JSON) |
|
||||||
|
| `material_code` | string | N | 자재 코드 (Material 전용) |
|
||||||
|
| `item_name` | string | N | 품명 (Material 전용) |
|
||||||
|
| `specification` | string | N | 규격 (Material 전용) |
|
||||||
|
| `is_inspection` | string | N | 검수 여부: `Y`, `N` |
|
||||||
|
| `search_tag` | string | N | 검색 태그 |
|
||||||
|
| `remarks` | string | N | 비고 |
|
||||||
|
| `options` | object | N | 옵션 (JSON) |
|
||||||
|
|
||||||
|
#### Request 예시
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "P-NEW-001",
|
||||||
|
"name": "신규 완제품",
|
||||||
|
"product_type": "FG",
|
||||||
|
"unit": "EA",
|
||||||
|
"category_id": 5,
|
||||||
|
"is_sellable": true,
|
||||||
|
"safety_stock": 50,
|
||||||
|
"attributes": {
|
||||||
|
"color": "red",
|
||||||
|
"size": "L"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "품목이 생성되었습니다.",
|
||||||
|
"data": {
|
||||||
|
"id": 999,
|
||||||
|
"code": "P-NEW-001",
|
||||||
|
"name": "신규 완제품",
|
||||||
|
"product_type": "FG",
|
||||||
|
"unit": "EA",
|
||||||
|
"category_id": 5,
|
||||||
|
"is_active": true,
|
||||||
|
"is_sellable": true,
|
||||||
|
"is_purchasable": false,
|
||||||
|
"is_producible": false,
|
||||||
|
"created_by": 1,
|
||||||
|
"created_at": "2025-12-10T10:00:00.000000Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **중복 코드 처리**: 코드가 이미 존재하면 자동으로 증가합니다.
|
||||||
|
> - `P-001` 중복 → `P-002`
|
||||||
|
> - `ABC` 중복 → `ABC-001`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. 품목 수정
|
||||||
|
|
||||||
|
**역할**: 기존 품목 정보 수정
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /api/v1/items/{id}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Path Parameters
|
||||||
|
|
||||||
|
| 파라미터 | 타입 | 필수 | 설명 |
|
||||||
|
|----------|------|------|------|
|
||||||
|
| `id` | integer | Y | 품목 ID |
|
||||||
|
|
||||||
|
#### Request Body
|
||||||
|
|
||||||
|
| 필드 | 타입 | 필수 | 설명 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `item_type` | string | Y | 품목 유형 (테이블 분기용) |
|
||||||
|
| `code` | string | N | 품목 코드 |
|
||||||
|
| `name` | string | N | 품목명 |
|
||||||
|
| ... | | | (생성과 동일한 필드, 모두 선택) |
|
||||||
|
|
||||||
|
#### Request 예시
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"item_type": "FG",
|
||||||
|
"name": "수정된 완제품명",
|
||||||
|
"safety_stock": 100
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "품목이 수정되었습니다.",
|
||||||
|
"data": {
|
||||||
|
"id": 999,
|
||||||
|
"code": "P-NEW-001",
|
||||||
|
"name": "수정된 완제품명",
|
||||||
|
"safety_stock": 100,
|
||||||
|
"updated_by": 1,
|
||||||
|
"updated_at": "2025-12-10T11:00:00.000000Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. 품목 삭제 (Soft Delete)
|
||||||
|
|
||||||
|
**역할**: 품목 삭제 (BOM 구성품으로 사용 중이면 삭제 불가)
|
||||||
|
|
||||||
|
```
|
||||||
|
DELETE /api/v1/items/{id}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Path Parameters
|
||||||
|
|
||||||
|
| 파라미터 | 타입 | 필수 | 설명 |
|
||||||
|
|----------|------|------|------|
|
||||||
|
| `id` | integer | Y | 품목 ID |
|
||||||
|
|
||||||
|
#### Query Parameters
|
||||||
|
|
||||||
|
| 파라미터 | 타입 | 필수 | 설명 |
|
||||||
|
|----------|------|------|------|
|
||||||
|
| `item_type` | string | N | 품목 유형. 기본값: `FG` |
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "품목이 삭제되었습니다.",
|
||||||
|
"data": "success"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 에러 Response (BOM 사용 중)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "해당 품목은 3건의 BOM에서 구성품으로 사용 중입니다."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. 품목 일괄 삭제
|
||||||
|
|
||||||
|
**역할**: 여러 품목 일괄 삭제
|
||||||
|
|
||||||
|
```
|
||||||
|
DELETE /api/v1/items/batch
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Request Body
|
||||||
|
|
||||||
|
| 필드 | 타입 | 필수 | 설명 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `item_type` | string | Y | 품목 유형 |
|
||||||
|
| `ids` | array | Y | 삭제할 품목 ID 배열 |
|
||||||
|
|
||||||
|
#### Request 예시
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"item_type": "FG",
|
||||||
|
"ids": [1, 2, 3, 4, 5]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 품목기준관리 (ItemMaster) API
|
||||||
|
|
||||||
|
품목 입력 화면의 구성(페이지, 섹션, 필드)을 관리하는 API입니다.
|
||||||
|
|
||||||
|
### 구조 개요
|
||||||
|
|
||||||
|
```
|
||||||
|
ItemMaster 계층 구조:
|
||||||
|
|
||||||
|
Page (페이지)
|
||||||
|
├── Section (섹션) - fields 타입
|
||||||
|
│ ├── Field (필드)
|
||||||
|
│ ├── Field (필드)
|
||||||
|
│ └── Field (필드)
|
||||||
|
└── Section (섹션) - bom 타입
|
||||||
|
├── BomItem (BOM 항목)
|
||||||
|
└── BomItem (BOM 항목)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Page**: 품목 유형별 입력 화면 (예: 완제품 등록 페이지)
|
||||||
|
- **Section**: 페이지 내 영역 구분 (예: 기본정보, BOM 구성)
|
||||||
|
- **Field**: 입력 필드 정의 (예: 품목코드, 품목명)
|
||||||
|
- **BomItem**: BOM 섹션의 구성품 정의
|
||||||
|
|
||||||
|
### 1. 초기화 데이터 로드
|
||||||
|
|
||||||
|
**역할**: 프론트엔드 앱 초기화 시 필요한 전체 ItemMaster 데이터 로드
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/item-master/init
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "조회되었습니다.",
|
||||||
|
"data": {
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"tenant_id": 1,
|
||||||
|
"group_id": null,
|
||||||
|
"page_name": "완제품 등록",
|
||||||
|
"item_type": "FG",
|
||||||
|
"absolute_path": "/items/fg/create",
|
||||||
|
"is_active": true,
|
||||||
|
"sections": [
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"title": "기본정보",
|
||||||
|
"type": "fields",
|
||||||
|
"order_no": 1,
|
||||||
|
"is_locked": false,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"id": 100,
|
||||||
|
"field_name": "품목코드",
|
||||||
|
"field_key": "code",
|
||||||
|
"field_type": "textbox",
|
||||||
|
"is_required": true,
|
||||||
|
"order_no": 1,
|
||||||
|
"is_locked": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sections": [...],
|
||||||
|
"fields": [...],
|
||||||
|
"customTabs": [...],
|
||||||
|
"unitOptions": [...]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Response 필드 설명
|
||||||
|
|
||||||
|
| 필드 | 설명 |
|
||||||
|
|------|------|
|
||||||
|
| `pages` | 페이지 목록 (섹션, 필드 중첩 포함) |
|
||||||
|
| `sections` | 모든 독립 섹션 목록 (재사용 가능) |
|
||||||
|
| `fields` | 모든 독립 필드 목록 (재사용 가능) |
|
||||||
|
| `customTabs` | 커스텀 탭 목록 (컬럼 설정 포함) |
|
||||||
|
| `unitOptions` | 단위 옵션 목록 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. 페이지 API
|
||||||
|
|
||||||
|
#### 페이지 목록 조회
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/item-master/pages
|
||||||
|
```
|
||||||
|
|
||||||
|
| Query 파라미터 | 타입 | 필수 | 설명 |
|
||||||
|
|----------------|------|------|------|
|
||||||
|
| `item_type` | string | N | 품목 유형 필터 |
|
||||||
|
|
||||||
|
#### 페이지 생성
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v1/item-master/pages
|
||||||
|
```
|
||||||
|
|
||||||
|
| Request Body | 타입 | 필수 | 설명 |
|
||||||
|
|--------------|------|------|------|
|
||||||
|
| `page_name` | string | Y | 페이지명 (최대 255자) |
|
||||||
|
| `item_type` | string | Y | 품목 유형: `FG`, `PT`, `SM`, `RM`, `CS` |
|
||||||
|
| `absolute_path` | string | N | 절대 경로 (최대 500자) |
|
||||||
|
|
||||||
|
#### 페이지 수정
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /api/v1/item-master/pages/{id}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 페이지 삭제
|
||||||
|
|
||||||
|
```
|
||||||
|
DELETE /api/v1/item-master/pages/{id}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. 섹션 API
|
||||||
|
|
||||||
|
#### 독립 섹션 목록 조회
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/item-master/sections
|
||||||
|
```
|
||||||
|
|
||||||
|
| Query 파라미터 | 타입 | 필수 | 설명 |
|
||||||
|
|----------------|------|------|------|
|
||||||
|
| `is_template` | boolean | N | 템플릿 섹션 필터 |
|
||||||
|
|
||||||
|
#### 독립 섹션 생성 (페이지 연결 없음)
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v1/item-master/sections
|
||||||
|
```
|
||||||
|
|
||||||
|
| Request Body | 타입 | 필수 | 설명 |
|
||||||
|
|--------------|------|------|------|
|
||||||
|
| `group_id` | integer | N | 계층 번호 |
|
||||||
|
| `title` | string | Y | 섹션 제목 (최대 255자) |
|
||||||
|
| `type` | string | Y | 섹션 타입: `fields`, `bom` |
|
||||||
|
|
||||||
|
#### 페이지에 섹션 생성 (연결)
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v1/item-master/pages/{pageId}/sections
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 섹션 복제
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v1/item-master/sections/{id}/clone
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 섹션 사용처 조회
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/item-master/sections/{id}/usage
|
||||||
|
```
|
||||||
|
|
||||||
|
**역할**: 해당 섹션이 어떤 페이지에서 사용되고 있는지 조회
|
||||||
|
|
||||||
|
#### 섹션 수정
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /api/v1/item-master/sections/{id}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 섹션 삭제
|
||||||
|
|
||||||
|
```
|
||||||
|
DELETE /api/v1/item-master/sections/{id}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 섹션 순서 변경
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /api/v1/item-master/pages/{pageId}/sections/reorder
|
||||||
|
```
|
||||||
|
|
||||||
|
| Request Body | 타입 | 필수 | 설명 |
|
||||||
|
|--------------|------|------|------|
|
||||||
|
| `items` | array | Y | `[{id: 1, order_no: 1}, {id: 2, order_no: 2}]` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. 필드 API
|
||||||
|
|
||||||
|
#### 독립 필드 목록 조회
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/item-master/fields
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 독립 필드 생성 (섹션 연결 없음)
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v1/item-master/fields
|
||||||
|
```
|
||||||
|
|
||||||
|
| Request Body | 타입 | 필수 | 설명 |
|
||||||
|
|--------------|------|------|------|
|
||||||
|
| `group_id` | integer | N | 계층 번호 |
|
||||||
|
| `field_name` | string | Y | 필드 표시명 (최대 255자) |
|
||||||
|
| `field_key` | string | N | 필드 키 (최대 80자, 영문 시작) |
|
||||||
|
| `field_type` | string | Y | 필드 타입 (아래 참조) |
|
||||||
|
| `is_required` | boolean | N | 필수 입력 여부 |
|
||||||
|
| `default_value` | string | N | 기본값 |
|
||||||
|
| `placeholder` | string | N | 플레이스홀더 (최대 255자) |
|
||||||
|
| `display_condition` | object | N | 표시 조건 (JSON) |
|
||||||
|
| `validation_rules` | object | N | 검증 규칙 (JSON) |
|
||||||
|
| `options` | array | N | 드롭다운 옵션 등 (JSON) |
|
||||||
|
| `properties` | object | N | 추가 속성 (JSON) |
|
||||||
|
| `is_locked` | boolean | N | 잠금 여부 |
|
||||||
|
|
||||||
|
#### 필드 타입 (field_type)
|
||||||
|
|
||||||
|
| 타입 | 설명 |
|
||||||
|
|------|------|
|
||||||
|
| `textbox` | 한 줄 텍스트 입력 |
|
||||||
|
| `number` | 숫자 입력 |
|
||||||
|
| `dropdown` | 드롭다운 선택 |
|
||||||
|
| `checkbox` | 체크박스 |
|
||||||
|
| `date` | 날짜 선택 |
|
||||||
|
| `textarea` | 여러 줄 텍스트 입력 |
|
||||||
|
|
||||||
|
#### 섹션에 필드 생성 (연결)
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v1/item-master/sections/{sectionId}/fields
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 필드 복제
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v1/item-master/fields/{id}/clone
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 필드 사용처 조회
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/item-master/fields/{id}/usage
|
||||||
|
```
|
||||||
|
|
||||||
|
**역할**: 해당 필드가 어떤 섹션에서 사용되고 있는지 조회
|
||||||
|
|
||||||
|
#### 필드 수정
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /api/v1/item-master/fields/{id}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 필드 삭제
|
||||||
|
|
||||||
|
```
|
||||||
|
DELETE /api/v1/item-master/fields/{id}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 공통 응답 형식
|
||||||
|
|
||||||
|
모든 API는 동일한 응답 구조를 따릅니다.
|
||||||
|
|
||||||
|
### 성공 응답
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "처리되었습니다.",
|
||||||
|
"data": { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 페이지네이션 응답
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "조회되었습니다.",
|
||||||
|
"data": {
|
||||||
|
"data": [...],
|
||||||
|
"current_page": 1,
|
||||||
|
"per_page": 20,
|
||||||
|
"total": 100,
|
||||||
|
"last_page": 5,
|
||||||
|
"from": 1,
|
||||||
|
"to": 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 에러 처리
|
||||||
|
|
||||||
|
### 에러 응답 형식
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "에러 메시지",
|
||||||
|
"errors": {
|
||||||
|
"field_name": ["검증 오류 메시지"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 주요 HTTP 상태 코드
|
||||||
|
|
||||||
|
| 코드 | 설명 |
|
||||||
|
|------|------|
|
||||||
|
| `200` | 성공 |
|
||||||
|
| `201` | 생성 성공 |
|
||||||
|
| `400` | 잘못된 요청 (검증 실패, 중복 코드 등) |
|
||||||
|
| `401` | 인증 필요 |
|
||||||
|
| `403` | 권한 없음 |
|
||||||
|
| `404` | 리소스 없음 |
|
||||||
|
| `422` | 검증 실패 (Validation Error) |
|
||||||
|
| `500` | 서버 오류 |
|
||||||
|
|
||||||
|
### 주요 에러 케이스
|
||||||
|
|
||||||
|
| 상황 | 메시지 예시 |
|
||||||
|
|------|-------------|
|
||||||
|
| 품목 없음 | "해당 품목을 찾을 수 없습니다." |
|
||||||
|
| 코드 중복 | "이미 사용 중인 품목코드입니다." |
|
||||||
|
| BOM 사용 중 | "해당 품목은 N건의 BOM에서 구성품으로 사용 중입니다." |
|
||||||
|
| 필수 필드 누락 | "품목코드는 필수입니다." |
|
||||||
|
| 잘못된 품목 유형 | "품목 유형은 FG, PT, SM, RM, CS 중 하나여야 합니다." |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 인증
|
||||||
|
|
||||||
|
모든 API는 다음 인증이 필요합니다:
|
||||||
|
|
||||||
|
1. **API Key**: `X-API-KEY` 헤더
|
||||||
|
2. **Bearer Token**: `Authorization: Bearer {token}` 헤더
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/v1/items
|
||||||
|
X-API-KEY: your-api-key
|
||||||
|
Authorization: Bearer your-access-token
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 변경 이력
|
||||||
|
|
||||||
|
| 날짜 | 버전 | 변경 내용 |
|
||||||
|
|------|------|----------|
|
||||||
|
| 2025-12-10 | 1.0 | 최초 작성 |
|
||||||
198
guides/menu-delete-verification-queries.md
Normal file
198
guides/menu-delete-verification-queries.md
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
# 메뉴 삭제 검증 쿼리
|
||||||
|
|
||||||
|
메뉴 영구 삭제 전/후 연관 데이터 확인용 SQL 쿼리
|
||||||
|
|
||||||
|
## 삭제 전 확인 쿼리
|
||||||
|
|
||||||
|
### 1. 특정 메뉴의 연관 권한 조회
|
||||||
|
```sql
|
||||||
|
-- 메뉴 ID를 기준으로 연관 권한 조회
|
||||||
|
-- {MENU_ID}를 실제 메뉴 ID로 변경
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
p.id,
|
||||||
|
p.name,
|
||||||
|
p.guard_name,
|
||||||
|
p.tenant_id
|
||||||
|
FROM permissions p
|
||||||
|
WHERE p.name LIKE 'menu:{MENU_ID}.%'
|
||||||
|
ORDER BY p.name;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 역할-권한 연결 조회
|
||||||
|
```sql
|
||||||
|
-- 해당 메뉴 권한이 어떤 역할에 할당되어 있는지 확인
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
r.id AS role_id,
|
||||||
|
r.name AS role_name,
|
||||||
|
r.tenant_id,
|
||||||
|
p.name AS permission_name
|
||||||
|
FROM role_has_permissions rhp
|
||||||
|
JOIN roles r ON rhp.role_id = r.id
|
||||||
|
JOIN permissions p ON rhp.permission_id = p.id
|
||||||
|
WHERE p.name LIKE 'menu:{MENU_ID}.%'
|
||||||
|
ORDER BY r.name, p.name;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 사용자 직접 권한 조회
|
||||||
|
```sql
|
||||||
|
-- 해당 메뉴 권한이 어떤 사용자에게 직접 할당되어 있는지 확인
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
u.id AS user_id,
|
||||||
|
u.name AS user_name,
|
||||||
|
u.email,
|
||||||
|
p.name AS permission_name
|
||||||
|
FROM model_has_permissions mhp
|
||||||
|
JOIN users u ON mhp.model_id = u.id AND mhp.model_type = 'App\\Models\\User'
|
||||||
|
JOIN permissions p ON mhp.permission_id = p.id
|
||||||
|
WHERE p.name LIKE 'menu:{MENU_ID}.%'
|
||||||
|
ORDER BY u.name, p.name;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 부서 권한 조회
|
||||||
|
```sql
|
||||||
|
-- 해당 메뉴 권한이 어떤 부서에 할당되어 있는지 확인
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
d.id AS dept_id,
|
||||||
|
d.name AS dept_name,
|
||||||
|
d.tenant_id,
|
||||||
|
p.name AS permission_name
|
||||||
|
FROM model_has_permissions mhp
|
||||||
|
JOIN departments d ON mhp.model_id = d.id AND mhp.model_type = 'App\\Models\\Tenants\\Department'
|
||||||
|
JOIN permissions p ON mhp.permission_id = p.id
|
||||||
|
WHERE p.name LIKE 'menu:{MENU_ID}.%'
|
||||||
|
ORDER BY d.name, p.name;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 종합 영향도 조회 (삭제 전 확인용)
|
||||||
|
```sql
|
||||||
|
-- 삭제 시 영향받는 모든 데이터 요약
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
'permissions' AS table_name,
|
||||||
|
COUNT(*) AS record_count
|
||||||
|
FROM permissions
|
||||||
|
WHERE name LIKE 'menu:{MENU_ID}.%'
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
'role_has_permissions' AS table_name,
|
||||||
|
COUNT(*) AS record_count
|
||||||
|
FROM role_has_permissions rhp
|
||||||
|
JOIN permissions p ON rhp.permission_id = p.id
|
||||||
|
WHERE p.name LIKE 'menu:{MENU_ID}.%'
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
'model_has_permissions (users)' AS table_name,
|
||||||
|
COUNT(*) AS record_count
|
||||||
|
FROM model_has_permissions mhp
|
||||||
|
JOIN permissions p ON mhp.permission_id = p.id
|
||||||
|
WHERE p.name LIKE 'menu:{MENU_ID}.%'
|
||||||
|
AND mhp.model_type = 'App\\Models\\User'
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
'model_has_permissions (departments)' AS table_name,
|
||||||
|
COUNT(*) AS record_count
|
||||||
|
FROM model_has_permissions mhp
|
||||||
|
JOIN permissions p ON mhp.permission_id = p.id
|
||||||
|
WHERE p.name LIKE 'menu:{MENU_ID}.%'
|
||||||
|
AND mhp.model_type = 'App\\Models\\Tenants\\Department';
|
||||||
|
```
|
||||||
|
|
||||||
|
## 삭제 후 확인 쿼리
|
||||||
|
|
||||||
|
### 1. 메뉴가 삭제되었는지 확인
|
||||||
|
```sql
|
||||||
|
-- 메뉴 테이블에서 해당 ID 확인 (soft delete 포함)
|
||||||
|
SELECT id, name, deleted_at FROM menus WHERE id = {MENU_ID};
|
||||||
|
|
||||||
|
-- 완전히 삭제된 경우 결과 없음
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 연관 권한이 삭제되었는지 확인
|
||||||
|
```sql
|
||||||
|
-- 연관 권한이 모두 삭제되었는지 확인 (결과가 0이어야 함)
|
||||||
|
SELECT COUNT(*) AS remaining_permissions
|
||||||
|
FROM permissions
|
||||||
|
WHERE name LIKE 'menu:{MENU_ID}.%';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 역할-권한 연결이 삭제되었는지 확인
|
||||||
|
```sql
|
||||||
|
-- FK CASCADE로 자동 삭제되었는지 확인
|
||||||
|
SELECT COUNT(*) AS remaining_role_permissions
|
||||||
|
FROM role_has_permissions rhp
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM permissions p WHERE p.id = rhp.permission_id
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 아카이브에 저장되었는지 확인
|
||||||
|
```sql
|
||||||
|
-- archived_records에서 삭제 기록 조회
|
||||||
|
SELECT
|
||||||
|
ar.id,
|
||||||
|
ar.batch_id,
|
||||||
|
ar.batch_description,
|
||||||
|
ar.record_type,
|
||||||
|
ar.original_id,
|
||||||
|
ar.deleted_at,
|
||||||
|
ar.notes,
|
||||||
|
JSON_EXTRACT(ar.main_data, '$.name') AS menu_name
|
||||||
|
FROM archived_records ar
|
||||||
|
WHERE ar.record_type = 'menu'
|
||||||
|
AND ar.original_id = {MENU_ID}
|
||||||
|
ORDER BY ar.deleted_at DESC;
|
||||||
|
|
||||||
|
-- 연관 테이블 데이터도 확인
|
||||||
|
SELECT
|
||||||
|
arr.table_name,
|
||||||
|
arr.record_count,
|
||||||
|
arr.data
|
||||||
|
FROM archived_record_relations arr
|
||||||
|
JOIN archived_records ar ON arr.archived_record_id = ar.id
|
||||||
|
WHERE ar.record_type = 'menu'
|
||||||
|
AND ar.original_id = {MENU_ID};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 글로벌 메뉴용 쿼리
|
||||||
|
|
||||||
|
글로벌 메뉴의 경우 `menu:{ID}` 대신 `global_menu:{ID}` 패턴 사용:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 글로벌 메뉴 권한 조회
|
||||||
|
SELECT * FROM permissions WHERE name LIKE 'global_menu:{MENU_ID}.%';
|
||||||
|
|
||||||
|
-- 글로벌 메뉴 참조하는 테넌트 메뉴 확인
|
||||||
|
SELECT id, tenant_id, name, global_menu_id
|
||||||
|
FROM menus
|
||||||
|
WHERE global_menu_id = {MENU_ID};
|
||||||
|
|
||||||
|
-- 삭제 후 참조 해제 확인 (global_menu_id가 NULL이고 is_customized가 true)
|
||||||
|
SELECT id, tenant_id, name, global_menu_id, is_customized
|
||||||
|
FROM menus
|
||||||
|
WHERE is_customized = 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 실행 예시
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# MySQL 콘솔에서 실행
|
||||||
|
mysql -u root -p samdb
|
||||||
|
|
||||||
|
# 또는 Laravel Tinker에서 실행
|
||||||
|
php artisan tinker
|
||||||
|
>>> DB::select("SELECT * FROM permissions WHERE name LIKE 'menu:123.%'");
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**최종 업데이트**: 2025-12-09
|
||||||
410
plans/flow-tests/item-delete-force-delete.json
Normal file
410
plans/flow-tests/item-delete-force-delete.json
Normal file
@@ -0,0 +1,410 @@
|
|||||||
|
{
|
||||||
|
"name": "품목 삭제 테스트 (Force Delete & 사용중 체크)",
|
||||||
|
"description": "품목 삭제 기능 테스트: 1) 사용되지 않는 품목은 Force Delete 2) 사용 중인 품목은 삭제 불가 에러 반환",
|
||||||
|
"version": "1.0",
|
||||||
|
"config": {
|
||||||
|
"baseUrl": "",
|
||||||
|
"timeout": 30000,
|
||||||
|
"stopOnFailure": true
|
||||||
|
},
|
||||||
|
"variables": {
|
||||||
|
"user_id": "{{$env.FLOW_TESTER_USER_ID}}",
|
||||||
|
"user_pwd": "{{$env.FLOW_TESTER_USER_PWD}}",
|
||||||
|
"ts": "{{$timestamp}}"
|
||||||
|
},
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"id": "login",
|
||||||
|
"name": "로그인",
|
||||||
|
"method": "POST",
|
||||||
|
"endpoint": "/login",
|
||||||
|
"body": {
|
||||||
|
"user_id": "{{user_id}}",
|
||||||
|
"user_pwd": "{{user_pwd}}"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"status": [200],
|
||||||
|
"jsonPath": {
|
||||||
|
"$.access_token": "@isString"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extract": {
|
||||||
|
"token": "$.access_token"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "create_test_product",
|
||||||
|
"name": "테스트용 제품 생성 (FG)",
|
||||||
|
"method": "POST",
|
||||||
|
"endpoint": "/items",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer {{login.token}}"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"product_type": "FG",
|
||||||
|
"code": "TEST-DEL-FG-{{ts}}",
|
||||||
|
"name": "삭제테스트용 완제품",
|
||||||
|
"unit": "EA",
|
||||||
|
"is_active": true
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"status": [200, 201],
|
||||||
|
"jsonPath": {
|
||||||
|
"$.success": true,
|
||||||
|
"$.data.id": "@isNumber"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extract": {
|
||||||
|
"product_id": "$.data.id",
|
||||||
|
"product_code": "$.data.code"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "create_test_material",
|
||||||
|
"name": "테스트용 자재 생성 (RM)",
|
||||||
|
"method": "POST",
|
||||||
|
"endpoint": "/items",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer {{login.token}}"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"product_type": "RM",
|
||||||
|
"code": "TEST-DEL-RM-{{ts}}",
|
||||||
|
"name": "삭제테스트용 원자재",
|
||||||
|
"unit": "EA",
|
||||||
|
"is_active": true
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"status": [200, 201],
|
||||||
|
"jsonPath": {
|
||||||
|
"$.success": true,
|
||||||
|
"$.data.id": "@isNumber"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extract": {
|
||||||
|
"material_id": "$.data.id",
|
||||||
|
"material_code": "$.data.code"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "delete_unused_product",
|
||||||
|
"name": "사용되지 않는 제품 삭제 (Force Delete 성공)",
|
||||||
|
"method": "DELETE",
|
||||||
|
"endpoint": "/items/{{create_test_product.product_id}}?item_type=FG",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer {{login.token}}"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"status": [200],
|
||||||
|
"jsonPath": {
|
||||||
|
"$.success": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "verify_product_deleted",
|
||||||
|
"name": "제품 영구 삭제 확인 (404 응답)",
|
||||||
|
"method": "GET",
|
||||||
|
"endpoint": "/items/{{create_test_product.product_id}}",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer {{login.token}}"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"status": [404],
|
||||||
|
"jsonPath": {
|
||||||
|
"$.success": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "delete_unused_material",
|
||||||
|
"name": "사용되지 않는 자재 삭제 (Force Delete 성공)",
|
||||||
|
"method": "DELETE",
|
||||||
|
"endpoint": "/items/{{create_test_material.material_id}}?item_type=RM",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer {{login.token}}"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"status": [200],
|
||||||
|
"jsonPath": {
|
||||||
|
"$.success": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "verify_material_deleted",
|
||||||
|
"name": "자재 영구 삭제 확인 (404 응답)",
|
||||||
|
"method": "GET",
|
||||||
|
"endpoint": "/items/{{create_test_material.material_id}}?item_type=RM",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer {{login.token}}"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"status": [404],
|
||||||
|
"jsonPath": {
|
||||||
|
"$.success": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "create_parent_product",
|
||||||
|
"name": "BOM 상위 제품 생성",
|
||||||
|
"method": "POST",
|
||||||
|
"endpoint": "/items",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer {{login.token}}"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"product_type": "FG",
|
||||||
|
"code": "TEST-BOM-PARENT-{{ts}}",
|
||||||
|
"name": "BOM 상위 제품",
|
||||||
|
"unit": "EA",
|
||||||
|
"is_active": true
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"status": [200, 201],
|
||||||
|
"jsonPath": {
|
||||||
|
"$.success": true,
|
||||||
|
"$.data.id": "@isNumber"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extract": {
|
||||||
|
"parent_id": "$.data.id"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "create_child_material",
|
||||||
|
"name": "BOM 구성품용 자재 생성",
|
||||||
|
"method": "POST",
|
||||||
|
"endpoint": "/items",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer {{login.token}}"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"product_type": "RM",
|
||||||
|
"code": "TEST-BOM-CHILD-{{ts}}",
|
||||||
|
"name": "BOM 구성품 자재",
|
||||||
|
"unit": "EA",
|
||||||
|
"is_active": true
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"status": [200, 201],
|
||||||
|
"jsonPath": {
|
||||||
|
"$.success": true,
|
||||||
|
"$.data.id": "@isNumber"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extract": {
|
||||||
|
"child_material_id": "$.data.id",
|
||||||
|
"child_material_code": "$.data.code"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "add_bom_component",
|
||||||
|
"name": "BOM 구성품 추가 (자재를 상위 제품에 연결)",
|
||||||
|
"method": "POST",
|
||||||
|
"endpoint": "/items/{{create_parent_product.parent_id}}/bom",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer {{login.token}}"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"ref_type": "MATERIAL",
|
||||||
|
"ref_id": "{{create_child_material.child_material_id}}",
|
||||||
|
"quantity": 2,
|
||||||
|
"unit": "EA"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"status": [200, 201],
|
||||||
|
"jsonPath": {
|
||||||
|
"$.success": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "delete_used_material",
|
||||||
|
"name": "사용 중인 자재 삭제 시도 (400 에러 - BOM 구성품)",
|
||||||
|
"method": "DELETE",
|
||||||
|
"endpoint": "/items/{{create_child_material.child_material_id}}?item_type=RM",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer {{login.token}}"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"status": [400],
|
||||||
|
"jsonPath": {
|
||||||
|
"$.success": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "create_batch_products",
|
||||||
|
"name": "일괄 삭제용 제품 1 생성",
|
||||||
|
"method": "POST",
|
||||||
|
"endpoint": "/items",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer {{login.token}}"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"product_type": "FG",
|
||||||
|
"code": "TEST-BATCH-1-{{ts}}",
|
||||||
|
"name": "일괄삭제 테스트 1",
|
||||||
|
"unit": "EA",
|
||||||
|
"is_active": true
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"status": [200, 201],
|
||||||
|
"jsonPath": {
|
||||||
|
"$.success": true,
|
||||||
|
"$.data.id": "@isNumber"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extract": {
|
||||||
|
"batch_id_1": "$.data.id"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "create_batch_products_2",
|
||||||
|
"name": "일괄 삭제용 제품 2 생성",
|
||||||
|
"method": "POST",
|
||||||
|
"endpoint": "/items",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer {{login.token}}"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"product_type": "FG",
|
||||||
|
"code": "TEST-BATCH-2-{{ts}}",
|
||||||
|
"name": "일괄삭제 테스트 2",
|
||||||
|
"unit": "EA",
|
||||||
|
"is_active": true
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"status": [200, 201],
|
||||||
|
"jsonPath": {
|
||||||
|
"$.success": true,
|
||||||
|
"$.data.id": "@isNumber"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extract": {
|
||||||
|
"batch_id_2": "$.data.id"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "batch_delete_unused",
|
||||||
|
"name": "사용되지 않는 제품들 일괄 삭제 (성공)",
|
||||||
|
"method": "DELETE",
|
||||||
|
"endpoint": "/items/batch",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer {{login.token}}"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"item_type": "FG",
|
||||||
|
"ids": ["{{create_batch_products.batch_id_1}}", "{{create_batch_products_2.batch_id_2}}"]
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"status": [200],
|
||||||
|
"jsonPath": {
|
||||||
|
"$.success": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "create_batch_for_fail",
|
||||||
|
"name": "일괄 삭제 실패 테스트용 제품 생성",
|
||||||
|
"method": "POST",
|
||||||
|
"endpoint": "/items",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer {{login.token}}"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"product_type": "FG",
|
||||||
|
"code": "TEST-BATCH-FAIL-{{ts}}",
|
||||||
|
"name": "일괄삭제 실패 테스트",
|
||||||
|
"unit": "EA",
|
||||||
|
"is_active": true
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"status": [200, 201],
|
||||||
|
"jsonPath": {
|
||||||
|
"$.success": true,
|
||||||
|
"$.data.id": "@isNumber"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extract": {
|
||||||
|
"batch_fail_id": "$.data.id"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "batch_delete_with_used",
|
||||||
|
"name": "사용 중인 자재 일괄 삭제 시도 (400 에러)",
|
||||||
|
"method": "DELETE",
|
||||||
|
"endpoint": "/items/batch",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer {{login.token}}"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"item_type": "RM",
|
||||||
|
"ids": ["{{create_child_material.child_material_id}}"]
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"status": [400],
|
||||||
|
"jsonPath": {
|
||||||
|
"$.success": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cleanup_bom",
|
||||||
|
"name": "정리: BOM 구성품 전체 삭제 (빈 배열로 교체)",
|
||||||
|
"method": "POST",
|
||||||
|
"endpoint": "/items/{{create_parent_product.parent_id}}/bom/replace",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer {{login.token}}"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"items": []
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"status": [200, 201, 404]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cleanup_parent",
|
||||||
|
"name": "정리: 상위 제품 삭제",
|
||||||
|
"method": "DELETE",
|
||||||
|
"endpoint": "/items/{{create_parent_product.parent_id}}?item_type=FG",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer {{login.token}}"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"status": [200, 404]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cleanup_child",
|
||||||
|
"name": "정리: 구성품 자재 삭제 (BOM 해제 후)",
|
||||||
|
"method": "DELETE",
|
||||||
|
"endpoint": "/items/{{create_child_material.child_material_id}}?item_type=RM",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer {{login.token}}"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"status": [200, 404]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cleanup_batch_fail",
|
||||||
|
"name": "정리: 일괄삭제 테스트용 제품 삭제",
|
||||||
|
"method": "DELETE",
|
||||||
|
"endpoint": "/items/{{create_batch_for_fail.batch_fail_id}}?item_type=FG",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer {{login.token}}"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"status": [200, 404]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user