Files
sam-react-prod/src/types/item-master-api.ts
byeongcheolryu 8fd9cf2d40 fix: field_key 저장 및 표시 기능 완전 수정
- field_key 저장 시 백엔드 형식({ID}_{사용자입력})으로 전송
- API 요청 전 field_key 유효성 검증 추가
- 계층구조 탭 필드 추가/수정 시 field_key 반영
- 섹션 탭에서 field_key 표시 시 사용자입력 부분만 추출
- sectionsAsTemplates useMemo에서 linkedSections/unlinkedSections 모두 수정
- 마스터 필드, 템플릿 필드 다이얼로그에서 field_key 입력 지원
- ItemMasterContext에 field_key 상태 업데이트 로직 추가
- transformers에서 field_key 변환 처리

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 19:57:52 +09:00

661 lines
17 KiB
TypeScript

// 품목기준관리 API 타입 정의
// API 응답 기준 snake_case 사용
// ============================================
// 공통 타입
// ============================================
/**
* 표준 API 응답 래퍼
*/
export interface ApiResponse<T> {
success: boolean;
message: string;
data: T;
}
/**
* 페이지네이션 메타데이터
*/
export interface PaginationMeta {
current_page: number;
per_page: number;
total: number;
last_page: number;
}
// ============================================
// 초기화 API
// ============================================
/**
* 초기화 API 응답 - 화면 진입 시 전체 데이터 로드
* GET /v1/item-master/init
*
* 2025-11-27 변경사항:
* - masterFields → 독립 필드(item_fields WHERE section_id IS NULL)로 대체 예정
* - fields 필드 추가: 모든 필드 목록 (독립 필드 + 섹션 내 필드)
*/
export interface InitResponse {
pages: ItemPageResponse[];
sections?: ItemSectionResponse[]; // 2025-11-26 추가: 모든 섹션 (독립 섹션 포함)
fields?: ItemFieldResponse[]; // 2025-11-27 추가: 모든 필드 (독립 필드 = section_id IS NULL)
sectionTemplates: SectionTemplateResponse[];
/** @deprecated 2025-11-27: item_fields로 통합됨. fields 필드 사용 권장 */
masterFields: MasterFieldResponse[];
customTabs: CustomTabResponse[];
tabColumns: Record<number, TabColumnResponse[]>; // tab_id를 key로 사용
unitOptions: UnitOptionResponse[];
}
// ============================================
// 페이지 관리
// ============================================
/**
* 페이지 생성/수정 요청
* POST /v1/item-master/pages
* PUT /v1/item-master/pages/{id}
*/
export interface ItemPageRequest {
page_name: string;
item_type: 'FG' | 'PT' | 'SM' | 'RM' | 'CS';
absolute_path?: string;
is_active?: boolean;
}
/**
* 페이지 응답
*/
export interface ItemPageResponse {
id: number;
tenant_id: number;
page_name: string;
item_type: string;
description: string | null; // 2025-11-26 추가: 페이지 설명
absolute_path: string | null;
is_active: boolean;
order_no: number; // 2025-11-26 추가: 순서 번호
created_by: number | null;
updated_by: number | null;
created_at: string;
updated_at: string;
sections?: ItemSectionResponse[]; // Nested 조회 시 포함
}
/**
* 페이지 순서 변경 요청
* PUT /v1/item-master/pages/reorder (향후 구현 가능성)
*/
export interface PageReorderRequest {
page_orders: Array<{
id: number;
order_no: number;
}>;
}
// ============================================
// 섹션 관리
// ============================================
/**
* 섹션 생성/수정 요청
* POST /v1/item-master/pages/{pageId}/sections
* PUT /v1/item-master/sections/{id}
*/
export interface ItemSectionRequest {
title: string;
type: 'fields' | 'bom';
}
/**
* 섹션 응답
* 2025-11-26: section_templates 테이블 통합으로 is_template, is_default, description, group_id 추가
*/
export interface ItemSectionResponse {
id: number;
tenant_id: number;
group_id: number | null; // 그룹 ID (독립 섹션 그룹화용)
page_id: number | null; // 페이지 ID (null이면 독립 섹션)
title: string;
type: 'fields' | 'bom';
order_no: number;
is_template: boolean; // 템플릿 여부 (section_templates 통합)
is_default: boolean; // 기본 템플릿 여부
description: string | null; // 섹션 설명
created_by: number | null;
updated_by: number | null;
created_at: string;
updated_at: string;
fields?: ItemFieldResponse[]; // Nested 조회 시 포함
bomItems?: BomItemResponse[]; // Nested 조회 시 포함 (camelCase)
bom_items?: BomItemResponse[]; // 2025-11-27: 백엔드가 snake_case로 반환
}
/**
* 섹션 순서 변경 요청
* PUT /v1/item-master/pages/{pageId}/sections/reorder
* 백엔드 API는 'items' 필드를 기대함
*/
export interface SectionReorderRequest {
items: Array<{
id: number;
order_no: number;
}>;
}
/**
* 독립 섹션 생성 요청
* POST /v1/item-master/sections
* 2025-11-26 신규 API
*/
export interface IndependentSectionRequest {
group_id?: number;
title: string;
type: 'fields' | 'bom';
is_template?: boolean;
is_default?: boolean;
description?: string;
}
/**
* 섹션 사용처 응답
* GET /v1/item-master/sections/{id}/usage
* 2025-11-26 신규 API
*/
export interface SectionUsageResponse {
section_id: number;
direct_page: {
id: number;
page_name: string;
item_type: string;
} | null;
linked_pages: Array<{
id: number;
page_name: string;
item_type: string;
}>;
total_usage_count: number;
}
/**
* 페이지에 섹션 연결 요청
* POST /v1/item-master/pages/{id}/link-section
* 2025-11-26 신규 API
*/
export interface LinkSectionRequest {
child_id: number; // 연결할 섹션 ID (백엔드: child_id)
order_no?: number;
}
// ============================================
// 필드 관리
// ============================================
/**
* 필드 생성/수정 요청
* POST /v1/item-master/sections/{sectionId}/fields
* PUT /v1/item-master/fields/{id}
*/
export interface ItemFieldRequest {
field_name: string;
field_key?: string; // 2025-11-28: 필드 키 (영문, 숫자, 언더스코어만 허용, 영문으로 시작)
field_type: 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea';
is_required?: boolean;
placeholder?: string;
default_value?: string;
display_condition?: Record<string, any>; // {"field_id": "1", "operator": "equals", "value": "true"}
validation_rules?: Record<string, any>; // {"min": 0, "max": 100, "pattern": "regex"}
options?: Array<{ label: string; value: string }>; // dropdown 옵션
properties?: Record<string, any>; // {"unit": "mm", "precision": 2, "format": "YYYY-MM-DD"}
is_locked?: boolean; // 2025-11-28: 잠금 여부
}
/**
* 필드 응답
* 2025-11-26: section_id를 nullable로 변경 (독립 필드 지원)
* 2025-11-27: item_master_fields 테이블 통합으로 category, description, is_common 추가
*/
export interface ItemFieldResponse {
id: number;
tenant_id: number;
group_id: number | null; // 그룹 ID (독립 필드 그룹화용)
section_id: number | null; // 섹션 ID (null이면 독립 필드/마스터 항목)
master_field_id?: number | null; // 마스터 항목 ID (마스터에서 가져온 경우)
field_name: string;
field_key: string | null; // 2025-11-28: 필드 키 (형식: {ID}_{사용자입력})
field_type: 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea';
order_no: number;
is_required: boolean;
placeholder: string | null;
default_value: string | null;
display_condition: Record<string, any> | null;
validation_rules: Record<string, any> | null;
options: Array<{ label: string; value: string }> | null;
properties: Record<string, any> | null;
// 2025-11-27 추가: item_master_fields 통합으로 추가된 필드
category: string | null; // 카테고리 (예: "공통", "완제품", "부품")
description: string | null; // 필드 설명
is_common: boolean; // 공통 필드 여부
// 2025-11-28 추가: 잠금 기능
is_locked: boolean; // 잠금 여부
locked_by: number | null; // 잠금 설정자
locked_at: string | null; // 잠금 시간
created_by: number | null;
updated_by: number | null;
created_at: string;
updated_at: string;
}
/**
* 필드 순서 변경 요청
* PUT /v1/item-master/sections/{sectionId}/fields/reorder
*/
export interface FieldReorderRequest {
field_orders: Array<{
id: number;
order_no: number;
}>;
}
/**
* 독립 필드 생성 요청
* POST /v1/item-master/fields
* 2025-11-26 신규 API
* 2025-11-27: item_master_fields 통합으로 category, description, is_common 추가
*/
export interface IndependentFieldRequest {
group_id?: number;
field_name: string;
field_key?: string; // 2025-11-28: 필드 키 (영문, 숫자, 언더스코어만 허용, 영문으로 시작)
field_type: 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea';
is_required?: boolean;
default_value?: string;
placeholder?: string;
options?: Array<{ label: string; value: string }>;
properties?: Record<string, any>;
validation_rules?: Record<string, any>;
display_condition?: Record<string, any>;
// 2025-11-27 추가: item_master_fields 통합으로 추가된 필드
category?: string;
description?: string;
is_common?: boolean;
is_locked?: boolean; // 2025-11-28: 잠금 여부
}
/**
* 필드 사용처 응답
* GET /v1/item-master/fields/{id}/usage
* 2025-11-26 신규 API
*/
export interface FieldUsageResponse {
field_id: number;
direct_section: {
id: number;
title: string;
type: 'fields' | 'bom';
} | null;
linked_sections: Array<{
id: number;
title: string;
type: 'fields' | 'bom';
}>;
total_usage_count: number;
}
/**
* 섹션에 필드 연결 요청
* POST /v1/item-master/sections/{id}/link-field
* 2025-11-26 신규 API
*/
export interface LinkFieldRequest {
child_id: number; // 연결할 필드 ID (백엔드: child_id)
order_no?: number;
}
// ============================================
// BOM 관리
// ============================================
/**
* BOM 항목 생성/수정 요청
* POST /v1/item-master/sections/{sectionId}/bom-items
* PUT /v1/item-master/bom-items/{id}
*/
export interface BomItemRequest {
item_code?: string;
item_name: string;
quantity: number;
unit?: string;
unit_price?: number;
total_price?: number;
spec?: string;
note?: string;
}
/**
* BOM 항목 응답
* 2025-11-26: section_id를 nullable로 변경 (독립 BOM 지원)
*/
export interface BomItemResponse {
id: number;
tenant_id: number;
group_id: number | null; // 그룹 ID (독립 BOM 그룹화용)
section_id: number | null; // 섹션 ID (null이면 독립 BOM)
item_code: string | null;
item_name: string;
quantity: number;
unit: string | null;
unit_price: number | null;
total_price: number | null;
spec: string | null;
note: string | null;
created_by: number | null;
updated_by: number | null;
created_at: string;
updated_at: string;
}
/**
* 독립 BOM 항목 생성 요청
* POST /v1/item-master/bom-items
* 2025-11-26 신규 API
*/
export interface IndependentBomItemRequest {
group_id?: number;
item_code?: string;
item_name: string;
quantity?: number;
unit?: string;
unit_price?: number;
spec?: string;
note?: string;
}
// ============================================
// 엔티티 관계 (Entity Relationships)
// 2025-11-27 신규: 독립 엔티티 + 링크 테이블 구조
// ============================================
/**
* 엔티티 타입 상수
*/
export type EntityType = 'page' | 'section' | 'field' | 'bom';
export type ParentEntityType = 'page' | 'section';
export type ChildEntityType = 'section' | 'field' | 'bom';
/**
* 엔티티 관계 응답
* entity_relationships 테이블의 레코드
*/
export interface EntityRelationshipResponse {
id: number;
tenant_id: number;
group_id: number; // 그룹 ID (1: 품목관리)
parent_type: ParentEntityType;
parent_id: number;
child_type: ChildEntityType;
child_id: number;
order_no: number;
metadata: Record<string, any> | null;
created_at: string;
updated_at: string;
}
/**
* 엔티티 연결 요청 (공통)
* POST /pages/{pageId}/link-section
* POST /pages/{pageId}/link-field
* POST /sections/{sectionId}/link-field
* POST /sections/{sectionId}/link-bom
*/
export interface LinkEntityRequest {
child_id: number;
order_no?: number;
}
/**
* 관계 순서 변경 요청
* POST /v1/item-master/relationships/reorder
*/
export interface ReorderRelationshipsRequest {
parent_type: ParentEntityType;
parent_id: number;
ordered_items: Array<{
child_type: ChildEntityType;
child_id: number;
}>;
}
/**
* 섹션에 BOM 연결 요청
* POST /v1/item-master/sections/{id}/link-bom
*/
export interface LinkBomRequest {
child_id: number;
order_no?: number;
}
// ============================================
// 페이지 구조 조회
// ============================================
/**
* 페이지 전체 구조 응답
* GET /v1/item-master/pages/{id}/structure
* 2025-11-27 업데이트: 링크 테이블 기반 구조
*/
export interface PageStructureResponse {
page: ItemPageResponse;
sections: Array<{
section: ItemSectionResponse;
order_no: number;
fields: Array<{
field: ItemFieldResponse;
order_no: number;
}>;
bom_items: Array<{
bom_item: BomItemResponse;
order_no: number;
}>;
}>;
direct_fields: Array<{
field: ItemFieldResponse;
order_no: number;
}>;
}
// ============================================
// 섹션 템플릿
// ============================================
/**
* 섹션 템플릿 생성/수정 요청
* POST /v1/item-master/section-templates
* PUT /v1/item-master/section-templates/{id}
*/
export interface SectionTemplateRequest {
title: string;
type: 'fields' | 'bom';
description?: string;
is_default?: boolean;
}
/**
* 섹션 템플릿 응답
*/
export interface SectionTemplateResponse {
id: number;
tenant_id: number;
title: string;
type: 'fields' | 'bom';
description: string | null;
is_default: boolean;
created_by: number | null;
updated_by: number | null;
created_at: string;
updated_at: string;
}
// ============================================
// 마스터 필드 (DEPRECATED)
// 2025-11-27: item_master_fields 테이블이 item_fields로 통합됨
// 독립 필드 = item_fields WHERE section_id IS NULL
// IndependentFieldRequest 및 ItemFieldResponse 사용 권장
// ============================================
/**
* 마스터 필드 생성/수정 요청
* @deprecated 2025-11-27: item_fields로 통합됨. IndependentFieldRequest 사용 권장
* POST /v1/item-master/master-fields → POST /v1/item-master/fields 사용
* PUT /v1/item-master/master-fields/{id} → PUT /v1/item-master/fields/{id} 사용
*/
export 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<string, any>;
properties?: Record<string, any>;
}
/**
* 마스터 필드 응답
* @deprecated 2025-11-27: item_fields로 통합됨. ItemFieldResponse 사용 권장
* 독립 필드 조회: GET /v1/item-master/fields?section_id=null
*/
export interface MasterFieldResponse {
id: number;
tenant_id: number;
field_name: string;
field_key: string | null; // 2025-11-28: 필드 키 추가 (형식: {ID}_{사용자입력})
field_type: 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea';
category: string | null;
description: string | null;
is_common: boolean;
default_value: string | null;
options: Array<{ label: string; value: string }> | null;
validation_rules: Record<string, any> | null;
properties: Record<string, any> | null;
created_by: number | null;
updated_by: number | null;
created_at: string;
updated_at: string;
}
// ============================================
// 커스텀 탭
// ============================================
/**
* 커스텀 탭 생성/수정 요청
* POST /v1/item-master/custom-tabs
* PUT /v1/item-master/custom-tabs/{id}
*/
export interface CustomTabRequest {
label: string;
icon?: string;
is_default?: boolean;
}
/**
* 커스텀 탭 응답
*/
export interface CustomTabResponse {
id: number;
tenant_id: number;
label: string;
icon: string | null;
is_default: boolean;
order_no: number;
created_by: number | null;
updated_by: number | null;
created_at: string;
updated_at: string;
}
/**
* 탭 순서 변경 요청
* PUT /v1/item-master/custom-tabs/reorder
*/
export interface TabReorderRequest {
tab_orders: Array<{
id: number;
order_no: number;
}>;
}
/**
* 탭 컬럼 설정 업데이트 요청
* PUT /v1/item-master/custom-tabs/{id}/columns
*/
export interface TabColumnUpdateRequest {
columns: Array<{
key: string;
label: string;
visible: boolean;
order: number;
}>;
}
/**
* 탭 컬럼 응답
*/
export interface TabColumnResponse {
key: string;
label: string;
visible: boolean;
order: number;
}
// ============================================
// 단위 옵션
// ============================================
/**
* 단위 옵션 생성 요청
* POST /v1/item-master/units
*/
export interface UnitOptionRequest {
label: string;
value: string;
}
/**
* 단위 옵션 응답
*/
export interface UnitOptionResponse {
id: number;
tenant_id: number;
label: string;
value: string;
created_by: number | null;
created_at: string;
updated_at: string;
}
// ============================================
// 에러 타입
// ============================================
/**
* API 에러 응답
*/
export interface ApiErrorResponse {
success: false;
message: string;
errors?: Record<string, string[]>; // Validation 에러
}
/**
* API 에러 클래스용 타입
*/
export interface ApiErrorData {
status: number;
message: string;
errors?: Record<string, string[]>;
}