From aeecbd8ae9e481634db07f701c44ef2465278745 Mon Sep 17 00:00:00 2001 From: Adisak Date: Fri, 28 Nov 2025 17:23:51 +0700 Subject: [PATCH 1/5] test type-multi --- src/controllers/SalaryPeriodController.ts | 527 ++++++++++++++++------ 1 file changed, 381 insertions(+), 146 deletions(-) diff --git a/src/controllers/SalaryPeriodController.ts b/src/controllers/SalaryPeriodController.ts index 57b3a30..b39b904 100644 --- a/src/controllers/SalaryPeriodController.ts +++ b/src/controllers/SalaryPeriodController.ts @@ -72,68 +72,68 @@ export class SalaryPeriodController extends Controller { const data = { group1id: salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP1" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - ) == null + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP1" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + ) == null ? null : salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP1" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - )?.id, + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP1" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + )?.id, group1IsClose: salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP1" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - ) == null + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP1" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + ) == null ? null : salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP1" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - )?.isClose, + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP1" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + )?.isClose, group2id: salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP2" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - ) == null + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP2" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + ) == null ? null : salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP2" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - )?.id, + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP2" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + )?.id, group2IsClose: salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP2" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - ) == null + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP2" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + ) == null ? null : salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP2" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - )?.isClose, + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP2" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + )?.isClose, effectiveDate: salaryPeriod.effectiveDate, period: salaryPeriod.period, }; @@ -192,8 +192,8 @@ export class SalaryPeriodController extends Controller { _salaryOrgGROUP2 == null ? 0 : _salaryOrgGROUP2.salaryProfiles.reduce((accumulator, object) => { - return accumulator + object.amountSpecial; - }, 0); + return accumulator + object.amountSpecial; + }, 0); const data = { org: item.root, total: item.total + (_salaryOrgGROUP2 == null ? 0 : _salaryOrgGROUP2.total), @@ -489,7 +489,7 @@ export class SalaryPeriodController extends Controller { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบระดับตำแหน่ง"); } let type = salaryProfile.type; - //SalaryRank + //SalaryRanks let salaryRanks: any = null; if (salaryProfile.amount != null) { salaryRanks = await this.salaryRankRepository.findOne({ @@ -583,9 +583,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -598,9 +598,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFull == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFull == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFull - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -613,9 +613,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFullHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFullHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFullHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -957,7 +957,7 @@ export class SalaryPeriodController extends Controller { let _null: any = null; salaryProfile.remark = body.remark == null ? _null : body.remark; let type = salaryProfile.type; - //SalaryRank + //SalaryRanks let salaryRanks: any = null; if (salaryProfile.amount != null) { salaryRanks = await this.salaryRankRepository.findOne({ @@ -1051,9 +1051,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -1066,9 +1066,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFull == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFull == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFull - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -1081,9 +1081,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFullHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFullHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFullHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -1200,6 +1200,7 @@ export class SalaryPeriodController extends Controller { return new HttpSuccess(); } + //OLD CHANGE TYPE-MULTI /** * API แก้ไขขั้น * @@ -1208,8 +1209,8 @@ export class SalaryPeriodController extends Controller { * @param {string} id profile Id * @param {string} type ประเภทการเลื่อน NONE->ไม่ได้เลื่อน HAFT->ครึ่งขั้น FULL->1ขั้น FULLHAFT->1.5ขั้น */ - @Post("change/type-multi") - async changeTypeMulti( + @Post("oldchange/type-multi") + async oldchangeTypeMulti( @Body() body: { profileId: string[]; type: string; isReserve: boolean; remark?: string | null }, @Request() req: RequestWithUser, ) { @@ -1293,7 +1294,7 @@ export class SalaryPeriodController extends Controller { let _null: any = null; salaryProfile.remark = body.remark == null ? _null : body.remark; let type = salaryProfile.type; - //SalaryRank + //SalaryRanks let salaryRanks: any = null; if (salaryProfile.amount != null) { salaryRanks = await this.salaryRankRepository.findOne({ @@ -1391,9 +1392,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -1406,9 +1407,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFull == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFull == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFull - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -1421,9 +1422,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFullHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFullHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFullHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -1542,6 +1543,240 @@ export class SalaryPeriodController extends Controller { } return new HttpSuccess(); } + + //NEW CHANGE TYPE-MULTI + @Post("change/type-multi") + async changeTypeMulti( + @Body() body: { profileId: string[]; type: string; isReserve: boolean; remark?: string | null }, + @Request() req: RequestWithUser, + ) { + await new permission().PermissionCreate(req, "SYS_SALARY_OFFICER"); + + // ----------------------------- + // 1) ดึง salaryProfiles ทีเดียว + // ----------------------------- + const salaryProfiles = await this.salaryProfileRepository.find({ + where: { id: In(body.profileId) }, + relations: ["salaryOrg", "salaryOrg.salaryPeriod"], + }); + + if (!salaryProfiles.length) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล salaryProfile"); + } + + // ----------------------------- + // 2) โหลด PosType, PosLevel, Salary, SalaryRanks ทั้งหมดทีเดียว + // ----------------------------- + const posTypes = await this.posTypeRepository.find(); + const posLevels = await this.posLevelRepository.find(); + const salaries = await this.salaryRepository.find({ where: { isActive: true } }); + const salaryRanks = await this.salaryRankRepository.find(); + + const posTypeMap = new Map(posTypes.map(x => [x.posTypeName, x])); + const posLevelMap = new Map(posLevels.map(x => [`${x.posTypeId}|${x.posLevelName}`, x])); + const salaryMap = new Map( + salaries.map(x => [`${x.posTypeId}|${x.posLevelId}|${x.isSpecial ? 1 : 0}`, x]), + ); + + const ranksBySalaryId = salaryRanks.reduce((acc, r: any) => { + if (!acc[r.salaryId]) acc[r.salaryId] = []; + acc[r.salaryId].push(r); + return acc; + }, {} as Record); + + // ----------------------------- + // 3) Loop profiles + // ----------------------------- + const profilesToSave: SalaryProfile[] = []; + const orgNeedRecalc = new Set(); + + for (const profile of salaryProfiles) { + const bodyType = body.type?.toUpperCase() ?? profile.type; + + // --- ตรวจ FULLHAFT → หา APR snapshot2 (เหมือนเดิม) --- + if (bodyType === "FULLHAFT" && profile.salaryOrg.salaryPeriod.period === "OCT") { + const checkPrev = await this.salaryProfileRepository.findOne({ + relations: ["salaryOrg", "salaryOrg.salaryPeriod"], + where: { + citizenId: profile.citizenId, + salaryOrg: { + salaryPeriod: { period: "APR", year: profile.salaryOrg.salaryPeriod.year }, + snapshot: "SNAP2", + }, + type: "FULL", + }, + }); + + if (checkPrev) { + throw new HttpError(404, "ไม่สามารถเลื่อนขั้นเกิน 2 ครั้งในปีเดียวกันได้"); + } + } + + // reserve & remark + profile.type = bodyType; + profile.isReserve = bodyType === "FULL" ? body.isReserve : false; + profile.remark = body.remark ?? ""; + + // --- apply posType/posLevel/salary --- + const posType = posTypeMap.get(profile.posType); + if (!posType) throw new HttpError(404, "ไม่พบประเภทตำแหน่ง"); + + const posLevel = posLevelMap.get(`${posType.id}|${profile.posLevel}`); + if (!posLevel) throw new HttpError(404, "ไม่พบระดับตำแหน่ง"); + + const salaryBase = salaryMap.get(`${posLevel.posTypeId}|${posLevel.id}|${profile.isSpecial ? 1 : 0}`); + if (!salaryBase) throw new HttpError(404, "ไม่พบระดับเงินเดือน"); + + const salaryId = Number(salaryBase.id); + const ranks = ranksBySalaryId[salaryId] ?? []; + + // --- หา rank --- + let rank: SalaryRanks | null = null; + + if (profile.amount != null) { + const amount = profile.amount; + + // หา rank ที่ใช้ type เดิม (เหมือนเดิม) + const possible = ranks + .filter((r: any) => r.salary >= amount && !r.isNext) + .sort((a: any, b: any) => a.salary - b.salary); + rank = possible[0] ?? null; + + if (!rank) { + const next = ranks + .filter((r: any) => r.salary > amount && r.isNext) + .sort((a: any, b: any) => a.salary - b.salary); + rank = next[0] ?? null; + } + + // --- FULLHAFT dynamic type adjustment (เหมือนเดิม) --- + if (bodyType === "FULLHAFT" && rank) { + const halfSpecial = rank.salaryHalfSpecial ?? 0; + const fullSpecial = rank.salaryFullSpecial ?? 0; + const fullHalfSpecial = rank.salaryFullHalfSpecial ?? 0; + + if (fullHalfSpecial > 0) { + if (fullSpecial === 0) profile.type = "HAFT"; + else if (halfSpecial === 0) profile.type = "FULL"; + else profile.type = "FULLHAFT"; + } + } + } + + // --- คำนวณเงินเดือน (logic เหมือนเดิม) --- + const calc = (sp: keyof SalaryRanks, next: keyof SalaryRanks) => { + const amountNext = rank?.[next] ?? 0; + return { + amountSpecial: rank?.[sp] ?? 0, + amountUse: profile.amount != null ? Number(amountNext) - Number(profile.amount) : 0, + positionSalaryAmount: amountNext, + isNext: rank?.isNext ?? 0, + }; + }; + + switch (profile.type) { + case "NONE": + profile.amountSpecial = 0; + profile.amountUse = 0; + profile.positionSalaryAmount = profile.amount ?? 0; + break; + case "PENDING": + profile.amountSpecial = 0; + profile.amountUse = 0; + profile.positionSalaryAmount = 0; + break; + case "HAFT": + Object.assign(profile, calc("salaryHalfSpecial", "salaryHalf")); + break; + case "FULL": + Object.assign(profile, calc("salaryFullSpecial", "salaryFull")); + break; + case "FULLHAFT": + Object.assign(profile, calc("salaryFullHalfSpecial", "salaryFullHalf")); + break; + } + + profile.lastUpdateUserId = req.user.sub; + profile.lastUpdateFullName = req.user.name; + profile.lastUpdatedAt = new Date(); + + // --- log diff (เหมือนเดิม) --- + const before = structuredClone(profile); + profilesToSave.push(profile); + setLogDataDiff(req, { before, after: profile }); + + orgNeedRecalc.add(profile.salaryOrg.id); + } + + // ----------------------------- + // 4) Save batch + // ----------------------------- + await this.salaryProfileRepository.save(profilesToSave); + + // ----------------------------- + // 5) Recalculate SalaryOrg (คง logic เดิม) + // ----------------------------- + for (const orgId of orgNeedRecalc) { + const org = await this.salaryOrgRepository.findOne({ + where: { id: orgId }, + relations: ["salaryProfiles", "salaryPeriod"], + }); + if (!org) continue; + + const beforeOrg = structuredClone(org); + + // SNAP1 / APR + if (org.snapshot === "SNAP1" && org.salaryPeriod.period === "APR") { + const countFull = org.salaryProfiles.filter(p => p.type === "FULL").length; + org.total = org.salaryProfiles.length; + org.fifteenPercent = Math.floor(org.total * 0.15); + org.quantityUsed = countFull; + org.remainQuota = org.fifteenPercent - countFull; + } + + // SNAP1 / OCT + if (org.snapshot === "SNAP1" && org.salaryPeriod.period === "OCT") { + const total = org.salaryProfiles.reduce((sum, p) => sum + (p.amount ?? 0), 0); + org.currentAmount = total; + org.sixPercentAmount = total * 0.06; + + const useAmount = org.salaryProfiles + .filter(p => ["HAFT", "FULL", "FULLHAFT"].includes(p.type)) + .reduce((s, p) => s + (p.amountUse ?? 0), 0); + + org.useAmount = useAmount; + org.remainingAmount = org.sixPercentAmount - useAmount; + + // --- SNAP2 APR recalc (เหมือนเดิม) --- + const salaryPeriodAPROld = await this.salaryPeriodRepository.findOne({ + where: { period: "APR", year: org.salaryPeriod.year } + }); + if (salaryPeriodAPROld) { + const orgSnap2Old: any = await this.salaryOrgRepository.findOne({ + where: { salaryPeriodId: salaryPeriodAPROld.id, rootId: org.rootId, group: org.group, snapshot: "SNAP2" }, + relations: ["salaryProfiles"] + }); + if (orgSnap2Old) { + const spent = orgSnap2Old.salaryProfiles.reduce((sum: number, p: any) => sum + (p.amountUse ?? 0), 0); + org.spentAmount = spent; + org.remainingAmount = (org.sixPercentAmount - useAmount) - spent; + } + } + } + + org.lastUpdateUserId = req.user.sub; + org.lastUpdateFullName = req.user.name; + org.lastUpdatedAt = new Date(); + + await this.salaryOrgRepository.save(org); + setLogDataDiff(req, { before: beforeOrg, after: org }); + } + + return new HttpSuccess(); + } + + + /** * API รายการอัตราเงินเดือน @@ -1689,25 +1924,25 @@ export class SalaryPeriodController extends Controller { }, ) - 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( + 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") + ); + } + } 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) @@ -1846,7 +2081,7 @@ export class SalaryPeriodController extends Controller { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบระดับตำแหน่ง"); } let type = salaryProfile.type; - //SalaryRank + //SalaryRanks let salaryRanks: any = null; if (salaryProfile.amount != null) { salaryRanks = await this.salaryRankRepository.findOne({ @@ -1940,9 +2175,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -1955,9 +2190,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFull == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFull == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFull - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -1970,9 +2205,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFullHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFullHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFullHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -2537,17 +2772,17 @@ export class SalaryPeriodController extends Controller { "salaryPeriod.revisionId", ]) - if (sortBy) { - query = query.orderBy( - `salaryPeriod.${sortBy}`, - descending ? "DESC" : "ASC" - ); - }else{ - query = query.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 + const [salaryPeriod, total] = await query .skip((page - 1) * pageSize) .take(pageSize) .getManyAndCount(); @@ -2794,27 +3029,27 @@ export class SalaryPeriodController extends Controller { 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) + [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) + [salaryPeriod.id, snapshot], + ).then((result) => result[0]?.count || 0) : Promise.resolve(0), ]); @@ -2827,27 +3062,27 @@ export class SalaryPeriodController extends Controller { await Promise.all([ salaryOrgCount > 0 ? AppDataSource.query( - ` + ` DELETE FROM salaryProfile WHERE salaryOrgId IN ( SELECT id FROM salaryOrg WHERE salaryPeriodId = ? AND snapshot = ? ) `, - [salaryPeriod.id, 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], - ) + [salaryPeriod.id, snapshot], + ) : Promise.resolve(), ]); @@ -3823,27 +4058,27 @@ export class SalaryPeriodController extends Controller { 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) + [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) + [salaryPeriod.id, snapshot], + ).then((result) => result[0]?.count || 0) : Promise.resolve(0), ]); @@ -3856,27 +4091,27 @@ export class SalaryPeriodController extends Controller { await Promise.all([ salaryOrgCount > 0 ? AppDataSource.query( - ` + ` DELETE FROM salaryProfile WHERE salaryOrgId IN ( SELECT id FROM salaryOrg WHERE salaryPeriodId = ? AND snapshot = ? ) `, - [salaryPeriod.id, 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], - ) + [salaryPeriod.id, snapshot], + ) : Promise.resolve(), ]); @@ -3928,22 +4163,22 @@ export class SalaryPeriodController extends Controller { // 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().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: 1000, keyword: "", year: salaryPeriod.year, period: salaryPeriod.period, - },false), + }, 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), + }, false), ]); console.timeEnd("⏱ API: Load initial data"); @@ -3964,7 +4199,7 @@ export class SalaryPeriodController extends Controller { keyword: "", year: salaryPeriod.year, period: salaryPeriod.period, - },false), + }, false), ); } const results = await Promise.all(promises); @@ -3989,7 +4224,7 @@ export class SalaryPeriodController extends Controller { keyword: "", year: salaryPeriod.year, period: salaryPeriod.period, - },false), + }, false), ); } const results = await Promise.all(promises); @@ -4713,8 +4948,8 @@ export class SalaryPeriodController extends Controller { }, }, ); - let request:any = response.data.access_token; - + let request: any = response.data.access_token; + if (current.getDate() == 1 && current.getMonth() == 2) { //snap1 วันที่ 1 มีนา salaryPeriod = await this.salaryPeriodRepository.findOne({ where: { From 3aeb893d55f9af3a00d34261afc64adc579a6d71 Mon Sep 17 00:00:00 2001 From: Adisak Date: Fri, 28 Nov 2025 17:59:44 +0700 Subject: [PATCH 2/5] revert --- src/controllers/SalaryPeriodController.ts | 109 ++++++++++------------ 1 file changed, 49 insertions(+), 60 deletions(-) diff --git a/src/controllers/SalaryPeriodController.ts b/src/controllers/SalaryPeriodController.ts index b39b904..7100697 100644 --- a/src/controllers/SalaryPeriodController.ts +++ b/src/controllers/SalaryPeriodController.ts @@ -1209,8 +1209,8 @@ export class SalaryPeriodController extends Controller { * @param {string} id profile Id * @param {string} type ประเภทการเลื่อน NONE->ไม่ได้เลื่อน HAFT->ครึ่งขั้น FULL->1ขั้น FULLHAFT->1.5ขั้น */ - @Post("oldchange/type-multi") - async oldchangeTypeMulti( + @Post("change/type-multi") + async changeTypeMulti( @Body() body: { profileId: string[]; type: string; isReserve: boolean; remark?: string | null }, @Request() req: RequestWithUser, ) { @@ -1545,8 +1545,8 @@ export class SalaryPeriodController extends Controller { } //NEW CHANGE TYPE-MULTI - @Post("change/type-multi") - async changeTypeMulti( + @Post("newchange/type-multi") + async newchangeTypeMulti( @Body() body: { profileId: string[]; type: string; isReserve: boolean; remark?: string | null }, @Request() req: RequestWithUser, ) { @@ -1572,28 +1572,25 @@ export class SalaryPeriodController extends Controller { const salaries = await this.salaryRepository.find({ where: { isActive: true } }); const salaryRanks = await this.salaryRankRepository.find(); + // Map lookup const posTypeMap = new Map(posTypes.map(x => [x.posTypeName, x])); const posLevelMap = new Map(posLevels.map(x => [`${x.posTypeId}|${x.posLevelName}`, x])); const salaryMap = new Map( salaries.map(x => [`${x.posTypeId}|${x.posLevelId}|${x.isSpecial ? 1 : 0}`, x]), ); - const ranksBySalaryId = salaryRanks.reduce((acc, r: any) => { if (!acc[r.salaryId]) acc[r.salaryId] = []; acc[r.salaryId].push(r); return acc; }, {} as Record); - // ----------------------------- - // 3) Loop profiles - // ----------------------------- const profilesToSave: SalaryProfile[] = []; const orgNeedRecalc = new Set(); for (const profile of salaryProfiles) { const bodyType = body.type?.toUpperCase() ?? profile.type; - // --- ตรวจ FULLHAFT → หา APR snapshot2 (เหมือนเดิม) --- + // --- ตรวจ FULLHAFT → หา APR snapshot2 --- if (bodyType === "FULLHAFT" && profile.salaryOrg.salaryPeriod.period === "OCT") { const checkPrev = await this.salaryProfileRepository.findOne({ relations: ["salaryOrg", "salaryOrg.salaryPeriod"], @@ -1612,11 +1609,6 @@ export class SalaryPeriodController extends Controller { } } - // reserve & remark - profile.type = bodyType; - profile.isReserve = bodyType === "FULL" ? body.isReserve : false; - profile.remark = body.remark ?? ""; - // --- apply posType/posLevel/salary --- const posType = posTypeMap.get(profile.posType); if (!posType) throw new HttpError(404, "ไม่พบประเภทตำแหน่ง"); @@ -1630,13 +1622,11 @@ export class SalaryPeriodController extends Controller { const salaryId = Number(salaryBase.id); const ranks = ranksBySalaryId[salaryId] ?? []; - // --- หา rank --- + // --- หา rank ตาม amount ก่อน dynamic type adjustment --- let rank: SalaryRanks | null = null; - if (profile.amount != null) { const amount = profile.amount; - // หา rank ที่ใช้ type เดิม (เหมือนเดิม) const possible = ranks .filter((r: any) => r.salary >= amount && !r.isNext) .sort((a: any, b: any) => a.salary - b.salary); @@ -1648,59 +1638,57 @@ export class SalaryPeriodController extends Controller { .sort((a: any, b: any) => a.salary - b.salary); rank = next[0] ?? null; } + } - // --- FULLHAFT dynamic type adjustment (เหมือนเดิม) --- - if (bodyType === "FULLHAFT" && rank) { - const halfSpecial = rank.salaryHalfSpecial ?? 0; - const fullSpecial = rank.salaryFullSpecial ?? 0; - const fullHalfSpecial = rank.salaryFullHalfSpecial ?? 0; + // --- คำนวณเงินเดือนตาม rank เดิมก่อน dynamic type adjustment --- + const calc = (sp: keyof SalaryRanks, next: keyof SalaryRanks) => ({ + amountSpecial: rank?.[sp] ?? 0, + amountUse: profile.amount != null && rank?.[next] != null + ? Number(rank[next]) - Number(profile.amount) + : 0, + positionSalaryAmount: rank?.[next] ?? 0, + isNext: rank?.isNext ?? 0 + }); - if (fullHalfSpecial > 0) { - if (fullSpecial === 0) profile.type = "HAFT"; - else if (halfSpecial === 0) profile.type = "FULL"; - else profile.type = "FULLHAFT"; - } + // --- FULLHAFT dynamic type adjustment หลังคำนวณเงินเดือน --- + let finalType = bodyType; + if (bodyType === "FULLHAFT" && rank) { + const halfSpecial = rank.salaryHalfSpecial ?? 0; + const fullSpecial = rank.salaryFullSpecial ?? 0; + const fullHalfSpecial = rank.salaryFullHalfSpecial ?? 0; + + if (fullHalfSpecial > 0) { + if (fullSpecial === 0) finalType = "HAFT"; + else if (halfSpecial === 0) finalType = "FULL"; + else finalType = "FULLHAFT"; } } - // --- คำนวณเงินเดือน (logic เหมือนเดิม) --- - const calc = (sp: keyof SalaryRanks, next: keyof SalaryRanks) => { - const amountNext = rank?.[next] ?? 0; - return { - amountSpecial: rank?.[sp] ?? 0, - amountUse: profile.amount != null ? Number(amountNext) - Number(profile.amount) : 0, - positionSalaryAmount: amountNext, - isNext: rank?.isNext ?? 0, - }; - }; + profile.type = finalType; + profile.isReserve = finalType === "FULL" ? body.isReserve : false; + profile.remark = body.remark ?? ""; - switch (profile.type) { - case "NONE": - profile.amountSpecial = 0; - profile.amountUse = 0; - profile.positionSalaryAmount = profile.amount ?? 0; - break; - case "PENDING": - profile.amountSpecial = 0; - profile.amountUse = 0; - profile.positionSalaryAmount = 0; - break; - case "HAFT": - Object.assign(profile, calc("salaryHalfSpecial", "salaryHalf")); - break; - case "FULL": - Object.assign(profile, calc("salaryFullSpecial", "salaryFull")); - break; - case "FULLHAFT": - Object.assign(profile, calc("salaryFullHalfSpecial", "salaryFullHalf")); - break; + if (finalType === "NONE") { + profile.amountSpecial = 0; + profile.amountUse = 0; + profile.positionSalaryAmount = profile.amount ?? 0; + } else if (finalType === "PENDING") { + profile.amountSpecial = 0; + profile.amountUse = 0; + profile.positionSalaryAmount = 0; + } else if (finalType === "HAFT") { + Object.assign(profile, calc("salaryHalfSpecial", "salaryHalf")); + } else if (finalType === "FULL") { + Object.assign(profile, calc("salaryFullSpecial", "salaryFull")); + } else if (finalType === "FULLHAFT") { + Object.assign(profile, calc("salaryFullHalfSpecial", "salaryFullHalf")); } profile.lastUpdateUserId = req.user.sub; profile.lastUpdateFullName = req.user.name; profile.lastUpdatedAt = new Date(); - // --- log diff (เหมือนเดิม) --- + // --- log diff --- const before = structuredClone(profile); profilesToSave.push(profile); setLogDataDiff(req, { before, after: profile }); @@ -1714,7 +1702,7 @@ export class SalaryPeriodController extends Controller { await this.salaryProfileRepository.save(profilesToSave); // ----------------------------- - // 5) Recalculate SalaryOrg (คง logic เดิม) + // 5) Recalculate SalaryOrg // ----------------------------- for (const orgId of orgNeedRecalc) { const org = await this.salaryOrgRepository.findOne({ @@ -1747,7 +1735,7 @@ export class SalaryPeriodController extends Controller { org.useAmount = useAmount; org.remainingAmount = org.sixPercentAmount - useAmount; - // --- SNAP2 APR recalc (เหมือนเดิม) --- + // --- SNAP2 APR recalc --- const salaryPeriodAPROld = await this.salaryPeriodRepository.findOne({ where: { period: "APR", year: org.salaryPeriod.year } }); @@ -1778,6 +1766,7 @@ export class SalaryPeriodController extends Controller { + /** * API รายการอัตราเงินเดือน * From dfb3e826760d14d0b5788c079de2998101a2afcd Mon Sep 17 00:00:00 2001 From: Adisak Date: Sun, 30 Nov 2025 12:45:54 +0700 Subject: [PATCH 3/5] test performace change/type multi --- src/controllers/SalaryPeriodController.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/controllers/SalaryPeriodController.ts b/src/controllers/SalaryPeriodController.ts index 7100697..9dedc2b 100644 --- a/src/controllers/SalaryPeriodController.ts +++ b/src/controllers/SalaryPeriodController.ts @@ -1209,8 +1209,8 @@ export class SalaryPeriodController extends Controller { * @param {string} id profile Id * @param {string} type ประเภทการเลื่อน NONE->ไม่ได้เลื่อน HAFT->ครึ่งขั้น FULL->1ขั้น FULLHAFT->1.5ขั้น */ - @Post("change/type-multi") - async changeTypeMulti( + @Post("oldchange/type-multi") + async oldchangeTypeMulti( @Body() body: { profileId: string[]; type: string; isReserve: boolean; remark?: string | null }, @Request() req: RequestWithUser, ) { @@ -1545,8 +1545,8 @@ export class SalaryPeriodController extends Controller { } //NEW CHANGE TYPE-MULTI - @Post("newchange/type-multi") - async newchangeTypeMulti( + @Post("change/type-multi") + async changeTypeMulti( @Body() body: { profileId: string[]; type: string; isReserve: boolean; remark?: string | null }, @Request() req: RequestWithUser, ) { From 10ae9c0d13b5659620aa1d373ea10952dcdca0fd Mon Sep 17 00:00:00 2001 From: Adisak Date: Sun, 30 Nov 2025 13:12:46 +0700 Subject: [PATCH 4/5] =?UTF-8?q?revert=20=E0=B8=A3=E0=B8=AD=E0=B9=81?= =?UTF-8?q?=E0=B8=81=E0=B9=89=E0=B9=84=E0=B8=82=E0=B8=84=E0=B8=B3=E0=B8=99?= =?UTF-8?q?=E0=B8=A7=E0=B8=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/SalaryPeriodController.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/controllers/SalaryPeriodController.ts b/src/controllers/SalaryPeriodController.ts index 9dedc2b..7100697 100644 --- a/src/controllers/SalaryPeriodController.ts +++ b/src/controllers/SalaryPeriodController.ts @@ -1209,8 +1209,8 @@ export class SalaryPeriodController extends Controller { * @param {string} id profile Id * @param {string} type ประเภทการเลื่อน NONE->ไม่ได้เลื่อน HAFT->ครึ่งขั้น FULL->1ขั้น FULLHAFT->1.5ขั้น */ - @Post("oldchange/type-multi") - async oldchangeTypeMulti( + @Post("change/type-multi") + async changeTypeMulti( @Body() body: { profileId: string[]; type: string; isReserve: boolean; remark?: string | null }, @Request() req: RequestWithUser, ) { @@ -1545,8 +1545,8 @@ export class SalaryPeriodController extends Controller { } //NEW CHANGE TYPE-MULTI - @Post("change/type-multi") - async changeTypeMulti( + @Post("newchange/type-multi") + async newchangeTypeMulti( @Body() body: { profileId: string[]; type: string; isReserve: boolean; remark?: string | null }, @Request() req: RequestWithUser, ) { From cbf5f2599ddfb7b451a86096c6acc5d5f22e8390 Mon Sep 17 00:00:00 2001 From: kittapath <> Date: Fri, 12 Dec 2025 01:37:27 +0700 Subject: [PATCH 5/5] add permission brother --- src/controllers/SalaryPeriodController.ts | 410 ++++++++++++---------- src/interfaces/permission.ts | 78 ++-- 2 files changed, 272 insertions(+), 216 deletions(-) diff --git a/src/controllers/SalaryPeriodController.ts b/src/controllers/SalaryPeriodController.ts index 7100697..cf8d1d8 100644 --- a/src/controllers/SalaryPeriodController.ts +++ b/src/controllers/SalaryPeriodController.ts @@ -72,68 +72,68 @@ export class SalaryPeriodController extends Controller { const data = { group1id: salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP1" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - ) == null + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP1" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + ) == null ? null : salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP1" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - )?.id, + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP1" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + )?.id, group1IsClose: salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP1" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - ) == null + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP1" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + ) == null ? null : salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP1" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - )?.isClose, + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP1" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + )?.isClose, group2id: salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP2" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - ) == null + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP2" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + ) == null ? null : salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP2" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - )?.id, + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP2" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + )?.id, group2IsClose: salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP2" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - ) == null + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP2" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + ) == null ? null : salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP2" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - )?.isClose, + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP2" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + )?.isClose, effectiveDate: salaryPeriod.effectiveDate, period: salaryPeriod.period, }; @@ -192,8 +192,8 @@ export class SalaryPeriodController extends Controller { _salaryOrgGROUP2 == null ? 0 : _salaryOrgGROUP2.salaryProfiles.reduce((accumulator, object) => { - return accumulator + object.amountSpecial; - }, 0); + return accumulator + object.amountSpecial; + }, 0); const data = { org: item.root, total: item.total + (_salaryOrgGROUP2 == null ? 0 : _salaryOrgGROUP2.total), @@ -583,9 +583,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -598,9 +598,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFull == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFull == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFull - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -613,9 +613,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFullHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFullHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFullHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -1051,9 +1051,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -1066,9 +1066,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFull == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFull == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFull - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -1081,9 +1081,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFullHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFullHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFullHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -1392,9 +1392,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -1407,9 +1407,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFull == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFull == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFull - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -1422,9 +1422,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFullHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFullHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFullHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -1543,7 +1543,7 @@ export class SalaryPeriodController extends Controller { } return new HttpSuccess(); } - + //NEW CHANGE TYPE-MULTI @Post("newchange/type-multi") async newchangeTypeMulti( @@ -1573,16 +1573,19 @@ export class SalaryPeriodController extends Controller { const salaryRanks = await this.salaryRankRepository.find(); // Map lookup - const posTypeMap = new Map(posTypes.map(x => [x.posTypeName, x])); - const posLevelMap = new Map(posLevels.map(x => [`${x.posTypeId}|${x.posLevelName}`, x])); + const posTypeMap = new Map(posTypes.map((x) => [x.posTypeName, x])); + const posLevelMap = new Map(posLevels.map((x) => [`${x.posTypeId}|${x.posLevelName}`, x])); const salaryMap = new Map( - salaries.map(x => [`${x.posTypeId}|${x.posLevelId}|${x.isSpecial ? 1 : 0}`, x]), + salaries.map((x) => [`${x.posTypeId}|${x.posLevelId}|${x.isSpecial ? 1 : 0}`, x]), + ); + const ranksBySalaryId = salaryRanks.reduce( + (acc, r: any) => { + if (!acc[r.salaryId]) acc[r.salaryId] = []; + acc[r.salaryId].push(r); + return acc; + }, + {} as Record, ); - const ranksBySalaryId = salaryRanks.reduce((acc, r: any) => { - if (!acc[r.salaryId]) acc[r.salaryId] = []; - acc[r.salaryId].push(r); - return acc; - }, {} as Record); const profilesToSave: SalaryProfile[] = []; const orgNeedRecalc = new Set(); @@ -1616,7 +1619,9 @@ export class SalaryPeriodController extends Controller { const posLevel = posLevelMap.get(`${posType.id}|${profile.posLevel}`); if (!posLevel) throw new HttpError(404, "ไม่พบระดับตำแหน่ง"); - const salaryBase = salaryMap.get(`${posLevel.posTypeId}|${posLevel.id}|${profile.isSpecial ? 1 : 0}`); + const salaryBase = salaryMap.get( + `${posLevel.posTypeId}|${posLevel.id}|${profile.isSpecial ? 1 : 0}`, + ); if (!salaryBase) throw new HttpError(404, "ไม่พบระดับเงินเดือน"); const salaryId = Number(salaryBase.id); @@ -1643,11 +1648,12 @@ export class SalaryPeriodController extends Controller { // --- คำนวณเงินเดือนตาม rank เดิมก่อน dynamic type adjustment --- const calc = (sp: keyof SalaryRanks, next: keyof SalaryRanks) => ({ amountSpecial: rank?.[sp] ?? 0, - amountUse: profile.amount != null && rank?.[next] != null - ? Number(rank[next]) - Number(profile.amount) - : 0, + amountUse: + profile.amount != null && rank?.[next] != null + ? Number(rank[next]) - Number(profile.amount) + : 0, positionSalaryAmount: rank?.[next] ?? 0, - isNext: rank?.isNext ?? 0 + isNext: rank?.isNext ?? 0, }); // --- FULLHAFT dynamic type adjustment หลังคำนวณเงินเดือน --- @@ -1715,7 +1721,7 @@ export class SalaryPeriodController extends Controller { // SNAP1 / APR if (org.snapshot === "SNAP1" && org.salaryPeriod.period === "APR") { - const countFull = org.salaryProfiles.filter(p => p.type === "FULL").length; + const countFull = org.salaryProfiles.filter((p) => p.type === "FULL").length; org.total = org.salaryProfiles.length; org.fifteenPercent = Math.floor(org.total * 0.15); org.quantityUsed = countFull; @@ -1729,7 +1735,7 @@ export class SalaryPeriodController extends Controller { org.sixPercentAmount = total * 0.06; const useAmount = org.salaryProfiles - .filter(p => ["HAFT", "FULL", "FULLHAFT"].includes(p.type)) + .filter((p) => ["HAFT", "FULL", "FULLHAFT"].includes(p.type)) .reduce((s, p) => s + (p.amountUse ?? 0), 0); org.useAmount = useAmount; @@ -1737,17 +1743,25 @@ export class SalaryPeriodController extends Controller { // --- SNAP2 APR recalc --- const salaryPeriodAPROld = await this.salaryPeriodRepository.findOne({ - where: { period: "APR", year: org.salaryPeriod.year } + where: { period: "APR", year: org.salaryPeriod.year }, }); if (salaryPeriodAPROld) { const orgSnap2Old: any = await this.salaryOrgRepository.findOne({ - where: { salaryPeriodId: salaryPeriodAPROld.id, rootId: org.rootId, group: org.group, snapshot: "SNAP2" }, - relations: ["salaryProfiles"] + where: { + salaryPeriodId: salaryPeriodAPROld.id, + rootId: org.rootId, + group: org.group, + snapshot: "SNAP2", + }, + relations: ["salaryProfiles"], }); if (orgSnap2Old) { - const spent = orgSnap2Old.salaryProfiles.reduce((sum: number, p: any) => sum + (p.amountUse ?? 0), 0); + const spent = orgSnap2Old.salaryProfiles.reduce( + (sum: number, p: any) => sum + (p.amountUse ?? 0), + 0, + ); org.spentAmount = spent; - org.remainingAmount = (org.sixPercentAmount - useAmount) - spent; + org.remainingAmount = org.sixPercentAmount - useAmount - spent; } } } @@ -1763,10 +1777,6 @@ export class SalaryPeriodController extends Controller { return new HttpSuccess(); } - - - - /** * API รายการอัตราเงินเดือน * @@ -1876,7 +1886,7 @@ export class SalaryPeriodController extends Controller { _data.child1 != undefined && _data.child1 != null ? _data.child1[0] != null ? `child1Id IN (:...child1)` - : `child1Id is null` + : `child1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` : "1=1", { child1: _data.child1, @@ -1911,7 +1921,7 @@ export class SalaryPeriodController extends Controller { { child4: _data.child4, }, - ) + ); if (body.sortBy) { if (body.sortBy === "posExecutive") { @@ -1919,18 +1929,16 @@ export class SalaryPeriodController extends Controller { .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" - ); + query = query.orderBy(`profile.${body.sortBy}`, body.descending ? "DESC" : "ASC"); } } else { - query = query.orderBy("profile.rootOrder", "ASC") + 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") + .addOrderBy("profile.posMasterNo", "ASC"); } const [salaryProfile, total] = await query @@ -2164,9 +2172,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -2179,9 +2187,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFull == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFull == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFull - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -2194,9 +2202,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFullHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFullHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFullHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -2759,16 +2767,14 @@ export class SalaryPeriodController extends Controller { "salaryPeriod.status", "salaryPeriod.year", "salaryPeriod.revisionId", - ]) + ]); if (sortBy) { - query = query.orderBy( - `salaryPeriod.${sortBy}`, - descending ? "DESC" : "ASC" - ); + query = query.orderBy(`salaryPeriod.${sortBy}`, descending ? "DESC" : "ASC"); } else { - query = query.orderBy("salaryPeriod.year", "DESC") - .addOrderBy("salaryPeriod.effectiveDate", "DESC") + query = query + .orderBy("salaryPeriod.year", "DESC") + .addOrderBy("salaryPeriod.effectiveDate", "DESC"); } const [salaryPeriod, total] = await query @@ -3018,27 +3024,27 @@ export class SalaryPeriodController extends Controller { 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) + [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) + [salaryPeriod.id, snapshot], + ).then((result) => result[0]?.count || 0) : Promise.resolve(0), ]); @@ -3051,27 +3057,27 @@ export class SalaryPeriodController extends Controller { await Promise.all([ salaryOrgCount > 0 ? AppDataSource.query( - ` + ` DELETE FROM salaryProfile WHERE salaryOrgId IN ( SELECT id FROM salaryOrg WHERE salaryPeriodId = ? AND snapshot = ? ) `, - [salaryPeriod.id, 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], - ) + [salaryPeriod.id, snapshot], + ) : Promise.resolve(), ]); @@ -4047,27 +4053,27 @@ export class SalaryPeriodController extends Controller { 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) + [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) + [salaryPeriod.id, snapshot], + ).then((result) => result[0]?.count || 0) : Promise.resolve(0), ]); @@ -4080,27 +4086,27 @@ export class SalaryPeriodController extends Controller { await Promise.all([ salaryOrgCount > 0 ? AppDataSource.query( - ` + ` DELETE FROM salaryProfile WHERE salaryOrgId IN ( SELECT id FROM salaryOrg WHERE salaryPeriodId = ? AND snapshot = ? ) `, - [salaryPeriod.id, 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], - ) + [salaryPeriod.id, snapshot], + ) : Promise.resolve(), ]); @@ -4152,22 +4158,40 @@ export class SalaryPeriodController extends Controller { // 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: 1000, - keyword: "", - year: salaryPeriod.year, - period: salaryPeriod.period, - }, 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), + 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: 1000, + keyword: "", + year: salaryPeriod.year, + period: salaryPeriod.period, + }, + 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"); @@ -4182,13 +4206,18 @@ export class SalaryPeriodController extends Controller { const promises = []; for (let index = 2; index <= page; index++) { promises.push( - new CallAPI().PostData({ headers: { authorization: request } }, "/org/unauthorize/new-salary/gen", { - page: index, - pageSize: 1000, - keyword: "", - year: salaryPeriod.year, - period: salaryPeriod.period, - }, false), + new CallAPI().PostData( + { headers: { authorization: request } }, + "/org/unauthorize/new-salary/gen", + { + page: index, + pageSize: 1000, + keyword: "", + year: salaryPeriod.year, + period: salaryPeriod.period, + }, + false, + ), ); } const results = await Promise.all(promises); @@ -4207,13 +4236,18 @@ export class SalaryPeriodController extends Controller { 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), + 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); @@ -4799,9 +4833,9 @@ export class SalaryPeriodController extends Controller { _salaryOrg.remainQuota = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100); } - _salaryOrg.createdUserId = "" + _salaryOrg.createdUserId = ""; _salaryOrg.createdFullName = "System Administrator"; - _salaryOrg.lastUpdateUserId = "" + _salaryOrg.lastUpdateUserId = ""; _salaryOrg.lastUpdateFullName = "System Administrator"; _salaryOrg.createdAt = new Date(); _salaryOrg.lastUpdatedAt = new Date(); @@ -4939,7 +4973,8 @@ export class SalaryPeriodController extends Controller { ); let request: any = response.data.access_token; - if (current.getDate() == 1 && current.getMonth() == 2) { //snap1 วันที่ 1 มีนา + if (current.getDate() == 1 && current.getMonth() == 2) { + //snap1 วันที่ 1 มีนา salaryPeriod = await this.salaryPeriodRepository.findOne({ where: { year: current.getFullYear(), @@ -4950,7 +4985,8 @@ export class SalaryPeriodController extends Controller { if (salaryPeriod) { this.performSnapshotOperationForCronjob("SNAP1", salaryPeriod.id, request); } - } else if (current.getDate() == 1 && current.getMonth() == 3) { //snap2 วันที่ 1 เมษา + } else if (current.getDate() == 1 && current.getMonth() == 3) { + //snap2 วันที่ 1 เมษา salaryPeriod = await this.salaryPeriodRepository.findOne({ where: { year: current.getFullYear(), @@ -4961,7 +4997,8 @@ export class SalaryPeriodController extends Controller { if (salaryPeriod) { this.performSnapshotOperationForCronjob("SNAP2", salaryPeriod.id, request); } - } else if (current.getDate() == 1 && current.getMonth() == 8) { //snap1 วันที่ 1 กันยา + } else if (current.getDate() == 1 && current.getMonth() == 8) { + //snap1 วันที่ 1 กันยา salaryPeriod = await this.salaryPeriodRepository.findOne({ where: { year: current.getFullYear(), @@ -4972,7 +5009,8 @@ export class SalaryPeriodController extends Controller { if (salaryPeriod) { this.performSnapshotOperationForCronjob("SNAP1", salaryPeriod.id, request); } - } else if (current.getDate() == 1 && current.getMonth() == 9) { //snap2 วันที่ 1 ตุลา + } else if (current.getDate() == 1 && current.getMonth() == 9) { + //snap2 วันที่ 1 ตุลา salaryPeriod = await this.salaryPeriodRepository.findOne({ where: { year: current.getFullYear(), diff --git a/src/interfaces/permission.ts b/src/interfaces/permission.ts index 6ff8977..a47c923 100644 --- a/src/interfaces/permission.ts +++ b/src/interfaces/permission.ts @@ -94,6 +94,15 @@ class CheckAuth { child4: null, privilege: "ROOT", }; + } else if (privilege == "PARENT") { + data = { + root: [x.orgRootId], + child1: [null], + child2: null, + child3: null, + child4: null, + privilege: "PARENT", + }; } else if (privilege == "CHILD") { data = { root: node >= 0 ? [x.orgRootId] : null, @@ -103,6 +112,15 @@ class CheckAuth { child4: node >= 4 ? [x.orgChild4Id] : null, privilege: "CHILD", }; + } else if (privilege == "BROTHER") { + data = { + // root: node >= 0 ? [x.orgRootId] : null, + root: node >= 0 ? [x.orgRootId] : null, + child1: node >= 2 ? [x.orgChild1Id] : null, + child2: node >= 3 ? [x.orgChild2Id] : null, + child3: node >= 4 ? [x.orgChild3Id] : null, + privilege: "BROTHER", + }; } else if (privilege == "NORMAL") { data = { root: [x.orgRootId], @@ -185,38 +203,38 @@ class CheckAuth { } public async checkOrg(token: any, keycloakId: string) { 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 - ) + 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, + ); - const data = { - orgRootId: x.orgRootId, - orgChild1Id: x.orgChild1Id, - orgChild2Id: x.orgChild2Id, - orgChild3Id: x.orgChild3Id, - orgChild4Id: x.orgChild4Id, - } + 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");