// /lib/vendor-export.ts import ExcelJS from "exceljs" import { TechVendor, TechVendorContact, TechVendorItem } from "@/db/schema/techVendors" import { exportTechVendorDetails } from "../service"; /** * 선택된 벤더의 모든 관련 정보를 통합 시트 형식으로 엑셀로 내보내는 함수 * - 기본정보 시트 * - 연락처 시트 * - 아이템 시트 * 각 시트에는 식별을 위한 벤더 코드, 벤더명, 세금ID가 포함됨 */ export async function exportVendorsWithRelatedData( vendors: TechVendor[], filename = "tech-vendors-detailed" ): Promise { if (!vendors.length) return; // 선택된 벤더 ID 목록 const vendorIds = vendors.map(vendor => vendor.id); try { // 서버로부터 모든 관련 데이터 가져오기 const vendorsWithDetails = await exportTechVendorDetails(vendorIds); if (!vendorsWithDetails.length) { throw new Error("내보내기 데이터를 가져오는 중 오류가 발생했습니다."); } // 워크북 생성 const workbook = new ExcelJS.Workbook(); // 데이터 타입 확인 (서비스에서 반환하는 실제 데이터 형태) const vendorData = vendorsWithDetails as unknown as any[]; // ===== 1. 기본 정보 시트 ===== createBasicInfoSheet(workbook, vendorData); // ===== 2. 연락처 시트 ===== createContactsSheet(workbook, vendorData); // ===== 3. 아이템 시트 ===== createItemsSheet(workbook, vendorData); // 파일 다운로드 const buffer = await workbook.xlsx.writeBuffer(); const blob = new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = `${filename}-${new Date().toISOString().split("T")[0]}.xlsx`; link.click(); URL.revokeObjectURL(url); return; } catch (error) { console.error("Export error:", error); throw error; } } // 기본 정보 시트 생성 함수 function createBasicInfoSheet( workbook: ExcelJS.Workbook, vendors: TechVendor[] ): void { const basicInfoSheet = workbook.addWorksheet("기본정보"); // 기본 정보 시트 헤더 설정 basicInfoSheet.columns = [ { header: "업체코드", key: "vendorCode", width: 15 }, { header: "업체명", key: "vendorName", width: 20 }, { header: "세금ID", key: "taxId", width: 15 }, { header: "국가", key: "country", width: 10 }, { header: "상태", key: "status", width: 15 }, { header: "이메일", key: "email", width: 20 }, { header: "전화번호", key: "phone", width: 15 }, { header: "웹사이트", key: "website", width: 20 }, { header: "주소", key: "address", width: 30 }, { header: "대표자명", key: "representativeName", width: 15 }, { header: "생성일", key: "createdAt", width: 15 }, { header: "벤더타입", key: "techVendorType", width: 15 }, { header: "대리점명", key: "agentName", width: 15 }, { header: "대리점연락처", key: "agentPhone", width: 15 }, { header: "대리점이메일", key: "agentEmail", width: 25 }, { header: "대리점주소", key: "agentAddress", width: 30 }, { header: "대리점국가", key: "agentCountry", width: 15 }, { header: "대리점영문국가명", key: "agentCountryEng", width: 20 }, ]; // 헤더 스타일 설정 applyHeaderStyle(basicInfoSheet); // 벤더 데이터 추가 vendors.forEach((vendor: TechVendor) => { basicInfoSheet.addRow({ vendorCode: vendor.vendorCode || "", vendorName: vendor.vendorName, taxId: vendor.taxId, country: vendor.country, status: getStatusText(vendor.status), // 상태 코드를 읽기 쉬운 텍스트로 변환 email: vendor.email, phone: vendor.phone, website: vendor.website, address: vendor.address, representativeName: vendor.representativeName, createdAt: vendor.createdAt ? formatDate(vendor.createdAt) : "", techVendorType: vendor.techVendorType?.split(',').join(', ') || vendor.techVendorType, }); }); } // 연락처 시트 생성 함수 function createContactsSheet( workbook: ExcelJS.Workbook, vendors: TechVendor[] ): void { const contactsSheet = workbook.addWorksheet("연락처"); contactsSheet.columns = [ // 벤더 식별 정보 { header: "업체코드", key: "vendorCode", width: 15 }, { header: "업체명", key: "vendorName", width: 20 }, { header: "세금ID", key: "taxId", width: 15 }, // 연락처 정보 { header: "이름", key: "contactName", width: 15 }, { header: "직책", key: "contactPosition", width: 15 }, { header: "이메일", key: "contactEmail", width: 25 }, { header: "전화번호", key: "contactPhone", width: 15 }, { header: "주요 연락처", key: "isPrimary", width: 10 }, ]; // 헤더 스타일 설정 applyHeaderStyle(contactsSheet); // 벤더별 연락처 데이터 추가 vendors.forEach((vendor: TechVendor) => { if (vendor.contacts && vendor.contacts.length > 0) { vendor.contacts.forEach((contact: TechVendorContact) => { contactsSheet.addRow({ // 벤더 식별 정보 vendorCode: vendor.vendorCode || "", vendorName: vendor.vendorName, taxId: vendor.taxId, // 연락처 정보 contactName: contact.contactName, contactPosition: contact.contactPosition || "", contactEmail: contact.contactEmail, contactPhone: contact.contactPhone || "", isPrimary: contact.isPrimary ? "예" : "아니오", }); }); } else { // 연락처가 없는 경우에도 벤더 정보만 추가 contactsSheet.addRow({ vendorCode: vendor.vendorCode || "", vendorName: vendor.vendorName, taxId: vendor.taxId, contactName: "", contactPosition: "", contactEmail: "", contactPhone: "", isPrimary: "", }); } }); } // 아이템 시트 생성 함수 function createItemsSheet( workbook: ExcelJS.Workbook, vendors: TechVendor[] ): void { const itemsSheet = workbook.addWorksheet("아이템"); itemsSheet.columns = [ // 벤더 식별 정보 { header: "업체코드", key: "vendorCode", width: 15 }, { header: "업체명", key: "vendorName", width: 20 }, { header: "세금ID", key: "taxId", width: 15 }, // 아이템 정보 { header: "아이템 코드", key: "itemCode", width: 15 }, { header: "아이템명", key: "itemName", width: 25 }, { header: "설명", key: "description", width: 30 }, { header: "등록일", key: "createdAt", width: 15 }, ]; // 헤더 스타일 설정 applyHeaderStyle(itemsSheet); // 벤더별 아이템 데이터 추가 vendors.forEach((vendor: TechVendor) => { if (vendor.items && vendor.items.length > 0) { vendor.items.forEach((item: TechVendorItem) => { itemsSheet.addRow({ // 벤더 식별 정보 vendorCode: vendor.vendorCode || "", vendorName: vendor.vendorName, taxId: vendor.taxId, // 아이템 정보 itemCode: item.itemCode, itemName: item.itemName, createdAt: item.createdAt ? formatDate(item.createdAt) : "", }); }); } else { // 아이템이 없는 경우에도 벤더 정보만 추가 itemsSheet.addRow({ vendorCode: vendor.vendorCode || "", vendorName: vendor.vendorName, taxId: vendor.taxId, itemCode: "", itemName: "", createdAt: "", }); } }); } // 헤더 스타일 적용 함수 function applyHeaderStyle(sheet: ExcelJS.Worksheet): void { const headerRow = sheet.getRow(1); headerRow.font = { bold: true }; headerRow.alignment = { horizontal: "center" }; headerRow.eachCell((cell: ExcelJS.Cell) => { cell.fill = { type: "pattern", pattern: "solid", fgColor: { argb: "FFCCCCCC" }, }; }); } // 날짜 포맷 함수 function formatDate(date: Date | string): string { if (!date) return ""; if (typeof date === 'string') { date = new Date(date); } return date.toISOString().split('T')[0]; } // 상태 코드를 읽기 쉬운 텍스트로 변환하는 함수 function getStatusText(status: string): string { const statusMap: Record = { "ACTIVE": "활성", "INACTIVE": "비활성", "BLACKLISTED": "거래 금지" }; return statusMap[status] || status; }