Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 59s

This commit is contained in:
Adisak 2026-01-19 14:27:10 +07:00
commit 1fec0fcc8c

View file

@ -70,68 +70,68 @@ export class SalaryPeriodEmployeeController extends Controller {
const data = {
group1id:
salaryPeriod.salaryOrgEmployees &&
salaryPeriod.salaryOrgEmployees.find(
(x) =>
x.group == "GROUP1" &&
x.rootId == body.rootId &&
x.snapshot == body.snapshot.toLocaleUpperCase(),
) == null
salaryPeriod.salaryOrgEmployees.find(
(x) =>
x.group == "GROUP1" &&
x.rootId == body.rootId &&
x.snapshot == body.snapshot.toLocaleUpperCase(),
) == null
? null
: salaryPeriod.salaryOrgEmployees &&
salaryPeriod.salaryOrgEmployees.find(
(x) =>
x.group == "GROUP1" &&
x.rootId == body.rootId &&
x.snapshot == body.snapshot.toLocaleUpperCase(),
)?.id,
salaryPeriod.salaryOrgEmployees.find(
(x) =>
x.group == "GROUP1" &&
x.rootId == body.rootId &&
x.snapshot == body.snapshot.toLocaleUpperCase(),
)?.id,
group1IsClose:
salaryPeriod.salaryOrgEmployees &&
salaryPeriod.salaryOrgEmployees.find(
(x) =>
x.group == "GROUP1" &&
x.rootId == body.rootId &&
x.snapshot == body.snapshot.toLocaleUpperCase(),
) == null
salaryPeriod.salaryOrgEmployees.find(
(x) =>
x.group == "GROUP1" &&
x.rootId == body.rootId &&
x.snapshot == body.snapshot.toLocaleUpperCase(),
) == null
? null
: salaryPeriod.salaryOrgEmployees &&
salaryPeriod.salaryOrgEmployees.find(
(x) =>
x.group == "GROUP1" &&
x.rootId == body.rootId &&
x.snapshot == body.snapshot.toLocaleUpperCase(),
)?.isClose,
salaryPeriod.salaryOrgEmployees.find(
(x) =>
x.group == "GROUP1" &&
x.rootId == body.rootId &&
x.snapshot == body.snapshot.toLocaleUpperCase(),
)?.isClose,
group2id:
salaryPeriod.salaryOrgEmployees &&
salaryPeriod.salaryOrgEmployees.find(
(x) =>
x.group == "GROUP2" &&
x.rootId == body.rootId &&
x.snapshot == body.snapshot.toLocaleUpperCase(),
) == null
salaryPeriod.salaryOrgEmployees.find(
(x) =>
x.group == "GROUP2" &&
x.rootId == body.rootId &&
x.snapshot == body.snapshot.toLocaleUpperCase(),
) == null
? null
: salaryPeriod.salaryOrgEmployees &&
salaryPeriod.salaryOrgEmployees.find(
(x) =>
x.group == "GROUP2" &&
x.rootId == body.rootId &&
x.snapshot == body.snapshot.toLocaleUpperCase(),
)?.id,
salaryPeriod.salaryOrgEmployees.find(
(x) =>
x.group == "GROUP2" &&
x.rootId == body.rootId &&
x.snapshot == body.snapshot.toLocaleUpperCase(),
)?.id,
group2IsClose:
salaryPeriod.salaryOrgEmployees &&
salaryPeriod.salaryOrgEmployees.find(
(x) =>
x.group == "GROUP2" &&
x.rootId == body.rootId &&
x.snapshot == body.snapshot.toLocaleUpperCase(),
) == null
salaryPeriod.salaryOrgEmployees.find(
(x) =>
x.group == "GROUP2" &&
x.rootId == body.rootId &&
x.snapshot == body.snapshot.toLocaleUpperCase(),
) == null
? null
: salaryPeriod.salaryOrgEmployees &&
salaryPeriod.salaryOrgEmployees.find(
(x) =>
x.group == "GROUP2" &&
x.rootId == body.rootId &&
x.snapshot == body.snapshot.toLocaleUpperCase(),
)?.isClose,
salaryPeriod.salaryOrgEmployees.find(
(x) =>
x.group == "GROUP2" &&
x.rootId == body.rootId &&
x.snapshot == body.snapshot.toLocaleUpperCase(),
)?.isClose,
effectiveDate: salaryPeriod.effectiveDate,
period: salaryPeriod.period,
};
@ -879,8 +879,8 @@ export class SalaryPeriodEmployeeController 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,
) {
@ -1063,6 +1063,264 @@ export class SalaryPeriodEmployeeController extends Controller {
return new HttpSuccess();
}
private chunkArray<T>(arr: T[], size: number): T[][] {
const result: T[][] = [];
for (let i = 0; i < arr.length; i += size) {
result.push(arr.slice(i, i + size));
}
return result;
}
private async processEmployeeProfile(
profileId: string,
body: any,
req: RequestWithUser,
affectedOrgIds: Set<string>,
) {
let salaryProfile = await this.salaryProfileRepository.findOne({
relations: ["salaryOrg", "salaryOrg.salaryPeriod"],
where: { id: profileId },
});
if (!salaryProfile) {
throw new HttpError(
HttpStatusCode.NOT_FOUND,
"ไม่พบข้อมูลการขอเงินเดือนผู้ใช้งานนี้ในระบบ",
);
}
// ===== FULLHAFT CHECK (เดิม) =====
if (body.type === "FULLHAFT") {
if (salaryProfile.salaryOrg.salaryPeriod.period === "OCT") {
const checkPreviousType =
await this.salaryProfileRepository.findOne({
relations: ["salaryOrg", "salaryOrg.salaryPeriod"],
where: {
citizenId: salaryProfile.citizenId,
salaryOrg: {
salaryPeriod: {
period: "APR",
year: salaryProfile.salaryOrg.salaryPeriod.year,
},
snapshot: "SNAP2",
},
type: "FULL",
},
});
if (checkPreviousType) {
throw new HttpError(
HttpStatusCode.NOT_FOUND,
"ไม่สามารถเลื่อนขั้นบุคลากรเกิน 2 ขั้นต่อปีได้",
);
}
}
}
// ===== isReserve (เดิม) =====
salaryProfile.isReserve =
body.type === "FULL" ? body.isReserve : false;
// ===== Type & Level check (เดิม) =====
const Type = await this.posTypeRepository.findOne({
where: { posTypeName: salaryProfile.posType },
});
if (!Type) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบประเภทตำแหน่ง");
}
const Level = await this.posLevelRepository.findOne({
where: {
posTypeId: Type.id,
posLevelName: salaryProfile.posLevel,
},
});
if (!Level) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบระดับตำแหน่ง");
}
// ===== type / remark =====
salaryProfile.type = body.type;
salaryProfile.remark = body.remark == null ? null : body.remark;
// ===== CALC SALARY (เดิม 100%) =====
salaryProfile = await this.calSalaryNew(
salaryProfile.type,
salaryProfile,
);
// ===== audit =====
salaryProfile.lastUpdateUserId = req.user.sub;
salaryProfile.lastUpdateFullName = req.user.name;
salaryProfile.lastUpdatedAt = new Date();
const before = structuredClone(salaryProfile);
await this.salaryProfileRepository.save(salaryProfile, {
data: req,
});
setLogDataDiff(req, { before, after: salaryProfile });
affectedOrgIds.add(salaryProfile.salaryOrg.id);
}
private async recalculateSalaryOrgEmployee(
orgId: string,
req: RequestWithUser,
) {
const salaryOrg = await this.salaryOrgRepository.findOne({
where: { id: orgId },
relations: ["salaryProfiles", "salaryPeriod"],
});
if (!salaryOrg) return;
if (salaryOrg.snapshot !== "SNAP1") return;
// ===================== APR =====================
if (salaryOrg.salaryPeriod.period === "APR") {
const amountFullType =
await this.salaryProfileRepository.count({
where: {
salaryOrgId: salaryOrg.id,
type: "FULL",
},
});
salaryOrg.total = salaryOrg.salaryProfiles.length;
salaryOrg.fifteenPercent = Math.floor(
(salaryOrg.total * 15) / 100,
);
salaryOrg.fifteenPoint = (salaryOrg.total * 15) % 100;
salaryOrg.quantityUsed = amountFullType;
salaryOrg.remainQuota =
salaryOrg.fifteenPercent - amountFullType;
salaryOrg.lastUpdateUserId = req.user.sub;
salaryOrg.lastUpdateFullName = req.user.name;
salaryOrg.lastUpdatedAt = new Date();
await this.salaryOrgRepository.save(salaryOrg, { data: req });
return;
}
// ===================== OCT =====================
if (salaryOrg.salaryPeriod.period === "OCT") {
const totalProfileAmount = Extension.sumObjectValues(
salaryOrg.salaryProfiles,
"amount",
);
salaryOrg.currentAmount = totalProfileAmount;
salaryOrg.total = salaryOrg.salaryProfiles.length;
salaryOrg.sixPercentAmount = totalProfileAmount * 0.06;
// ===== APR SNAP2 spentAmount =====
let totalAmount = 0;
const salaryPeriodAPROld =
await this.salaryPeriodRepository.findOne({
where: {
year: salaryOrg.salaryPeriod.year,
period: "APR",
},
});
if (salaryPeriodAPROld) {
const salaryOrgSnap2Old =
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",
);
}
salaryOrg.spentAmount = totalAmount;
// ===== sumAmountUse (current OCT SNAP1) =====
const sumAmountUse =
await AppDataSource.getRepository(
SalaryProfileEmployee,
)
.createQueryBuilder("salaryProfileEmployee")
.select(
"SUM(salaryProfileEmployee.amountUse)",
"totalAmount",
)
.where({
salaryOrgId: salaryOrg.id,
type: In(["HAFT", "FULL", "FULLHAFT"]),
})
.getRawOne();
salaryOrg.useAmount =
sumAmountUse?.totalAmount ?? 0;
salaryOrg.remainingAmount =
salaryOrg.sixPercentAmount -
salaryOrg.useAmount -
salaryOrg.spentAmount;
salaryOrg.lastUpdateUserId = req.user.sub;
salaryOrg.lastUpdateFullName = req.user.name;
salaryOrg.lastUpdatedAt = new Date();
await this.salaryOrgRepository.save(salaryOrg, { data: req });
return;
}
}
@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_WAGE");
body.type = body.type.toUpperCase();
const BATCH_SIZE = 20;
const affectedOrgIds = new Set<string>();
const batches = this.chunkArray(body.profileId, BATCH_SIZE);
for (const batch of batches) {
await Promise.all(
batch.map(profileId =>
this.processEmployeeProfile(
profileId,
body,
req,
affectedOrgIds,
),
),
);
}
// === recalc salaryOrg ทีเดียวต่อ org ===
for (const orgId of affectedOrgIds) {
await this.recalculateSalaryOrgEmployee(orgId, req);
}
return new HttpSuccess();
}
/**
* API
*
@ -1159,21 +1417,21 @@ export class SalaryPeriodEmployeeController extends Controller {
}),
)
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")
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)
@ -2470,8 +2728,8 @@ export class SalaryPeriodEmployeeController extends Controller {
}
}
//console.log("group", group);
//console.log("step", step);
//console.log("group", group);
//console.log("step", step);
if (type == "HAFT") {
@ -2484,7 +2742,7 @@ export class SalaryPeriodEmployeeController extends Controller {
step = step + 1.5;
stepUp = 1.5;
}
//console.log("step+type", step);
//console.log("step+type", step);
//หาขั้นสูงสุดในกลุ่มนั้น
let salaryRankMax = await this.salaryRankRepository.findOne({
where: {
@ -2496,9 +2754,9 @@ export class SalaryPeriodEmployeeController extends Controller {
order: { step: "DESC" },
});
//console.log("salaryRankMax.step", salaryRankMax?.step);
//console.log("salaryProfile.amount", salaryProfile.amount);
//console.log("salaryFormula.salaryMax", salaryFormula?.salaryMax);
//console.log("salaryRankMax.step", salaryRankMax?.step);
//console.log("salaryProfile.amount", salaryProfile.amount);
//console.log("salaryFormula.salaryMax", salaryFormula?.salaryMax);
//เงินเดือนเกินตาราง
//****หา shot ที่ +ขั้น แล้วแก้เป็นหาเงินเดือนที่ใกล้เคียงกับขั้นผังเก่าก่อนแล้วค่อย +ขั้นที่เลื่อนเข้าไป ex.เงินตันที่ 20000 ไปหาผังใหม่ได้ใกล้เคียง 20100 ยึดตัวเลขนี้ไว้แล้วค่อย +ขั้นในผังใหม่ขึ้นไป
if (
@ -2510,7 +2768,7 @@ export class SalaryPeriodEmployeeController extends Controller {
salaryFormula.salaryMax != null &&
salaryFormula.salaryMax > salaryProfile.amount))
) {
//console.log("in function เกินตาราง");
//console.log("in function เกินตาราง");
group = group + 1;
//เงินเดือนในกลุ่มต่อไป
@ -2533,7 +2791,7 @@ export class SalaryPeriodEmployeeController extends Controller {
// (step - (salaryRankMax == null ? 0 : salaryRankMax.step) - 0.5);
step = (salaryRankAmount == null ? 1 : salaryRankAmount.step) + stepUp; //****หาขั้นของผังใหม่แล้ว + ด้วยขั้นที่ได้เลื่อน
//console.log("step in if", step);
//console.log("step in if", step);
}
let whereCondition: any = {