# 품목기준관리 API 명세서

**작성일**: 2025-11-20
**버전**: v1.0
**작성자**: 프론트엔드 개발팀
**수신**: 백엔드 개발팀

---

## 📋 목차

1. [개요](#1-개요)
2. [인증 및 공통 사항](#2-인증-및-공통-사항)
3. [데이터베이스 테이블 정의](#3-데이터베이스-테이블-정의)
4. [API 엔드포인트](#4-api-엔드포인트)
5. [요청/응답 예시](#5-요청응답-예시)
6. [에러 처리](#6-에러-처리)
7. [구현 우선순위](#7-구현-우선순위)

---

## 1. 개요

### 1.1 목적
품목기준관리 화면에서 사용할 API 개발 요청

### 1.2 주요 기능
- 품목 유형별 페이지 관리 (FG, PT, SM, RM, CS)
- 계층구조 기반 섹션 및 필드 관리
- BOM(Bill of Materials) 항목 관리
- 섹션 템플릿 및 마스터 필드 관리
- 커스텀 탭 및 단위 관리

### 1.3 기술 요구사항
- ✅ **Service-First 패턴** 적용
- ✅ **Multi-tenant**: `tenant_id` 기반 격리, `BelongsToTenant` 스코프
- ✅ **Soft Delete**: 모든 테이블 적용
- ✅ **감사 로그**: 생성/수정/삭제 시 `audit_logs` 기록
- ✅ **i18n**: 메시지는 `__('message.xxx')` 키만 사용
- ✅ **실시간 저장**: 모든 CUD 작업 즉시 처리 (일괄 저장 없음)

### 1.4 저장 전략
**중요**: 프론트엔드에서 **실시간 저장** 방식 사용
- 페이지/섹션/필드 생성 즉시 API 호출
- 수정/삭제/순서변경 즉시 API 호출
- 일괄 저장(Batch Save) API 불필요

---

## 2. 인증 및 공통 사항

### 2.1 인증
```
Headers:
  X-API-KEY: {api_key}
  Authorization: Bearer {sanctum_token}
```

### 2.2 Base URL
```
http://api.sam.kr/api/v1/item-master
```

### 2.3 공통 응답 형식

**성공 응답**:
```json
{
  "success": true,
  "message": "message.created",
  "data": { ... }
}
```

**에러 응답**:
```json
{
  "success": false,
  "message": "error.validation_failed",
  "errors": {
    "page_name": ["페이지명은 필수입니다."]
  }
}
```

### 2.4 공통 컬럼
모든 테이블에 다음 컬럼 포함:
- `tenant_id` (BIGINT, NOT NULL, INDEX)
- `created_by` (BIGINT, NULL)
- `updated_by` (BIGINT, NULL)
- `deleted_by` (BIGINT, NULL)
- `created_at` (TIMESTAMP)
- `updated_at` (TIMESTAMP)
- `deleted_at` (TIMESTAMP, NULL) - Soft Delete

---

## 3. 데이터베이스 테이블 정의

### 3.1 item_pages (품목 페이지)

```sql
CREATE TABLE item_pages (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT UNSIGNED NOT NULL COMMENT '테넌트 ID',
    page_name VARCHAR(255) NOT NULL COMMENT '페이지명',
    item_type ENUM('FG', 'PT', 'SM', 'RM', 'CS') NOT NULL COMMENT '품목 유형 (완제품/반제품/부자재/원자재/소모품)',
    absolute_path VARCHAR(500) NULL COMMENT '절대 경로',
    is_active TINYINT(1) DEFAULT 1 COMMENT '활성 여부',
    created_by BIGINT UNSIGNED NULL COMMENT '생성자 ID',
    updated_by BIGINT UNSIGNED NULL COMMENT '수정자 ID',
    deleted_by BIGINT UNSIGNED NULL COMMENT '삭제자 ID',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP NULL,

    INDEX idx_tenant_id (tenant_id),
    INDEX idx_item_type (item_type),
    FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='품목 페이지';
```

### 3.2 item_sections (섹션 인스턴스)

```sql
CREATE TABLE item_sections (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT UNSIGNED NOT NULL COMMENT '테넌트 ID',
    page_id BIGINT UNSIGNED NOT NULL COMMENT '페이지 ID',
    title VARCHAR(255) NOT NULL COMMENT '섹션 제목',
    type ENUM('fields', 'bom') NOT NULL DEFAULT 'fields' COMMENT '섹션 타입 (필드형/BOM형)',
    order_no INT NOT NULL DEFAULT 0 COMMENT '정렬 순서',
    created_by BIGINT UNSIGNED NULL,
    updated_by BIGINT UNSIGNED NULL,
    deleted_by BIGINT UNSIGNED NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP NULL,

    INDEX idx_tenant_page (tenant_id, page_id),
    INDEX idx_order (page_id, order_no),
    FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
    FOREIGN KEY (page_id) REFERENCES item_pages(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='품목 섹션 인스턴스';
```

### 3.3 item_fields (필드)

```sql
CREATE TABLE item_fields (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT UNSIGNED NOT NULL COMMENT '테넌트 ID',
    section_id BIGINT UNSIGNED NOT NULL COMMENT '섹션 ID',
    field_name VARCHAR(255) NOT NULL COMMENT '필드명',
    field_type ENUM('textbox', 'number', 'dropdown', 'checkbox', 'date', 'textarea') NOT NULL COMMENT '필드 타입',
    order_no INT NOT NULL DEFAULT 0 COMMENT '정렬 순서',
    is_required TINYINT(1) DEFAULT 0 COMMENT '필수 여부',
    default_value TEXT NULL COMMENT '기본값',
    placeholder VARCHAR(255) NULL COMMENT '플레이스홀더',
    display_condition JSON NULL COMMENT '표시 조건 {"field_id": "1", "operator": "equals", "value": "true"}',
    validation_rules JSON NULL COMMENT '검증 규칙 {"min": 0, "max": 100, "pattern": "regex"}',
    options JSON NULL COMMENT '드롭다운 옵션 [{"label": "옵션1", "value": "val1"}]',
    properties JSON NULL COMMENT '필드 속성 {"unit": "mm", "precision": 2, "format": "YYYY-MM-DD"}',
    created_by BIGINT UNSIGNED NULL,
    updated_by BIGINT UNSIGNED NULL,
    deleted_by BIGINT UNSIGNED NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP NULL,

    INDEX idx_tenant_section (tenant_id, section_id),
    INDEX idx_order (section_id, order_no),
    FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
    FOREIGN KEY (section_id) REFERENCES item_sections(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='품목 필드';
```

### 3.4 item_bom_items (BOM 항목)

```sql
CREATE TABLE item_bom_items (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT UNSIGNED NOT NULL COMMENT '테넌트 ID',
    section_id BIGINT UNSIGNED NOT NULL COMMENT '섹션 ID',
    item_code VARCHAR(100) NULL COMMENT '품목 코드',
    item_name VARCHAR(255) NOT NULL COMMENT '품목명',
    quantity DECIMAL(15, 4) NOT NULL DEFAULT 0 COMMENT '수량',
    unit VARCHAR(50) NULL COMMENT '단위',
    unit_price DECIMAL(15, 2) NULL COMMENT '단가',
    total_price DECIMAL(15, 2) NULL COMMENT '총액',
    spec TEXT NULL COMMENT '사양',
    note TEXT NULL COMMENT '비고',
    created_by BIGINT UNSIGNED NULL,
    updated_by BIGINT UNSIGNED NULL,
    deleted_by BIGINT UNSIGNED NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP NULL,

    INDEX idx_tenant_section (tenant_id, section_id),
    FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
    FOREIGN KEY (section_id) REFERENCES item_sections(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='BOM 항목';
```

### 3.5 section_templates (섹션 템플릿)

```sql
CREATE TABLE section_templates (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT UNSIGNED NOT NULL COMMENT '테넌트 ID',
    title VARCHAR(255) NOT NULL COMMENT '템플릿명',
    type ENUM('fields', 'bom') NOT NULL DEFAULT 'fields' COMMENT '섹션 타입',
    description TEXT NULL COMMENT '설명',
    is_default TINYINT(1) DEFAULT 0 COMMENT '기본 템플릿 여부',
    created_by BIGINT UNSIGNED NULL,
    updated_by BIGINT UNSIGNED NULL,
    deleted_by BIGINT UNSIGNED NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP NULL,

    INDEX idx_tenant (tenant_id),
    FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='섹션 템플릿';
```

### 3.6 item_master_fields (마스터 필드)

```sql
CREATE TABLE item_master_fields (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT UNSIGNED NOT NULL COMMENT '테넌트 ID',
    field_name VARCHAR(255) NOT NULL COMMENT '필드명',
    field_type ENUM('textbox', 'number', 'dropdown', 'checkbox', 'date', 'textarea') NOT NULL COMMENT '필드 타입',
    category VARCHAR(100) NULL COMMENT '카테고리',
    description TEXT NULL COMMENT '설명',
    is_common TINYINT(1) DEFAULT 0 COMMENT '공통 필드 여부',
    default_value TEXT NULL COMMENT '기본값',
    options JSON NULL COMMENT '옵션',
    validation_rules JSON NULL COMMENT '검증 규칙',
    properties JSON NULL COMMENT '속성',
    created_by BIGINT UNSIGNED NULL,
    updated_by BIGINT UNSIGNED NULL,
    deleted_by BIGINT UNSIGNED NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP NULL,

    INDEX idx_tenant (tenant_id),
    INDEX idx_category (category),
    FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='마스터 필드';
```

### 3.7 custom_tabs (커스텀 탭)

```sql
CREATE TABLE custom_tabs (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT UNSIGNED NOT NULL COMMENT '테넌트 ID',
    label VARCHAR(255) NOT NULL COMMENT '탭 라벨',
    icon VARCHAR(100) NULL COMMENT '아이콘',
    is_default TINYINT(1) DEFAULT 0 COMMENT '기본 탭 여부',
    order_no INT NOT NULL DEFAULT 0 COMMENT '정렬 순서',
    created_by BIGINT UNSIGNED NULL,
    updated_by BIGINT UNSIGNED NULL,
    deleted_by BIGINT UNSIGNED NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP NULL,

    INDEX idx_tenant (tenant_id),
    INDEX idx_order (tenant_id, order_no),
    FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='커스텀 탭';
```

### 3.8 tab_columns (탭별 컬럼 설정)

```sql
CREATE TABLE tab_columns (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT UNSIGNED NOT NULL COMMENT '테넌트 ID',
    tab_id BIGINT UNSIGNED NOT NULL COMMENT '탭 ID',
    columns JSON NOT NULL COMMENT '컬럼 설정 [{"key": "name", "label": "품목명", "visible": true, "order": 0}]',
    created_by BIGINT UNSIGNED NULL,
    updated_by BIGINT UNSIGNED NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    UNIQUE KEY uk_tenant_tab (tenant_id, tab_id),
    FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
    FOREIGN KEY (tab_id) REFERENCES custom_tabs(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='탭별 컬럼 설정';
```

### 3.9 unit_options (단위 옵션)

```sql
CREATE TABLE unit_options (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT UNSIGNED NOT NULL COMMENT '테넌트 ID',
    label VARCHAR(100) NOT NULL COMMENT '단위 라벨',
    value VARCHAR(50) NOT NULL COMMENT '단위 값',
    created_by BIGINT UNSIGNED NULL,
    updated_by BIGINT UNSIGNED NULL,
    deleted_by BIGINT UNSIGNED NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP NULL,

    INDEX idx_tenant (tenant_id),
    FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='단위 옵션';
```

---

## 4. API 엔드포인트

### 4.1 초기화 API

#### `GET /v1/item-master/init`
**목적**: 화면 진입 시 전체 초기 데이터 로드

**Request**: 없음

**Response**:
```json
{
  "success": true,
  "message": "message.fetched",
  "data": {
    "pages": [
      {
        "id": 1,
        "page_name": "완제품 A",
        "item_type": "FG",
        "absolute_path": "/FG/완제품 A",
        "is_active": true,
        "sections": [
          {
            "id": 1,
            "title": "기본 정보",
            "type": "fields",
            "order_no": 0,
            "fields": [...]
          }
        ]
      }
    ],
    "sectionTemplates": [...],
    "masterFields": [...],
    "customTabs": [...],
    "tabColumns": {...},
    "unitOptions": [...]
  }
}
```

**참고**:
- `pages`는 `sections`, `fields`, `bomItems`를 중첩(nested) 포함
- 한 번의 API 호출로 모든 데이터 로드

---

### 4.2 페이지 관리

#### `GET /v1/item-master/pages`
**목적**: 페이지 목록 조회 (섹션/필드 포함)

**Query Parameters**:
| 파라미터 | 타입 | 필수 | 설명 |
|---------|------|------|------|
| `item_type` | string | 선택 | 품목 유형 필터 (FG, PT, SM, RM, CS) |

**Response**: 초기화 API의 `data.pages`와 동일

---

#### `POST /v1/item-master/pages`
**목적**: 페이지 생성

**Request Body**:
```json
{
  "page_name": "완제품 A",
  "item_type": "FG",
  "absolute_path": "/FG/완제품 A"
}
```

**Validation**:
- `page_name`: required, string, max:255
- `item_type`: required, in:FG,PT,SM,RM,CS
- `absolute_path`: nullable, string, max:500

**Response**:
```json
{
  "success": true,
  "message": "message.created",
  "data": {
    "id": 1,
    "page_name": "완제품 A",
    "item_type": "FG",
    "absolute_path": "/FG/완제품 A",
    "is_active": true,
    "sections": [],
    "created_at": "2025-11-20T10:00:00.000000Z",
    "updated_at": "2025-11-20T10:00:00.000000Z"
  }
}
```

---

#### `PUT /v1/item-master/pages/{id}`
**목적**: 페이지 수정

**Request Body**:
```json
{
  "page_name": "완제품 A (수정)",
  "absolute_path": "/FG/완제품 A (수정)"
}
```

**Validation**:
- `page_name`: string, max:255
- `absolute_path`: nullable, string, max:500

**Response**: 수정된 페이지 정보 반환

---

#### `DELETE /v1/item-master/pages/{id}`
**목적**: 페이지 삭제 (Soft Delete)

**Request**: 없음

**Response**:
```json
{
  "success": true,
  "message": "message.deleted"
}
```

**참고**:
- Soft Delete 처리 (`deleted_at` 업데이트)
- 하위 섹션/필드도 함께 Soft Delete 처리 (Cascade)

---

### 4.3 섹션 관리

#### `POST /v1/item-master/pages/{pageId}/sections`
**목적**: 섹션 생성

**Request Body**:
```json
{
  "title": "기본 정보",
  "type": "fields"
}
```

**Validation**:
- `title`: required, string, max:255
- `type`: required, in:fields,bom

**Response**:
```json
{
  "success": true,
  "message": "message.created",
  "data": {
    "id": 1,
    "page_id": 1,
    "title": "기본 정보",
    "type": "fields",
    "order_no": 0,
    "fields": []
  }
}
```

**참고**:
- `order_no`는 자동 계산 (해당 페이지의 마지막 섹션 order + 1)

---

#### `PUT /v1/item-master/sections/{id}`
**목적**: 섹션 수정

**Request Body**:
```json
{
  "title": "기본 정보 (수정)"
}
```

**Validation**:
- `title`: string, max:255

**Response**: 수정된 섹션 정보 반환

---

#### `DELETE /v1/item-master/sections/{id}`
**목적**: 섹션 삭제 (Soft Delete)

**Request**: 없음

**Response**:
```json
{
  "success": true,
  "message": "message.deleted"
}
```

**참고**: 하위 필드도 함께 Soft Delete 처리

---

#### `PUT /v1/item-master/pages/{pageId}/sections/reorder`
**목적**: 섹션 순서 변경 (드래그 앤 드롭)

**Request Body**:
```json
{
  "section_orders": [
    {"id": 2, "order_no": 0},
    {"id": 1, "order_no": 1},
    {"id": 3, "order_no": 2}
  ]
}
```

**Validation**:
- `section_orders`: required, array
- `section_orders.*.id`: required, exists:item_sections,id
- `section_orders.*.order_no`: required, integer, min:0

**Response**:
```json
{
  "success": true,
  "message": "message.reordered"
}
```

---

### 4.4 필드 관리

#### `POST /v1/item-master/sections/{sectionId}/fields`
**목적**: 필드 생성

**Request Body**:
```json
{
  "field_name": "제품명",
  "field_type": "textbox",
  "is_required": true,
  "placeholder": "제품명을 입력하세요",
  "validation_rules": {
    "min": 1,
    "max": 100
  },
  "properties": {
    "unit": "mm",
    "precision": 2
  }
}
```

**Validation**:
- `field_name`: required, string, max:255
- `field_type`: required, in:textbox,number,dropdown,checkbox,date,textarea
- `is_required`: boolean
- `placeholder`: nullable, string, max:255
- `validation_rules`: nullable, json
- `properties`: nullable, json
- `options`: nullable, json (field_type=dropdown일 때 필수)

**Response**:
```json
{
  "success": true,
  "message": "message.created",
  "data": {
    "id": 1,
    "section_id": 1,
    "field_name": "제품명",
    "field_type": "textbox",
    "order_no": 0,
    "is_required": true,
    "placeholder": "제품명을 입력하세요",
    "validation_rules": {...},
    "properties": {...}
  }
}
```

---

#### `PUT /v1/item-master/fields/{id}`
**목적**: 필드 수정

**Request Body**: 생성과 동일 (모든 필드 선택적)

**Response**: 수정된 필드 정보 반환

---

#### `DELETE /v1/item-master/fields/{id}`
**목적**: 필드 삭제 (Soft Delete)

**Request**: 없음

**Response**:
```json
{
  "success": true,
  "message": "message.deleted"
}
```

---

#### `PUT /v1/item-master/sections/{sectionId}/fields/reorder`
**목적**: 필드 순서 변경

**Request Body**:
```json
{
  "field_orders": [
    {"id": 3, "order_no": 0},
    {"id": 1, "order_no": 1},
    {"id": 2, "order_no": 2}
  ]
}
```

**Validation**: 섹션 순서 변경과 동일

**Response**:
```json
{
  "success": true,
  "message": "message.reordered"
}
```

---

### 4.5 BOM 관리

#### `POST /v1/item-master/sections/{sectionId}/bom-items`
**목적**: BOM 항목 생성

**Request Body**:
```json
{
  "item_code": "MAT-001",
  "item_name": "철판",
  "quantity": 10.5,
  "unit": "kg",
  "unit_price": 5000.00,
  "total_price": 52500.00,
  "spec": "두께 2mm, 스테인리스",
  "note": "비고 사항"
}
```

**Validation**:
- `item_code`: nullable, string, max:100
- `item_name`: required, string, max:255
- `quantity`: required, numeric, min:0
- `unit`: nullable, string, max:50
- `unit_price`: nullable, numeric, min:0
- `total_price`: nullable, numeric, min:0
- `spec`: nullable, string
- `note`: nullable, string

**Response**:
```json
{
  "success": true,
  "message": "message.created",
  "data": {
    "id": 1,
    "section_id": 1,
    "item_code": "MAT-001",
    "item_name": "철판",
    "quantity": 10.5,
    "unit": "kg",
    "unit_price": 5000.00,
    "total_price": 52500.00,
    "spec": "두께 2mm, 스테인리스",
    "note": "비고 사항"
  }
}
```

---

#### `PUT /v1/item-master/bom-items/{id}`
**목적**: BOM 항목 수정

**Request Body**: 생성과 동일 (모든 필드 선택적)

**Response**: 수정된 BOM 항목 반환

---

#### `DELETE /v1/item-master/bom-items/{id}`
**목적**: BOM 항목 삭제 (Soft Delete)

**Request**: 없음

**Response**:
```json
{
  "success": true,
  "message": "message.deleted"
}
```

---

### 4.6 섹션 템플릿

#### `GET /v1/item-master/section-templates`
**목적**: 섹션 템플릿 목록 조회

**Request**: 없음

**Response**:
```json
{
  "success": true,
  "message": "message.fetched",
  "data": [
    {
      "id": 1,
      "title": "기본 정보 템플릿",
      "type": "fields",
      "description": "제품 기본 정보 입력용 템플릿",
      "is_default": true
    }
  ]
}
```

---

#### `POST /v1/item-master/section-templates`
**목적**: 섹션 템플릿 생성

**Request Body**:
```json
{
  "title": "기본 정보 템플릿",
  "type": "fields",
  "description": "제품 기본 정보 입력용 템플릿",
  "is_default": false
}
```

**Validation**:
- `title`: required, string, max:255
- `type`: required, in:fields,bom
- `description`: nullable, string
- `is_default`: boolean

**Response**: 생성된 템플릿 정보 반환

---

#### `PUT /v1/item-master/section-templates/{id}`
**목적**: 섹션 템플릿 수정

**Request Body**: 생성과 동일 (모든 필드 선택적)

**Response**: 수정된 템플릿 정보 반환

---

#### `DELETE /v1/item-master/section-templates/{id}`
**목적**: 섹션 템플릿 삭제 (Soft Delete)

**Request**: 없음

**Response**:
```json
{
  "success": true,
  "message": "message.deleted"
}
```

---

### 4.7 마스터 필드

#### `GET /v1/item-master/master-fields`
**목적**: 마스터 필드 목록 조회

**Query Parameters**:
| 파라미터 | 타입 | 필수 | 설명 |
|---------|------|------|------|
| `category` | string | 선택 | 카테고리 필터 |

**Response**:
```json
{
  "success": true,
  "message": "message.fetched",
  "data": [
    {
      "id": 1,
      "field_name": "제품명",
      "field_type": "textbox",
      "category": "기본정보",
      "description": "제품 이름",
      "is_common": true,
      "options": null,
      "validation_rules": {"max": 100},
      "properties": null
    }
  ]
}
```

---

#### `POST /v1/item-master/master-fields`
**목적**: 마스터 필드 생성

**Request Body**:
```json
{
  "field_name": "제품명",
  "field_type": "textbox",
  "category": "기본정보",
  "description": "제품 이름",
  "is_common": true,
  "validation_rules": {"max": 100}
}
```

**Validation**:
- `field_name`: required, string, max:255
- `field_type`: required, in:textbox,number,dropdown,checkbox,date,textarea
- `category`: nullable, string, max:100
- `description`: nullable, string
- `is_common`: boolean
- `options`: nullable, json
- `validation_rules`: nullable, json
- `properties`: nullable, json

**Response**: 생성된 마스터 필드 정보 반환

---

#### `PUT /v1/item-master/master-fields/{id}`
**목적**: 마스터 필드 수정

**Request Body**: 생성과 동일 (모든 필드 선택적)

**Response**: 수정된 마스터 필드 정보 반환

---

#### `DELETE /v1/item-master/master-fields/{id}`
**목적**: 마스터 필드 삭제 (Soft Delete)

**Request**: 없음

**Response**:
```json
{
  "success": true,
  "message": "message.deleted"
}
```

---

### 4.8 커스텀 탭

#### `GET /v1/item-master/custom-tabs`
**목적**: 커스텀 탭 목록 조회

**Request**: 없음

**Response**:
```json
{
  "success": true,
  "message": "message.fetched",
  "data": [
    {
      "id": 1,
      "label": "품질",
      "icon": "quality-icon",
      "is_default": false,
      "order_no": 0
    }
  ]
}
```

---

#### `POST /v1/item-master/custom-tabs`
**목적**: 커스텀 탭 생성

**Request Body**:
```json
{
  "label": "품질",
  "icon": "quality-icon",
  "is_default": false
}
```

**Validation**:
- `label`: required, string, max:255
- `icon`: nullable, string, max:100
- `is_default`: boolean

**Response**: 생성된 탭 정보 반환 (order_no 자동 계산)

---

#### `PUT /v1/item-master/custom-tabs/{id}`
**목적**: 커스텀 탭 수정

**Request Body**: 생성과 동일 (모든 필드 선택적)

**Response**: 수정된 탭 정보 반환

---

#### `DELETE /v1/item-master/custom-tabs/{id}`
**목적**: 커스텀 탭 삭제 (Soft Delete)

**Request**: 없음

**Response**:
```json
{
  "success": true,
  "message": "message.deleted"
}
```

---

#### `PUT /v1/item-master/custom-tabs/reorder`
**목적**: 탭 순서 변경

**Request Body**:
```json
{
  "tab_orders": [
    {"id": 2, "order_no": 0},
    {"id": 1, "order_no": 1}
  ]
}
```

**Validation**: 섹션 순서 변경과 동일

**Response**:
```json
{
  "success": true,
  "message": "message.reordered"
}
```

---

#### `PUT /v1/item-master/custom-tabs/{id}/columns`
**목적**: 탭별 컬럼 설정

**Request Body**:
```json
{
  "columns": [
    {"key": "name", "label": "품목명", "visible": true, "order": 0},
    {"key": "code", "label": "품목코드", "visible": true, "order": 1},
    {"key": "price", "label": "가격", "visible": false, "order": 2}
  ]
}
```

**Validation**:
- `columns`: required, array
- `columns.*.key`: required, string
- `columns.*.label`: required, string
- `columns.*.visible`: required, boolean
- `columns.*.order`: required, integer

**Response**:
```json
{
  "success": true,
  "message": "message.updated",
  "data": {
    "tab_id": 1,
    "columns": [...]
  }
}
```

---

### 4.9 단위 관리

#### `GET /v1/item-master/units`
**목적**: 단위 목록 조회

**Request**: 없음

**Response**:
```json
{
  "success": true,
  "message": "message.fetched",
  "data": [
    {"id": 1, "label": "킬로그램", "value": "kg"},
    {"id": 2, "label": "미터", "value": "m"}
  ]
}
```

---

#### `POST /v1/item-master/units`
**목적**: 단위 생성

**Request Body**:
```json
{
  "label": "킬로그램",
  "value": "kg"
}
```

**Validation**:
- `label`: required, string, max:100
- `value`: required, string, max:50

**Response**: 생성된 단위 정보 반환

---

#### `DELETE /v1/item-master/units/{id}`
**목적**: 단위 삭제 (Soft Delete)

**Request**: 없음

**Response**:
```json
{
  "success": true,
  "message": "message.deleted"
}
```

---

## 5. 요청/응답 예시

### 5.1 페이지 생성 → 섹션 추가 → 필드 추가 흐름

**1단계: 페이지 생성**
```bash
POST /v1/item-master/pages
{
  "page_name": "완제품 A",
  "item_type": "FG",
  "absolute_path": "/FG/완제품 A"
}

→ Response: {"data": {"id": 1, ...}}
```

**2단계: 섹션 추가**
```bash
POST /v1/item-master/pages/1/sections
{
  "title": "기본 정보",
  "type": "fields"
}

→ Response: {"data": {"id": 1, "page_id": 1, ...}}
```

**3단계: 필드 추가**
```bash
POST /v1/item-master/sections/1/fields
{
  "field_name": "제품명",
  "field_type": "textbox",
  "is_required": true
}

→ Response: {"data": {"id": 1, "section_id": 1, ...}}
```

---

### 5.2 BOM 섹션 생성 → BOM 항목 추가

**1단계: BOM 섹션 생성**
```bash
POST /v1/item-master/pages/1/sections
{
  "title": "자재 목록",
  "type": "bom"
}

→ Response: {"data": {"id": 2, "type": "bom", ...}}
```

**2단계: BOM 항목 추가**
```bash
POST /v1/item-master/sections/2/bom-items
{
  "item_name": "철판",
  "quantity": 10.5,
  "unit": "kg",
  "unit_price": 5000
}

→ Response: {"data": {"id": 1, "section_id": 2, ...}}
```

---

## 6. 에러 처리

### 6.1 에러 응답 형식

```json
{
  "success": false,
  "message": "error.validation_failed",
  "errors": {
    "page_name": ["페이지명은 필수입니다."],
    "item_type": ["올바른 품목 유형을 선택하세요."]
  }
}
```

### 6.2 주요 에러 코드

| HTTP 상태 | message 키 | 설명 |
|----------|-----------|------|
| 400 | error.validation_failed | 유효성 검증 실패 |
| 401 | error.unauthorized | 인증 실패 |
| 403 | error.forbidden | 권한 없음 |
| 404 | error.not_found | 리소스를 찾을 수 없음 |
| 422 | error.unprocessable | 처리할 수 없는 요청 |
| 500 | error.internal_server | 서버 내부 오류 |

### 6.3 Tenant 격리 에러

```json
{
  "success": false,
  "message": "error.forbidden",
  "errors": {
    "tenant_id": ["다른 테넌트의 리소스에 접근할 수 없습니다."]
  }
}
```

**참고**: `BelongsToTenant` 스코프가 자동으로 처리하므로 404 반환

---

## 7. 구현 우선순위

### 🔴 우선순위 1 (필수 - 화면 기본 동작)

1. **초기화 API**: `GET /v1/item-master/init`
2. **페이지 CRUD**:
   - `GET /v1/item-master/pages`
   - `POST /v1/item-master/pages`
   - `PUT /v1/item-master/pages/{id}`
   - `DELETE /v1/item-master/pages/{id}`
3. **섹션 CRUD**:
   - `POST /v1/item-master/pages/{pageId}/sections`
   - `PUT /v1/item-master/sections/{id}`
   - `DELETE /v1/item-master/sections/{id}`
4. **필드 CRUD**:
   - `POST /v1/item-master/sections/{sectionId}/fields`
   - `PUT /v1/item-master/fields/{id}`
   - `DELETE /v1/item-master/fields/{id}`

### 🟡 우선순위 2 (중요 - 핵심 기능)

5. **BOM 관리**:
   - `POST /v1/item-master/sections/{sectionId}/bom-items`
   - `PUT /v1/item-master/bom-items/{id}`
   - `DELETE /v1/item-master/bom-items/{id}`
6. **순서 변경**:
   - `PUT /v1/item-master/pages/{pageId}/sections/reorder`
   - `PUT /v1/item-master/sections/{sectionId}/fields/reorder`
7. **단위 관리**:
   - `GET /v1/item-master/units`
   - `POST /v1/item-master/units`
   - `DELETE /v1/item-master/units/{id}`

### 🟢 우선순위 3 (부가 기능)

8. **섹션 템플릿**: 전체 CRUD
9. **마스터 필드**: 전체 CRUD
10. **커스텀 탭**: 전체 CRUD + 컬럼 설정

---

## 📌 참고 사항

### 1. Cascade 삭제 정책
- 페이지 삭제 시 → 하위 섹션/필드 모두 Soft Delete
- 섹션 삭제 시 → 하위 필드 모두 Soft Delete
- 모두 `deleted_at` 업데이트로 처리

### 2. order_no 자동 계산
- 새로운 섹션/필드 생성 시 자동으로 마지막 순서 + 1
- 프론트엔드에서 order_no 전달 불필요

### 3. Nested 조회 최적화
- `GET /v1/item-master/pages`: with(['sections.fields', 'sections.bomItems'])
- Eager Loading으로 N+1 문제 방지

### 4. 감사 로그
- 모든 생성/수정/삭제 시 `audit_logs` 기록
- `action`: created, updated, deleted
- `before`, `after` 필드에 변경 전후 데이터 JSON 저장

### 5. i18n 메시지 키
```php
// lang/ko/message.php
return [
    'fetched' => '조회되었습니다.',
    'created' => '생성되었습니다.',
    'updated' => '수정되었습니다.',
    'deleted' => '삭제되었습니다.',
    'reordered' => '순서가 변경되었습니다.',
];
```

---

## ✅ 체크리스트

백엔드 개발 완료 전 확인사항:

```
□ Service-First 패턴 적용 (Controller는 DI + Service 호출만)
□ BelongsToTenant scope 모든 모델에 적용
□ SoftDeletes 모든 모델에 적용
□ 공통 컬럼 (tenant_id, created_by, updated_by, deleted_by) 포함
□ 감사 로그 생성/수정/삭제 시 기록
□ i18n 메시지 키 사용 (__('message.xxx'))
□ FormRequest 검증
□ Swagger 문서화 (app/Swagger/v1/ItemMasterApi.php)
□ Cascade 삭제 정책 적용
□ Nested 조회 최적화 (Eager Loading)
□ order_no 자동 계산 로직
□ 실시간 저장 지원 (일괄 저장 없음)
```

---

## 📞 문의

**프론트엔드 개발팀**: [연락처]
**백엔드 개발팀**: [연락처]

---

**문서 버전**: v1.0
**작성일**: 2025-11-20
**다음 리뷰 예정일**: 백엔드 구현 완료 후
