refactor: 품목기준관리 서비스 레이어 도입 및 버그 수정
서비스 레이어 리팩토링: - services/ 폴더 생성 (fieldService, masterFieldService, sectionService, pageService, templateService, attributeService) - 도메인 로직 중앙화 (validation, parsing, transform) - hooks와 dialogs에서 서비스 호출로 변경 버그 수정: - 섹션탭 실시간 동기화 문제 수정 (sectionsAsTemplates 중복 제거 순서 변경) - 422 Validation Error 수정 (createIndependentField → addFieldToSection) - 페이지 삭제 시 섹션-필드 연결 유지 (refreshIndependentSections 대신 직접 이동) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
/**
|
||||
* Master Field Service
|
||||
* 마스터 필드(항목탭) 관련 도메인 로직 중앙화
|
||||
* - validation (fieldService 재사용)
|
||||
* - parsing
|
||||
* - transform (폼 ↔ API)
|
||||
* - defaults
|
||||
*/
|
||||
|
||||
import type { ItemMasterField } from '@/contexts/ItemMasterContext';
|
||||
import { fieldService, type SingleFieldValidation } from './fieldService';
|
||||
|
||||
// ===== Types =====
|
||||
|
||||
export type MasterFieldType = 'textbox' | 'dropdown' | 'checkbox' | 'number' | 'date' | 'textarea';
|
||||
export type AttributeType = 'custom' | 'unit' | 'material' | 'surface';
|
||||
|
||||
export interface MasterFieldFormData {
|
||||
name: string;
|
||||
key: string;
|
||||
inputType: MasterFieldType;
|
||||
required: boolean;
|
||||
category: string;
|
||||
description: string;
|
||||
options: string; // 콤마 구분 문자열
|
||||
attributeType: AttributeType;
|
||||
multiColumn: boolean;
|
||||
columnCount: number;
|
||||
columnNames: string[];
|
||||
}
|
||||
|
||||
export interface MasterFieldValidationResult {
|
||||
valid: boolean;
|
||||
errors: {
|
||||
field_name?: string;
|
||||
field_key?: string;
|
||||
field_type?: string;
|
||||
};
|
||||
}
|
||||
|
||||
// ===== Service =====
|
||||
|
||||
export const masterFieldService = {
|
||||
// ===== Validation (fieldService 재사용) =====
|
||||
|
||||
/**
|
||||
* 전체 마스터 필드 폼 유효성 검사
|
||||
*/
|
||||
validate: (data: Partial<MasterFieldFormData>): MasterFieldValidationResult => {
|
||||
const errors: MasterFieldValidationResult['errors'] = {};
|
||||
|
||||
// 필드명 검증 (fieldService 재사용)
|
||||
const nameValidation = fieldService.validateFieldName(data.name || '');
|
||||
if (!nameValidation.valid) {
|
||||
errors.field_name = (nameValidation as { valid: false; error: string }).error;
|
||||
}
|
||||
|
||||
// 필드 키 검증 (fieldService 재사용)
|
||||
const keyValidation = fieldService.validateFieldKey(data.key || '');
|
||||
if (!keyValidation.valid) {
|
||||
errors.field_key = (keyValidation as { valid: false; error: string }).error;
|
||||
}
|
||||
|
||||
return {
|
||||
valid: Object.keys(errors).length === 0,
|
||||
errors,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 필드명 유효성 검사 (fieldService 위임)
|
||||
*/
|
||||
validateFieldName: fieldService.validateFieldName,
|
||||
|
||||
/**
|
||||
* 필드 키 유효성 검사 (fieldService 위임)
|
||||
*/
|
||||
validateFieldKey: fieldService.validateFieldKey,
|
||||
|
||||
/**
|
||||
* 필드 키 패턴 정규식 (fieldService 재사용)
|
||||
*/
|
||||
fieldKeyPattern: fieldService.fieldKeyPattern,
|
||||
|
||||
/**
|
||||
* 필드 키가 유효한지 간단 체크 (fieldService 위임)
|
||||
*/
|
||||
isFieldKeyValid: fieldService.isFieldKeyValid,
|
||||
|
||||
// ===== Parsing =====
|
||||
|
||||
/**
|
||||
* field_key에서 사용자 입력 부분 추출 (fieldService 위임)
|
||||
* 형식: {ID}_{사용자입력} → 사용자입력 반환
|
||||
*/
|
||||
extractUserInputFromFieldKey: fieldService.extractUserInputFromFieldKey,
|
||||
|
||||
/**
|
||||
* 옵션 문자열을 배열로 파싱 (fieldService 위임)
|
||||
*/
|
||||
parseOptionsFromString: fieldService.parseOptionsFromString,
|
||||
|
||||
/**
|
||||
* 옵션 배열을 문자열로 변환 (fieldService 위임)
|
||||
*/
|
||||
optionsToString: fieldService.optionsToString,
|
||||
|
||||
// ===== Transform =====
|
||||
|
||||
/**
|
||||
* 폼 데이터 → API 요청 객체 변환
|
||||
*/
|
||||
toApiRequest: (
|
||||
formData: MasterFieldFormData
|
||||
): Omit<ItemMasterField, 'id' | 'tenant_id' | 'created_by' | 'updated_by' | 'created_at' | 'updated_at'> => {
|
||||
const supportsMultiColumn = formData.inputType === 'textbox' || formData.inputType === 'textarea';
|
||||
|
||||
return {
|
||||
field_name: formData.name,
|
||||
field_key: formData.key,
|
||||
field_type: formData.inputType,
|
||||
category: formData.category || null,
|
||||
description: formData.description || null,
|
||||
is_common: false,
|
||||
default_value: null,
|
||||
options: formData.inputType === 'dropdown'
|
||||
? fieldService.parseOptionsFromString(formData.options)
|
||||
: null,
|
||||
validation_rules: null,
|
||||
properties: {
|
||||
required: formData.required,
|
||||
attributeType: formData.inputType === 'dropdown' ? formData.attributeType : undefined,
|
||||
multiColumn: supportsMultiColumn ? formData.multiColumn : undefined,
|
||||
columnCount: supportsMultiColumn && formData.multiColumn ? formData.columnCount : undefined,
|
||||
columnNames: supportsMultiColumn && formData.multiColumn ? formData.columnNames : undefined,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* ItemMasterField → 폼 데이터 변환 (수정 시 폼에 채우기 위함)
|
||||
*/
|
||||
toFormData: (field: ItemMasterField): MasterFieldFormData => {
|
||||
const properties = field.properties as Record<string, any> | null;
|
||||
|
||||
return {
|
||||
name: field.field_name,
|
||||
key: masterFieldService.extractUserInputFromFieldKey(field.field_key),
|
||||
inputType: field.field_type || 'textbox',
|
||||
required: properties?.required || false,
|
||||
category: field.category || '공통',
|
||||
description: field.description || '',
|
||||
options: masterFieldService.optionsToString(field.options),
|
||||
attributeType: properties?.attributeType || 'custom',
|
||||
multiColumn: properties?.multiColumn || false,
|
||||
columnCount: properties?.columnCount || 2,
|
||||
columnNames: properties?.columnNames || ['컬럼1', '컬럼2'],
|
||||
};
|
||||
},
|
||||
|
||||
// ===== Defaults =====
|
||||
|
||||
/**
|
||||
* 새 마스터 필드 생성 시 기본값
|
||||
*/
|
||||
getDefaultFormData: (): MasterFieldFormData => ({
|
||||
name: '',
|
||||
key: '',
|
||||
inputType: 'textbox',
|
||||
required: false,
|
||||
category: '공통',
|
||||
description: '',
|
||||
options: '',
|
||||
attributeType: 'custom',
|
||||
multiColumn: false,
|
||||
columnCount: 2,
|
||||
columnNames: ['컬럼1', '컬럼2'],
|
||||
}),
|
||||
|
||||
/**
|
||||
* 지원하는 필드 타입 목록 (fieldService 재사용)
|
||||
*/
|
||||
fieldTypes: fieldService.fieldTypes,
|
||||
|
||||
/**
|
||||
* 지원하는 속성 타입 목록
|
||||
*/
|
||||
attributeTypes: [
|
||||
{ value: 'custom', label: '직접 입력' },
|
||||
{ value: 'unit', label: '단위' },
|
||||
{ value: 'material', label: '재질' },
|
||||
{ value: 'surface', label: '표면처리' },
|
||||
] as const,
|
||||
|
||||
/**
|
||||
* 다중 컬럼 지원 여부 확인
|
||||
*/
|
||||
supportsMultiColumn: (inputType: MasterFieldType): boolean => {
|
||||
return inputType === 'textbox' || inputType === 'textarea';
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user