From 573a76448ea2dab7432a1ec0ff1a54c1dd342595 Mon Sep 17 00:00:00 2001 From: "DESKTOP-1R2VSQH\\Lenovo ThinkPad E490" Date: Thu, 23 Apr 2026 09:13:05 +0700 Subject: [PATCH] refactor(registry): exportToExcelPosition --- package.json | 1 + .../components/PositionReview/Table.vue | 113 ++++++------- .../10_registry/interface/review/Edit.ts | 1 + .../10_registry/utils/exportPosition.ts | 148 ++++++++++++++++++ 4 files changed, 208 insertions(+), 55 deletions(-) create mode 100644 src/modules/10_registry/utils/exportPosition.ts diff --git a/package.json b/package.json index fc5a6bd..d84d6c4 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@tato30/vue-pdf": "^1.5.1", "@vuepic/vue-datepicker": "^3.6.3", "bma-org-chart": "^0.0.7", + "exceljs": "^4.4.0", "html-to-image": "^1.11.13", "keycloak-js": "^20.0.2", "moment": "^2.29.4", diff --git a/src/modules/10_registry/components/PositionReview/Table.vue b/src/modules/10_registry/components/PositionReview/Table.vue index 3d20dd6..a79e32f 100644 --- a/src/modules/10_registry/components/PositionReview/Table.vue +++ b/src/modules/10_registry/components/PositionReview/Table.vue @@ -24,6 +24,8 @@ import type { DataPosition } from "@/modules/10_registry/interface/review/Edit"; // import DialogSort from "@/modules/04_registryPerson/views/edit/components/DialogSort.vue"; // import CurruncyInput from "@/components/CurruncyInput.vue"; +import { exportToExcelPosition } from "@/modules/10_registry/utils/exportPosition"; + const dataStore = useDataStore(); const store = useGovernmentPosDataStore(); const $q = useQuasar(); @@ -445,67 +447,68 @@ function classColorRow(isDelete: boolean, isEdit: boolean, isEntry: boolean) { /** ฟังก์ชันดาวน์โหลดไฟล Excel */ function exportToExcel() { - const newData = rows.value.map((e: DataPosition, index: number) => { - return { - no: index + 1, - commandDateAffect: date2Thai(e.commandDateAffect), - positionName: e.positionName, - positionType: e.positionType, - positionLevel: e.positionLevel - ? e.positionLevel - : e.positionCee - ? e.positionCee - : "", - positionExecutive: e.positionExecutive, - amount: e.amount, - mouthSalaryAmount: e.mouthSalaryAmount, - positionSalaryAmount: e.positionSalaryAmount, - organization: findOrgName({ - root: e.orgRoot, - child1: e.orgChild1, - child2: e.orgChild2, - child3: e.orgChild3, - child4: e.orgChild4, - }), - posNo: - e.posNoAbb && e.posNo - ? `${e.posNoAbb} ${e.posNo}` - : e.posNo - ? e.posNo - : "", - posNumCodeSit: - e.posNumCodeSitAbb && e.posNumCodeSit - ? `${e.posNumCodeSit} (${e.posNumCodeSitAbb})` - : e.posNumCodeSit - ? e.posNumCodeSit - : "", - commandNo: - e.commandNo && e.commandYear - ? `${e.commandNo}/${Number(e.commandYear) + 543}` - : "", - commandDateSign: date2Thai(e.commandDateSign), - commandCode: store.convertCommandCodeName(e.commandCode), - remark: e.remark, - }; - }); + exportToExcelPosition(rows.value); + // const newData = rows.value.map((e: DataPosition, index: number) => { + // return { + // no: index + 1, + // commandDateAffect: date2Thai(e.commandDateAffect), + // positionName: e.positionName, + // positionType: e.positionType, + // positionLevel: e.positionLevel + // ? e.positionLevel + // : e.positionCee + // ? e.positionCee + // : "", + // positionExecutive: e.positionExecutive, + // amount: e.amount, + // mouthSalaryAmount: e.mouthSalaryAmount, + // positionSalaryAmount: e.positionSalaryAmount, + // organization: findOrgName({ + // root: e.orgRoot, + // child1: e.orgChild1, + // child2: e.orgChild2, + // child3: e.orgChild3, + // child4: e.orgChild4, + // }), + // posNo: + // e.posNoAbb && e.posNo + // ? `${e.posNoAbb} ${e.posNo}` + // : e.posNo + // ? e.posNo + // : "", + // posNumCodeSit: + // e.posNumCodeSitAbb && e.posNumCodeSit + // ? `${e.posNumCodeSit} (${e.posNumCodeSitAbb})` + // : e.posNumCodeSit + // ? e.posNumCodeSit + // : "", + // commandNo: + // e.commandNo && e.commandYear + // ? `${e.commandNo}/${Number(e.commandYear) + 543}` + // : "", + // commandDateSign: date2Thai(e.commandDateSign), + // commandCode: store.convertCommandCodeName(e.commandCode), + // remark: e.remark, + // }; + // }); - const headers = columns.value.map((item: any) => item.label) || []; // หัวคอลัมน์ภาษาไทย - const worksheet = XLSX.utils.json_to_sheet(newData, { - header: visibleColumns.value, - }); + // const headers = columns.value.map((item: any) => item.label) || []; // หัวคอลัมน์ภาษาไทย + // const worksheet = XLSX.utils.json_to_sheet(newData, { + // header: visibleColumns.value, + // }); //แทรกหัวคอลัมน์ภาษาไทย (ใช้ A1, B1, C1 แทน) - XLSX.utils.sheet_add_aoa(worksheet, [headers], { origin: "A1" }); + // XLSX.utils.sheet_add_aoa(worksheet, [headers], { origin: "A1" }); // Create a new workbook and append the worksheet - const workbook = XLSX.utils.book_new(); + // const workbook = XLSX.utils.book_new(); - XLSX.utils.book_append_sheet( - workbook, - worksheet, - `รายการประวัติตำแหน่งเงินเดือน`, - ); - XLSX.writeFile(workbook, "รายการประวัติตำแหน่งเงินเดือน.xlsx"); + // XLSX.utils.book_append_sheet( + // workbook, + // worksheet, + // `รายการประวัติตำแหน่งเงินเดือน`, + // ); + // XLSX.writeFile(workbook, "รายการประวัติตำแหน่งเงินเดือน.xlsx"); } // const commandCodeOptions = ref(store.commandCodeData); //รายการปรเภทคำสั่ง diff --git a/src/modules/10_registry/interface/review/Edit.ts b/src/modules/10_registry/interface/review/Edit.ts index 04041b2..545dc35 100644 --- a/src/modules/10_registry/interface/review/Edit.ts +++ b/src/modules/10_registry/interface/review/Edit.ts @@ -71,6 +71,7 @@ interface DataPosition { status: string; posNumCodeSitAbb: string; posNumCodeSit: string; + positionExecutiveField: string; } export type { DataSalaryPos, DataPosition }; diff --git a/src/modules/10_registry/utils/exportPosition.ts b/src/modules/10_registry/utils/exportPosition.ts new file mode 100644 index 0000000..ccd1dbf --- /dev/null +++ b/src/modules/10_registry/utils/exportPosition.ts @@ -0,0 +1,148 @@ +import ExcelJS from "exceljs"; +import { useCounterMixin } from "@/stores/mixin"; +import { useGovernmentPosDataStore } from "@/modules/10_registry/store/Position"; + +import type { DataPosition } from "@/modules/10_registry/interface/review/Edit"; + +const { date2Thai } = useCounterMixin(); +const store = useGovernmentPosDataStore(); + +export async function exportToExcelPosition(data: DataPosition[]) { + const workbook = new ExcelJS.Workbook(); + const worksheet = workbook.addWorksheet("รายการประวัติตำแหน่งเงินเดือน"); + + // --- ส่วนที่ 1: สร้าง Master Data Sheet สำหรับอ้างอิง ID --- + // เราจะซ่อนแผ่นงานนี้ไว้ (hidden) เพื่อใช้ทำ Dropdown และ VLOOKUP + const masterSheet = workbook.addWorksheet("MasterData", { state: "hidden" }); + const masterData = store.commandCodeData; // [{id: 1, name: "ย้าย"}, ...] + + masterData.forEach((item, index) => { + masterSheet.getCell(`A${index + 1}`).value = item.name; + masterSheet.getCell(`B${index + 1}`).value = item.id; + }); + + // --- ส่วนที่ 2: กำหนด Columns --- + worksheet.columns = [ + { header: "ลำดับ", key: "no", width: 8 }, + { header: "วันที่คำสั่งมีผล", key: "commandDateAffect", width: 18 }, + { header: "ตำแหน่งในสายงาน", key: "positionName", width: 25 }, + { header: "ตำแหน่งประเภท", key: "positionType", width: 18 }, + { header: "ระดับ", key: "positionLevel", width: 12 }, + { header: "ระดับซี", key: "positionCee", width: 12 }, + { header: "สายงาน", key: "positionLine", width: 20 }, + { header: "ด้าน/สาขา", key: "positionPathSide", width: 15 }, + { header: "ตำแหน่งทางการบริหาร", key: "positionExecutive", width: 20 }, + { header: "ด้านทางการบริหาร", key: "positionExecutiveField", width: 20 }, + { header: "เงินเดือน", key: "amount", width: 15 }, + { header: "เงินค่าตอบแทนรายเดือน", key: "mouthSalaryAmount", width: 15 }, + { header: "เงินประจำตำแหน่ง", key: "positionSalaryAmount", width: 15 }, + { header: "เงินค่าตอบแทนพิเศษ", key: "amountSpecial", width: 15 }, + { header: "หน่วยงาน", key: "organization", width: 30 }, + { header: "ส่วนราชการระดับ 1", key: "orgChild1", width: 20 }, + { header: "ส่วนราชการระดับ 2", key: "orgChild2", width: 20 }, + { header: "ส่วนราชการระดับ 3", key: "orgChild3", width: 20 }, + { header: "ส่วนราชการระดับ 4", key: "orgChild4", width: 20 }, + { header: "ตัวย่อเลขที่ตำแหน่ง", key: "posNoAbb", width: 15 }, + { header: "เลขที่ตำแหน่ง", key: "posNo", width: 15 }, + { header: "หน่วยงานที่ออกคำสั่ง", key: "posNumCodeSit", width: 20 }, + { + header: "ตัวย่อหน่วยงานที่ออกคำสั่ง", + key: "posNumCodeSitAbb", + width: 15, + }, + { header: "เลขที่คำสั่ง", key: "commandNo", width: 15 }, + { header: "ปีเลขที่คำสั่ง", key: "commandYear", width: 12 }, + { header: "วันที่ลงนาม", key: "commandDateSign", width: 18 }, + { header: "ประเภทคำสั่ง", key: "commandCode", width: 25 }, // AA + { header: "หมายเหตุ", key: "remark", width: 20 }, + { header: "commandId", key: "commandId", width: 20 }, // AC (ลำดับที่ 29) + ]; + + // 3. Map ข้อมูล + const newData = data.map((e, index) => ({ + no: index + 1, + commandDateAffect: date2Thai(e.commandDateAffect), + positionName: e.positionName, + positionType: e.positionType, + positionLevel: e.positionLevel, + positionCee: e.positionCee, + positionLine: e.positionLine || "", + positionPathSide: e.positionPathSide || "", + positionExecutive: e.positionExecutive, + positionExecutiveField: e.positionExecutiveField || "", + amount: e.amount, + mouthSalaryAmount: e.mouthSalaryAmount || 0, + positionSalaryAmount: e.positionSalaryAmount, + amountSpecial: e.amountSpecial || 0, + organization: e.orgRoot, + orgChild1: e.orgChild1, + orgChild2: e.orgChild2, + orgChild3: e.orgChild3, + orgChild4: e.orgChild4, + posNoAbb: e.posNoAbb, + posNo: e.posNo, + posNumCodeSit: e.posNumCodeSit, + posNumCodeSitAbb: e.posNumCodeSitAbb, + commandNo: e.commandNo, + commandYear: e.commandYear ? Number(e.commandYear) + 543 : "", + commandDateSign: date2Thai(e.commandDateSign), + commandCode: store.convertCommandCodeName(e.commandCode), + remark: e.remark, + commandId: e.commandCode, // ใส่ค่าเริ่มต้นไว้ + })); + + worksheet.addRows(newData); + + // 4. ตกแต่งสี, Dropdown และ สูตร VLOOKUP + newData.forEach((_, index) => { + const rowIndex = index + 2; + + // --- ระบายสีเหลืองเข้ม (ถอด # ออกจาก ARGB) --- + ["B", "O", "U", "V", "X", "Z"].forEach((col) => { + worksheet.getCell(`${col}${rowIndex}`).fill = { + type: "pattern", + pattern: "solid", + fgColor: { argb: "FFBF00" }, // ลบ # ออก + }; + }); + + // --- ระบายสีเหลืองอ่อน --- + ["F", "G", "H", "N", "P", "Q", "R", "S", "T", "W", "Y"].forEach((col) => { + worksheet.getCell(`${col}${rowIndex}`).fill = { + type: "pattern", + pattern: "solid", + fgColor: { argb: "FFFFD7" }, // ลบ # ออก + }; + }); + + // --- ทำ Dropdown คอลัมน์ AA --- + // อ้างอิงรายการจาก MasterData Sheet จะทำให้ Excel ทำงานได้เสถียรกว่า (กรณีรายการเยอะ) + worksheet.getCell(`AA${rowIndex}`).dataValidation = { + type: "list", + allowBlank: true, + formulae: [`MasterData!$A$1:$A$${masterData.length}`], + }; + + // --- ผูกสูตรให้ commandId (AC) เปลี่ยนตามการเลือกใน AA --- + // AA คือประเภทคำสั่ง, AB คือหมายเหตุ, AC คือ commandId + worksheet.getCell(`AC${rowIndex}`).value = { + formula: `=IFERROR(VLOOKUP(AA${rowIndex}, MasterData!$A$1:$B$${masterData.length}, 2, FALSE), "")`, + }; + }); + + // 5. สไตล์ Header + worksheet.getRow(1).font = { bold: true }; + worksheet.getRow(1).alignment = { vertical: "middle", horizontal: "center" }; + + // 6. เขียนไฟล์ + const buffer = await workbook.xlsx.writeBuffer(); + const blob = new Blob([buffer], { + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = "รายการประวัติตำแหน่งเงินเดือน.xlsx"; + a.click(); + window.URL.revokeObjectURL(url); +}