From 14f61e24947fb92dd71ec0a7196a6e815f8e66da Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 21 Jul 2025 07:54:26 +0000 Subject: (최겸)기술영업 RFQ 담당자 초대, 요구사항 반영 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../table/excel-import.tsx | 130 +++++++++++++++------ 1 file changed, 92 insertions(+), 38 deletions(-) (limited to 'lib/tech-vendor-possible-items/table/excel-import.tsx') diff --git a/lib/tech-vendor-possible-items/table/excel-import.tsx b/lib/tech-vendor-possible-items/table/excel-import.tsx index fbf984dd..743879b3 100644 --- a/lib/tech-vendor-possible-items/table/excel-import.tsx +++ b/lib/tech-vendor-possible-items/table/excel-import.tsx @@ -3,21 +3,35 @@ import * as ExcelJS from 'exceljs'; import { ImportTechVendorPossibleItemData, ImportResult, importTechVendorPossibleItems } from '../service'; import { saveAs } from "file-saver"; +import { decryptWithServerAction } from "@/components/drm/drmUtils" +import { toast } from 'sonner'; export interface ExcelImportResult extends ImportResult { errorFileUrl?: string; } /** - * Excel 파일에서 tech vendor possible items 데이터를 읽고 import + * Excel 파일에서 tech vendor possible items 데이터를 읽고 import (새로운 스키마 버전) */ export async function importTechVendorPossibleItemsFromExcel( file: File ): Promise { + try { - const buffer = await file.arrayBuffer(); - const workbook = new ExcelJS.Workbook(); - await workbook.xlsx.load(buffer); + // DRM 복호화 처리 - 서버 액션 직접 호출 + let arrayBuffer: ArrayBuffer; + try { + toast.info("파일 복호화 중..."); + arrayBuffer = await decryptWithServerAction(file); + } catch (decryptError) { + console.error("파일 복호화 실패, 원본 파일 사용:", decryptError); + toast.warning("파일 복호화에 실패하여 원본 파일을 사용합니다."); + // 복호화 실패 시 원본 파일 사용 + arrayBuffer = await file.arrayBuffer(); + } + // ExcelJS 워크북 로드 + const workbook = new ExcelJS.Workbook(); + await workbook.xlsx.load(arrayBuffer); // 첫 번째 워크시트에서 데이터 읽기 const worksheet = workbook.getWorksheet(1); @@ -33,31 +47,48 @@ export async function importTechVendorPossibleItemsFromExcel( const data: ImportTechVendorPossibleItemData[] = []; // 데이터 행 읽기 (헤더 제외) + // 새로운 스키마: 벤더이메일, 아이템코드, 공종, 선종, 아이템리스트, 서브아이템리스트 worksheet.eachRow((row, rowNumber) => { if (rowNumber === 1) return; // 헤더 건너뛰기 - const itemCode = row.getCell(1).value?.toString()?.trim(); - const vendorCode = row.getCell(2).value?.toString()?.trim(); - const vendorEmail = row.getCell(3).value?.toString()?.trim(); + const vendorEmail = row.getCell(1).value?.toString()?.trim(); // 필수 + const itemCode = row.getCell(2).value?.toString()?.trim(); // 필수 + const workType = row.getCell(3).value?.toString()?.trim(); // 선택 + const shipTypes = row.getCell(4).value?.toString()?.trim(); // 선택 + const itemList = row.getCell(5).value?.toString()?.trim(); // 선택 + const subItemList = row.getCell(6).value?.toString()?.trim(); // 선택 + const vendorCode = row.getCell(7).value?.toString()?.trim(); // 선택 (호환성) // 빈 행 건너뛰기 - if (!itemCode && !vendorCode && !vendorEmail) return; + if (!vendorEmail && !itemCode && !workType && !shipTypes && !itemList && !subItemList && !vendorCode) { + return; + } - // 벤더 코드 또는 이메일 중 하나는 있어야 함 - if (itemCode && (vendorCode || vendorEmail)) { + // 필수 필드 체크: 벤더이메일, 아이템코드 + if (!vendorEmail || !itemCode) { + // 불완전한 데이터도 포함하여 에러 처리 data.push({ - vendorCode: vendorCode || '', - vendorEmail: vendorEmail || '', - itemCode, - }); - } else { - // 불완전한 데이터 처리 - data.push({ - vendorCode: vendorCode || '', vendorEmail: vendorEmail || '', itemCode: itemCode || '', + workType: workType || undefined, + shipTypes: shipTypes || undefined, + itemList: itemList || undefined, + subItemList: subItemList || undefined, + vendorCode: vendorCode || undefined, }); + return; } + + // 완전한 데이터 추가 + data.push({ + vendorEmail, + itemCode, + workType: workType || undefined, + shipTypes: shipTypes || undefined, + itemList: itemList || undefined, + subItemList: subItemList || undefined, + vendorCode: vendorCode || undefined, + }); }); if (data.length === 0) { @@ -99,7 +130,7 @@ export async function importTechVendorPossibleItemsFromExcel( } /** - * 실패한 항목들을 포함한 오류 Excel 파일 생성 + * 실패한 항목들을 포함한 오류 Excel 파일 생성 (새로운 스키마 버전) */ async function createErrorExcelFile( failedRows: ImportResult['failedRows'] @@ -108,12 +139,16 @@ async function createErrorExcelFile( const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet('Import 오류 목록'); - // 헤더 설정 + // 헤더 설정 (새로운 스키마에 맞춰) worksheet.columns = [ { header: '행 번호', key: 'row', width: 10 }, + { header: '벤더이메일', key: 'vendorEmail', width: 30 }, { header: '아이템코드', key: 'itemCode', width: 20 }, + { header: '공종', key: 'workType', width: 15 }, + { header: '선종', key: 'shipTypes', width: 20 }, + { header: '아이템리스트', key: 'itemList', width: 30 }, + { header: '서브아이템리스트', key: 'subItemList', width: 30 }, { header: '벤더코드', key: 'vendorCode', width: 15 }, - { header: '벤더이메일', key: 'vendorEmail', width: 30 }, { header: '오류 내용', key: 'error', width: 60 }, { header: '해결 방법', key: 'solution', width: 40 }, ]; @@ -142,19 +177,25 @@ async function createErrorExcelFile( failedRows.forEach((item) => { let solution = '시스템 관리자에게 문의하세요'; - if (item.error.includes('벤더 코드') || item.error.includes('벤더 이메일')) { - solution = '등록된 벤더 코드 또는 이메일인지 확인하세요'; + if (item.error.includes('벤더 이메일')) { + solution = '올바른 이메일 형식으로 등록된 벤더 이메일인지 확인하세요'; } else if (item.error.includes('아이템 코드')) { - solution = '벤더 타입에 맞는 아이템 코드인지 확인하세요'; + solution = '아이템 코드가 누락되었거나 잘못된 형식입니다'; } else if (item.error.includes('이미 존재')) { - solution = '중복된 조합입니다. 제거하거나 건너뛰세요'; + solution = '중복된 조합입니다. 기존 데이터를 확인하세요'; + } else if (item.error.includes('찾을 수 없습니다')) { + solution = '벤더 이메일이 시스템에 등록되어 있는지 확인하세요'; } const row = worksheet.addRow({ row: item.row, - itemCode: item.itemCode || '누락', - vendorCode: item.vendorCode || '누락', vendorEmail: item.vendorEmail || '누락', + itemCode: item.itemCode || '누락', + workType: item.workType || '', + shipTypes: item.shipTypes || '', + itemList: item.itemList || '', + subItemList: item.subItemList || '', + vendorCode: item.vendorCode || '', error: item.error, solution: solution, }); @@ -169,24 +210,35 @@ async function createErrorExcelFile( }); }); - // 안내사항 추가 + // 안내사항 추가 (새로운 스키마에 맞춰) const instructionSheet = workbook.addWorksheet('오류 해결 가이드'); const instructions = [ - ['📋 오류 유형별 해결 방법', ''], + ['📋 새로운 스키마 Import 가이드', ''], ['', ''], - ['1. 벤더 코드/이메일 오류:', ''], - [' • 시스템에 등록된 벤더 코드 또는 이메일인지 확인', ''], + ['📌 필수 필드:', ''], + [' • 벤더이메일: 시스템에 등록된 벤더의 이메일 주소', ''], + [' • 아이템코드: 처리할 아이템의 코드', ''], + ['', ''], + ['📌 선택 필드:', ''], + [' • 공종: 작업 유형 (예: 용접, 도장, 기계 등)', ''], + [' • 선종: 선박 유형 (예: 컨테이너선, 벌크선, 탱커 등)', ''], + [' • 아이템리스트: 아이템에 대한 상세 설명', ''], + [' • 서브아이템리스트: 세부 아이템들에 대한 설명', ''], + [' • 벤더코드: 호환성을 위한 선택 필드', ''], + ['', ''], + ['🔍 오류 유형별 해결 방법:', ''], + ['', ''], + ['1. 벤더 이메일 오류:', ''], + [' • 올바른 이메일 형식 확인 (예: vendor@example.com)', ''], + [' • 시스템에 등록된 벤더 이메일인지 확인', ''], [' • 벤더 관리 메뉴에서 등록 상태 확인', ''], - [' • 벤더 코드가 없으면 벤더 이메일로 대체 가능', ''], ['', ''], ['2. 아이템 코드 오류:', ''], - [' • 벤더 타입과 일치하는 아이템인지 확인', ''], - [' • 조선 벤더 → item_shipbuilding 테이블', ''], - [' • 해양TOP 벤더 → item_offshore_top 테이블', ''], - [' • 해양HULL 벤더 → item_offshore_hull 테이블', ''], + [' • 아이템 코드가 누락되지 않았는지 확인', ''], + [' • 특수문자나 공백이 포함되지 않았는지 확인', ''], ['', ''], ['3. 중복 오류:', ''], - [' • 이미 등록된 벤더-아이템 조합', ''], + [' • 동일한 벤더 + 아이템코드 + 공종 + 선종 조합', ''], [' • 기존 데이터 확인 후 중복 제거', ''], ['', ''], ['📞 추가 문의: 시스템 관리자', ''], @@ -196,12 +248,14 @@ async function createErrorExcelFile( 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 = 50; + instructionSheet.getColumn(1).width = 60; // 파일 생성 및 다운로드 const buffer = await workbook.xlsx.writeBuffer(); -- cgit v1.2.3