# 품목기준관리 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 **다음 리뷰 예정일**: 백엔드 구현 완료 후