# 품목기준관리 API 요청서 **작성일**: 2025-11-25 **요청자**: 프론트엔드 개발팀 **대상**: 백엔드 개발팀 **프로젝트**: SAM MES System - 품목기준관리 (Item Master Data Management) --- ## 1. 개요 ### 1.1 목적 품목기준관리 화면에서 품목의 메타데이터(페이지, 섹션, 필드)를 동적으로 정의하기 위한 백엔드 API 개발 요청 ### 1.2 프론트엔드 구현 현황 - 프론트엔드 UI 구현 완료 - API 클라이언트 코드 작성 완료 (`src/lib/api/item-master.ts`) - 타입 정의 완료 (`src/types/item-master-api.ts`) - Next.js API 프록시 구조 적용 (HttpOnly 쿠키 인증) ### 1.3 API 기본 정보 | 항목 | 값 | |------|-----| | Base URL | `/api/v1/item-master` | | 인증 방식 | `auth.apikey + auth:sanctum` (HttpOnly Cookie) | | Content-Type | `application/json` | | 응답 형식 | 표준 API 응답 래퍼 사용 | ### 1.4 표준 응답 형식 ```json { "success": true, "message": "message.fetched", "data": { ... } } ``` --- ## 2. 필수 API 엔드포인트 ### 2.1 초기화 API (최우선) #### `GET /api/v1/item-master/init` **목적**: 화면 진입 시 전체 데이터를 한 번에 로드 **Request**: 없음 (JWT에서 tenant_id 자동 추출) **Response**: ```typescript interface InitResponse { pages: ItemPageResponse[]; // 페이지 목록 (섹션, 필드 포함) sectionTemplates: SectionTemplateResponse[]; // 섹션 템플릿 목록 masterFields: MasterFieldResponse[]; // 마스터 필드 목록 customTabs: CustomTabResponse[]; // 커스텀 탭 목록 tabColumns: Record; // 탭별 컬럼 설정 unitOptions: UnitOptionResponse[]; // 단위 옵션 목록 } ``` **중요**: `pages` 응답 시 `sections`와 `fields`를 Nested로 포함해야 함 **예시 응답**: ```json { "success": true, "message": "message.fetched", "data": { "pages": [ { "id": 1, "page_name": "기본정보", "item_type": "FG", "is_active": true, "sections": [ { "id": 1, "title": "품목코드 정보", "type": "fields", "order_no": 1, "fields": [ { "id": 1, "field_name": "품목코드", "field_type": "textbox", "is_required": true, "master_field_id": null, "order_no": 1 } ] } ] } ], "sectionTemplates": [...], "masterFields": [...], "customTabs": [...], "tabColumns": {...}, "unitOptions": [...] } } ``` --- ### 2.2 페이지 관리 API #### `POST /api/v1/item-master/pages` **목적**: 새 페이지 생성 **Request Body**: ```typescript interface ItemPageRequest { page_name: string; // 페이지명 (필수) item_type: 'FG' | 'PT' | 'SM' | 'RM' | 'CS'; // 품목유형 (필수) absolute_path?: string; // 절대경로 (선택) is_active?: boolean; // 활성화 여부 (기본: true) } ``` **Response**: `ItemPageResponse` --- #### `PUT /api/v1/item-master/pages/{id}` **목적**: 페이지 수정 **Path Parameter**: `id` - 페이지 ID **Request Body**: `Partial` **Response**: `ItemPageResponse` --- #### `DELETE /api/v1/item-master/pages/{id}` **목적**: 페이지 삭제 (Soft Delete) **Path Parameter**: `id` - 페이지 ID **Response**: `{ success: true, message: "message.deleted" }` --- ### 2.3 섹션 관리 API #### `POST /api/v1/item-master/pages/{pageId}/sections` **목적**: 페이지에 새 섹션 추가 **Path Parameter**: `pageId` - 페이지 ID **Request Body**: ```typescript interface ItemSectionRequest { title: string; // 섹션명 (필수) type: 'fields' | 'bom'; // 섹션 타입 (필수) template_id?: number; // 템플릿 ID (선택) - 템플릿에서 생성 시 } ``` **중요 - 템플릿 적용 로직**: - `template_id`가 전달되면 해당 템플릿의 필드들을 복사하여 새 섹션에 추가 - 템플릿의 필드들은 `master_field_id` 연결 관계도 복사 **Response**: `ItemSectionResponse` (생성된 섹션 + 필드 포함) --- #### `PUT /api/v1/item-master/sections/{id}` **목적**: 섹션 수정 (제목 변경 등) **Path Parameter**: `id` - 섹션 ID **Request Body**: `Partial` **Response**: `ItemSectionResponse` --- #### `DELETE /api/v1/item-master/sections/{id}` **목적**: 섹션 삭제 **Path Parameter**: `id` - 섹션 ID **Response**: `{ success: true, message: "message.deleted" }` --- #### `PUT /api/v1/item-master/pages/{pageId}/sections/reorder` **목적**: 섹션 순서 변경 (드래그앤드롭) **Path Parameter**: `pageId` - 페이지 ID **Request Body**: ```typescript interface SectionReorderRequest { section_orders: Array<{ id: number; // 섹션 ID order_no: number; // 새 순서 }>; } ``` **Response**: `ItemSectionResponse[]` --- ### 2.4 필드 관리 API #### `POST /api/v1/item-master/sections/{sectionId}/fields` **목적**: 섹션에 새 필드 추가 **Path Parameter**: `sectionId` - 섹션 ID **Request Body**: ```typescript interface ItemFieldRequest { field_name: string; // 필드명 (필수) field_type: 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea'; // 필드 타입 (필수) // 마스터 필드 연결 (핵심 기능) master_field_id?: number; // 마스터 필드 ID (마스터에서 선택한 경우) // 선택 속성 is_required?: boolean; placeholder?: string; default_value?: string; options?: Array<{ label: string; value: string }>; // dropdown 옵션 validation_rules?: Record; properties?: Record; // 조건부 표시 설정 (신규 기능) display_condition?: { field_key: string; // 조건 필드 키 expected_value: string; // 예상 값 target_field_ids?: string[]; // 표시할 필드 ID 목록 target_section_ids?: string[]; // 표시할 섹션 ID 목록 }[]; } ``` **중요 - master_field_id 처리**: - 프론트엔드에서 "마스터 항목 선택" 모드로 필드 추가 시 `master_field_id` 전달 - 백엔드에서 해당 마스터 필드의 속성을 참조하여 기본값 설정 - 마스터 필드가 수정되면 연결된 필드도 동기화 필요 (옵션) **Response**: `ItemFieldResponse` --- #### `PUT /api/v1/item-master/fields/{id}` **목적**: 필드 수정 **Path Parameter**: `id` - 필드 ID **Request Body**: `Partial` **Response**: `ItemFieldResponse` --- #### `DELETE /api/v1/item-master/fields/{id}` **목적**: 필드 삭제 **Path Parameter**: `id` - 필드 ID **Response**: `{ success: true, message: "message.deleted" }` --- #### `PUT /api/v1/item-master/sections/{sectionId}/fields/reorder` **목적**: 필드 순서 변경 (드래그앤드롭) **Path Parameter**: `sectionId` - 섹션 ID **Request Body**: ```typescript interface FieldReorderRequest { field_orders: Array<{ id: number; // 필드 ID order_no: number; // 새 순서 }>; } ``` **Response**: `ItemFieldResponse[]` --- ### 2.5 섹션 템플릿 API #### `GET /api/v1/item-master/section-templates` **목적**: 섹션 템플릿 목록 조회 **Response**: `SectionTemplateResponse[]` --- #### `POST /api/v1/item-master/section-templates` **목적**: 새 섹션 템플릿 생성 **Request Body**: ```typescript interface SectionTemplateRequest { title: string; // 템플릿명 (필수) type: 'fields' | 'bom'; // 타입 (필수) description?: string; // 설명 (선택) is_default?: boolean; // 기본 템플릿 여부 (선택) // 템플릿에 포함될 필드들 fields?: Array<{ field_name: string; field_type: string; master_field_id?: number; is_required?: boolean; options?: Array<{ label: string; value: string }>; properties?: Record; }>; } ``` **Response**: `SectionTemplateResponse` --- #### `PUT /api/v1/item-master/section-templates/{id}` **목적**: 섹션 템플릿 수정 **Response**: `SectionTemplateResponse` --- #### `DELETE /api/v1/item-master/section-templates/{id}` **목적**: 섹션 템플릿 삭제 **Response**: `{ success: true, message: "message.deleted" }` --- ### 2.6 마스터 필드 API #### `GET /api/v1/item-master/master-fields` **목적**: 마스터 필드 목록 조회 **Response**: `MasterFieldResponse[]` --- #### `POST /api/v1/item-master/master-fields` **목적**: 새 마스터 필드 생성 **Request Body**: ```typescript interface MasterFieldRequest { field_name: string; // 필드명 (필수) field_type: 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea'; // 필드 타입 (필수) category?: string; // 카테고리 (선택) - 예: "기본정보", "스펙정보" description?: string; // 설명 (선택) is_common?: boolean; // 공통 항목 여부 (선택) default_value?: string; options?: Array<{ label: string; value: string }>; validation_rules?: Record; properties?: Record; } ``` **Response**: `MasterFieldResponse` --- #### `PUT /api/v1/item-master/master-fields/{id}` **목적**: 마스터 필드 수정 **Response**: `MasterFieldResponse` --- #### `DELETE /api/v1/item-master/master-fields/{id}` **목적**: 마스터 필드 삭제 **주의**: 해당 마스터 필드를 참조하는 필드(`master_field_id`)가 있을 경우 처리 방안 필요 - 옵션 1: 삭제 불가 (참조 무결성) - 옵션 2: 참조 해제 후 삭제 **Response**: `{ success: true, message: "message.deleted" }` --- ### 2.7 BOM 관리 API #### `POST /api/v1/item-master/sections/{sectionId}/bom-items` **목적**: BOM 항목 추가 **Request Body**: ```typescript interface BomItemRequest { item_code?: string; item_name: string; // 필수 quantity: number; // 필수 unit?: string; unit_price?: number; total_price?: number; spec?: string; note?: string; } ``` **Response**: `BomItemResponse` --- #### `PUT /api/v1/item-master/bom-items/{id}` **목적**: BOM 항목 수정 **Response**: `BomItemResponse` --- #### `DELETE /api/v1/item-master/bom-items/{id}` **목적**: BOM 항목 삭제 **Response**: `{ success: true, message: "message.deleted" }` --- ### 2.8 커스텀 탭 API #### `GET /api/v1/item-master/custom-tabs` **목적**: 커스텀 탭 목록 조회 **Response**: `CustomTabResponse[]` --- #### `POST /api/v1/item-master/custom-tabs` **목적**: 새 커스텀 탭 생성 **Request Body**: ```typescript interface CustomTabRequest { label: string; // 탭 레이블 (필수) icon?: string; // 아이콘 (선택) is_default?: boolean; // 기본 탭 여부 (선택) } ``` **Response**: `CustomTabResponse` --- #### `PUT /api/v1/item-master/custom-tabs/{id}` **목적**: 커스텀 탭 수정 **Response**: `CustomTabResponse` --- #### `DELETE /api/v1/item-master/custom-tabs/{id}` **목적**: 커스텀 탭 삭제 **Response**: `{ success: true, message: "message.deleted" }` --- #### `PUT /api/v1/item-master/custom-tabs/reorder` **목적**: 탭 순서 변경 **Request Body**: ```typescript interface TabReorderRequest { tab_orders: Array<{ id: number; order_no: number; }>; } ``` **Response**: `{ success: true }` --- #### `PUT /api/v1/item-master/custom-tabs/{id}/columns` **목적**: 탭별 컬럼 설정 업데이트 **Request Body**: ```typescript interface TabColumnUpdateRequest { columns: Array<{ key: string; label: string; visible: boolean; order: number; }>; } ``` **Response**: `TabColumnResponse[]` --- ### 2.9 단위 옵션 API #### `GET /api/v1/item-master/unit-options` **목적**: 단위 옵션 목록 조회 **Response**: `UnitOptionResponse[]` --- #### `POST /api/v1/item-master/unit-options` **목적**: 새 단위 옵션 추가 **Request Body**: ```typescript interface UnitOptionRequest { label: string; // 표시명 (예: "개") value: string; // 값 (예: "EA") } ``` **Response**: `UnitOptionResponse` --- #### `DELETE /api/v1/item-master/unit-options/{id}` **목적**: 단위 옵션 삭제 **Response**: `{ success: true, message: "message.deleted" }` --- ## 3. 데이터베이스 스키마 제안 ### 3.1 item_master_pages ```sql CREATE TABLE item_master_pages ( id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, tenant_id BIGINT UNSIGNED NOT NULL, page_name VARCHAR(100) NOT NULL, item_type ENUM('FG', 'PT', 'SM', 'RM', 'CS') NOT NULL, absolute_path VARCHAR(500) NULL, order_no INT NOT NULL DEFAULT 0, is_active BOOLEAN NOT NULL DEFAULT TRUE, created_by BIGINT UNSIGNED NULL, updated_by BIGINT UNSIGNED NULL, deleted_by BIGINT UNSIGNED NULL, created_at TIMESTAMP NULL, updated_at TIMESTAMP NULL, deleted_at TIMESTAMP NULL, INDEX idx_tenant (tenant_id), FOREIGN KEY (tenant_id) REFERENCES tenants(id) ); ``` ### 3.2 item_master_sections ```sql CREATE TABLE item_master_sections ( id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, tenant_id BIGINT UNSIGNED NOT NULL, page_id BIGINT UNSIGNED NOT NULL, title VARCHAR(100) NOT NULL, type ENUM('fields', 'bom') NOT NULL DEFAULT 'fields', order_no INT NOT NULL DEFAULT 0, created_by BIGINT UNSIGNED NULL, updated_by BIGINT UNSIGNED NULL, deleted_by BIGINT UNSIGNED NULL, created_at TIMESTAMP NULL, updated_at TIMESTAMP NULL, deleted_at TIMESTAMP NULL, INDEX idx_tenant_page (tenant_id, page_id), FOREIGN KEY (tenant_id) REFERENCES tenants(id), FOREIGN KEY (page_id) REFERENCES item_master_pages(id) ON DELETE CASCADE ); ``` ### 3.3 item_master_fields ```sql CREATE TABLE item_master_fields ( id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, tenant_id BIGINT UNSIGNED NOT NULL, section_id BIGINT UNSIGNED NOT NULL, master_field_id BIGINT UNSIGNED NULL, -- 마스터 필드 참조 field_name VARCHAR(100) NOT NULL, field_type ENUM('textbox', 'number', 'dropdown', 'checkbox', 'date', 'textarea') NOT NULL, order_no INT NOT NULL DEFAULT 0, is_required BOOLEAN NOT NULL DEFAULT FALSE, placeholder VARCHAR(200) NULL, default_value VARCHAR(500) NULL, display_condition JSON NULL, -- 조건부 표시 설정 validation_rules JSON NULL, options JSON NULL, -- dropdown 옵션 properties JSON NULL, -- 추가 속성 (컬럼 설정 등) created_by BIGINT UNSIGNED NULL, updated_by BIGINT UNSIGNED NULL, deleted_by BIGINT UNSIGNED NULL, created_at TIMESTAMP NULL, updated_at TIMESTAMP NULL, deleted_at TIMESTAMP NULL, INDEX idx_tenant_section (tenant_id, section_id), INDEX idx_master_field (master_field_id), FOREIGN KEY (tenant_id) REFERENCES tenants(id), FOREIGN KEY (section_id) REFERENCES item_master_sections(id) ON DELETE CASCADE, FOREIGN KEY (master_field_id) REFERENCES item_master_master_fields(id) ON DELETE SET NULL ); ``` ### 3.4 item_master_master_fields (마스터 필드) ```sql CREATE TABLE item_master_master_fields ( id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, tenant_id BIGINT UNSIGNED NOT NULL, field_name VARCHAR(100) NOT NULL, field_type ENUM('textbox', 'number', 'dropdown', 'checkbox', 'date', 'textarea') NOT NULL, category VARCHAR(50) NULL, description TEXT NULL, is_common BOOLEAN NOT NULL DEFAULT FALSE, default_value VARCHAR(500) NULL, options JSON NULL, validation_rules JSON NULL, properties JSON NULL, created_by BIGINT UNSIGNED NULL, updated_by BIGINT UNSIGNED NULL, deleted_by BIGINT UNSIGNED NULL, created_at TIMESTAMP NULL, updated_at TIMESTAMP NULL, deleted_at TIMESTAMP NULL, INDEX idx_tenant (tenant_id), INDEX idx_category (tenant_id, category), FOREIGN KEY (tenant_id) REFERENCES tenants(id) ); ``` ### 3.5 item_master_section_templates (섹션 템플릿) ```sql CREATE TABLE item_master_section_templates ( id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, tenant_id BIGINT UNSIGNED NOT NULL, title VARCHAR(100) NOT NULL, type ENUM('fields', 'bom') NOT NULL DEFAULT 'fields', description TEXT NULL, is_default BOOLEAN NOT NULL DEFAULT FALSE, created_by BIGINT UNSIGNED NULL, updated_by BIGINT UNSIGNED NULL, deleted_by BIGINT UNSIGNED NULL, created_at TIMESTAMP NULL, updated_at TIMESTAMP NULL, deleted_at TIMESTAMP NULL, INDEX idx_tenant (tenant_id), FOREIGN KEY (tenant_id) REFERENCES tenants(id) ); ``` ### 3.6 item_master_template_fields (템플릿 필드) ```sql CREATE TABLE item_master_template_fields ( id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, tenant_id BIGINT UNSIGNED NOT NULL, template_id BIGINT UNSIGNED NOT NULL, master_field_id BIGINT UNSIGNED NULL, field_name VARCHAR(100) NOT NULL, field_type ENUM('textbox', 'number', 'dropdown', 'checkbox', 'date', 'textarea') NOT NULL, order_no INT NOT NULL DEFAULT 0, is_required BOOLEAN NOT NULL DEFAULT FALSE, placeholder VARCHAR(200) NULL, default_value VARCHAR(500) NULL, options JSON NULL, properties JSON NULL, created_at TIMESTAMP NULL, updated_at TIMESTAMP NULL, INDEX idx_template (template_id), FOREIGN KEY (tenant_id) REFERENCES tenants(id), FOREIGN KEY (template_id) REFERENCES item_master_section_templates(id) ON DELETE CASCADE, FOREIGN KEY (master_field_id) REFERENCES item_master_master_fields(id) ON DELETE SET NULL ); ``` --- ## 4. 핵심 비즈니스 로직 ### 4.1 마스터 필드 연결 (`master_field_id`) **시나리오**: 사용자가 필드 추가 시 "마스터 항목 선택" 모드로 추가 **프론트엔드 동작**: 1. 마스터 필드 목록에서 선택 2. 선택된 마스터 필드의 속성을 폼에 자동 채움 3. 저장 시 `master_field_id` 포함하여 전송 **백엔드 처리**: ```php // ItemFieldService.php public function create(int $sectionId, array $data): ItemField { // master_field_id가 있으면 마스터 필드에서 기본값 가져오기 if (!empty($data['master_field_id'])) { $masterField = MasterField::findOrFail($data['master_field_id']); // 마스터 필드의 속성을 기본값으로 사용 (명시적 값이 없는 경우) $data = array_merge([ 'field_type' => $masterField->field_type, 'options' => $masterField->options, 'validation_rules' => $masterField->validation_rules, 'properties' => $masterField->properties, ], $data); } return ItemField::create($data); } ``` ### 4.2 섹션 템플릿 적용 **시나리오**: 사용자가 섹션 추가 시 "템플릿에서 선택" 모드로 추가 **프론트엔드 동작**: 1. 템플릿 목록에서 선택 2. 선택된 템플릿 정보로 섹션 생성 요청 3. `template_id` 포함하여 전송 **백엔드 처리**: ```php // ItemSectionService.php public function create(int $pageId, array $data): ItemSection { $section = ItemSection::create([ 'page_id' => $pageId, 'title' => $data['title'], 'type' => $data['type'], ]); // template_id가 있으면 템플릿의 필드들을 복사 if (!empty($data['template_id'])) { $templateFields = TemplateField::where('template_id', $data['template_id']) ->orderBy('order_no') ->get(); foreach ($templateFields as $index => $tf) { ItemField::create([ 'section_id' => $section->id, 'master_field_id' => $tf->master_field_id, // 마스터 연결 유지 'field_name' => $tf->field_name, 'field_type' => $tf->field_type, 'order_no' => $index, 'is_required' => $tf->is_required, 'options' => $tf->options, 'properties' => $tf->properties, ]); } } return $section->load('fields'); } ``` ### 4.3 조건부 표시 설정 **JSON 구조**: ```json { "display_condition": [ { "field_key": "item_type", "expected_value": "FG", "target_field_ids": ["5", "6", "7"] }, { "field_key": "item_type", "expected_value": "PT", "target_section_ids": ["3"] } ] } ``` **활용**: 프론트엔드에서 품목 데이터 입력 시 해당 조건에 따라 필드/섹션을 동적으로 표시/숨김 --- ## 5. 우선순위 ### Phase 1 (필수 - 즉시) 1. `GET /api/v1/item-master/init` - 초기화 API 2. 페이지 CRUD API 3. 섹션 CRUD API (순서변경 포함) 4. 필드 CRUD API (순서변경 포함, `master_field_id` 지원) ### Phase 2 (중요 - 1주 내) 5. 마스터 필드 CRUD API 6. 섹션 템플릿 CRUD API 7. 템플릿 필드 관리 ### Phase 3 (선택 - 2주 내) 8. BOM 항목 관리 API 9. 커스텀 탭 API 10. 단위 옵션 API --- ## 6. 참고 사항 ### 6.1 프론트엔드 코드 위치 - API 클라이언트: `src/lib/api/item-master.ts` - 타입 정의: `src/types/item-master-api.ts` - 메인 컴포넌트: `src/components/items/ItemMasterDataManagement.tsx` ### 6.2 기존 API 문서 - `claudedocs/[API-2025-11-24] item-management-dynamic-api-spec.md` - 품목관리 동적 화면 API ### 6.3 Multi-Tenancy - 모든 테이블에 `tenant_id` 컬럼 필수 - JWT에서 tenant_id 자동 추출 - `BelongsToTenant` Trait 적용 필요 ### 6.4 에러 응답 형식 ```json { "success": false, "message": "error.validation_failed", "errors": { "field_name": ["필드명은 필수입니다."], "field_type": ["유효하지 않은 필드 타입입니다."] } } ``` --- ## 7. 연락처 질문이나 협의 사항이 있으면 언제든 연락 바랍니다. **프론트엔드 담당**: [담당자명] **작성일**: 2025-11-25