# 품목기준관리 API 추가 요청 - 섹션 템플릿 하위 데이터 **요청일**: 2025-11-25 **버전**: v1.1 **작성자**: 프론트엔드 개발팀 **수신**: 백엔드 개발팀 **긴급도**: 🔴 높음 --- ## 📋 목차 1. [요청 배경](#1-요청-배경) 2. [데이터베이스 테이블 추가](#2-데이터베이스-테이블-추가) 3. [API 엔드포인트 추가](#3-api-엔드포인트-추가) 4. [init API 응답 수정](#4-init-api-응답-수정) 5. [구현 우선순위](#5-구현-우선순위) --- ## 1. 요청 배경 ### 1.1 문제 상황 - 섹션탭 > 일반 섹션에 항목(필드) 추가 후 **새로고침 시 데이터 사라짐** - 섹션탭 > 모듈 섹션(BOM)에 BOM 품목 추가 후 **새로고침 시 데이터 사라짐** - 원인: 섹션 템플릿 하위 데이터를 저장/조회하는 API 없음 ### 1.2 현재 상태 비교 | 구분 | 계층구조 (정상) | 섹션 템플릿 (문제) | |------|----------------|-------------------| | 섹션/템플릿 CRUD | ✅ 있음 | ✅ 있음 | | 필드 CRUD | ✅ `/sections/{id}/fields` | ❌ **없음** | | BOM 품목 CRUD | ✅ `/sections/{id}/bom-items` | ❌ **없음** | | init 응답에 중첩 포함 | ✅ `fields`, `bomItems` 포함 | ❌ **미포함** | ### 1.3 요청 내용 1. 섹션 템플릿 필드 테이블 및 CRUD API 추가 2. 섹션 템플릿 BOM 품목 테이블 및 CRUD API 추가 3. init API 응답에 섹션 템플릿 하위 데이터 중첩 포함 4. **🔴 [추가] 계층구조 섹션 ↔ 섹션 템플릿 데이터 동기화** --- ## 2. 데이터베이스 테이블 추가 ### 2.0 section_templates 테이블 수정 (데이터 동기화용) **요구사항**: 계층구조에서 생성한 섹션과 섹션탭의 템플릿이 **동일한 데이터**로 연동되어야 함 **현재 문제**: ``` 계층구조 섹션 생성 시: ├── item_sections 테이블에 저장 (id: 1) └── section_templates 테이블에 저장 (id: 1) → 두 개의 별도 데이터! 연결 없음! ``` **해결 방안**: `section_templates`에 `section_id` 컬럼 추가 ```sql ALTER TABLE section_templates ADD COLUMN section_id BIGINT UNSIGNED NULL COMMENT '연결된 계층구조 섹션 ID (동기화용)' AFTER tenant_id, ADD INDEX idx_section_id (section_id), ADD FOREIGN KEY (section_id) REFERENCES item_sections(id) ON DELETE SET NULL; ``` **동기화 동작**: | 액션 | 동작 | |------|------| | 계층구조에서 섹션 생성 | `item_sections` + `section_templates` 생성, `section_id`로 연결 | | 계층구조에서 섹션 수정 | `item_sections` 수정 → 연결된 `section_templates`도 수정 | | 계층구조에서 섹션 삭제 | `item_sections` 삭제 → 연결된 `section_templates`의 `section_id` = NULL | | 섹션탭에서 템플릿 수정 | `section_templates` 수정 → 연결된 `item_sections`도 수정 | | 섹션탭에서 템플릿 삭제 | `section_templates` 삭제 → 연결된 `item_sections`는 유지 | **init API 응답 수정** (section_id 포함): ```json { "sectionTemplates": [ { "id": 1, "section_id": 5, // 연결된 계층구조 섹션 ID (없으면 null) "title": "일반 섹션", "type": "fields", ... } ] } ``` --- ### 2.1 section_template_fields (섹션 템플릿 필드) **참고**: 기존 `item_fields` 테이블 구조와 유사하게 설계 ```sql CREATE TABLE section_template_fields ( id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, tenant_id BIGINT UNSIGNED NOT NULL COMMENT '테넌트 ID', template_id BIGINT UNSIGNED NOT NULL COMMENT '섹션 템플릿 ID', field_name VARCHAR(255) NOT NULL COMMENT '필드명', field_key VARCHAR(100) 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 '필수 여부', options JSON NULL COMMENT '드롭다운 옵션 ["옵션1", "옵션2"]', multi_column TINYINT(1) DEFAULT 0 COMMENT '다중 컬럼 여부', column_count INT NULL COMMENT '컬럼 수', column_names JSON NULL COMMENT '컬럼명 목록 ["컬럼1", "컬럼2"]', description 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_template (tenant_id, template_id), INDEX idx_order (template_id, order_no), FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE, FOREIGN KEY (template_id) REFERENCES section_templates(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='섹션 템플릿 필드'; ``` ### 2.2 section_template_bom_items (섹션 템플릿 BOM 품목) **참고**: 기존 `item_bom_items` 테이블 구조와 유사하게 설계 ```sql CREATE TABLE section_template_bom_items ( id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, tenant_id BIGINT UNSIGNED NOT NULL COMMENT '테넌트 ID', template_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 '비고', 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_template (tenant_id, template_id), INDEX idx_order (template_id, order_no), FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE, FOREIGN KEY (template_id) REFERENCES section_templates(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='섹션 템플릿 BOM 품목'; ``` --- ## 3. API 엔드포인트 추가 ### 3.1 섹션 템플릿 필드 관리 (우선순위 1) #### `POST /v1/item-master/section-templates/{templateId}/fields` **목적**: 템플릿 필드 생성 **Request Body**: ```json { "field_name": "품목코드", "field_key": "item_code", "field_type": "textbox", "is_required": true, "options": null, "multi_column": false, "column_count": null, "column_names": null, "description": "품목 고유 코드" } ``` **Validation**: - `field_name`: required, string, max:255 - `field_key`: required, string, max:100, alpha_dash - `field_type`: required, in:textbox,number,dropdown,checkbox,date,textarea - `is_required`: boolean - `options`: nullable, array (dropdown 타입일 경우) - `multi_column`: boolean - `column_count`: nullable, integer, min:2, max:10 - `column_names`: nullable, array - `description`: nullable, string **Response**: ```json { "success": true, "message": "message.created", "data": { "id": 1, "template_id": 1, "field_name": "품목코드", "field_key": "item_code", "field_type": "textbox", "order_no": 0, "is_required": true, "options": null, "multi_column": false, "column_count": null, "column_names": null, "description": "품목 고유 코드", "created_at": "2025-11-25T10:00:00.000000Z", "updated_at": "2025-11-25T10:00:00.000000Z" } } ``` **참고**: - `order_no`는 자동 계산 (해당 템플릿의 마지막 필드 order + 1) --- #### `PUT /v1/item-master/section-templates/{templateId}/fields/{fieldId}` **목적**: 템플릿 필드 수정 **Request Body**: ```json { "field_name": "품목코드 (수정)", "field_type": "dropdown", "options": ["옵션1", "옵션2"], "is_required": false } ``` **Validation**: POST와 동일 (모든 필드 optional) **Response**: 수정된 필드 정보 반환 --- #### `DELETE /v1/item-master/section-templates/{templateId}/fields/{fieldId}` **목적**: 템플릿 필드 삭제 (Soft Delete) **Request**: 없음 **Response**: ```json { "success": true, "message": "message.deleted" } ``` --- #### `PUT /v1/item-master/section-templates/{templateId}/fields/reorder` **목적**: 템플릿 필드 순서 변경 **Request Body**: ```json { "field_orders": [ { "id": 3, "order_no": 0 }, { "id": 1, "order_no": 1 }, { "id": 2, "order_no": 2 } ] } ``` **Validation**: - `field_orders`: required, array - `field_orders.*.id`: required, exists:section_template_fields,id - `field_orders.*.order_no`: required, integer, min:0 **Response**: ```json { "success": true, "message": "message.updated", "data": [ { "id": 3, "order_no": 0 }, { "id": 1, "order_no": 1 }, { "id": 2, "order_no": 2 } ] } ``` --- ### 3.2 섹션 템플릿 BOM 품목 관리 (우선순위 2) #### `POST /v1/item-master/section-templates/{templateId}/bom-items` **목적**: 템플릿 BOM 품목 생성 **Request Body**: ```json { "item_code": "PART-001", "item_name": "부품 A", "quantity": 2, "unit": "EA", "unit_price": 15000, "spec": "100x50x20", "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 - `spec`: nullable, string - `note`: nullable, string **Response**: ```json { "success": true, "message": "message.created", "data": { "id": 1, "template_id": 2, "item_code": "PART-001", "item_name": "부품 A", "quantity": 2, "unit": "EA", "unit_price": 15000, "total_price": 30000, "spec": "100x50x20", "note": "필수 부품", "order_no": 0, "created_at": "2025-11-25T10:00:00.000000Z", "updated_at": "2025-11-25T10:00:00.000000Z" } } ``` **참고**: - `total_price`는 서버에서 자동 계산 (`quantity * unit_price`) - `order_no`는 자동 계산 (해당 템플릿의 마지막 BOM 품목 order + 1) --- #### `PUT /v1/item-master/section-templates/{templateId}/bom-items/{itemId}` **목적**: 템플릿 BOM 품목 수정 **Request Body**: ```json { "item_name": "부품 A (수정)", "quantity": 3, "unit_price": 12000 } ``` **Validation**: POST와 동일 (모든 필드 optional) **Response**: 수정된 BOM 품목 정보 반환 --- #### `DELETE /v1/item-master/section-templates/{templateId}/bom-items/{itemId}` **목적**: 템플릿 BOM 품목 삭제 (Soft Delete) **Request**: 없음 **Response**: ```json { "success": true, "message": "message.deleted" } ``` --- #### `PUT /v1/item-master/section-templates/{templateId}/bom-items/reorder` **목적**: 템플릿 BOM 품목 순서 변경 **Request Body**: ```json { "item_orders": [ { "id": 3, "order_no": 0 }, { "id": 1, "order_no": 1 }, { "id": 2, "order_no": 2 } ] } ``` **Validation**: - `item_orders`: required, array - `item_orders.*.id`: required, exists:section_template_bom_items,id - `item_orders.*.order_no`: required, integer, min:0 **Response**: ```json { "success": true, "message": "message.updated", "data": [...] } ``` --- ## 4. init API 응답 수정 ### 4.1 현재 응답 (문제) ```json { "success": true, "data": { "sectionTemplates": [ { "id": 1, "title": "일반 섹션", "type": "fields", "description": null, "is_default": false }, { "id": 2, "title": "BOM 섹션", "type": "bom", "description": null, "is_default": false } ] } } ``` ### 4.2 수정 요청 `sectionTemplates`에 하위 데이터 중첩 포함: ```json { "success": true, "data": { "sectionTemplates": [ { "id": 1, "title": "일반 섹션", "type": "fields", "description": null, "is_default": false, "fields": [ { "id": 1, "field_name": "품목코드", "field_key": "item_code", "field_type": "textbox", "order_no": 0, "is_required": true, "options": null, "multi_column": false, "column_count": null, "column_names": null, "description": "품목 고유 코드" } ] }, { "id": 2, "title": "BOM 섹션", "type": "bom", "description": null, "is_default": false, "bomItems": [ { "id": 1, "item_code": "PART-001", "item_name": "부품 A", "quantity": 2, "unit": "EA", "unit_price": 15000, "total_price": 30000, "spec": "100x50x20", "note": "필수 부품", "order_no": 0 } ] } ] } } ``` **참고**: - `type: "fields"` 템플릿: `fields` 배열 포함 - `type: "bom"` 템플릿: `bomItems` 배열 포함 - 기존 `pages` 응답의 중첩 구조와 동일한 패턴 --- ## 5. 구현 우선순위 | 우선순위 | 작업 내용 | 예상 공수 | |---------|----------|----------| | 🔴 0 | `section_templates`에 `section_id` 컬럼 추가 (동기화용) | 0.5일 | | 🔴 0 | 계층구조 섹션 생성 시 `section_templates` 자동 생성 로직 | 0.5일 | | 🔴 1 | `section_template_fields` 테이블 생성 | 0.5일 | | 🔴 1 | 섹션 템플릿 필드 CRUD API (5개) | 1일 | | 🔴 1 | init API 응답에 `fields` 중첩 포함 | 0.5일 | | 🟡 2 | `section_template_bom_items` 테이블 생성 | 0.5일 | | 🟡 2 | 섹션 템플릿 BOM 품목 CRUD API (5개) | 1일 | | 🟡 2 | init API 응답에 `bomItems` 중첩 포함 | 0.5일 | | 🟢 3 | 양방향 동기화 로직 (섹션↔템플릿 수정 시 상호 반영) | 1일 | | 🟢 3 | Swagger 문서 업데이트 | 0.5일 | **총 예상 공수**: 백엔드 6.5일 --- ## 6. 프론트엔드 연동 계획 ### 6.1 API 완료 후 프론트엔드 작업 | 작업 | 설명 | 의존성 | |------|------|--------| | 타입 정의 수정 | `SectionTemplateResponse`에 `fields`, `bomItems`, `section_id` 추가 | init API 수정 후 | | Context 수정 | 섹션 템플릿 필드/BOM API 호출 로직 추가 | CRUD API 완료 후 | | 로컬 상태 제거 | `default_fields` 로컬 관리 로직 → API 연동으로 교체 | CRUD API 완료 후 | | 동기화 UI | 계층구조↔섹션탭 간 데이터 자동 반영 | section_id 추가 후 | ### 6.2 타입 수정 예시 **현재** (`src/types/item-master-api.ts`): ```typescript export interface SectionTemplateResponse { id: number; title: string; type: 'fields' | 'bom'; description?: string; is_default: boolean; } ``` **수정 후**: ```typescript export interface SectionTemplateResponse { id: number; section_id?: number | null; // 연결된 계층구조 섹션 ID title: string; type: 'fields' | 'bom'; description?: string; is_default: boolean; fields?: SectionTemplateFieldResponse[]; // type='fields'일 때 bomItems?: SectionTemplateBomItemResponse[]; // type='bom'일 때 } ``` ### 6.3 동기화 시나리오 정리 ``` [시나리오 1] 계층구조에서 섹션 생성 └─ 백엔드: item_sections + section_templates 동시 생성 (section_id로 연결) └─ 프론트: init 재조회 → 양쪽 탭에 데이터 표시 [시나리오 2] 계층구조에서 필드 추가/수정 └─ 백엔드: item_fields 저장 → 연결된 section_template_fields도 동기화 └─ 프론트: init 재조회 → 섹션탭에 필드 반영 [시나리오 3] 섹션탭에서 필드 추가/수정 └─ 백엔드: section_template_fields 저장 → 연결된 item_fields도 동기화 └─ 프론트: init 재조회 → 계층구조탭에 필드 반영 [시나리오 4] 섹션탭에서 독립 템플릿 생성 (section_id = null) └─ 백엔드: section_templates만 생성 (계층구조와 무관) └─ 프론트: 섹션탭에서만 사용 가능한 템플릿 ``` --- ## 📞 문의 질문 있으시면 프론트엔드 팀으로 연락 주세요. --- **작성일**: 2025-11-25 **기준 문서**: `[API-2025-11-20] item-master-specification.md`