summaryrefslogtreecommitdiff
path: root/lib/bidding/manage/export-bidding-items-to-excel.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bidding/manage/export-bidding-items-to-excel.ts')
-rw-r--r--lib/bidding/manage/export-bidding-items-to-excel.ts161
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)
+}
+