hrms-api-org/src/services/ExecuteSalaryEmployeeCurrentService.ts
harid 616ccf9e64
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m10s
Linear Flow (PlacementService) #224
2026-06-18 18:29:03 +07:00

262 lines
11 KiB
TypeScript

import { Double } from "typeorm";
import { AppDataSource } from "../database/data-source";
import HttpError from "../interfaces/http-error";
import HttpStatusCode from "../interfaces/http-status";
import HttpStatus from "../interfaces/http-status";
import { Profile } from "../entities/Profile";
import { ProfileEmployee } from "../entities/ProfileEmployee";
import { ProfileSalary } from "../entities/ProfileSalary";
import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory";
import { OrgRoot } from "../entities/OrgRoot";
import { EmployeePosMaster } from "../entities/EmployeePosMaster";
import { EmployeePosition } from "../entities/EmployeePosition";
import { Command } from "../entities/Command";
import { setLogDataDiff } from "../interfaces/utils";
import { CreatePosMasterHistoryEmployee } from "./PositionService";
/**
* Input: ข้อมูล 1 คนสำหรับ endpoint excexute/salary-employee-current
* (C-PM-22, 24 — เปลี่ยนตำแหน่งปัจจุบันของลูกจ้าง + salary ใหม่)
*/
export interface SalaryEmployeeCurrentItem {
profileId: string;
amount?: Double | null;
amountSpecial?: Double | null;
positionSalaryAmount?: Double | null;
mouthSalaryAmount?: Double | null;
positionType: string | null;
positionLevel: string | null;
posmasterId: string;
positionId: string;
commandId?: string | null;
orgRoot?: string | null;
orgChild1?: string | null;
orgChild2?: string | null;
orgChild3?: string | null;
orgChild4?: string | null;
commandNo: string | null;
commandYear: number | null;
posNo: string | null;
posNoAbb: string | null;
commandDateAffect?: Date | string | null;
commandDateSign?: Date | string | null;
positionName: string | null;
commandCode?: string | null;
commandName?: string | null;
remark: string | null;
}
/**
* Context สำหรับ audit/log
*/
export interface SalaryEmployeeCurrentExecutionContext {
user: { sub: string; name: string };
req?: any;
}
/**
* Service สำหรับสร้าง ProfileSalary ของลูกจ้าง + อัปเดตตำแหน่งปัจจุบัน (เปลี่ยนตำแหน่ง)
*
* ใช้กับ commandType: C-PM-22, 24
*
* - endpoint /org/command/excexute/salary-employee-current เรียกผ่าน service นี้ (thin wrapper)
* - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow)
*
* Behavior ทั้งหมด preserve จาก CommandController.newSalaryEmployeeAndUpdateCurrent ต้นฉบับ
*/
export class ExecuteSalaryEmployeeCurrentService {
private commandRepository = AppDataSource.getRepository(Command);
private profileRepository = AppDataSource.getRepository(Profile);
private profileEmployeeRepository = AppDataSource.getRepository(ProfileEmployee);
private salaryRepo = AppDataSource.getRepository(ProfileSalary);
private salaryHistoryRepo = AppDataSource.getRepository(ProfileSalaryHistory);
private employeePosMasterRepository = AppDataSource.getRepository(EmployeePosMaster);
private employeePositionRepository = AppDataSource.getRepository(EmployeePosition);
private orgRootRepository = AppDataSource.getRepository(OrgRoot);
/**
* ประมวลผลสร้าง ProfileSalary + อัปเดตตำแหน่งปัจจุบันของลูกจ้าง
*/
async executeSalaryEmployeeCurrent(
data: SalaryEmployeeCurrentItem[],
ctx: SalaryEmployeeCurrentExecutionContext,
): Promise<void> {
console.log("[ExecuteSalaryEmployeeCurrentService] Starting executeSalaryEmployeeCurrent");
console.log("[ExecuteSalaryEmployeeCurrentService] Request body count:", data?.length);
const req = ctx.req;
// ─────────────────────────────────────────────────────────────
// Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date)
// ─────────────────────────────────────────────────────────────
const toDate = (v: any): Date | null => {
if (v == null || v === "") return null;
if (v instanceof Date) return isNaN(v.getTime()) ? null : v;
const d = new Date(v);
return isNaN(d.getTime()) ? null : d;
};
for (const item of data ?? []) {
const it = item as any;
it.commandDateAffect = toDate(it.commandDateAffect);
it.commandDateSign = toDate(it.commandDateSign);
}
let _posNumCodeSit: string = "";
let _posNumCodeSitAbb: string = "";
const _command = await this.commandRepository.findOne({
where: { id: data.find((x) => x.commandId)?.commandId ?? "" },
});
if (_command) {
if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") {
const orgRootDeputy = await this.orgRootRepository.findOne({
where: {
isDeputy: true,
orgRevision: {
orgRevisionIsCurrent: true,
orgRevisionIsDraft: false,
},
},
relations: ["orgRevision"],
});
_posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร";
_posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป.";
} else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") {
_posNumCodeSit = "กรุงเทพมหานคร";
_posNumCodeSitAbb = "กทม.";
} else {
let _profileAdmin = await this.profileRepository.findOne({
where: {
keycloak: _command?.createdUserId.toString(),
current_holders: {
orgRevision: {
orgRevisionIsCurrent: true,
orgRevisionIsDraft: false,
},
},
},
relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"],
});
_posNumCodeSit =
_profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ??
"";
_posNumCodeSitAbb =
_profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot
.orgRootShortName ?? "";
}
}
await Promise.all(
data.map(async (item) => {
const profile: any = await this.profileEmployeeRepository.findOneBy({ id: item.profileId });
if (!profile) {
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
}
const dest_item = await this.salaryRepo.findOne({
where: { profileEmployeeId: item.profileId },
order: { order: "DESC" },
});
const before = null;
const dataSalary = new ProfileSalary();
dataSalary.posNumCodeSit = _posNumCodeSit;
dataSalary.posNumCodeSitAbb = _posNumCodeSitAbb;
const meta = {
order: dest_item == null ? 1 : dest_item.order + 1,
createdUserId: ctx.user.sub,
createdFullName: ctx.user.name,
lastUpdateUserId: ctx.user.sub,
lastUpdateFullName: ctx.user.name,
createdAt: new Date(),
lastUpdatedAt: new Date(),
};
Object.assign(dataSalary, {
...item,
...meta,
profileEmployeeId: item.profileId,
profileId: undefined,
});
const history = new ProfileSalaryHistory();
Object.assign(history, { ...dataSalary, id: undefined });
await this.salaryRepo.save(dataSalary, { data: req });
setLogDataDiff(req, { before, after: dataSalary });
history.profileSalaryId = dataSalary.id;
await this.salaryHistoryRepo.save(history, { data: req });
const posMaster = await this.employeePosMasterRepository.findOne({
where: { id: item.posmasterId },
relations: ["orgRoot"],
});
if (posMaster == null)
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้");
const posMasterOld = await this.employeePosMasterRepository.findOne({
where: {
current_holderId: item.profileId,
orgRevisionId: posMaster.orgRevisionId,
},
});
if (posMasterOld != null) {
posMasterOld.current_holderId = null;
posMasterOld.lastUpdatedAt = new Date();
}
// if (posMasterOld != null) posMasterOld.next_holderId = null;
const positionOld = await this.employeePositionRepository.findOne({
where: {
posMasterId: posMasterOld?.id,
positionIsSelected: true,
},
});
if (positionOld != null) {
positionOld.positionIsSelected = false;
await this.employeePositionRepository.save(positionOld);
}
const checkPosition = await this.employeePositionRepository.find({
where: {
posMasterId: item.posmasterId,
positionIsSelected: true,
},
});
if (checkPosition.length > 0) {
const clearPosition = checkPosition.map((positions) => ({
...positions,
positionIsSelected: false,
}));
await this.employeePositionRepository.save(clearPosition);
}
posMaster.current_holderId = item.profileId;
posMaster.lastUpdatedAt = new Date();
posMaster.next_holderId = null;
if (posMasterOld != null) {
await this.employeePosMasterRepository.save(posMasterOld);
await CreatePosMasterHistoryEmployee(posMasterOld.id, req);
}
await this.employeePosMasterRepository.save(posMaster);
const positionNew = await this.employeePositionRepository.findOne({
where: {
id: item.positionId,
posMasterId: item.posmasterId,
},
});
if (positionNew != null) {
positionNew.positionIsSelected = true;
profile.posLevelId = positionNew.posLevelId;
profile.posTypeId = positionNew.posTypeId;
profile.position = positionNew.positionName;
profile.employeeOc = posMaster?.orgRoot?.orgRootName ?? null;
profile.positionEmployeePositionId = positionNew.positionName;
profile.amount = item.amount ?? null;
profile.amountSpecial = item.amountSpecial ?? null;
await this.profileEmployeeRepository.save(profile);
await this.employeePositionRepository.save(positionNew);
}
await CreatePosMasterHistoryEmployee(posMaster.id, req);
}),
);
console.log("[ExecuteSalaryEmployeeCurrentService] executeSalaryEmployeeCurrent completed successfully");
}
}