"use client" import { z } from "zod" import { createItem } from "../service" // 아이템 생성 서버 액션 // 아이템 데이터 검증을 위한 Zod 스키마 const itemSchema = z.object({ itemCode: z.string().min(1, "자재그룹코드는 필수입니다"), itemName: z.string().min(1, "자재그룹명은 필수입니다"), description: z.string().nullable().optional(), parentItemCode: z.string().max(18).nullable().optional(), itemLevel: z.number().int().min(1).max(5).nullable().optional(), deleteFlag: z.string().max(1).nullable().optional(), unitOfMeasure: z.string().max(3).nullable().optional(), steelType: z.string().max(2).nullable().optional(), gradeMaterial: z.string().max(50).nullable().optional(), changeDate: z.string().max(8).nullable().optional(), baseUnitOfMeasure: z.string().max(3).nullable().optional(), }); interface ProcessResult { successCount: number; errorCount: number; errors?: Array<{ row: number; message: string }>; } /** * Excel 파일에서 가져온 아이템 데이터 처리하는 함수 */ export async function processFileImport( jsonData: Record[], progressCallback?: (current: number, total: number) => void ): Promise { // 결과 카운터 초기화 let successCount = 0; let errorCount = 0; const errors: Array<{ row: number; message: string }> = []; // 빈 행 등 필터링 const dataRows = jsonData.filter(row => { // 빈 행 건너뛰기 if (Object.values(row).every(val => !val)) { return false; } return true; }); // 데이터 행이 없으면 빈 결과 반환 if (dataRows.length === 0) { return { successCount: 0, errorCount: 0 }; } // 각 행에 대해 처리 for (let i = 0; i < dataRows.length; i++) { const row = dataRows[i]; const rowIndex = i + 1; // 사용자에게 표시할 행 번호는 1부터 시작 // 진행 상황 콜백 호출 if (progressCallback) { progressCallback(i + 1, dataRows.length); } try { // 필드 매핑 (한글/영문 필드명 모두 지원) const itemCode = row["자재그룹코드"] || row["itemCode"] || row["item_code"] || ""; const itemName = row["자재그룹명"] || row["itemName"] || row["item_name"] || ""; const description = row["상세"] || row["description"] || null; const parentItemCode = row["부모 아이템 코드"] || row["parentItemCode"] || row["parent_item_code"] || null; const itemLevel = row["레벨"] || row["itemLevel"] || row["item_level"] || null; const deleteFlag = row["삭제 플래그"] || row["deleteFlag"] || row["delete_flag"] || null; const unitOfMeasure = row["단위"] || row["unitOfMeasure"] || row["unit_of_measure"] || null; const steelType = row["강종"] || row["steelType"] || row["steel_type"] || null; const gradeMaterial = row["등급 재질"] || row["gradeMaterial"] || row["grade_material"] || null; const changeDate = row["변경일자"] || row["changeDate"] || row["change_date"] || null; const baseUnitOfMeasure = row["기본단위"] || row["baseUnitOfMeasure"] || row["base_unit_of_measure"] || null; // 데이터 정제 const cleanedRow = { itemCode: typeof itemCode === 'string' ? itemCode.trim() : String(itemCode).trim(), itemName: typeof itemName === 'string' ? itemName.trim() : String(itemName).trim(), description: description ? (typeof description === 'string' ? description : String(description)) : null, parentItemCode: parentItemCode ? (typeof parentItemCode === 'string' ? parentItemCode.trim() : String(parentItemCode).trim()) : null, itemLevel: itemLevel ? (typeof itemLevel === 'number' ? itemLevel : parseInt(String(itemLevel))) : null, deleteFlag: deleteFlag ? (typeof deleteFlag === 'string' ? deleteFlag.trim() : String(deleteFlag).trim()) : null, unitOfMeasure: unitOfMeasure ? (typeof unitOfMeasure === 'string' ? unitOfMeasure.trim() : String(unitOfMeasure).trim()) : null, steelType: steelType ? (typeof steelType === 'string' ? steelType.trim() : String(steelType).trim()) : null, gradeMaterial: gradeMaterial ? (typeof gradeMaterial === 'string' ? gradeMaterial.trim() : String(gradeMaterial).trim()) : null, changeDate: changeDate ? (typeof changeDate === 'string' ? changeDate.trim() : String(changeDate).trim()) : null, baseUnitOfMeasure: baseUnitOfMeasure ? (typeof baseUnitOfMeasure === 'string' ? baseUnitOfMeasure.trim() : String(baseUnitOfMeasure).trim()) : null, }; // 데이터 유효성 검사 const validationResult = itemSchema.safeParse(cleanedRow); if (!validationResult.success) { const errorMessage = validationResult.error.errors.map( err => `${err.path.join('.')}: ${err.message}` ).join(', '); errors.push({ row: rowIndex, message: errorMessage }); errorCount++; continue; } // 아이템 생성 서버 액션 호출 const result = await createItem({ itemCode: cleanedRow.itemCode, itemName: cleanedRow.itemName, description: cleanedRow.description, parentItemCode: cleanedRow.parentItemCode, itemLevel: cleanedRow.itemLevel, deleteFlag: cleanedRow.deleteFlag, unitOfMeasure: cleanedRow.unitOfMeasure, steelType: cleanedRow.steelType, gradeMaterial: cleanedRow.gradeMaterial, changeDate: cleanedRow.changeDate, baseUnitOfMeasure: cleanedRow.baseUnitOfMeasure, }); if (result.success || !result.error) { successCount++; } else { errors.push({ row: rowIndex, message: result.message || result.error || "알 수 없는 오류" }); errorCount++; } } catch (error) { console.error(`${rowIndex}행 처리 오류:`, error); errors.push({ row: rowIndex, message: error instanceof Error ? error.message : "알 수 없는 오류" }); errorCount++; } // 비동기 작업 쓰로틀링 if (i % 5 === 0) { await new Promise(resolve => setTimeout(resolve, 10)); } } // 처리 결과 반환 return { successCount, errorCount, errors: errors.length > 0 ? errors : undefined }; }