diff --git a/package.json b/package.json index 6e2ddd2c2..088a0ead7 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "axios": "^1.6.7", "bma-org-chart": "^0.0.8", "esri-loader": "^3.7.0", + "exceljs": "^4.4.0", "html-to-image": "^1.11.13", "keycloak-js": "^20.0.2", "moment": "^2.29.4", diff --git a/src/modules/04_registryPerson/interface/response/Edit.ts b/src/modules/04_registryPerson/interface/response/Edit.ts index 8af17bae8..d9deaf0bb 100644 --- a/src/modules/04_registryPerson/interface/response/Edit.ts +++ b/src/modules/04_registryPerson/interface/response/Edit.ts @@ -70,6 +70,7 @@ interface DataPosition { status: string; posNumCodeSitAbb: string; posNumCodeSit: string; + positionExecutiveField: string; } export type { DataSalaryPos, DataPosition }; diff --git a/src/modules/04_registryPerson/utils/exportPosition.ts b/src/modules/04_registryPerson/utils/exportPosition.ts new file mode 100644 index 000000000..144033429 --- /dev/null +++ b/src/modules/04_registryPerson/utils/exportPosition.ts @@ -0,0 +1,157 @@ +import ExcelJS from "exceljs"; +import { useEditPosDataStore } from "@/modules/04_registryPerson/stores/Edit"; + +import type { DataPosition } from "@/modules/04_registryPerson/interface/response/Edit"; + +const store = useEditPosDataStore(); + +// ฟังก์ชันแปลงวันที่จาก ISO format เป็นรูปแบบ dd/mm/yyyy (เช่น 18/05/2564) +function formatDateToDDMMYYYY(dateString: string | null | Date): string { + if (!dateString) return ""; + + const date = new Date(dateString); + if (isNaN(date.getTime())) return ""; + + const day = String(date.getDate()).padStart(2, "0"); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const year = date.getFullYear() + 543; // แปลงเป็นปีพุทธศักราช + + return `${day}/${month}/${year}`; +} + +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: "commandCodeName", width: 25 }, // AA + { header: "หมายเหตุ", key: "remark", width: 20 }, + { header: "commandId", key: "commandId", width: 20 }, // AC (ลำดับที่ 29) + { header: "commandCode", key: "commandCode", width: 20 }, // AD (ลำดับที่ 30) + ]; + + // 3. Map ข้อมูล + const newData = data.map((e, index) => ({ + no: index + 1, + commandDateAffect: e.commandDateAffect + ? formatDateToDDMMYYYY(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 || 0, + mouthSalaryAmount: e.mouthSalaryAmount || 0, + positionSalaryAmount: e.positionSalaryAmount || 0, + 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: e.commandDateSign + ? formatDateToDDMMYYYY(e.commandDateSign) + : "", + commandCodeName: store.convertCommandCodeName(e.commandCode), + remark: e.remark, + commandId: e.commandId || "", + commandCode: e.commandCode || "", // ใส่ค่าเริ่มต้นไว้ + })); + + worksheet.addRows(newData); + + // 4. ตกแต่งสี, Dropdown และ สูตร VLOOKUP + newData.forEach((_, index) => { + const rowIndex = index + 2; + + // --- ตั้งค่า Format สำหรับเซลล์วันที่ --- + // เซลล์ B (commandDateAffect) และ Z (commandDateSign) + const dateAffectCell = worksheet.getCell(`B${rowIndex}`); + const dateSignCell = worksheet.getCell(`Z${rowIndex}`); + + [dateAffectCell, dateSignCell].forEach((cell) => { + cell.numFmt = "dd/mm/yyyy"; + }); + + // --- ทำ Dropdown คอลัมน์ AA --- + // อ้างอิงรายการจาก MasterData Sheet จะทำให้ Excel ทำงานได้เสถียรกว่า (กรณีรายการเยอะ) + worksheet.getCell(`AA${rowIndex}`).dataValidation = { + type: "list", + allowBlank: true, + formulae: [`MasterData!$A$1:$A$${masterData.length}`], + }; + + // --- ผูกสูตรให้ commandCode (AD) เปลี่ยนตามการเลือกใน AA --- + // AA คือประเภทคำสั่ง, AD คือ commandCode + worksheet.getCell(`AD${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); +} diff --git a/src/modules/04_registryPerson/views/edit/components/Table.vue b/src/modules/04_registryPerson/views/edit/components/Table.vue index 74849426c..470c010ac 100644 --- a/src/modules/04_registryPerson/views/edit/components/Table.vue +++ b/src/modules/04_registryPerson/views/edit/components/Table.vue @@ -8,6 +8,7 @@ import config from "@/app.config"; import { useRoute } from "vue-router"; import { useCounterMixin } from "@/stores/mixin"; import { useEditPosDataStore } from "@/modules/04_registryPerson/stores/Edit"; +import { exportToExcelPosition } from "@/modules/04_registryPerson/utils/exportPosition"; import type { QTableColumn } from "quasar"; import type { FormDataSalary } from "@/modules/04_registryPerson/interface/index/Edit"; @@ -430,66 +431,67 @@ function classColorRow(isDelete: boolean, isEdit: boolean, isEntry: boolean) { /** ฟังก์ชันดาวน์โหลดไฟล Excel */ function exportToExcel() { - const newData = rows.value.map((e: DataPosition) => { - return { - 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) => { + // return { + // 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" }); + // //แทรกหัวคอลัมน์ภาษาไทย (ใช้ A1, B1, C1 แทน) + // XLSX.utils.sheet_add_aoa(worksheet, [headers], { origin: "A1" }); - // Create a new workbook and append the worksheet - const workbook = XLSX.utils.book_new(); + // // Create a new workbook and append the worksheet + // 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); //รายการปรเภทคำสั่ง