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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user