refactor: 품목관리 시스템 리팩토링 및 Sales 페이지 추가

DynamicItemForm 개선:
- 품목코드 자동생성 기능 추가
- 조건부 표시 로직 개선
- 불필요한 컴포넌트 정리 (DynamicField, DynamicSection 등)
- 타입 시스템 단순화

새로운 기능:
- Sales 페이지 마이그레이션 (견적관리, 거래처관리)
- 공통 컴포넌트 추가 (atoms, molecules, organisms, templates)

문서화:
- 구현 문서 및 참조 문서 추가

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2025-12-04 12:48:41 +09:00
parent 0552b02ba9
commit 3be5714805
73 changed files with 9318 additions and 4353 deletions

View File

@@ -0,0 +1,377 @@
/**
* 품목코드/품목명 자동생성 유틸리티
*
* MVP용 프론트엔드 구현 - 하드코딩 내역은 추후 백엔드 API로 이관 필요
*
* @see claudedocs/item-master/[REF] item-code-hardcoding.md
*/
// ============================================
// [하드코딩] 영문약어 매핑 테이블
// TODO: 추후 백엔드 API 또는 품목기준관리에서 설정 가능하도록 변경
// ============================================
export const ITEM_CODE_PREFIX_MAP: Record<string, string> = {
// 부품 - 조립품
'가이드레일': 'GR',
'케이스': 'CASE',
'브라켓': 'BRK',
// 부품 - 구매품
'모터': 'MOTOR',
'제어기': 'CTL',
'전동개폐기': 'OPENER',
'스위치': 'SW',
'센서': 'SENSOR',
'리모컨': 'REMOTE',
// 부품 - 절곡품
'레일': 'RAIL',
'커버': 'COVER',
'플레이트': 'PLATE',
// 제품
'스크린': 'SCREEN',
'셔터': 'SHUTTER',
'방화스크린': 'FIRE-SCR',
'롤스크린': 'ROLL-SCR',
// 원자재
'알루미늄': 'ALU',
'스틸': 'STEEL',
'철판': 'STEEL',
// 부자재/소모품
'볼트': 'BOLT',
'너트': 'NUT',
'와셔': 'WASHER',
'나사': 'SCREW',
};
// ============================================
// [하드코딩] 절곡품 코드 체계
// TODO: 추후 품목기준관리에서 설정 가능하도록 변경
// ============================================
export const BENDING_CODE_SYSTEM = {
// 품목명코드 (category2)
: {
'R': '가이드레일',
'S': '스크린',
'C': '케이스',
'B': '박스',
'T': '트림',
'L': '라스틱',
'G': '기타',
} as Record<string, string>,
// 종류코드 (category3)
: {
'M': '마감',
'T': '티',
'C': '채널',
'D': '단면',
'S': '상부',
'U': '하부',
'F': '플랫',
'P': '피스',
'L': '리드',
'B': '브라켓',
'E': '엔드',
'I': '이음',
'A': '각재',
} as Record<string, string>,
// 길이코드 매핑 (mm → 코드)
: {
1219: '12',
2438: '24',
3000: '30',
3500: '35',
4000: '40',
4150: '41',
4200: '42',
4300: '43',
} as Record<number, string>,
};
// ============================================
// [하드코딩] 조립품 설치유형 매핑
// TODO: 추후 품목기준관리에서 설정 가능하도록 변경
// ============================================
export const INSTALLATION_TYPE_MAP: Record<string, string> = {
'standard': '표준형',
'top': '상부형',
'bottom': '하부형',
'side': '측면형',
'custom': '맞춤형',
};
/**
* 품목명에서 영문약어 추출
* @param itemName 품목명 (한글)
* @returns 영문약어 또는 기본값
*/
export function getItemCodePrefix(itemName: string): string {
// 정확한 매칭 먼저 시도
if (ITEM_CODE_PREFIX_MAP[itemName]) {
return ITEM_CODE_PREFIX_MAP[itemName];
}
// 부분 매칭 시도 (품목명에 키워드가 포함된 경우)
for (const [keyword, prefix] of Object.entries(ITEM_CODE_PREFIX_MAP)) {
if (itemName.includes(keyword)) {
return prefix;
}
}
// 매칭 실패 시 품목명의 첫 글자들로 생성 (임시)
// 예: "새로운품목" → "ITEM" (기본값)
return 'ITEM';
}
/**
* 기존 품목 목록에서 다음 순번 계산
* @param existingCodes 기존 품목코드 배열 (예: ["GR-001", "GR-002"])
* @param prefix 영문약어 (예: "GR")
* @returns 다음 순번 (예: "003")
*/
export function getNextSequence(existingCodes: string[], prefix: string): string {
const pattern = new RegExp(`^${prefix}-(\\d+)$`, 'i');
let maxSeq = 0;
existingCodes.forEach(code => {
const match = code.match(pattern);
if (match) {
const seq = parseInt(match[1], 10);
if (seq > maxSeq) {
maxSeq = seq;
}
}
});
return String(maxSeq + 1).padStart(3, '0');
}
/**
* 품목코드 생성 (영문약어-순번)
* @param itemName 품목명 (한글)
* @param existingCodes 기존 품목코드 배열
* @returns 새 품목코드 (예: "GR-003")
*/
export function generateItemCode(itemName: string, existingCodes: string[]): string {
const prefix = getItemCodePrefix(itemName);
const sequence = getNextSequence(existingCodes, prefix);
return `${prefix}-${sequence}`;
}
/**
* 절곡품 품목코드 생성 (품목명코드 + 종류코드 + 길이코드)
* @param category2 품목명코드 (R, S, C 등)
* @param category3 종류코드 (M, T, C 등)
* @param lengthMm 길이 (mm)
* @returns 품목코드 (예: "RC24")
*/
export function generateBendingItemCode(
category2: string,
category3: string,
lengthMm: number
): string {
// 길이코드 변환
let lengthCode = BENDING_CODE_SYSTEM.[lengthMm];
if (!lengthCode) {
// 매핑에 없으면 100으로 나눈 값 사용
lengthCode = String(Math.floor(lengthMm / 100)).padStart(2, '0');
}
return `${category2}${category3}${lengthCode}`;
}
/**
* 조립품 품목명 생성 (품목명 + 설치유형 + 측면규격*길이코드)
* @param itemName 기본 품목명 (가이드레일)
* @param installationType 설치유형 (standard → 표준형)
* @param sideSpecWidth 측면규격 너비
* @param sideSpecHeight 측면규격 높이
* @param lengthMm 길이 (mm)
* @returns 조합된 품목명 (예: "가이드레일표준형50*60*24")
*/
export function generateAssemblyItemName(
itemName: string,
installationType: string,
sideSpecWidth?: number,
sideSpecHeight?: number,
lengthMm?: number
): string {
const installationTypeKorean = INSTALLATION_TYPE_MAP[installationType] || installationType;
let result = `${itemName}${installationTypeKorean}`;
if (sideSpecWidth && sideSpecHeight && lengthMm) {
// 길이코드 변환
let lengthCode = BENDING_CODE_SYSTEM.[lengthMm];
if (!lengthCode) {
lengthCode = String(Math.floor(lengthMm / 100)).padStart(2, '0');
}
result += `${sideSpecWidth}*${sideSpecHeight}*${lengthCode}`;
}
return result;
}
/**
* 절곡품 품목명 생성 (품목명 + 종류 + 규격)
* @param category2Label 품목명 라벨 (가이드레일)
* @param category3Label 종류 라벨 (채널)
* @param specification 규격
* @returns 조합된 품목명
*/
export function generateBendingItemName(
category2Label: string,
category3Label: string,
specification?: string
): string {
let result = `${category2Label} ${category3Label}`;
if (specification) {
result += ` ${specification}`;
}
return result;
}
/**
* 구매품 품목명 생성 (품목명 + 규격)
* @param itemName 기본 품목명
* @param specification 규격
* @returns 조합된 품목명 (예: "모터 0.4KW")
*/
export function generatePurchasedItemName(
itemName: string,
specification?: string
): string {
if (specification) {
return `${itemName} ${specification}`;
}
return itemName;
}
// ============================================
// 절곡 부품 품목코드 자동생성 (간소화 버전)
// 2025-12-03 추가
// ============================================
/**
* 문자열에서 마지막 괄호 안의 단일 문자 추출
* @param str 입력 문자열 (예: "가이드레일(벽면형) (R)")
* @returns 괄호 안의 문자 (예: "R") 또는 빈 문자열
*/
export function extractParenthesisCode(str: string): string {
if (!str) return '';
// 마지막 괄호 안의 내용 추출 (예: "(R)" → "R")
const match = str.match(/\(([A-Za-z가-힣])\)\s*$/);
if (match) {
return match[1];
}
// 대안: 마지막 괄호가 없으면 앞쪽 괄호에서 추출 시도
const altMatch = str.match(/\(([A-Za-z가-힣])\)/);
return altMatch ? altMatch[1] : '';
}
/**
* 절곡품 품목코드 생성 (품목명코드 + 종류코드)
* @param itemNameValue 품목명 드롭다운 값 (예: "가이드레일(벽면형) (R)")
* @param categoryValue 종류 드롭다운 값 (예: "본체 (M)")
* @param shapeLengthValue 모양&길이 드롭다운 값 (예: "2438" 또는 "30")
* @returns 품목코드 (예: "RM" 또는 "RM30")
*/
export function generateBendingItemCodeSimple(
itemNameValue: string,
categoryValue: string,
shapeLengthValue?: string
): string {
const itemNameCode = extractParenthesisCode(itemNameValue);
const categoryCode = extractParenthesisCode(categoryValue);
if (!itemNameCode && !categoryValue) return '';
let code = `${itemNameCode}${categoryCode}`;
// 모양&길이가 있으면 길이 축약 추가
if (shapeLengthValue) {
// 숫자만 추출
const lengthNum = parseInt(shapeLengthValue.replace(/[^0-9]/g, ''), 10);
if (lengthNum > 0) {
// 100으로 나눈 값 (예: 2438 → 24, 3000 → 30)
const lengthCode = Math.floor(lengthNum / 100).toString();
code += lengthCode;
}
}
return code;
}
// ============================================
// 조립 부품 품목명/규격 자동생성
// 2025-12-03 추가
// ============================================
/**
* 조립 부품 품목명 생성 (품목명 + 가로x세로)
* @param itemName 선택한 품목명 (가이드레일)
* @param sideSpecWidth 측면규격 가로 (mm)
* @param sideSpecHeight 측면규격 세로 (mm)
* @returns 조합된 품목명 (예: "가이드레일 50x60")
*/
export function generateAssemblyItemNameSimple(
itemName: string,
sideSpecWidth?: number | string,
sideSpecHeight?: number | string
): string {
if (!itemName) return '';
if (sideSpecWidth && sideSpecHeight) {
return `${itemName} ${sideSpecWidth}x${sideSpecHeight}`;
}
return itemName;
}
/**
* 조립 부품 규격 생성 (가로x세로x길이)
* @param sideSpecWidth 측면규격 가로 (mm)
* @param sideSpecHeight 측면규격 세로 (mm)
* @param assemblyLength 길이 (mm) - 네자리 그대로 사용
* @returns 조합된 규격 (예: "50x60x2438")
*/
export function generateAssemblySpecification(
sideSpecWidth?: number | string,
sideSpecHeight?: number | string,
assemblyLength?: number | string
): string {
if (!sideSpecWidth || !sideSpecHeight || !assemblyLength) {
return '';
}
return `${sideSpecWidth}x${sideSpecHeight}x${assemblyLength}`;
}
// ============================================
// 하드코딩 내역 목록 (문서화용)
// ============================================
export const HARDCODED_ITEMS = {
ITEM_CODE_PREFIX_MAP: {
description: '품목명 → 영문약어 매핑 테이블',
location: 'itemCodeGenerator.ts',
migrationTarget: '품목기준관리 API 또는 별도 설정 테이블',
},
BENDING_CODE_SYSTEM: {
description: '절곡품 코드 체계 (품목명코드, 종류코드, 길이코드)',
location: 'itemCodeGenerator.ts',
migrationTarget: '품목기준관리 API 또는 별도 설정 테이블',
},
INSTALLATION_TYPE_MAP: {
description: '조립품 설치유형 매핑',
location: 'itemCodeGenerator.ts',
migrationTarget: '품목기준관리 API 또는 별도 설정 테이블',
},
};