diff options
Diffstat (limited to 'lib/bidding/manage/export-bidding-items-to-excel.ts')
| -rw-r--r-- | lib/bidding/manage/export-bidding-items-to-excel.ts | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/lib/bidding/manage/export-bidding-items-to-excel.ts b/lib/bidding/manage/export-bidding-items-to-excel.ts new file mode 100644 index 00000000..814648a7 --- /dev/null +++ b/lib/bidding/manage/export-bidding-items-to-excel.ts @@ -0,0 +1,161 @@ +import ExcelJS from "exceljs" +import { PRItemInfo } from "@/components/bidding/manage/bidding-items-editor" +import { getProjectCodesByIds } from "./project-utils" + +/** + * 입찰품목 목록을 Excel로 내보내기 + */ +export async function exportBiddingItemsToExcel( + items: PRItemInfo[], + { + filename = "입찰품목목록", + }: { + filename?: string + } = {} +): Promise<void> { + // 프로젝트 ID 목록 수집 + const projectIds = items + .map((item) => item.projectId) + .filter((id): id is number => id != null && id > 0) + + // 프로젝트 코드 맵 조회 + const projectCodeMap = await getProjectCodesByIds(projectIds) + + // 헤더 정의 + const headers = [ + "프로젝트코드", + "프로젝트명", + "자재그룹코드", + "자재그룹명", + "자재코드", + "자재명", + "수량", + "수량단위", + "중량", + "중량단위", + "납품요청일", + "가격단위", + "구매단위", + "자재순중량", + "내정단가", + "내정금액", + "내정통화", + "예산금액", + "예산통화", + "실적금액", + "실적통화", + "WBS코드", + "WBS명", + "코스트센터코드", + "코스트센터명", + "GL계정코드", + "GL계정명", + "PR번호", + ] + + // 데이터 행 생성 + const dataRows = items.map((item) => { + // 프로젝트 코드 조회 + const projectCode = item.projectId + ? projectCodeMap.get(item.projectId) || "" + : "" + + return [ + projectCode, + item.projectInfo || "", + item.materialGroupNumber || "", + item.materialGroupInfo || "", + item.materialNumber || "", + item.materialInfo || "", + item.quantity || "", + item.quantityUnit || "", + item.totalWeight || "", + item.weightUnit || "", + item.requestedDeliveryDate || "", + item.priceUnit || "", + item.purchaseUnit || "", + item.materialWeight || "", + item.targetUnitPrice || "", + item.targetAmount || "", + item.targetCurrency || "KRW", + item.budgetAmount || "", + item.budgetCurrency || "KRW", + item.actualAmount || "", + item.actualCurrency || "KRW", + item.wbsCode || "", + item.wbsName || "", + item.costCenterCode || "", + item.costCenterName || "", + item.glAccountCode || "", + item.glAccountName || "", + item.prNumber || "", + ] + }) + + // 최종 sheetData + const sheetData = [headers, ...dataRows] + + // ExcelJS로 파일 생성 및 다운로드 + await createAndDownloadExcel(sheetData, headers.length, filename) +} + +/** + * Excel 파일 생성 및 다운로드 + */ +async function createAndDownloadExcel( + sheetData: any[][], + columnCount: number, + filename: string +): Promise<void> { + // ExcelJS 워크북/시트 생성 + const workbook = new ExcelJS.Workbook() + const worksheet = workbook.addWorksheet("Sheet1") + + // 칼럼별 최대 길이 추적 + const maxColumnLengths = Array(columnCount).fill(0) + sheetData.forEach((row) => { + row.forEach((cellValue, colIdx) => { + const cellText = cellValue?.toString() ?? "" + if (cellText.length > maxColumnLengths[colIdx]) { + maxColumnLengths[colIdx] = cellText.length + } + }) + }) + + // 시트에 데이터 추가 + 헤더 스타일 + sheetData.forEach((arr, idx) => { + const row = worksheet.addRow(arr) + + // 헤더 스타일 적용 (첫 번째 행) + if (idx === 0) { + row.font = { bold: true } + row.alignment = { horizontal: "center" } + row.eachCell((cell) => { + cell.fill = { + type: "pattern", + pattern: "solid", + fgColor: { argb: "FFCCCCCC" }, + } + }) + } + }) + + // 칼럼 너비 자동 조정 + maxColumnLengths.forEach((len, idx) => { + // 최소 너비 10, +2 여백 + worksheet.getColumn(idx + 1).width = Math.max(len + 2, 10) + }) + + // 최종 파일 다운로드 + 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}.xlsx` + link.click() + URL.revokeObjectURL(url) +} + |
