feat: 단가관리 페이지 마이그레이션 및 HR 관리 기능 추가

## 단가관리 (Pricing Management)
- 단가 목록 페이지 (IntegratedListTemplateV2 공통 템플릿 적용)
- 단가 등록/수정 폼 (원가/마진 자동 계산)
- 이력 조회, 수정 이력, 최종 확정 다이얼로그
- 판매관리 > 단가관리 네비게이션 메뉴 추가

## HR 관리 (Human Resources)
- 사원관리 (목록, 등록, 수정, 상세, CSV 업로드)
- 부서관리 (트리 구조)
- 근태관리 (기본 구조)

## 품목관리 개선
- Radix UI Select controlled mode 버그 수정 (key prop 적용)
- DynamicItemForm 파일 업로드 지원
- 수정 페이지 데이터 로딩 개선

## 문서화
- 단가관리 마이그레이션 체크리스트
- HR 관리 구현 체크리스트
- Radix UI Select 버그 수정 가이드

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2025-12-06 11:36:38 +09:00
parent 751e65f59b
commit 48dbba0e5f
59 changed files with 9888 additions and 101 deletions

View File

@@ -322,12 +322,126 @@ export async function deleteBOMLine(
// ===== 파일 업로드 =====
/** 파일 타입 */
export type ItemFileType = 'specification' | 'certification' | 'bending_diagram';
/** 파일 업로드 옵션 */
export interface UploadFileOptions {
/** 인증번호 (certification 타입일 때) */
certificationNumber?: string;
/** 인증 시작일 (certification 타입일 때) */
certificationStartDate?: string;
/** 인증 종료일 (certification 타입일 때) */
certificationEndDate?: string;
/** 절곡 상세 정보 (bending_diagram 타입일 때) */
bendingDetails?: Array<{
angle: number;
length: number;
type: string;
}>;
}
/** 파일 업로드 응답 */
export interface UploadFileResponse {
file_type: string;
file_url: string;
file_path: string;
file_name: string;
product: Record<string, unknown>;
}
/**
* 파일 업로드 (시방서, 인정서, 전개도 등)
* 품목 파일 업로드 (ID 기반, 프록시 사용)
*
* @param itemCode - 품목 코드
* HttpOnly 쿠키 인증을 위해 Next.js API 프록시를 경유합니다.
*
* @param itemId - 품목 ID (숫자)
* @param file - 업로드할 파일
* @param fileType - 파일 유형 (specification, certification, bending_diagram)
* @param options - 추가 옵션 (certification 관련 필드, bending_details 등)
*
* @example
* // 시방서 업로드
* await uploadItemFile(123, specFile, 'specification');
*
* // 인정서 업로드 (추가 정보 포함)
* await uploadItemFile(123, certFile, 'certification', {
* certificationNumber: 'CERT-001',
* certificationStartDate: '2025-01-01',
* certificationEndDate: '2026-01-01',
* });
*
* // 절곡/조립 전개도 업로드
* await uploadItemFile(123, diagramFile, 'bending_diagram');
*/
export async function uploadItemFile(
itemId: number,
file: File,
fileType: ItemFileType,
options?: UploadFileOptions
): Promise<UploadFileResponse> {
const formData = new FormData();
formData.append('file', file);
formData.append('type', fileType);
// certification 관련 추가 필드
if (fileType === 'certification' && options) {
if (options.certificationNumber) {
formData.append('certification_number', options.certificationNumber);
}
if (options.certificationStartDate) {
formData.append('certification_start_date', options.certificationStartDate);
}
if (options.certificationEndDate) {
formData.append('certification_end_date', options.certificationEndDate);
}
}
// bending_diagram 관련 추가 필드
if (fileType === 'bending_diagram' && options?.bendingDetails) {
formData.append('bending_details', JSON.stringify(options.bendingDetails));
}
// 프록시 경유: /api/proxy/items/{id}/files → /api/v1/items/{id}/files
const response = await fetch(`/api/proxy/items/${itemId}/files`, {
method: 'POST',
body: formData,
credentials: 'include',
// Content-Type은 FormData 사용 시 자동 설정됨 (boundary 포함)
});
const data = await handleApiResponse<ApiResponse<UploadFileResponse>>(response);
return data.data;
}
/**
* 품목 파일 삭제 (ID 기반, 프록시 사용)
*
* @param itemId - 품목 ID (숫자)
* @param fileType - 파일 유형 (specification, certification, bending_diagram)
*
* @example
* await deleteItemFile(123, 'specification');
*/
export async function deleteItemFile(
itemId: number,
fileType: ItemFileType
): Promise<{ file_type: string; deleted: boolean; product: Record<string, unknown> }> {
// 프록시 경유: /api/proxy/items/{id}/files/{type} → /api/v1/items/{id}/files/{type}
const response = await fetch(`/api/proxy/items/${itemId}/files/${fileType}`, {
method: 'DELETE',
credentials: 'include',
});
const data = await handleApiResponse<ApiResponse<{ file_type: string; deleted: boolean; product: Record<string, unknown> }>>(response);
return data.data;
}
// ===== 레거시 파일 업로드 (하위 호환성) =====
/**
* @deprecated uploadItemFile 사용 권장 (ID 기반)
* 파일 업로드 (시방서, 인정서, 전개도 등) - 품목 코드 기반
*/
export async function uploadFile(
itemCode: string,
@@ -359,10 +473,8 @@ export async function uploadFile(
}
/**
* 파일 삭제
*
* @param itemCode - 품목 코드
* @param fileType - 파일 유형
* @deprecated deleteItemFile 사용 권장 (ID 기반)
* 파일 삭제 - 품목 코드 기반
*/
export async function deleteFile(
itemCode: string,