From a4e3e5d4173841aa90f8366dddc1d0eece9e4bac Mon Sep 17 00:00:00 2001 From: Adisak Date: Wed, 1 Oct 2025 11:20:33 +0700 Subject: [PATCH 1/4] update cronjob snapshot --- src/controllers/SalaryPeriodController.ts | 1748 +++++++++++++++++---- src/interfaces/call-api.ts | 4 +- 2 files changed, 1402 insertions(+), 350 deletions(-) diff --git a/src/controllers/SalaryPeriodController.ts b/src/controllers/SalaryPeriodController.ts index ec67bd8..d7ec146 100644 --- a/src/controllers/SalaryPeriodController.ts +++ b/src/controllers/SalaryPeriodController.ts @@ -30,7 +30,9 @@ import { SalaryOrgEmployee } from "../entities/SalaryOrgEmployee"; import { SalaryProfileEmployee } from "../entities/SalaryProfileEmployee"; import { RequestWithUser } from "../middlewares/user"; import permission from "../interfaces/permission"; +import querystring from "querystring"; import { setLogDataDiff } from "../interfaces/utils"; +import axios from "axios"; @Route("api/v1/salary/period") @Tags("Salary") @@ -3628,62 +3630,27 @@ export class SalaryPeriodController extends Controller { return new HttpSuccess(); } - /** - * Cronjob SalaryPeriod - */ - async CronjobSalaryPeriod() { - const current = new Date(); - let salaryPeriod: any; - let request: any; - if (current.getDate() == 1 && current.getMonth() == 2) { - salaryPeriod = await this.salaryPeriodRepository.findOne({ - where: { - year: current.getFullYear(), - period: "APR", - isActive: true, - }, - }); - if (salaryPeriod) { - this.SnapshotSalarys("SNAP1", salaryPeriod.id, request); - } - } else if (current.getDate() == 1 && current.getMonth() == 3) { - salaryPeriod = await this.salaryPeriodRepository.findOne({ - where: { - year: current.getFullYear(), - period: "APR", - isActive: true, - }, - }); - if (salaryPeriod) { - this.SnapshotSalarys("SNAP2", salaryPeriod.id, request); - } - } else if (current.getDate() == 1 && current.getMonth() == 8) { - salaryPeriod = await this.salaryPeriodRepository.findOne({ - where: { - year: current.getFullYear(), - period: "OCT", - isActive: true, - }, - }); - if (salaryPeriod) { - this.SnapshotSalarys("SNAP1", salaryPeriod.id, request); - } - } else if (current.getDate() == 1 && current.getMonth() == 9) { - salaryPeriod = await this.salaryPeriodRepository.findOne({ - where: { - year: current.getFullYear(), - period: "OCT", - isActive: true, - }, - }); - if (salaryPeriod) { - this.SnapshotSalarys("SNAP2", salaryPeriod.id, request); + private async performSnapshotOperationForCronjob( + snapshot: string, + salaryPeriodId: string, + request: RequestWithUser, + ) { + // ปรับ connection timeout เพื่อรองรับการประมวลผลข้อมูลขนาดใหญ่ + const connection = AppDataSource; + if (connection.isInitialized) { + try { + // ปรับ query timeout สำหรับ long-running operations + await connection.query("SET SESSION wait_timeout = 3600"); // 1 hour + await connection.query("SET SESSION interactive_timeout = 3600"); + await connection.query("SET SESSION max_execution_time = 0"); // No limit + await connection.query("SET SESSION net_read_timeout = 600"); // 10 minutes + await connection.query("SET SESSION net_write_timeout = 600"); // 10 minutes + console.log("✅ ปรับ database session timeout เรียบร้อย"); + } catch (sessionErr) { + console.warn("⚠️ ไม่สามารถปรับ session timeout ได้:", sessionErr); } } - } - public async SnapshotSalarys(snapshot: string, salaryPeriodId: string, request: any) { - snapshot = snapshot.toLocaleUpperCase(); const salaryPeriod = await this.salaryPeriodRepository.findOne({ where: { id: salaryPeriodId }, }); @@ -3691,268 +3658,713 @@ export class SalaryPeriodController extends Controller { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบรอบการสร้างเงินเดือน"); } - const salaryOrg = await this.salaryOrgRepository.find({ - where: { salaryPeriodId: salaryPeriod.id, snapshot: snapshot }, - }); - const salaryProfile = await this.salaryProfileRepository.find({ - where: { salaryOrgId: In(salaryOrg.map((x) => x.id)) }, - }); - await this.salaryOrgRepository.remove(salaryOrg, { data: request }); - await this.salaryProfileRepository.remove(salaryProfile, { data: request }); + // Fast bulk delete approach - ไม่ต้อง load ข้อมูลมาก่อนลบ + console.time("⏱ Cleanup: Fast bulk delete"); - const salaryOrgEmployee = await this.salaryOrgEmployeeRepository.find({ - where: { salaryPeriodId: salaryPeriod.id, snapshot: snapshot }, - }); - const salaryProfileEmployee = await this.salaryProfileEmployeeRepository.find({ - where: { salaryOrgId: In(salaryOrgEmployee.map((x) => x.id)) }, - }); - await this.salaryProfileEmployeeRepository.remove(salaryProfileEmployee, { data: request }); - await this.salaryOrgEmployeeRepository.remove(salaryOrgEmployee, { data: request }); + // สำหรับข้อมูลจำนวนมาก ใช้ TRUNCATE หรือ bulk delete + // Option 1: ถ้าต้องการลบข้อมูลทั้งหมดของ snapshot + if (snapshot === "SNAP1" || snapshot === "SNAP2") { + console.log(`🚀 ใช้ Super Fast Delete สำหรับ ${snapshot}`); - let orgs = await new CallAPI().GetData(request, "/org/unauthorize/active/root/id"); - let total = 100; - let _orgProfiles = await new CallAPI().PostData(request, "/org/unauthorize/salary/gen", { - page: 1, - pageSize: 100, - keyword: "", - year: salaryPeriod.year, - period: salaryPeriod.period, - }); - let orgProfiles = _orgProfiles.data; - total = _orgProfiles.total; - if (total > 100) { - const page = Math.ceil(total / 100); - for (let index = 2; index <= page; index++) { - await new CallAPI() - .PostData(request, "/org/unauthorize/profile/salary/gen", { - page: index, - pageSize: 100, - keyword: "", - year: salaryPeriod.year, - period: salaryPeriod.period, - }) - .then((x) => { - Array.prototype.push.apply(orgProfiles, x.data); - }); + // Super fast approach: ลบแบบ sequential เพื่อเคารพ foreign key constraints + // แต่ใช้ bulk delete แทนการ load + remove + + // For extremely large datasets (100K+ records), use batch delete to prevent timeouts + const isLargeDataset = await AppDataSource.query( + ` + SELECT COUNT(*) as count FROM salaryOrg WHERE salaryPeriodId = ? AND snapshot = ? + `, + [salaryPeriod.id, snapshot], + ).then((result) => result[0]?.count > 50000); + + if (isLargeDataset) { + console.log(`🔥 ข้อมูลขนาดใหญ่มาก - ใช้ Batch Delete`); + + // Batch delete for large datasets to prevent timeout + let deletedProfiles = 0, + deletedProfileEmployees = 0; + + // Delete profiles in batches + while (true) { + const result = await AppDataSource.query( + ` + DELETE sp FROM salaryProfile sp + INNER JOIN salaryOrg so ON sp.salaryOrgId = so.id + WHERE so.salaryPeriodId = ? AND so.snapshot = ? + LIMIT 10000 + `, + [salaryPeriod.id, snapshot], + ); + + deletedProfiles += result.affectedRows; + if (result.affectedRows === 0) break; + + // Small delay to prevent overwhelming the database + await new Promise((resolve) => setTimeout(resolve, 100)); + } + + // Delete profile employees in batches + while (true) { + const result = await AppDataSource.query( + ` + DELETE spe FROM salaryProfileEmployee spe + INNER JOIN salaryOrgEmployee soe ON spe.salaryOrgId = soe.id + WHERE soe.salaryPeriodId = ? AND soe.snapshot = ? + LIMIT 10000 + `, + [salaryPeriod.id, snapshot], + ); + + deletedProfileEmployees += result.affectedRows; + if (result.affectedRows === 0) break; + + await new Promise((resolve) => setTimeout(resolve, 100)); + } + + // Delete orgs and org employees + const [deletedOrg, deletedOrgEmployee] = await Promise.all([ + AppDataSource.query(`DELETE FROM salaryOrg WHERE salaryPeriodId = ? AND snapshot = ?`, [ + salaryPeriod.id, + snapshot, + ]), + AppDataSource.query( + `DELETE FROM salaryOrgEmployee WHERE salaryPeriodId = ? AND snapshot = ?`, + [salaryPeriod.id, snapshot], + ), + ]); + + console.log( + `✅ Batch Delete เสร็จสิ้น - Profiles: ${deletedProfiles}, ProfileEmployees: ${deletedProfileEmployees}, Orgs: ${deletedOrg.affectedRows}, OrgEmployees: ${deletedOrgEmployee.affectedRows}`, + ); + } else { + // Standard fast delete for smaller datasets + // Step 1: ลบ SalaryProfile ก่อน + const deletedProfiles = await AppDataSource.query( + ` + DELETE sp FROM salaryProfile sp + INNER JOIN salaryOrg so ON sp.salaryOrgId = so.id + WHERE so.salaryPeriodId = ? AND so.snapshot = ? + `, + [salaryPeriod.id, snapshot], + ); + + // Step 2: ลบ SalaryProfileEmployee + const deletedProfileEmployees = await AppDataSource.query( + ` + DELETE spe FROM salaryProfileEmployee spe + INNER JOIN salaryOrgEmployee soe ON spe.salaryOrgId = soe.id + WHERE soe.salaryPeriodId = ? AND soe.snapshot = ? + `, + [salaryPeriod.id, snapshot], + ); + + // Step 3: ลบ SalaryOrg และ SalaryOrgEmployee + const [deletedOrg, deletedOrgEmployee] = await Promise.all([ + AppDataSource.query(`DELETE FROM salaryOrg WHERE salaryPeriodId = ? AND snapshot = ?`, [ + salaryPeriod.id, + snapshot, + ]), + AppDataSource.query( + `DELETE FROM salaryOrgEmployee WHERE salaryPeriodId = ? AND snapshot = ?`, + [salaryPeriod.id, snapshot], + ), + ]); + + console.log( + `✅ Super Fast Delete เสร็จสิ้น - Profiles: ${deletedProfiles.affectedRows}, ProfileEmployees: ${deletedProfileEmployees.affectedRows}, Orgs: ${deletedOrg.affectedRows}, OrgEmployees: ${deletedOrgEmployee.affectedRows}`, + ); + } + } else { + // Fallback: Standard bulk delete approach สำหรับ snapshots อื่นๆ + console.log(`📊 ใช้ Standard Bulk Delete สำหรับ ${snapshot}`); + + // Get counts for logging (optional, can be removed for even better performance) + const [salaryOrgCount, salaryOrgEmployeeCount] = await Promise.all([ + this.salaryOrgRepository.count({ + where: { salaryPeriodId: salaryPeriod.id, snapshot: snapshot }, + }), + this.salaryOrgEmployeeRepository.count({ + where: { salaryPeriodId: salaryPeriod.id, snapshot: snapshot }, + }), + ]); + + if (salaryOrgCount > 0 || salaryOrgEmployeeCount > 0) { + // Get profile counts using subqueries + const [salaryProfileCount, salaryProfileEmployeeCount] = await Promise.all([ + salaryOrgCount > 0 + ? AppDataSource.query( + ` + SELECT COUNT(*) as count FROM salaryProfile + WHERE salaryOrgId IN ( + SELECT id FROM salaryOrg + WHERE salaryPeriodId = ? AND snapshot = ? + ) + `, + [salaryPeriod.id, snapshot], + ).then((result) => result[0]?.count || 0) + : Promise.resolve(0), + salaryOrgEmployeeCount > 0 + ? AppDataSource.query( + ` + SELECT COUNT(*) as count FROM salaryProfileEmployee + WHERE salaryOrgId IN ( + SELECT id FROM salaryOrgEmployee + WHERE salaryPeriodId = ? AND snapshot = ? + ) + `, + [salaryPeriod.id, snapshot], + ).then((result) => result[0]?.count || 0) + : Promise.resolve(0), + ]); + + console.log( + `🧹 ลบข้อมูลเก่า: SalaryProfile=${salaryProfileCount}, SalaryProfileEmployee=${salaryProfileEmployeeCount}, SalaryOrg=${salaryOrgCount}, SalaryOrgEmployee=${salaryOrgEmployeeCount}`, + ); + + // Fast bulk delete - ลบโดยใช้ query โดยตรง (เร็วกว่า 10-50 เท่า) + // First, delete child records (profiles) using bulk delete + await Promise.all([ + salaryOrgCount > 0 + ? AppDataSource.query( + ` + DELETE FROM salaryProfile + WHERE salaryOrgId IN ( + SELECT id FROM salaryOrg + WHERE salaryPeriodId = ? AND snapshot = ? + ) + `, + [salaryPeriod.id, snapshot], + ) + : Promise.resolve(), + salaryOrgEmployeeCount > 0 + ? AppDataSource.query( + ` + DELETE FROM salaryProfileEmployee + WHERE salaryOrgId IN ( + SELECT id FROM salaryOrgEmployee + WHERE salaryPeriodId = ? AND snapshot = ? + ) + `, + [salaryPeriod.id, snapshot], + ) + : Promise.resolve(), + ]); + + // Then, delete parent records (orgs) + await Promise.all([ + AppDataSource.query( + ` + DELETE FROM salaryOrg + WHERE salaryPeriodId = ? AND snapshot = ? + `, + [salaryPeriod.id, snapshot], + ), + AppDataSource.query( + ` + DELETE FROM salaryOrgEmployee + WHERE salaryPeriodId = ? AND snapshot = ? + `, + [salaryPeriod.id, snapshot], + ), + ]); + } else { + console.log(`ℹ️ ไม่มีข้อมูลที่ต้องลบสำหรับ ${snapshot}`); } } - total = 100; - let _orgProfileEmployees = await new CallAPI().PostData( - request, - "/org/unauthorize/profile/salary/employee/gen", - { + + console.timeEnd("⏱ Cleanup: Fast bulk delete"); + + //snap บางสำนัก + //.chin + // const targetRootIds = [ + // "d7e98989-b5ce-47d6-93c3-ab63ed486348", + // "e0545eca-5d0a-4a1c-8bbd-e3e25c2521db", + // "26989ffa-d5ab-4bbd-ac97-130646cd1da6", + // "6f9b30e1-757a-40d5-b053-61eb1b91c0f0", + // "eaf65f33-25e9-4956-9dba-5d909f5eb595", + // "a3efed2c-3f4b-476d-95e6-9f7e0585ae25", + // ]; + //.me + // const targetRootIds = [ + // "b89a4467-7ee3-4706-8db7-f366555f826c", + // "585648a9-e634-43fc-9360-5fd4189136ab", + // "d6e3daa0-284a-428f-aa43-0750fa74e974", + // "ed3ddcfb-f882-4499-817b-aff73e5be87c", + // "f8ce98ca-a691-4c89-abde-875f559afb3a", + // ]; + + // orgs = orgs.filter((x: any) => targetRootIds.includes(x.rootId)); + + // Parallel loading of initial data and API calls + console.time("⏱ API: Load initial data"); + const [orgs, revisionId, _orgProfiles, _orgProfileEmployees] = await Promise.all([ + new CallAPI().GetData({ headers: { authorization: request } }, "/org/unauthorize/active/root/id",false), + new CallAPI().GetData({ headers: { authorization: request } }, "/org/unauthorize/revision/latest",false), + new CallAPI().PostData({ headers: { authorization: request } }, "/org/unauthorize/new-salary/gen", { page: 1, - pageSize: 100, + pageSize: 1000, keyword: "", year: salaryPeriod.year, period: salaryPeriod.period, - }, - ); - let orgProfileEmployees = _orgProfileEmployees.data; - total = _orgProfileEmployees.total; - if (total > 100) { - const page = Math.ceil(total / 100); + },false), + new CallAPI().PostData({ headers: { authorization: request } }, "/org/unauthorize/new-salary/employee/gen", { + page: 1, + pageSize: 1000, + keyword: "", + year: salaryPeriod.year, + period: salaryPeriod.period, + },false), + ]); + console.timeEnd("⏱ API: Load initial data"); + + let total = 1000; + let orgProfiles = _orgProfiles.data; + total = _orgProfiles.total; + console.log(`total: ${total}`); + + if (total > 1000) { + console.time("⏱ API: Load additional profile pages"); + const page = Math.ceil(total / 1000); + const promises = []; for (let index = 2; index <= page; index++) { - await new CallAPI() - .PostData(request, "/org/unauthorize/profile/salary/employee/gen", { + promises.push( + new CallAPI().PostData({ headers: { authorization: request } }, "/org/unauthorize/new-salary/gen", { page: index, - pageSize: 100, + pageSize: 1000, keyword: "", year: salaryPeriod.year, period: salaryPeriod.period, - }) - .then((x) => { - Array.prototype.push.apply(orgProfileEmployees, x.data); - }); + },false), + ); } + const results = await Promise.all(promises); + results.forEach((x) => { + Array.prototype.push.apply(orgProfiles, x.data); + }); + console.timeEnd("⏱ API: Load additional profile pages"); } - let revisionId = await new CallAPI().GetData(request, "/org/unauthorize/revision/latest"); - const before = null; + total = 1000; + let orgProfileEmployees = _orgProfileEmployees.data; + total = _orgProfileEmployees.total; + console.log(`totalEmp: ${total}`); + if (total > 1000) { + console.time("⏱ API: Load additional employee pages"); + const page = Math.ceil(total / 1000); + const promises = []; + for (let index = 2; index <= page; index++) { + promises.push( + new CallAPI().PostData({ headers: { authorization: request } }, "/org/unauthorize/new-salary/employee/gen", { + page: index, + pageSize: 1000, + keyword: "", + year: salaryPeriod.year, + period: salaryPeriod.period, + },false), + ); + } + const results = await Promise.all(promises); + results.forEach((x) => { + Array.prototype.push.apply(orgProfileEmployees, x.data); + }); + console.timeEnd("⏱ API: Load additional employee pages"); + } + + let _null: any = null; + // const beforeSalaryPeriod = structuredClone(salaryPeriod); salaryPeriod.revisionId = revisionId; - salaryPeriod.lastUpdateUserId = request.user.sub; - salaryPeriod.lastUpdateFullName = request.user.name; + salaryPeriod.lastUpdateUserId = ""; + salaryPeriod.lastUpdateFullName = "System Administrator"; salaryPeriod.lastUpdatedAt = new Date(); await this.salaryPeriodRepository.save(salaryPeriod, { data: request }); - // setLogDataDiff(request, { before, after: salaryPeriod }); - let _null: any = null; - await Promise.all( - orgs.map(async (root: any) => { - let salaryOrgNew = Object.assign(new SalaryOrg()); - salaryOrgNew.salaryPeriodId = salaryPeriod.id; - salaryOrgNew.status = "PENDING"; - salaryOrgNew.rootId = root.rootId; - salaryOrgNew.rootDnaId = root.rootDnaId ?? _null; - salaryOrgNew.root = root.root; - salaryOrgNew.revisionId = salaryPeriod.revisionId; - salaryOrgNew.snapshot = snapshot; - salaryOrgNew.createdUserId = request.user.sub; - salaryOrgNew.createdFullName = request.user.name; - salaryOrgNew.lastUpdateUserId = request.user.sub; - salaryOrgNew.lastUpdateFullName = request.user.name; - salaryOrgNew.createdAt = new Date(); - salaryOrgNew.lastUpdatedAt = new Date(); - salaryOrgNew.group = "GROUP1"; - await this.salaryOrgRepository.save(salaryOrgNew, { data: request }); - // setLogDataDiff(request, { before, after: salaryOrgNew }); + // setLogDataDiff(request, { before: beforeSalaryPeriod, after: salaryPeriod }); - if (salaryPeriod.period != "SPECIAL") { - delete salaryOrgNew.id; - salaryOrgNew.group = "GROUP2"; - await this.salaryOrgRepository.save(salaryOrgNew, { data: request }); - // setLogDataDiff(request, { before, after: salaryOrgNew }); + // Prepare SalaryOrg records for batch insert + const salaryOrgsToSave: SalaryOrg[] = []; + + orgs.forEach((root: any) => { + // GROUP1 + let salaryOrgNew = Object.assign(new SalaryOrg()); + delete salaryOrgNew.id; + salaryOrgNew.salaryPeriodId = salaryPeriod.id; + salaryOrgNew.status = "PENDING"; + salaryOrgNew.rootId = root.rootId; + salaryOrgNew.rootDnaId = root.rootDnaId ?? _null; + salaryOrgNew.root = root.root; + salaryOrgNew.revisionId = salaryPeriod.revisionId; + salaryOrgNew.snapshot = snapshot; + salaryOrgNew.createdUserId = ""; + salaryOrgNew.createdFullName = "System Administrator"; + salaryOrgNew.lastUpdateUserId = ""; + salaryOrgNew.lastUpdateFullName = "System Administrator"; + salaryOrgNew.createdAt = new Date(); + salaryOrgNew.lastUpdatedAt = new Date(); + salaryOrgNew.group = "GROUP1"; + salaryOrgsToSave.push(salaryOrgNew); + + // GROUP2 (if not SPECIAL) + if (salaryPeriod.period != "SPECIAL") { + let salaryOrgNew2 = Object.assign(new SalaryOrg(), salaryOrgNew); + delete salaryOrgNew2.id; + salaryOrgNew2.group = "GROUP2"; + salaryOrgsToSave.push(salaryOrgNew2); + } + }); + + // Batch insert SalaryOrg with error handling + console.time("⏱ Insert: SalaryOrg batch"); + if (salaryOrgsToSave.length > 0) { + try { + await this.salaryOrgRepository.save(salaryOrgsToSave, { data: request }); + console.log(`✅ [SNAP: ${snapshot}] บันทึก ${salaryOrgsToSave.length} SalaryOrg สำเร็จ`); + } catch (saveError) { + console.error("❌ Error saving SalaryOrg batch, trying chunked approach:", saveError); + // Retry with smaller chunks if failed + const chunkSize = 50; + for (let i = 0; i < salaryOrgsToSave.length; i += chunkSize) { + const chunk = salaryOrgsToSave.slice(i, i + chunkSize); + await this.salaryOrgRepository.save(chunk, { data: request }); + console.log( + `✅ Saved SalaryOrg chunk ${Math.floor(i / chunkSize) + 1}/${Math.ceil(salaryOrgsToSave.length / chunkSize)}`, + ); + + // Small delay to prevent overwhelming the connection + await new Promise((resolve) => setTimeout(resolve, 100)); } - }), - ); - - await Promise.all( - orgs.map(async (root: any) => { - let salaryOrgNew = Object.assign(new SalaryOrgEmployee()); - salaryOrgNew.salaryPeriodId = salaryPeriod.id; - salaryOrgNew.status = "PENDING"; - salaryOrgNew.rootId = root.rootId; - salaryOrgNew.rootDnaId = root.rootDnaId ?? _null; - salaryOrgNew.root = root.root; - salaryOrgNew.revisionId = salaryPeriod.revisionId; - salaryOrgNew.snapshot = snapshot; - salaryOrgNew.createdUserId = request.user.sub; - salaryOrgNew.createdFullName = request.user.name; - salaryOrgNew.lastUpdateUserId = request.user.sub; - salaryOrgNew.lastUpdateFullName = request.user.name; - salaryOrgNew.createdAt = new Date(); - salaryOrgNew.lastUpdatedAt = new Date(); - salaryOrgNew.group = "GROUP1"; - await this.salaryOrgRepository.save(salaryOrgNew, { data: request }); - // setLogDataDiff(request, { before, after: salaryOrgNew }); - - if (salaryPeriod.period != "SPECIAL") { - delete salaryOrgNew.id; - salaryOrgNew.group = "GROUP2"; - await this.salaryOrgEmployeeRepository.save(salaryOrgNew, { data: request }); - // setLogDataDiff(request, { before, after: salaryOrgNew }); - } - }), - ); - - if (salaryPeriod.period != "SPECIAL") { - await Promise.all( - orgProfiles.map(async (profile: any) => { - let group = "GROUP1"; - if ( - (profile.posType == "ทั่วไป" && profile.posLevel == "ทักษะพิเศษ") || - (profile.posType == "วิชาการ" && profile.posLevel == "เชี่ยวชาญ") || - (profile.posType == "วิชาการ" && profile.posLevel == "ทรงคุณวุฒิ") || - (profile.posType == "อำนวยการ" && profile.posLevel == "สูง") || - (profile.posType == "บริหาร" && profile.posLevel == "ต้น") || - (profile.posType == "บริหาร" && profile.posLevel == "สูง") - ) { - group = "GROUP2"; - } - const salaryOrgNew = await this.salaryOrgRepository.findOne({ - where: { - salaryPeriodId: salaryPeriod.id, - rootId: profile.rootId, - snapshot: snapshot, - group: group, - }, - }); - - if (salaryOrgNew != null) { - let salaryProfileNew = Object.assign(new SalaryProfile(), profile); - delete salaryProfileNew.id; - salaryProfileNew.salaryOrgId = salaryOrgNew.id; - salaryProfileNew.revisionId = salaryPeriod.revisionId; - salaryProfileNew.createdUserId = request.user.sub; - salaryProfileNew.createdFullName = request.user.name; - salaryProfileNew.lastUpdateUserId = request.user.sub; - salaryProfileNew.lastUpdateFullName = request.user.name; - - if (snapshot == "SNAP2") { - const salaryOrgOld = await this.salaryOrgRepository.find({ - where: { salaryPeriodId: salaryPeriod.id, snapshot: "SNAP1" }, - }); - const salaryOld = await this.salaryProfileRepository.findOne({ - where: { - citizenId: salaryProfileNew.citizenId, - salaryOrgId: In(salaryOrgOld.map((x) => x.id)), - }, - }); - salaryProfileNew.type = salaryOld == null ? 0 : salaryOld.type; - salaryProfileNew.amount = salaryOld == null ? 0 : salaryOld.amount; - salaryProfileNew.amountSpecial = salaryOld == null ? 0 : salaryOld.amountSpecial; - salaryProfileNew.amountUse = salaryOld == null ? 0 : salaryOld.amountUse; - salaryProfileNew.positionSalaryAmount = - salaryOld == null ? 0 : salaryOld.positionSalaryAmount; - salaryProfileNew.isNext = salaryOld == null ? false : salaryOld.isNext; - salaryProfileNew.isSpecial = salaryOld == null ? false : salaryOld.isSpecial; - salaryProfileNew.isReserve = salaryOld == null ? false : salaryOld.isReserve; - salaryProfileNew.isRetired = salaryOld == null ? false : salaryOld.isRetired; - } - salaryProfileNew.createdUserId = request.user.sub; - salaryProfileNew.createdFullName = request.user.name; - salaryProfileNew.lastUpdateUserId = request.user.sub; - salaryProfileNew.lastUpdateFullName = request.user.name; - salaryProfileNew.createdAt = new Date(); - salaryProfileNew.lastUpdatedAt = new Date(); - await this.salaryProfileRepository.save(salaryProfileNew, { data: request }); - // setLogDataDiff(request, { before, after: salaryProfileNew }); - } - }), - ); - await Promise.all( - orgProfileEmployees.map(async (profile: any) => { - const salaryOrgNew = await this.salaryOrgEmployeeRepository.findOne({ - where: { - salaryPeriodId: salaryPeriod.id, - rootId: profile.rootId, - snapshot: snapshot, - group: "GROUP1", - }, - }); - - if (salaryOrgNew != null) { - let salaryProfileNew = Object.assign(new SalaryProfileEmployee(), profile); - delete salaryProfileNew.id; - salaryProfileNew.salaryOrgId = salaryOrgNew.id; - salaryProfileNew.revisionId = salaryPeriod.revisionId; - salaryProfileNew.createdUserId = request.user.sub; - salaryProfileNew.createdFullName = request.user.name; - salaryProfileNew.lastUpdateUserId = request.user.sub; - salaryProfileNew.lastUpdateFullName = request.user.name; - - if (snapshot == "SNAP2") { - const salaryOrgOld = await this.salaryOrgEmployeeRepository.find({ - where: { salaryPeriodId: salaryPeriod.id, snapshot: "SNAP1" }, - }); - const salaryOld = await this.salaryProfileEmployeeRepository.findOne({ - where: { - citizenId: salaryProfileNew.citizenId, - salaryOrgId: In(salaryOrgOld.map((x) => x.id)), - }, - }); - salaryProfileNew.type = salaryOld == null ? 0 : salaryOld.type; - salaryProfileNew.amount = salaryOld == null ? 0 : salaryOld.amount; - salaryProfileNew.amountSpecial = salaryOld == null ? 0 : salaryOld.amountSpecial; - salaryProfileNew.amountUse = salaryOld == null ? 0 : salaryOld.amountUse; - salaryProfileNew.positionSalaryAmount = - salaryOld == null ? 0 : salaryOld.positionSalaryAmount; - salaryProfileNew.isNext = salaryOld == null ? false : salaryOld.isNext; - salaryProfileNew.isSpecial = salaryOld == null ? false : salaryOld.isSpecial; - salaryProfileNew.isReserve = salaryOld == null ? false : salaryOld.isReserve; - salaryProfileNew.isRetired = salaryOld == null ? false : salaryOld.isRetired; - } - salaryProfileNew.createdUserId = request.user.sub; - salaryProfileNew.createdFullName = request.user.name; - salaryProfileNew.lastUpdateUserId = request.user.sub; - salaryProfileNew.lastUpdateFullName = request.user.name; - salaryProfileNew.createdAt = new Date(); - salaryProfileNew.lastUpdatedAt = new Date(); - await this.salaryProfileEmployeeRepository.save(salaryProfileNew, { data: request }); - // setLogDataDiff(request, { before, after: salaryProfileNew }); - } - }), - ); + } } + console.timeEnd("⏱ Insert: SalaryOrg batch"); + // Prepare SalaryOrgEmployee records for batch insert + const salaryOrgEmployeesToSave: SalaryOrgEmployee[] = []; + + orgs.forEach((root: any) => { + // GROUP1 + let salaryOrgNew = Object.assign(new SalaryOrgEmployee()); + salaryOrgNew.salaryPeriodId = salaryPeriod.id; + salaryOrgNew.status = "PENDING"; + salaryOrgNew.rootId = root.rootId; + salaryOrgNew.rootDnaId = root.rootDnaId ?? _null; + salaryOrgNew.root = root.root; + salaryOrgNew.revisionId = salaryPeriod.revisionId; + salaryOrgNew.snapshot = snapshot; + salaryOrgNew.createdUserId = ""; + salaryOrgNew.createdFullName = "System Administrator"; + salaryOrgNew.lastUpdateUserId = ""; + salaryOrgNew.lastUpdateFullName = "System Administrator"; + salaryOrgNew.group = "GROUP1"; + salaryOrgNew.createdAt = new Date(); + salaryOrgNew.lastUpdatedAt = new Date(); + salaryOrgEmployeesToSave.push(salaryOrgNew); + + // GROUP2 (if not SPECIAL) + if (salaryPeriod.period != "SPECIAL") { + let salaryOrgNew2 = Object.assign(new SalaryOrgEmployee(), salaryOrgNew); + delete salaryOrgNew2.id; + salaryOrgNew2.group = "GROUP2"; + salaryOrgEmployeesToSave.push(salaryOrgNew2); + } + }); + + // Batch insert SalaryOrgEmployee with error handling + console.time("⏱ Insert: SalaryOrgEmployee batch"); + if (salaryOrgEmployeesToSave.length > 0) { + try { + await this.salaryOrgEmployeeRepository.save(salaryOrgEmployeesToSave, { data: request }); + console.log( + `✅ [SNAP: ${snapshot}] บันทึก ${salaryOrgEmployeesToSave.length} SalaryOrgEmployee สำเร็จ`, + ); + } catch (saveError) { + console.error( + "❌ Error saving SalaryOrgEmployee batch, trying chunked approach:", + saveError, + ); + // Retry with smaller chunks if failed + const chunkSize = 50; + for (let i = 0; i < salaryOrgEmployeesToSave.length; i += chunkSize) { + const chunk = salaryOrgEmployeesToSave.slice(i, i + chunkSize); + await this.salaryOrgEmployeeRepository.save(chunk, { data: request }); + console.log( + `✅ Saved SalaryOrgEmployee chunk ${Math.floor(i / chunkSize) + 1}/${Math.ceil(salaryOrgEmployeesToSave.length / chunkSize)}`, + ); + + // Small delay to prevent overwhelming the connection + await new Promise((resolve) => setTimeout(resolve, 100)); + } + } + } + console.timeEnd("⏱ Insert: SalaryOrgEmployee batch"); + if (salaryPeriod.period != "SPECIAL") { + //console.log(`step1`); + console.time("⏱ Step1: Load SalaryOrg (New)"); + const salaryOrgList = await this.salaryOrgRepository.find({ + where: { salaryPeriodId: salaryPeriod.id, snapshot }, + }); + const salaryOrgMap = new Map(salaryOrgList.map((org) => [`${org.rootId}-${org.group}`, org])); + console.timeEnd("⏱ Step1: Load SalaryOrg (New)"); + + console.time("⏱ Step2: Load SalaryProfileOld (SNAP1)"); + let salaryOldMap = new Map(); + if (snapshot === "SNAP2") { + const salaryOldList = await this.salaryProfileRepository + .createQueryBuilder("profile") + .innerJoin("profile.salaryOrg", "org") + .where("org.salaryPeriodId = :periodId", { periodId: salaryPeriod.id }) + .andWhere("org.snapshot = :snapshot", { snapshot: "SNAP1" }) + .getMany(); + + salaryOldMap = new Map(salaryOldList.map((item) => [item.citizenId, item])); + } + console.timeEnd("⏱ Step2: Load SalaryProfileOld (SNAP1)"); + + console.time("⏱ Step3: Process Profiles"); + + const allProfilesToSave: SalaryProfile[] = []; + + for (const profile of orgProfiles) { + let group = "GROUP1"; + const { posType, posLevel } = profile; + + if ( + (posType === "ทั่วไป" && posLevel === "ทักษะพิเศษ") || + (posType === "วิชาการ" && ["เชี่ยวชาญ", "ทรงคุณวุฒิ"].includes(posLevel)) || + (posType === "อำนวยการ" && posLevel === "สูง") || + (posType === "บริหาร" && ["ต้น", "สูง"].includes(posLevel)) + ) { + group = "GROUP2"; + } + + const salaryOrgNew = salaryOrgMap.get(`${profile.rootId}-${group}`); + if (!salaryOrgNew) { + console.warn(`⚠️ [SNAP: ${snapshot}] ไม่พบ salaryOrg สำหรับ rootId: ${profile.rootId}`); + continue; + } + + const salaryProfileNew = Object.assign(new SalaryProfile(), profile); + delete salaryProfileNew.id; + + salaryProfileNew.salaryOrgId = salaryOrgNew.id; + salaryProfileNew.revisionId = salaryPeriod.revisionId; + salaryProfileNew.createdUserId = ""; + salaryProfileNew.createdFullName = "System Administrator"; + salaryProfileNew.lastUpdateUserId = ""; + salaryProfileNew.lastUpdateFullName = "System Administrator"; + salaryProfileNew.createdAt = new Date(); + salaryProfileNew.lastUpdatedAt = new Date(); + + if (snapshot === "SNAP2") { + const salaryOld = salaryOldMap.get(salaryProfileNew.citizenId); + salaryProfileNew.type = salaryOld?.type ?? "PENDING"; + salaryProfileNew.amount = salaryOld?.amount ?? 0; + salaryProfileNew.amountSpecial = salaryOld?.amountSpecial ?? 0; + salaryProfileNew.amountUse = salaryOld?.amountUse ?? 0; + salaryProfileNew.positionSalaryAmount = salaryOld?.positionSalaryAmount ?? 0; + salaryProfileNew.remark = salaryOld?.remark ?? null; + salaryProfileNew.isNext = salaryOld?.isNext ?? false; + salaryProfileNew.isSpecial = salaryOld?.isSpecial ?? false; + salaryProfileNew.isReserve = salaryOld?.isReserve ?? false; + salaryProfileNew.isRetired = salaryOld?.isRetired ?? false; + salaryProfileNew.isGood = salaryOld?.isGood ?? false; + } + + // const beforeSalaryProfileNew = structuredClone(salaryProfileNew); + // setLogDataDiff(request, { before: beforeSalaryProfileNew, after: salaryProfileNew }); + allProfilesToSave.push(salaryProfileNew); + } + + console.timeEnd("⏱ Step3: Process Profiles"); + + // Batch insert SalaryProfile with chunking for memory efficiency and connection management + console.time("⏱ Step4: Save All Profiles (Chunked)"); + const chunkSize = 300; // Reduced chunk size to prevent timeout and SSL errors + for (let i = 0; i < allProfilesToSave.length; i += chunkSize) { + const chunk = allProfilesToSave.slice(i, i + chunkSize); + try { + await this.salaryProfileRepository + .createQueryBuilder() + .insert() + .into(SalaryProfile) + .values(chunk) + .orIgnore() + .execute(); + + // Progress logging every 10 chunks and small delay to prevent connection overload + if ((i / chunkSize) % 10 === 0) { + console.log(`📝 บันทึก SalaryProfile: ${i + chunk.length}/${allProfilesToSave.length}`); + // Small delay to prevent SSL connection issues + await new Promise((resolve) => setTimeout(resolve, 50)); + } + } catch (chunkErr) { + console.error(`❌ Error saving SalaryProfile chunk ${i}-${i + chunk.length}:`, chunkErr); + // Retry this chunk with even smaller size + const retryChunkSize = 50; + for (let j = 0; j < chunk.length; j += retryChunkSize) { + const retryChunk = chunk.slice(j, j + retryChunkSize); + await this.salaryProfileRepository + .createQueryBuilder() + .insert() + .into(SalaryProfile) + .values(retryChunk) + .orIgnore() + .execute(); + await new Promise((resolve) => setTimeout(resolve, 100)); + } + } + } + console.timeEnd("⏱ Step4: Save All Profiles (Chunked)"); + console.log( + `✅ บันทึก ${allProfilesToSave.length} SalaryProfile สำเร็จ (แบ่งเป็น chunks ของ ${chunkSize})`, + ); + + //**********************/ + console.log("orgProfileEmployees length:", orgProfileEmployees.length); + const profilesEmpToSave: SalaryProfileEmployee[] = []; + console.time("⏱ SalaryProfileEmployee - Total Time"); + console.time("⏱ Step5: Process ProfileEmps"); + + // Pre-load salaryOrgEmployee เพื่อลด query + const salaryOrgEmployeeMap = new Map(); + const salaryOrgEmployeeList = await this.salaryOrgEmployeeRepository.find({ + where: { + salaryPeriodId: salaryPeriod.id, + snapshot: snapshot, + group: "GROUP1", + }, + }); + salaryOrgEmployeeList.forEach((org) => { + salaryOrgEmployeeMap.set(org.rootId, org); + }); + + // Pre-load old data for SNAP2 + let salaryEmpOldMap = new Map(); + if (snapshot == "SNAP2") { + const salaryOrgOld = await this.salaryOrgEmployeeRepository.find({ + where: { salaryPeriodId: salaryPeriod.id, snapshot: "SNAP1" }, + }); + if (salaryOrgOld.length > 0) { + const salaryOldList = await this.salaryProfileEmployeeRepository.find({ + where: { + salaryOrgId: In(salaryOrgOld.map((x) => x.id)), + }, + }); + salaryOldList.forEach((item) => { + salaryEmpOldMap.set(item.citizenId, item); + }); + } + } + + for (const profile of orgProfileEmployees) { + const salaryOrgNew = salaryOrgEmployeeMap.get(profile.rootId); + if (salaryOrgNew != null) { + let salaryProfileNew = Object.assign(new SalaryProfileEmployee(), profile); + delete salaryProfileNew.id; + + salaryProfileNew.salaryOrgId = salaryOrgNew.id; + salaryProfileNew.revisionId = salaryPeriod.revisionId; + salaryProfileNew.createdUserId = ""; + salaryProfileNew.createdFullName = "System Administrator"; + salaryProfileNew.lastUpdateUserId = ""; + salaryProfileNew.lastUpdateFullName = "System Administrator"; + salaryProfileNew.createdAt = new Date(); + salaryProfileNew.lastUpdatedAt = new Date(); + + if (snapshot == "SNAP2") { + const salaryOld = salaryEmpOldMap.get(salaryProfileNew.citizenId); + salaryProfileNew.type = salaryOld?.type ?? 0; + salaryProfileNew.salaryLevelNew = salaryOld?.salaryLevelNew ?? null; + salaryProfileNew.groupNew = salaryOld?.groupNew ?? null; + salaryProfileNew.amount = salaryOld?.amount ?? 0; + salaryProfileNew.amountSpecial = salaryOld?.amountSpecial ?? 0; + salaryProfileNew.amountUse = salaryOld?.amountUse ?? 0; + salaryProfileNew.positionSalaryAmount = salaryOld?.positionSalaryAmount ?? 0; + salaryProfileNew.positionSalaryDayAmount = salaryOld?.positionSalaryDayAmount ?? 0; + salaryProfileNew.positionSalaryAmountPer = salaryOld?.positionSalaryAmountPer ?? 0; + salaryProfileNew.remark = salaryOld?.remark ?? null; + salaryProfileNew.isNext = salaryOld?.isNext ?? false; + salaryProfileNew.isSpecial = salaryOld?.isSpecial ?? false; + salaryProfileNew.isReserve = salaryOld?.isReserve ?? false; + salaryProfileNew.isRetired = salaryOld?.isRetired ?? false; + salaryProfileNew.isGood = salaryOld?.isGood ?? false; + } + profilesEmpToSave.push(salaryProfileNew); + } else { + console.warn( + `⚠️ [SNAP: ${snapshot}] ไม่พบ salaryOrgEmployee สำหรับ rootId: ${profile.rootId}`, + ); + } + } + console.timeEnd("⏱ Step5: Process ProfileEmps"); + + // Batch insert SalaryProfileEmployee with chunking and error recovery + console.time("⏱ Step6: Save ProfileEmployees (Chunked)"); + if (profilesEmpToSave.length > 0) { + const chunkSize = 300; // Reduced chunk size for better stability and SSL handling + for (let i = 0; i < profilesEmpToSave.length; i += chunkSize) { + const chunk = profilesEmpToSave.slice(i, i + chunkSize); + try { + await this.salaryProfileEmployeeRepository + .createQueryBuilder() + .insert() + .into(SalaryProfileEmployee) + .values(chunk) + .orIgnore() + .execute(); + + // Progress logging every 10 chunks with small delay + if ((i / chunkSize) % 10 === 0) { + console.log( + `📝 บันทึก SalaryProfileEmployee: ${i + chunk.length}/${profilesEmpToSave.length}`, + ); + // Small delay to prevent SSL connection issues + await new Promise((resolve) => setTimeout(resolve, 50)); + } + } catch (chunkErr) { + console.error( + `❌ Error saving SalaryProfileEmployee chunk ${i}-${i + chunk.length}:`, + chunkErr, + ); + // Retry this chunk with even smaller size + const retryChunkSize = 50; + for (let j = 0; j < chunk.length; j += retryChunkSize) { + const retryChunk = chunk.slice(j, j + retryChunkSize); + await this.salaryProfileEmployeeRepository + .createQueryBuilder() + .insert() + .into(SalaryProfileEmployee) + .values(retryChunk) + .orIgnore() + .execute(); + await new Promise((resolve) => setTimeout(resolve, 100)); + } + } + } + console.log( + `✅ บันทึก ${profilesEmpToSave.length} SalaryProfileEmployee สำเร็จ (แบ่งเป็น chunks ของ ${chunkSize})`, + ); + } + console.timeEnd("⏱ Step6: Save ProfileEmployees (Chunked)"); + console.timeEnd("⏱ SalaryProfileEmployee - Total Time"); + + // Clear large arrays to free memory progressively + console.time("⏱ Memory: Cleanup large arrays"); + allProfilesToSave.length = 0; + profilesEmpToSave.length = 0; + orgProfiles.length = 0; + orgProfileEmployees.length = 0; + + // Clear Maps + salaryOrgMap.clear(); + salaryOldMap.clear(); + salaryOrgEmployeeMap.clear(); + salaryEmpOldMap.clear(); + + // Force garbage collection if available (Node.js with --expose-gc) + if (global.gc) { + global.gc(); + console.log("🧹 Garbage collection executed"); + } + console.timeEnd("⏱ Memory: Cleanup large arrays"); + } + console.time("⏱ Final: Load Updated SalaryOrg"); const salaryOrgNew = await this.salaryOrgRepository.find({ where: { salaryPeriodId: salaryPeriod.id, snapshot: snapshot }, relations: ["salaryProfiles"], @@ -3961,37 +4373,44 @@ export class SalaryPeriodController extends Controller { where: { salaryPeriodId: salaryPeriod.id, snapshot: snapshot }, relations: ["salaryProfiles"], }); + console.timeEnd("⏱ Final: Load Updated SalaryOrg"); if (salaryPeriod.period == "OCT") { + console.time("⏱ Step7: Process OCT SalaryOrg"); const salaryPeriodAPROld = await this.salaryPeriodRepository.findOne({ where: { year: salaryPeriod.year, period: "APR", }, }); + + // Pre-load all old salary data for OCT optimization + let aprSalaryOrgMap = new Map(); + if (salaryPeriodAPROld != null) { + const aprSalaryOrgs = await this.salaryOrgRepository.find({ + where: { + salaryPeriodId: salaryPeriodAPROld.id, + snapshot: "SNAP2", + }, + relations: ["salaryProfiles"], + }); + aprSalaryOrgs.forEach((org) => { + const key = `${org.rootDnaId}-${org.group}`; + const totalAmount = Extension.sumObjectValues(org.salaryProfiles, "amountUse"); + aprSalaryOrgMap.set(key, totalAmount); + }); + } + await Promise.all( salaryOrgNew.map(async (_salaryOrg: SalaryOrg) => { - let totalAmount = 0; - if (salaryPeriodAPROld != null) { - const salaryOrgSnap2Old: any = await this.salaryOrgRepository.findOne({ - where: { - salaryPeriodId: salaryPeriodAPROld.id, - rootId: _salaryOrg.rootId, - group: _salaryOrg.group, - snapshot: "SNAP2", - }, - relations: ["salaryProfiles"], - }); - totalAmount = - salaryOrgSnap2Old == null - ? 0 - : Extension.sumObjectValues(salaryOrgSnap2Old.salaryProfiles, "amountUse"); - } + const totalAmount = + aprSalaryOrgMap.get(`${_salaryOrg.rootDnaId}-${_salaryOrg.group}`) || 0; + // const before_salaryOrg = structuredClone(_salaryOrg); if (snapshot == "SNAP2") { const salaryOrgSnap1 = await this.salaryOrgRepository.findOne({ where: { salaryPeriodId: salaryPeriod.id, - rootId: _salaryOrg.rootId, + rootDnaId: _salaryOrg.rootDnaId, group: _salaryOrg.group, snapshot: "SNAP1", }, @@ -4003,6 +4422,12 @@ export class SalaryPeriodController extends Controller { _salaryOrg.sixPercentAmount = totalProfile * 0.06; _salaryOrg.spentAmount = totalAmount; _salaryOrg.remainingAmount = totalProfile * 0.06 - totalAmount; + + //เพิ่มคำนวน 15% + _salaryOrg.total = _salaryOrg.salaryProfiles.length; + _salaryOrg.fifteenPercent = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100); + _salaryOrg.fifteenPoint = (_salaryOrg.salaryProfiles.length * 15) % 100; + _salaryOrg.remainQuota = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100); } else { _salaryOrg.currentAmount = salaryOrgSnap1.currentAmount; _salaryOrg.total = salaryOrgSnap1.total; @@ -4010,50 +4435,70 @@ export class SalaryPeriodController extends Controller { _salaryOrg.spentAmount = salaryOrgSnap1.spentAmount; _salaryOrg.useAmount = salaryOrgSnap1.useAmount; _salaryOrg.remainingAmount = salaryOrgSnap1.remainingAmount; + + //เพิ่มคำนวน 15% + _salaryOrg.fifteenPercent = salaryOrgSnap1.fifteenPercent; + _salaryOrg.fifteenPoint = salaryOrgSnap1.fifteenPoint; + _salaryOrg.quantityUsed = salaryOrgSnap1.quantityUsed; + _salaryOrg.remainQuota = salaryOrgSnap1.remainQuota; } } else { const totalProfile = Extension.sumObjectValues(_salaryOrg.salaryProfiles, "amount"); + _salaryOrg.currentAmount = totalProfile; _salaryOrg.total = _salaryOrg.salaryProfiles.length; _salaryOrg.sixPercentAmount = totalProfile * 0.06; _salaryOrg.spentAmount = totalAmount; _salaryOrg.remainingAmount = totalProfile * 0.06 - totalAmount; + + //เพิ่มคำนวน 15% + _salaryOrg.total = _salaryOrg.salaryProfiles.length; + _salaryOrg.fifteenPercent = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100); + _salaryOrg.fifteenPoint = (_salaryOrg.salaryProfiles.length * 15) % 100; + _salaryOrg.remainQuota = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100); } - _salaryOrg.createdUserId = request.user.sub; - _salaryOrg.createdFullName = request.user.name; - _salaryOrg.lastUpdateUserId = request.user.sub; - _salaryOrg.lastUpdateFullName = request.user.name; + _salaryOrg.createdUserId = ""; + _salaryOrg.createdFullName = "System Administrator"; + _salaryOrg.lastUpdateUserId = ""; + _salaryOrg.lastUpdateFullName = "System Administrator"; _salaryOrg.createdAt = new Date(); _salaryOrg.lastUpdatedAt = new Date(); await this.salaryOrgRepository.save(_salaryOrg, { data: request }); - // setLogDataDiff(request, { before, after: _salaryOrg }); + // setLogDataDiff(request, { before: before_salaryOrg, after: _salaryOrg }); }), ); + console.timeEnd("⏱ Step7: Process OCT SalaryOrg"); + + console.time("⏱ Step8: Process OCT SalaryOrgEmployee"); + // Pre-load APR employee salary data + let aprSalaryOrgEmployeeMap = new Map(); + if (salaryPeriodAPROld != null) { + const aprSalaryOrgEmployees = await this.salaryOrgEmployeeRepository.find({ + where: { + salaryPeriodId: salaryPeriodAPROld.id, + snapshot: "SNAP2", + }, + relations: ["salaryProfiles"], + }); + aprSalaryOrgEmployees.forEach((org) => { + const key = `${org.rootDnaId}-${org.group}`; + const totalAmount = Extension.sumObjectValues(org.salaryProfiles, "amountUse"); + aprSalaryOrgEmployeeMap.set(key, totalAmount); + }); + } + await Promise.all( salaryOrgEmployeeNew.map(async (_salaryOrg: SalaryOrgEmployee) => { - let totalAmount = 0; - if (salaryPeriodAPROld != null) { - const salaryOrgSnap2Old: any = await this.salaryOrgEmployeeRepository.findOne({ - where: { - salaryPeriodId: salaryPeriodAPROld.id, - rootId: _salaryOrg.rootId, - group: _salaryOrg.group, - snapshot: "SNAP2", - }, - relations: ["salaryProfiles"], - }); - totalAmount = - salaryOrgSnap2Old == null - ? 0 - : Extension.sumObjectValues(salaryOrgSnap2Old.salaryProfiles, "amountUse"); - } + const totalAmount = + aprSalaryOrgEmployeeMap.get(`${_salaryOrg.rootDnaId}-${_salaryOrg.group}`) || 0; + // const before_salaryOrg = structuredClone(_salaryOrg); if (snapshot == "SNAP2") { const salaryOrgSnap1 = await this.salaryOrgEmployeeRepository.findOne({ where: { salaryPeriodId: salaryPeriod.id, - rootId: _salaryOrg.rootId, + rootDnaId: _salaryOrg.rootDnaId, group: _salaryOrg.group, snapshot: "SNAP1", }, @@ -4065,6 +4510,12 @@ export class SalaryPeriodController extends Controller { _salaryOrg.sixPercentAmount = totalProfile * 0.06; _salaryOrg.spentAmount = totalAmount; _salaryOrg.remainingAmount = totalProfile * 0.06 - totalAmount; + + //เพิ่มคำนวน 15% + _salaryOrg.total = _salaryOrg.salaryProfiles.length; + _salaryOrg.fifteenPercent = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100); + _salaryOrg.fifteenPoint = (_salaryOrg.salaryProfiles.length * 15) % 100; + _salaryOrg.remainQuota = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100); } else { _salaryOrg.currentAmount = salaryOrgSnap1.currentAmount; _salaryOrg.total = salaryOrgSnap1.total; @@ -4072,6 +4523,12 @@ export class SalaryPeriodController extends Controller { _salaryOrg.spentAmount = salaryOrgSnap1.spentAmount; _salaryOrg.useAmount = salaryOrgSnap1.useAmount; _salaryOrg.remainingAmount = salaryOrgSnap1.remainingAmount; + + //เพิ่มคำนวน 15% + _salaryOrg.fifteenPercent = salaryOrgSnap1.fifteenPercent; + _salaryOrg.fifteenPoint = salaryOrgSnap1.fifteenPoint; + _salaryOrg.quantityUsed = salaryOrgSnap1.quantityUsed; + _salaryOrg.remainQuota = salaryOrgSnap1.remainQuota; } } else { const totalProfile = Extension.sumObjectValues(_salaryOrg.salaryProfiles, "amount"); @@ -4080,26 +4537,36 @@ export class SalaryPeriodController extends Controller { _salaryOrg.sixPercentAmount = totalProfile * 0.06; _salaryOrg.spentAmount = totalAmount; _salaryOrg.remainingAmount = totalProfile * 0.06 - totalAmount; + + //เพิ่มคำนวน 15% + _salaryOrg.total = _salaryOrg.salaryProfiles.length; + _salaryOrg.fifteenPercent = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100); + _salaryOrg.fifteenPoint = (_salaryOrg.salaryProfiles.length * 15) % 100; + _salaryOrg.remainQuota = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100); } - _salaryOrg.createdUserId = request.user.sub; - _salaryOrg.createdFullName = request.user.name; - _salaryOrg.lastUpdateUserId = request.user.sub; - _salaryOrg.lastUpdateFullName = request.user.name; + _salaryOrg.createdUserId = "" + _salaryOrg.createdFullName = "System Administrator"; + _salaryOrg.lastUpdateUserId = "" + _salaryOrg.lastUpdateFullName = "System Administrator"; _salaryOrg.createdAt = new Date(); _salaryOrg.lastUpdatedAt = new Date(); await this.salaryOrgEmployeeRepository.save(_salaryOrg, { data: request }); - // setLogDataDiff(request, { before, after: _salaryOrg }); + // setLogDataDiff(request, { before: before_salaryOrg, after: _salaryOrg }); }), ); + console.timeEnd("⏱ Step8: Process OCT SalaryOrgEmployee"); } else if (salaryPeriod.period == "APR") { + console.time("⏱ Step7: Process APR SalaryOrg"); await Promise.all( salaryOrgNew.map(async (_salaryOrg: SalaryOrg) => { + // const before_salaryOrg = structuredClone(_salaryOrg); + if (snapshot == "SNAP2") { const salaryOrgSnap1 = await this.salaryOrgRepository.findOne({ where: { salaryPeriodId: salaryPeriod.id, - rootId: _salaryOrg.rootId, + rootDnaId: _salaryOrg.rootDnaId, group: _salaryOrg.group, snapshot: "SNAP1", }, @@ -4123,23 +4590,28 @@ export class SalaryPeriodController extends Controller { _salaryOrg.remainQuota = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100); } - _salaryOrg.createdUserId = request.user.sub; - _salaryOrg.createdFullName = request.user.name; - _salaryOrg.lastUpdateUserId = request.user.sub; - _salaryOrg.lastUpdateFullName = request.user.name; + _salaryOrg.createdUserId = ""; + _salaryOrg.createdFullName = "System Administrator"; + _salaryOrg.lastUpdateUserId = ""; + _salaryOrg.lastUpdateFullName = "System Administrator"; _salaryOrg.createdAt = new Date(); _salaryOrg.lastUpdatedAt = new Date(); await this.salaryOrgRepository.save(_salaryOrg, { data: request }); - // setLogDataDiff(request, { before, after: _salaryOrg }); + // setLogDataDiff(request, { before: before_salaryOrg, after: _salaryOrg }); }), ); + console.timeEnd("⏱ Step7: Process APR SalaryOrg"); + + console.time("⏱ Step8: Process APR SalaryOrgEmployee"); await Promise.all( salaryOrgEmployeeNew.map(async (_salaryOrg: SalaryOrgEmployee) => { + // const before_salaryOrg = structuredClone(_salaryOrg); + if (snapshot == "SNAP2") { const salaryOrgSnap1 = await this.salaryOrgEmployeeRepository.findOne({ where: { salaryPeriodId: salaryPeriod.id, - rootId: _salaryOrg.rootId, + rootDnaId: _salaryOrg.rootDnaId, group: _salaryOrg.group, snapshot: "SNAP1", }, @@ -4149,32 +4621,610 @@ export class SalaryPeriodController extends Controller { _salaryOrg.fifteenPercent = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100); _salaryOrg.fifteenPoint = (_salaryOrg.salaryProfiles.length * 15) % 100; _salaryOrg.remainQuota = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100); + // console.log("fifteenAPR: case 1"); } else { _salaryOrg.total = salaryOrgSnap1.total; _salaryOrg.fifteenPercent = salaryOrgSnap1.fifteenPercent; _salaryOrg.fifteenPoint = salaryOrgSnap1.fifteenPoint; _salaryOrg.quantityUsed = salaryOrgSnap1.quantityUsed; _salaryOrg.remainQuota = salaryOrgSnap1.remainQuota; + // console.log("fifteenAPR: case 2"); } } else { _salaryOrg.total = _salaryOrg.salaryProfiles.length; _salaryOrg.fifteenPercent = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100); _salaryOrg.fifteenPoint = (_salaryOrg.salaryProfiles.length * 15) % 100; _salaryOrg.remainQuota = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100); + // console.log("fifteenAPR: case 3"); } - _salaryOrg.createdUserId = request.user.sub; - _salaryOrg.createdFullName = request.user.name; - _salaryOrg.lastUpdateUserId = request.user.sub; - _salaryOrg.lastUpdateFullName = request.user.name; + _salaryOrg.createdUserId = ""; + _salaryOrg.createdFullName = "System Administrator"; + _salaryOrg.lastUpdateUserId = ""; + _salaryOrg.lastUpdateFullName = "System Administrator"; _salaryOrg.createdAt = new Date(); _salaryOrg.lastUpdatedAt = new Date(); await this.salaryOrgEmployeeRepository.save(_salaryOrg, { data: request }); - // setLogDataDiff(request, { before, after: _salaryOrg }); + // setLogDataDiff(request, { before: before_salaryOrg, after: _salaryOrg }); }), ); + console.timeEnd("⏱ Step8: Process APR SalaryOrgEmployee"); + } + + console.timeEnd("⏱ TOTAL SNAPSHOT PROCESSING TIME"); + console.log(`✅✅✅ [SNAPSHOT:${snapshot} เสร็จสิ้น - Period: ${salaryPeriod.period}]) ✅✅✅`); + console.log( + `📊 สรุปข้อมูล: SalaryOrg=${salaryOrgNew.length}, SalaryOrgEmployee=${salaryOrgEmployeeNew.length}`, + ); + return new HttpSuccess(); + } + + /** + * Cronjob SalaryPeriod + */ + async CronjobSalaryPeriod() { + const current = new Date(); + let salaryPeriod: any; + + let body = { + client_id: "gettoken", + client_secret: process.env.AUTH_ACCOUNT_SECRET, + grant_type: "client_credentials", + }; + + const postData = querystring.stringify(body); + const response = await axios.post( + `${process.env.KC_URL}/realms/${process.env.KC_REALMS}/protocol/openid-connect/token`, + postData, + { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + api_key: process.env.API_KEY, + }, + }, + ); + let request:any = response.data.access_token; + + if (current.getDate() == 1 && current.getMonth() == 2) { //snap1 วันที่ 1 มีนา + salaryPeriod = await this.salaryPeriodRepository.findOne({ + where: { + year: current.getFullYear(), + period: "APR", + isActive: true, + }, + }); + if (salaryPeriod) { + this.performSnapshotOperationForCronjob("SNAP1", salaryPeriod.id, request); + } + } else if (current.getDate() == 1 && current.getMonth() == 3) { //snap2 วันที่ 1 เมษา + salaryPeriod = await this.salaryPeriodRepository.findOne({ + where: { + year: current.getFullYear(), + period: "APR", + isActive: true, + }, + }); + if (salaryPeriod) { + this.performSnapshotOperationForCronjob("SNAP2", salaryPeriod.id, request); + } + } else if (current.getDate() == 1 && current.getMonth() == 8) { //snap1 วันที่ 1 กันยา + salaryPeriod = await this.salaryPeriodRepository.findOne({ + where: { + year: current.getFullYear(), + period: "OCT", + isActive: true, + }, + }); + if (salaryPeriod) { + this.performSnapshotOperationForCronjob("SNAP1", salaryPeriod.id, request); + } + } else if (current.getDate() == 1 && current.getMonth() == 9) { //snap2 วันที่ 1 ตุลา + salaryPeriod = await this.salaryPeriodRepository.findOne({ + where: { + year: current.getFullYear(), + period: "OCT", + isActive: true, + }, + }); + if (salaryPeriod) { + this.performSnapshotOperationForCronjob("SNAP2", salaryPeriod.id, request); + } } } + //Old + // public async SnapshotSalarys(snapshot: string, salaryPeriodId: string, request: any) { + // snapshot = snapshot.toLocaleUpperCase(); + // const salaryPeriod = await this.salaryPeriodRepository.findOne({ + // where: { id: salaryPeriodId }, + // }); + // if (!salaryPeriod) { + // throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบรอบการสร้างเงินเดือน"); + // } + + // const salaryOrg = await this.salaryOrgRepository.find({ + // where: { salaryPeriodId: salaryPeriod.id, snapshot: snapshot }, + // }); + // const salaryProfile = await this.salaryProfileRepository.find({ + // where: { salaryOrgId: In(salaryOrg.map((x) => x.id)) }, + // }); + // await this.salaryOrgRepository.remove(salaryOrg, { data: request }); + // await this.salaryProfileRepository.remove(salaryProfile, { data: request }); + + // const salaryOrgEmployee = await this.salaryOrgEmployeeRepository.find({ + // where: { salaryPeriodId: salaryPeriod.id, snapshot: snapshot }, + // }); + // const salaryProfileEmployee = await this.salaryProfileEmployeeRepository.find({ + // where: { salaryOrgId: In(salaryOrgEmployee.map((x) => x.id)) }, + // }); + // await this.salaryProfileEmployeeRepository.remove(salaryProfileEmployee, { data: request }); + // await this.salaryOrgEmployeeRepository.remove(salaryOrgEmployee, { data: request }); + + // let orgs = await new CallAPI().GetData(request, "/org/unauthorize/active/root/id"); + // let total = 100; + // let _orgProfiles = await new CallAPI().PostData(request, "/org/unauthorize/salary/gen", { + // page: 1, + // pageSize: 100, + // keyword: "", + // year: salaryPeriod.year, + // period: salaryPeriod.period, + // }); + // let orgProfiles = _orgProfiles.data; + // total = _orgProfiles.total; + // if (total > 100) { + // const page = Math.ceil(total / 100); + // for (let index = 2; index <= page; index++) { + // await new CallAPI() + // .PostData(request, "/org/unauthorize/profile/salary/gen", { + // page: index, + // pageSize: 100, + // keyword: "", + // year: salaryPeriod.year, + // period: salaryPeriod.period, + // }) + // .then((x) => { + // Array.prototype.push.apply(orgProfiles, x.data); + // }); + // } + // } + // total = 100; + // let _orgProfileEmployees = await new CallAPI().PostData( + // request, + // "/org/unauthorize/profile/salary/employee/gen", + // { + // page: 1, + // pageSize: 100, + // keyword: "", + // year: salaryPeriod.year, + // period: salaryPeriod.period, + // }, + // ); + // let orgProfileEmployees = _orgProfileEmployees.data; + // total = _orgProfileEmployees.total; + // if (total > 100) { + // const page = Math.ceil(total / 100); + // for (let index = 2; index <= page; index++) { + // await new CallAPI() + // .PostData(request, "/org/unauthorize/profile/salary/employee/gen", { + // page: index, + // pageSize: 100, + // keyword: "", + // year: salaryPeriod.year, + // period: salaryPeriod.period, + // }) + // .then((x) => { + // Array.prototype.push.apply(orgProfileEmployees, x.data); + // }); + // } + // } + // let revisionId = await new CallAPI().GetData(request, "/org/unauthorize/revision/latest"); + // const before = null; + // salaryPeriod.revisionId = revisionId; + // salaryPeriod.lastUpdateUserId = request.user.sub; + // salaryPeriod.lastUpdateFullName = request.user.name; + // salaryPeriod.lastUpdatedAt = new Date(); + // await this.salaryPeriodRepository.save(salaryPeriod, { data: request }); + // // setLogDataDiff(request, { before, after: salaryPeriod }); + // let _null: any = null; + // await Promise.all( + // orgs.map(async (root: any) => { + // let salaryOrgNew = Object.assign(new SalaryOrg()); + // salaryOrgNew.salaryPeriodId = salaryPeriod.id; + // salaryOrgNew.status = "PENDING"; + // salaryOrgNew.rootId = root.rootId; + // salaryOrgNew.rootDnaId = root.rootDnaId ?? _null; + // salaryOrgNew.root = root.root; + // salaryOrgNew.revisionId = salaryPeriod.revisionId; + // salaryOrgNew.snapshot = snapshot; + // salaryOrgNew.createdUserId = request.user.sub; + // salaryOrgNew.createdFullName = request.user.name; + // salaryOrgNew.lastUpdateUserId = request.user.sub; + // salaryOrgNew.lastUpdateFullName = request.user.name; + // salaryOrgNew.createdAt = new Date(); + // salaryOrgNew.lastUpdatedAt = new Date(); + // salaryOrgNew.group = "GROUP1"; + // await this.salaryOrgRepository.save(salaryOrgNew, { data: request }); + // // setLogDataDiff(request, { before, after: salaryOrgNew }); + + // if (salaryPeriod.period != "SPECIAL") { + // delete salaryOrgNew.id; + // salaryOrgNew.group = "GROUP2"; + // await this.salaryOrgRepository.save(salaryOrgNew, { data: request }); + // // setLogDataDiff(request, { before, after: salaryOrgNew }); + // } + // }), + // ); + + // await Promise.all( + // orgs.map(async (root: any) => { + // let salaryOrgNew = Object.assign(new SalaryOrgEmployee()); + // salaryOrgNew.salaryPeriodId = salaryPeriod.id; + // salaryOrgNew.status = "PENDING"; + // salaryOrgNew.rootId = root.rootId; + // salaryOrgNew.rootDnaId = root.rootDnaId ?? _null; + // salaryOrgNew.root = root.root; + // salaryOrgNew.revisionId = salaryPeriod.revisionId; + // salaryOrgNew.snapshot = snapshot; + // salaryOrgNew.createdUserId = request.user.sub; + // salaryOrgNew.createdFullName = request.user.name; + // salaryOrgNew.lastUpdateUserId = request.user.sub; + // salaryOrgNew.lastUpdateFullName = request.user.name; + // salaryOrgNew.createdAt = new Date(); + // salaryOrgNew.lastUpdatedAt = new Date(); + // salaryOrgNew.group = "GROUP1"; + // await this.salaryOrgRepository.save(salaryOrgNew, { data: request }); + // // setLogDataDiff(request, { before, after: salaryOrgNew }); + + // if (salaryPeriod.period != "SPECIAL") { + // delete salaryOrgNew.id; + // salaryOrgNew.group = "GROUP2"; + // await this.salaryOrgEmployeeRepository.save(salaryOrgNew, { data: request }); + // // setLogDataDiff(request, { before, after: salaryOrgNew }); + // } + // }), + // ); + + // if (salaryPeriod.period != "SPECIAL") { + // await Promise.all( + // orgProfiles.map(async (profile: any) => { + // let group = "GROUP1"; + // if ( + // (profile.posType == "ทั่วไป" && profile.posLevel == "ทักษะพิเศษ") || + // (profile.posType == "วิชาการ" && profile.posLevel == "เชี่ยวชาญ") || + // (profile.posType == "วิชาการ" && profile.posLevel == "ทรงคุณวุฒิ") || + // (profile.posType == "อำนวยการ" && profile.posLevel == "สูง") || + // (profile.posType == "บริหาร" && profile.posLevel == "ต้น") || + // (profile.posType == "บริหาร" && profile.posLevel == "สูง") + // ) { + // group = "GROUP2"; + // } + // const salaryOrgNew = await this.salaryOrgRepository.findOne({ + // where: { + // salaryPeriodId: salaryPeriod.id, + // rootId: profile.rootId, + // snapshot: snapshot, + // group: group, + // }, + // }); + + // if (salaryOrgNew != null) { + // let salaryProfileNew = Object.assign(new SalaryProfile(), profile); + // delete salaryProfileNew.id; + // salaryProfileNew.salaryOrgId = salaryOrgNew.id; + // salaryProfileNew.revisionId = salaryPeriod.revisionId; + // salaryProfileNew.createdUserId = request.user.sub; + // salaryProfileNew.createdFullName = request.user.name; + // salaryProfileNew.lastUpdateUserId = request.user.sub; + // salaryProfileNew.lastUpdateFullName = request.user.name; + + // if (snapshot == "SNAP2") { + // const salaryOrgOld = await this.salaryOrgRepository.find({ + // where: { salaryPeriodId: salaryPeriod.id, snapshot: "SNAP1" }, + // }); + // const salaryOld = await this.salaryProfileRepository.findOne({ + // where: { + // citizenId: salaryProfileNew.citizenId, + // salaryOrgId: In(salaryOrgOld.map((x) => x.id)), + // }, + // }); + // salaryProfileNew.type = salaryOld == null ? 0 : salaryOld.type; + // salaryProfileNew.amount = salaryOld == null ? 0 : salaryOld.amount; + // salaryProfileNew.amountSpecial = salaryOld == null ? 0 : salaryOld.amountSpecial; + // salaryProfileNew.amountUse = salaryOld == null ? 0 : salaryOld.amountUse; + // salaryProfileNew.positionSalaryAmount = + // salaryOld == null ? 0 : salaryOld.positionSalaryAmount; + // salaryProfileNew.isNext = salaryOld == null ? false : salaryOld.isNext; + // salaryProfileNew.isSpecial = salaryOld == null ? false : salaryOld.isSpecial; + // salaryProfileNew.isReserve = salaryOld == null ? false : salaryOld.isReserve; + // salaryProfileNew.isRetired = salaryOld == null ? false : salaryOld.isRetired; + // } + // salaryProfileNew.createdUserId = request.user.sub; + // salaryProfileNew.createdFullName = request.user.name; + // salaryProfileNew.lastUpdateUserId = request.user.sub; + // salaryProfileNew.lastUpdateFullName = request.user.name; + // salaryProfileNew.createdAt = new Date(); + // salaryProfileNew.lastUpdatedAt = new Date(); + // await this.salaryProfileRepository.save(salaryProfileNew, { data: request }); + // // setLogDataDiff(request, { before, after: salaryProfileNew }); + // } + // }), + // ); + // await Promise.all( + // orgProfileEmployees.map(async (profile: any) => { + // const salaryOrgNew = await this.salaryOrgEmployeeRepository.findOne({ + // where: { + // salaryPeriodId: salaryPeriod.id, + // rootId: profile.rootId, + // snapshot: snapshot, + // group: "GROUP1", + // }, + // }); + + // if (salaryOrgNew != null) { + // let salaryProfileNew = Object.assign(new SalaryProfileEmployee(), profile); + // delete salaryProfileNew.id; + // salaryProfileNew.salaryOrgId = salaryOrgNew.id; + // salaryProfileNew.revisionId = salaryPeriod.revisionId; + // salaryProfileNew.createdUserId = request.user.sub; + // salaryProfileNew.createdFullName = request.user.name; + // salaryProfileNew.lastUpdateUserId = request.user.sub; + // salaryProfileNew.lastUpdateFullName = request.user.name; + + // if (snapshot == "SNAP2") { + // const salaryOrgOld = await this.salaryOrgEmployeeRepository.find({ + // where: { salaryPeriodId: salaryPeriod.id, snapshot: "SNAP1" }, + // }); + // const salaryOld = await this.salaryProfileEmployeeRepository.findOne({ + // where: { + // citizenId: salaryProfileNew.citizenId, + // salaryOrgId: In(salaryOrgOld.map((x) => x.id)), + // }, + // }); + // salaryProfileNew.type = salaryOld == null ? 0 : salaryOld.type; + // salaryProfileNew.amount = salaryOld == null ? 0 : salaryOld.amount; + // salaryProfileNew.amountSpecial = salaryOld == null ? 0 : salaryOld.amountSpecial; + // salaryProfileNew.amountUse = salaryOld == null ? 0 : salaryOld.amountUse; + // salaryProfileNew.positionSalaryAmount = + // salaryOld == null ? 0 : salaryOld.positionSalaryAmount; + // salaryProfileNew.isNext = salaryOld == null ? false : salaryOld.isNext; + // salaryProfileNew.isSpecial = salaryOld == null ? false : salaryOld.isSpecial; + // salaryProfileNew.isReserve = salaryOld == null ? false : salaryOld.isReserve; + // salaryProfileNew.isRetired = salaryOld == null ? false : salaryOld.isRetired; + // } + // salaryProfileNew.createdUserId = request.user.sub; + // salaryProfileNew.createdFullName = request.user.name; + // salaryProfileNew.lastUpdateUserId = request.user.sub; + // salaryProfileNew.lastUpdateFullName = request.user.name; + // salaryProfileNew.createdAt = new Date(); + // salaryProfileNew.lastUpdatedAt = new Date(); + // await this.salaryProfileEmployeeRepository.save(salaryProfileNew, { data: request }); + // // setLogDataDiff(request, { before, after: salaryProfileNew }); + // } + // }), + // ); + // } + + // const salaryOrgNew = await this.salaryOrgRepository.find({ + // where: { salaryPeriodId: salaryPeriod.id, snapshot: snapshot }, + // relations: ["salaryProfiles"], + // }); + // const salaryOrgEmployeeNew = await this.salaryOrgEmployeeRepository.find({ + // where: { salaryPeriodId: salaryPeriod.id, snapshot: snapshot }, + // relations: ["salaryProfiles"], + // }); + // if (salaryPeriod.period == "OCT") { + // const salaryPeriodAPROld = await this.salaryPeriodRepository.findOne({ + // where: { + // year: salaryPeriod.year, + // period: "APR", + // }, + // }); + // await Promise.all( + // salaryOrgNew.map(async (_salaryOrg: SalaryOrg) => { + // let totalAmount = 0; + // if (salaryPeriodAPROld != null) { + // const salaryOrgSnap2Old: any = await this.salaryOrgRepository.findOne({ + // where: { + // salaryPeriodId: salaryPeriodAPROld.id, + // rootId: _salaryOrg.rootId, + // group: _salaryOrg.group, + // snapshot: "SNAP2", + // }, + // relations: ["salaryProfiles"], + // }); + // totalAmount = + // salaryOrgSnap2Old == null + // ? 0 + // : Extension.sumObjectValues(salaryOrgSnap2Old.salaryProfiles, "amountUse"); + // } + + // if (snapshot == "SNAP2") { + // const salaryOrgSnap1 = await this.salaryOrgRepository.findOne({ + // where: { + // salaryPeriodId: salaryPeriod.id, + // rootId: _salaryOrg.rootId, + // group: _salaryOrg.group, + // snapshot: "SNAP1", + // }, + // }); + // if (salaryOrgSnap1 == null) { + // const totalProfile = Extension.sumObjectValues(_salaryOrg.salaryProfiles, "amount"); + // _salaryOrg.currentAmount = totalProfile; + // _salaryOrg.total = _salaryOrg.salaryProfiles.length; + // _salaryOrg.sixPercentAmount = totalProfile * 0.06; + // _salaryOrg.spentAmount = totalAmount; + // _salaryOrg.remainingAmount = totalProfile * 0.06 - totalAmount; + // } else { + // _salaryOrg.currentAmount = salaryOrgSnap1.currentAmount; + // _salaryOrg.total = salaryOrgSnap1.total; + // _salaryOrg.sixPercentAmount = salaryOrgSnap1.sixPercentAmount; + // _salaryOrg.spentAmount = salaryOrgSnap1.spentAmount; + // _salaryOrg.useAmount = salaryOrgSnap1.useAmount; + // _salaryOrg.remainingAmount = salaryOrgSnap1.remainingAmount; + // } + // } else { + // const totalProfile = Extension.sumObjectValues(_salaryOrg.salaryProfiles, "amount"); + // _salaryOrg.currentAmount = totalProfile; + // _salaryOrg.total = _salaryOrg.salaryProfiles.length; + // _salaryOrg.sixPercentAmount = totalProfile * 0.06; + // _salaryOrg.spentAmount = totalAmount; + // _salaryOrg.remainingAmount = totalProfile * 0.06 - totalAmount; + // } + + // _salaryOrg.createdUserId = request.user.sub; + // _salaryOrg.createdFullName = request.user.name; + // _salaryOrg.lastUpdateUserId = request.user.sub; + // _salaryOrg.lastUpdateFullName = request.user.name; + // _salaryOrg.createdAt = new Date(); + // _salaryOrg.lastUpdatedAt = new Date(); + // await this.salaryOrgRepository.save(_salaryOrg, { data: request }); + // // setLogDataDiff(request, { before, after: _salaryOrg }); + // }), + // ); + // await Promise.all( + // salaryOrgEmployeeNew.map(async (_salaryOrg: SalaryOrgEmployee) => { + // let totalAmount = 0; + // if (salaryPeriodAPROld != null) { + // const salaryOrgSnap2Old: any = await this.salaryOrgEmployeeRepository.findOne({ + // where: { + // salaryPeriodId: salaryPeriodAPROld.id, + // rootId: _salaryOrg.rootId, + // group: _salaryOrg.group, + // snapshot: "SNAP2", + // }, + // relations: ["salaryProfiles"], + // }); + // totalAmount = + // salaryOrgSnap2Old == null + // ? 0 + // : Extension.sumObjectValues(salaryOrgSnap2Old.salaryProfiles, "amountUse"); + // } + + // if (snapshot == "SNAP2") { + // const salaryOrgSnap1 = await this.salaryOrgEmployeeRepository.findOne({ + // where: { + // salaryPeriodId: salaryPeriod.id, + // rootId: _salaryOrg.rootId, + // group: _salaryOrg.group, + // snapshot: "SNAP1", + // }, + // }); + // if (salaryOrgSnap1 == null) { + // const totalProfile = Extension.sumObjectValues(_salaryOrg.salaryProfiles, "amount"); + // _salaryOrg.currentAmount = totalProfile; + // _salaryOrg.total = _salaryOrg.salaryProfiles.length; + // _salaryOrg.sixPercentAmount = totalProfile * 0.06; + // _salaryOrg.spentAmount = totalAmount; + // _salaryOrg.remainingAmount = totalProfile * 0.06 - totalAmount; + // } else { + // _salaryOrg.currentAmount = salaryOrgSnap1.currentAmount; + // _salaryOrg.total = salaryOrgSnap1.total; + // _salaryOrg.sixPercentAmount = salaryOrgSnap1.sixPercentAmount; + // _salaryOrg.spentAmount = salaryOrgSnap1.spentAmount; + // _salaryOrg.useAmount = salaryOrgSnap1.useAmount; + // _salaryOrg.remainingAmount = salaryOrgSnap1.remainingAmount; + // } + // } else { + // const totalProfile = Extension.sumObjectValues(_salaryOrg.salaryProfiles, "amount"); + // _salaryOrg.currentAmount = totalProfile; + // _salaryOrg.total = _salaryOrg.salaryProfiles.length; + // _salaryOrg.sixPercentAmount = totalProfile * 0.06; + // _salaryOrg.spentAmount = totalAmount; + // _salaryOrg.remainingAmount = totalProfile * 0.06 - totalAmount; + // } + + // _salaryOrg.createdUserId = request.user.sub; + // _salaryOrg.createdFullName = request.user.name; + // _salaryOrg.lastUpdateUserId = request.user.sub; + // _salaryOrg.lastUpdateFullName = request.user.name; + // _salaryOrg.createdAt = new Date(); + // _salaryOrg.lastUpdatedAt = new Date(); + // await this.salaryOrgEmployeeRepository.save(_salaryOrg, { data: request }); + // // setLogDataDiff(request, { before, after: _salaryOrg }); + // }), + // ); + // } else if (salaryPeriod.period == "APR") { + // await Promise.all( + // salaryOrgNew.map(async (_salaryOrg: SalaryOrg) => { + // if (snapshot == "SNAP2") { + // const salaryOrgSnap1 = await this.salaryOrgRepository.findOne({ + // where: { + // salaryPeriodId: salaryPeriod.id, + // rootId: _salaryOrg.rootId, + // group: _salaryOrg.group, + // snapshot: "SNAP1", + // }, + // }); + // if (salaryOrgSnap1 == null) { + // _salaryOrg.total = _salaryOrg.salaryProfiles.length; + // _salaryOrg.fifteenPercent = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100); + // _salaryOrg.fifteenPoint = (_salaryOrg.salaryProfiles.length * 15) % 100; + // _salaryOrg.remainQuota = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100); + // } else { + // _salaryOrg.total = salaryOrgSnap1.total; + // _salaryOrg.fifteenPercent = salaryOrgSnap1.fifteenPercent; + // _salaryOrg.fifteenPoint = salaryOrgSnap1.fifteenPoint; + // _salaryOrg.quantityUsed = salaryOrgSnap1.quantityUsed; + // _salaryOrg.remainQuota = salaryOrgSnap1.remainQuota; + // } + // } else { + // _salaryOrg.total = _salaryOrg.salaryProfiles.length; + // _salaryOrg.fifteenPercent = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100); + // _salaryOrg.fifteenPoint = (_salaryOrg.salaryProfiles.length * 15) % 100; + // _salaryOrg.remainQuota = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100); + // } + + // _salaryOrg.createdUserId = request.user.sub; + // _salaryOrg.createdFullName = request.user.name; + // _salaryOrg.lastUpdateUserId = request.user.sub; + // _salaryOrg.lastUpdateFullName = request.user.name; + // _salaryOrg.createdAt = new Date(); + // _salaryOrg.lastUpdatedAt = new Date(); + // await this.salaryOrgRepository.save(_salaryOrg, { data: request }); + // // setLogDataDiff(request, { before, after: _salaryOrg }); + // }), + // ); + // await Promise.all( + // salaryOrgEmployeeNew.map(async (_salaryOrg: SalaryOrgEmployee) => { + // if (snapshot == "SNAP2") { + // const salaryOrgSnap1 = await this.salaryOrgEmployeeRepository.findOne({ + // where: { + // salaryPeriodId: salaryPeriod.id, + // rootId: _salaryOrg.rootId, + // group: _salaryOrg.group, + // snapshot: "SNAP1", + // }, + // }); + // if (salaryOrgSnap1 == null) { + // _salaryOrg.total = _salaryOrg.salaryProfiles.length; + // _salaryOrg.fifteenPercent = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100); + // _salaryOrg.fifteenPoint = (_salaryOrg.salaryProfiles.length * 15) % 100; + // _salaryOrg.remainQuota = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100); + // } else { + // _salaryOrg.total = salaryOrgSnap1.total; + // _salaryOrg.fifteenPercent = salaryOrgSnap1.fifteenPercent; + // _salaryOrg.fifteenPoint = salaryOrgSnap1.fifteenPoint; + // _salaryOrg.quantityUsed = salaryOrgSnap1.quantityUsed; + // _salaryOrg.remainQuota = salaryOrgSnap1.remainQuota; + // } + // } else { + // _salaryOrg.total = _salaryOrg.salaryProfiles.length; + // _salaryOrg.fifteenPercent = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100); + // _salaryOrg.fifteenPoint = (_salaryOrg.salaryProfiles.length * 15) % 100; + // _salaryOrg.remainQuota = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100); + // } + + // _salaryOrg.createdUserId = request.user.sub; + // _salaryOrg.createdFullName = request.user.name; + // _salaryOrg.lastUpdateUserId = request.user.sub; + // _salaryOrg.lastUpdateFullName = request.user.name; + // _salaryOrg.createdAt = new Date(); + // _salaryOrg.lastUpdatedAt = new Date(); + // await this.salaryOrgEmployeeRepository.save(_salaryOrg, { data: request }); + // // setLogDataDiff(request, { before, after: _salaryOrg }); + // }), + // ); + // } + // } /** * API เจ้าหน้าที่ส่ง ผอ diff --git a/src/interfaces/call-api.ts b/src/interfaces/call-api.ts index c0042fd..ea8d1f0 100644 --- a/src/interfaces/call-api.ts +++ b/src/interfaces/call-api.ts @@ -55,7 +55,7 @@ class CallAPI { } } //Post - public async PostData(request: any, @Path() path: any, sendData: any) { + public async PostData(request: any, @Path() path: any, sendData: any, log = true) { const token = "Bearer " + request.headers.authorization.replace("Bearer ", ""); const url = process.env.API_URL + path; try { @@ -67,6 +67,7 @@ class CallAPI { }, }); console.log("processing data..."); + if (log) addLogSequence(request, { action: "request", status: "success", @@ -80,6 +81,7 @@ class CallAPI { }); return response.data.result; } catch (error) { + if (log) addLogSequence(request, { action: "request", status: "error", From f2fcdbaaaef8ca97ab1c16644f1836dc7e38bf31 Mon Sep 17 00:00:00 2001 From: Adisak Date: Wed, 1 Oct 2025 13:24:26 +0700 Subject: [PATCH 2/4] sort --- src/controllers/SalaryController.ts | 34 ++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/controllers/SalaryController.ts b/src/controllers/SalaryController.ts index 90b72c7..975fc25 100644 --- a/src/controllers/SalaryController.ts +++ b/src/controllers/SalaryController.ts @@ -306,9 +306,11 @@ export class SalaryController extends Controller { @Query("page") page: number = 1, @Query("pageSize") pageSize: number = 10, @Query("keyword") keyword?: string, + @Query("sortBy") sortBy?: string, + @Query("descending") descending?: boolean, ) { await new permission().PermissionList(request, "SYS_SALARY_CHART_OFFICER"); - const [salary, total] = await AppDataSource.getRepository(Salarys) + let query = await AppDataSource.getRepository(Salarys) .createQueryBuilder("salary") .leftJoinAndSelect("salary.posType_", "posType_") .leftJoinAndSelect("salary.posLevel_", "posLevel_") @@ -319,13 +321,35 @@ export class SalaryController extends Controller { .orWhere("posLevel_.posLevelName LIKE :keyword", { keyword: `%${keyword}%` }); }), ) - .orderBy("salary.isActive", "DESC") - .addOrderBy("posType_.posTypeRank", "DESC") - .addOrderBy("posLevel_.posLevelRank", "DESC") + + if (sortBy) { + if(sortBy === "posType"){ + query = query.orderBy( + `posType_.posTypeName`, + descending ? "DESC" : "ASC" + ); + }else if(sortBy === "posLevel"){ + query = query.orderBy( + `posLevel_.posLevelName`, + descending ? "DESC" : "ASC" + ); + }else{ + query = query.orderBy( + `salary.${sortBy}`, + descending ? "DESC" : "ASC" + ); + } + }else{ + query = query.orderBy("salary.isActive", "DESC") + .addOrderBy("posType_.posTypeRank", "DESC") + .addOrderBy("posLevel_.posLevelRank", "DESC") + } + + const [salary, total] = await query .skip((page - 1) * pageSize) .take(pageSize) .getManyAndCount(); - + const _salary = salary.map((item) => ({ id: item.id, name: item.name, From 28b7816763e7a91b19b75a4cd7ac9169c7143bd5 Mon Sep 17 00:00:00 2001 From: Adisak Date: Wed, 1 Oct 2025 16:02:18 +0700 Subject: [PATCH 3/4] =?UTF-8?q?sort=20=E0=B9=80=E0=B8=87=E0=B8=B4=E0=B8=99?= =?UTF-8?q?=E0=B9=80=E0=B8=94=E0=B8=B7=E0=B8=AD=E0=B8=99/=E0=B8=84?= =?UTF-8?q?=E0=B9=88=E0=B8=B2=E0=B8=88=E0=B9=89=E0=B8=B2=E0=B8=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/SalaryEmployeeController.ts | 20 ++++++-- .../SalaryFormulaEmployeeController.ts | 36 ++++++++++++- src/controllers/SalaryPeriodController.ts | 50 +++++++++++++++---- .../SalaryPeriodEmployeeController.ts | 24 +++++++-- 4 files changed, 113 insertions(+), 17 deletions(-) diff --git a/src/controllers/SalaryEmployeeController.ts b/src/controllers/SalaryEmployeeController.ts index feecfd8..089e22b 100644 --- a/src/controllers/SalaryEmployeeController.ts +++ b/src/controllers/SalaryEmployeeController.ts @@ -245,9 +245,11 @@ export class SalaryEmployeeController extends Controller { @Query("page") page: number = 1, @Query("pageSize") pageSize: number = 10, @Query("keyword") keyword?: string, + @Query("sortBy") sortBy?: string, + @Query("descending") descending?: boolean, ) { await new permission().PermissionList(request, "SYS_WAGE_CHART_EMP"); - const [salaryEmployee, total] = await AppDataSource.getRepository(SalaryEmployee) + let query = await AppDataSource.getRepository(SalaryEmployee) .createQueryBuilder("salaryEmployee") .andWhere( new Brackets((qb) => { @@ -257,12 +259,24 @@ export class SalaryEmployeeController extends Controller { ); }), ) - .orderBy("salaryEmployee.isActive", "DESC") - .addOrderBy("salaryEmployee.group", "ASC") + + if (sortBy) { + query = query.orderBy( + `salaryEmployee.${sortBy}`, + descending ? "DESC" : "ASC" + ); + }else{ + query = query.orderBy("salaryEmployee.isActive", "DESC") + .addOrderBy("salaryEmployee.group", "ASC") + } + + const [salaryEmployee, total] = await query .skip((page - 1) * pageSize) .take(pageSize) .getManyAndCount(); + + const _salaryEmployee = salaryEmployee.map((item) => ({ id: item.id, name: item.name, diff --git a/src/controllers/SalaryFormulaEmployeeController.ts b/src/controllers/SalaryFormulaEmployeeController.ts index d35b899..3f64005 100644 --- a/src/controllers/SalaryFormulaEmployeeController.ts +++ b/src/controllers/SalaryFormulaEmployeeController.ts @@ -237,8 +237,10 @@ export class SalaryFormulaEmployeeController extends Controller { @Query("pageSize") pageSize: number = 10, @Query("keyword") keyword?: string, @Query("posTypeId") posTypeId?: string, + @Query("sortBy") sortBy?: string, + @Query("descending") descending?: boolean, ) { - const [getFormula, total] = await AppDataSource.getRepository(SalaryFormulaEmployee) + let query = await AppDataSource.getRepository(SalaryFormulaEmployee) .createQueryBuilder("salaryFormulaEmployee") .leftJoinAndSelect("salaryFormulaEmployee.salaryEmployee", "salaryEmployee") .leftJoinAndSelect("salaryFormulaEmployee.posType", "posType") @@ -269,10 +271,42 @@ export class SalaryFormulaEmployeeController extends Controller { ) .andWhere(posTypeId == undefined ? "1=1" : { posTypeId: Like(`%${posTypeId}%`) }) .orderBy("salaryFormulaEmployee.lastUpdatedAt", "DESC") + + if (sortBy) { + if(sortBy === "group"){ + query = query.orderBy( + `salaryEmployee.group`, + descending ? "DESC" : "ASC" + ); + }else if(sortBy === "posLevel"){ + query = query.orderBy( + `posLevel.posLevelName`, + descending ? "DESC" : "ASC" + ); + }else if(sortBy === "posType"){ + query = query.orderBy( + `posType.posTypeName`, + descending ? "DESC" : "ASC" + ); + // }else if(sortBy === "salaryEmployeeMin"){ + // query = query.orderBy( + // `salaryEmployee.group`, + // descending ? "DESC" : "ASC" + // ); + }else{ + query = query.orderBy( + `salaryFormulaEmployee.${sortBy}`, + descending ? "DESC" : "ASC" + ); + } + } + + const [getFormula, total] = await query .skip((page - 1) * pageSize) .take(pageSize) .getManyAndCount(); + const mapFormula = getFormula.map((item) => ({ id: item.id, posLevel: diff --git a/src/controllers/SalaryPeriodController.ts b/src/controllers/SalaryPeriodController.ts index d7ec146..19ccedd 100644 --- a/src/controllers/SalaryPeriodController.ts +++ b/src/controllers/SalaryPeriodController.ts @@ -1560,6 +1560,8 @@ export class SalaryPeriodController extends Controller { keyword?: string; type?: any; isRetire?: string | null; + sortBy?: string; + descending?: boolean; }, ) { await new permission().PermissionList(request, "SYS_SALARY_OFFICER"); @@ -1572,7 +1574,7 @@ export class SalaryPeriodController extends Controller { if (!salaryOrg) { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบรอบการขึ้นเงินเดือน"); } - const [salaryProfile, total] = await AppDataSource.getRepository(SalaryProfile) + let query = await AppDataSource.getRepository(SalaryProfile) .createQueryBuilder("profile") .andWhere( new Brackets((qb) => { @@ -1686,12 +1688,28 @@ export class SalaryPeriodController extends Controller { child4: _data.child4, }, ) - .orderBy("profile.rootOrder", "ASC") - .addOrderBy("profile.child1Order", "ASC") - .addOrderBy("profile.child2Order", "ASC") - .addOrderBy("profile.child3Order", "ASC") - .addOrderBy("profile.child4Order", "ASC") - .addOrderBy("profile.posMasterNo", "ASC") + + if (body.sortBy) { + if(body.sortBy === "posExecutive"){ + query = query + .orderBy( `profile.posExecutive`,body.descending ? "DESC" : "ASC") + .addOrderBy( `profile.positionExecutiveField`,body.descending ? "DESC" : "ASC"); + }else{ + query = query.orderBy( + `profile.${body.sortBy}`, + body.descending ? "DESC" : "ASC" + ); + } + }else{ + query = query.orderBy("profile.rootOrder", "ASC") + .addOrderBy("profile.child1Order", "ASC") + .addOrderBy("profile.child2Order", "ASC") + .addOrderBy("profile.child3Order", "ASC") + .addOrderBy("profile.child4Order", "ASC") + .addOrderBy("profile.posMasterNo", "ASC") + } + + const [salaryProfile, total] = await query .skip((body.page - 1) * body.pageSize) .take(body.pageSize) .getManyAndCount(); @@ -2499,9 +2517,11 @@ export class SalaryPeriodController extends Controller { @Query("pageSize") pageSize: number = 10, @Query("keyword") keyword?: string, @Query("year") year: number = 2024, + @Query("sortBy") sortBy?: string, + @Query("descending") descending?: boolean, ) { await new permission().PermissionList(request, "SYS_SALARY_ROUND"); - const [salaryPeriod, total] = await AppDataSource.getRepository(SalaryPeriod) + let query = await AppDataSource.getRepository(SalaryPeriod) .createQueryBuilder("salaryPeriod") .andWhere(year != 0 ? "salaryPeriod.year LIKE :year" : "1=1", { year: `${year}` }) .orWhere("salaryPeriod.period LIKE :keyword", { keyword: `${keyword}` }) @@ -2516,8 +2536,18 @@ export class SalaryPeriodController extends Controller { "salaryPeriod.year", "salaryPeriod.revisionId", ]) - .orderBy("salaryPeriod.year", "DESC") - .addOrderBy("salaryPeriod.effectiveDate", "DESC") + + if (sortBy) { + query = query.orderBy( + `salaryPeriod.${sortBy}`, + descending ? "DESC" : "ASC" + ); + }else{ + query = query.orderBy("salaryPeriod.year", "DESC") + .addOrderBy("salaryPeriod.effectiveDate", "DESC") + } + + const [salaryPeriod, total] = await query .skip((page - 1) * pageSize) .take(pageSize) .getManyAndCount(); diff --git a/src/controllers/SalaryPeriodEmployeeController.ts b/src/controllers/SalaryPeriodEmployeeController.ts index 5ec3200..86e9702 100644 --- a/src/controllers/SalaryPeriodEmployeeController.ts +++ b/src/controllers/SalaryPeriodEmployeeController.ts @@ -1080,6 +1080,8 @@ export class SalaryPeriodEmployeeController extends Controller { keyword?: string; type?: any; isRetire?: string | null; + sortBy?: string, + descending?: boolean, }, ) { await new permission().PermissionList(request, "SYS_WAGE"); @@ -1091,7 +1093,7 @@ export class SalaryPeriodEmployeeController extends Controller { if (!salaryOrg) { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบรอบการขึ้นเงินเดือน"); } - const [salaryProfile, total] = await AppDataSource.getRepository(SalaryProfileEmployee) + let query = await AppDataSource.getRepository(SalaryProfileEmployee) .createQueryBuilder("profile") .andWhere( new Brackets((qb) => { @@ -1156,8 +1158,24 @@ export class SalaryPeriodEmployeeController extends Controller { ); }), ) - .orderBy("profile.citizenId", "ASC") - .addOrderBy("profile.isReserve", "ASC") + + if (body.sortBy) { + if(body.sortBy === "posLevel"){ + query = query + .orderBy( `profile.posTypeShort`,body.descending ? "DESC" : "ASC") + .addOrderBy( `profile.posLevel`,body.descending ? "DESC" : "ASC"); + }else{ + query = query.orderBy( + `profile.${body.sortBy}`, + body.descending ? "DESC" : "ASC" + ); + } + }else{ + query = query.orderBy("profile.citizenId", "ASC") + .addOrderBy("profile.isReserve", "ASC") + } + + const [salaryProfile, total] = await query .skip((body.page - 1) * body.pageSize) .take(body.pageSize) .getManyAndCount(); From 3084c7e9daacbe76792cdf010e131453f47fcfc7 Mon Sep 17 00:00:00 2001 From: Harid Promsri <52228846+Harid-999@users.noreply.github.com> Date: Tue, 21 Oct 2025 14:52:19 +0700 Subject: [PATCH 4/4] Merge pull request #53 from Frappet/issue/#1883 fix log --- src/interfaces/permission.ts | 84 +++++++++++++----------------------- 1 file changed, 31 insertions(+), 53 deletions(-) diff --git a/src/interfaces/permission.ts b/src/interfaces/permission.ts index cfe76e6..6ff8977 100644 --- a/src/interfaces/permission.ts +++ b/src/interfaces/permission.ts @@ -184,61 +184,39 @@ class CheckAuth { }); } public async checkOrg(token: any, keycloakId: string) { - try { - // Validate required environment variables - const REDIS_HOST = process.env.REDIS_HOST; - const REDIS_PORT = process.env.REDIS_PORT ? Number(process.env.REDIS_PORT) : 6379; + const redisClient = await this.redis.createClient({ + host: process.env.REDIS_HOST, + port: process.env.REDIS_PORT, + }) + const getAsync = promisify(redisClient.get).bind(redisClient) + try { + let reply = await getAsync("org_" + keycloakId) + if (reply != null) { + reply = JSON.parse(reply) + } else { + if (!keycloakId) throw new Error("No KeycloakId provided") + const x = await new CallAPI().GetData( + { + headers: { authorization: token }, + }, + `/org/permission/checkOrg/${keycloakId}`, + false + ) - if (!REDIS_HOST) { - throw new Error("REDIS_HOST is not set in environment variables"); - } + const data = { + orgRootId: x.orgRootId, + orgChild1Id: x.orgChild1Id, + orgChild2Id: x.orgChild2Id, + orgChild3Id: x.orgChild3Id, + orgChild4Id: x.orgChild4Id, + } - console.log(`[REDIS] Connecting to Redis at ${REDIS_HOST}:${REDIS_PORT}`); - - // Create Redis client - const redisClient = this.redis.createClient({ - socket: { - host: REDIS_HOST, - port: REDIS_PORT, - }, - }); - - redisClient.on("error", (err: any) => { - console.error("[REDIS] Connection error:", err.message); - }); - - await redisClient.connect(); - console.log("[REDIS] Connected successfully!"); - - const getAsync = promisify(redisClient.get).bind(redisClient); - - let reply = await getAsync("org_" + keycloakId); - if (reply != null) { - reply = JSON.parse(reply); - } else { - if (!keycloakId) throw new Error("No KeycloakId provided"); - const x = await new CallAPI().GetData( - { - headers: { authorization: token }, - }, - `/org/permission/checkOrg/${keycloakId}`, - false, - ); - - const data = { - orgRootId: x.orgRootId, - orgChild1Id: x.orgChild1Id, - orgChild2Id: x.orgChild2Id, - orgChild3Id: x.orgChild3Id, - orgChild4Id: x.orgChild4Id, - }; - - return data; - } - } catch (error) { - console.error("Error calling API:", error); - throw error; - } + return data + } + } catch (error) { + console.error("Error calling API:", error) + throw error + } } public async PermissionCreate(req: RequestWithUser, system: string) { return await this.Permission(req, system, "CREATE");