import * as ExcelJS from 'exceljs'; import { saveAs } from 'file-saver'; /** * itemCode 문자열을 분할하여 배열로 반환 * 공백이나 콤마로 구분된 여러 itemCode를 처리 * 빈 itemCode의 경우 빈 문자열 하나를 포함한 배열 반환 */ export function splitItemCodes(itemCode: string): string[] { if (!itemCode || typeof itemCode !== 'string') { return [""]; // 빈 itemCode의 경우 빈 문자열 하나 반환 } const trimmedCode = itemCode.trim(); if (trimmedCode === '') { return [""]; // 공백만 있는 경우도 빈 문자열 하나 반환 } // 공백과 콤마로 분할하고, trim 처리 (빈 문자열도 유지) return trimmedCode .split(/[\s,]+/) .map(code => code.trim()); } /** * 임시 IM 코드 생성 (4자리 숫자 형식) */ export function generateTempIMCode(startNumber: number): string { return `IM${startNumber.toString().padStart(4, '0')}`; } /** * itemCode가 비어있거나 유효하지 않을 때 임시 코드 생성 * @param itemCodes 분할된 itemCode 배열 * @param startNumber 시작 번호 * @returns 처리된 itemCode 배열 */ export function processEmptyItemCodes(itemCodes: string[], startNumber: number): { codes: string[], nextNumber: number } { const processedCodes: string[] = []; let currentNumber = startNumber; for (const code of itemCodes) { if (!code || code.trim() === '') { // 빈 코드인 경우 임시 코드 생성 processedCodes.push(generateTempIMCode(currentNumber)); currentNumber++; } else { // 유효한 코드인 경우 그대로 사용 processedCodes.push(code); } } return { codes: processedCodes, nextNumber: currentNumber }; } /** * 에러 정보를 포함한 Excel 파일 생성 및 다운로드 */ export async function createErrorExcelFile( errors: Array<{ row: number; message: string; itemCode?: string; workType?: string; originalData?: Record; }>, itemType: 'top' | 'hull' | 'ship' ): Promise { try { const workbook = new ExcelJS.Workbook(); // 에러 시트 생성 const errorSheet = workbook.addWorksheet('Import 오류 목록'); // 헤더 설정 const headers = [ '행 번호', '아이템코드', '기능(공종)', '자재명', '자재명(상세)', '선종', // 조선 아이템의 경우 '오류 내용', '해결 방법' ]; errorSheet.addRow(headers); // 헤더 스타일 const headerRow = errorSheet.getRow(1); headerRow.eachCell((cell) => { cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFF6B6B' } }; cell.font = { bold: true, color: { argb: 'FFFFFFFF' } }; cell.border = { top: { style: 'thin' }, left: { style: 'thin' }, bottom: { style: 'thin' }, right: { style: 'thin' } }; }); // 에러 데이터 추가 errors.forEach((error) => { const originalData = error.originalData || {}; const errorRow = errorSheet.addRow([ error.row, error.itemCode || originalData.itemCode || '', error.workType || originalData.workType || '', originalData.itemList || '', originalData.subItemList || '', originalData.shipTypes || '', // 조선 아이템의 경우 error.message, getSolutionMessage(error.message) ]); // 에러 행 스타일 errorRow.eachCell((cell, colNumber) => { if (colNumber === 7) { // 오류 내용 컬럼 cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFFE0E0' } }; } cell.border = { top: { style: 'thin' }, left: { style: 'thin' }, bottom: { style: 'thin' }, right: { style: 'thin' } }; }); }); // 안내 시트 생성 const instructionSheet = workbook.addWorksheet('오류 해결 가이드'); const instructions = [ ['📌 오류 해결 방법 안내', ''], ['', ''], ['🔍 중복 아이템코드 오류', ''], ['• 원인: 이미 존재하는 아이템코드입니다.', ''], ['• 해결: 다른 아이템코드를 사용하거나 기존 아이템을 수정하세요.', ''], ['', ''], ['🔍 필수 필드 누락 오류', ''], ['• 원인: 기능(공종) 등 필수 필드가 비어있습니다.', ''], ['• 해결: 모든 필수 필드를 입력하세요.', ''], ['', ''], ['🔍 데이터 형식 오류', ''], ['• 원인: 데이터 형식이 올바르지 않습니다.', ''], ['• 해결: 올바른 형식으로 데이터를 입력하세요.', ''], ['', ''], ['📞 추가 문의: 시스템 관리자', ''] ]; instructions.forEach((rowData, index) => { const row = instructionSheet.addRow(rowData); if (index === 0) { row.getCell(1).font = { bold: true, size: 14, color: { argb: 'FF1F4E79' } }; } else if (rowData[0]?.includes('📌') || rowData[0]?.includes('🔍')) { row.getCell(1).font = { bold: true, color: { argb: 'FF1F4E79' } }; } else if (rowData[0]?.includes(':')) { row.getCell(1).font = { bold: true, color: { argb: 'FF1F4E79' } }; } }); instructionSheet.getColumn(1).width = 60; // 기본 컬럼 너비 설정 errorSheet.getColumn(1).width = 10; // 행 번호 errorSheet.getColumn(2).width = 20; // 아이템코드 errorSheet.getColumn(3).width = 15; // 기능(공종) errorSheet.getColumn(4).width = 30; // 자재명 errorSheet.getColumn(5).width = 30; // 자재명(상세) errorSheet.getColumn(6).width = 20; // 선종 errorSheet.getColumn(7).width = 60; // 오류 내용 errorSheet.getColumn(8).width = 40; // 해결 방법 // 파일 생성 및 다운로드 const buffer = await workbook.xlsx.writeBuffer(); const blob = new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }); const itemTypeNames = { top: '해양TOP', hull: '해양HULL', ship: '조선' }; const fileName = `${itemTypeNames[itemType]}_Import_오류_${new Date().toISOString().split('T')[0]}_${Date.now()}.xlsx`; saveAs(blob, fileName); return fileName; } catch (error) { console.error("오류 파일 생성 중 오류:", error); return ''; } } /** * 오류 메시지에 따른 해결 방법 반환 */ function getSolutionMessage(errorMessage: string): string { if (errorMessage.includes('중복')) { return '다른 아이템코드를 사용하거나 기존 아이템을 수정하세요.'; } else if (errorMessage.includes('필수')) { return '모든 필수 필드를 입력하세요.'; } else if (errorMessage.includes('형식')) { return '올바른 형식으로 데이터를 입력하세요.'; } else { return '데이터를 확인하고 다시 시도하세요.'; } } /** * 확장된 ProcessResult 인터페이스 */ export interface ExtendedProcessResult { successCount: number; errorCount: number; errors: Array<{ row: number; message: string; itemCode?: string; workType?: string; originalData?: Record; }>; processedItemCodes: string[]; // 성공적으로 처리된 아이템코드들 duplicateItemCodes: string[]; // 중복된 아이템코드들 }