import { Double } from "typeorm"; import { AppDataSource } from "../database/data-source"; import HttpError from "../interfaces/http-error"; import HttpStatusCode from "../interfaces/http-status"; import { Profile } from "../entities/Profile"; import { ProfileSalary } from "../entities/ProfileSalary"; import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory"; import { ProfileAssistance } from "../entities/ProfileAssistance"; import { ProfileAssistanceHistory } from "../entities/ProfileAssistanceHistory"; import { OrgRoot } from "../entities/OrgRoot"; import { PosMaster } from "../entities/PosMaster"; import { Command } from "../entities/Command"; import { checkCommandType, removePostMasterAct, removeProfileInOrganize, setLogDataDiff, } from "../interfaces/utils"; import { CreatePosMasterHistoryOfficer } from "./PositionService"; import { deleteUser } from "../keycloak"; /** * Input: ข้อมูล 1 คนสำหรับ endpoint excexute/salary * (C-PM-13 โอน, C-PM-15 ช่วยราชการ, C-PM-16 เกษียณ/ปลดเกษียณ) */ export interface SalaryItem { profileId: string; amount?: Double | null; amountSpecial?: Double | null; positionSalaryAmount?: Double | null; mouthSalaryAmount?: Double | null; positionExecutive: string | null; positionExecutiveField?: string | null; positionArea?: string | null; positionType: string | null; positionLevel: string | null; commandId?: string | null; leaveReason?: string | null; dateLeave?: Date | string | null; isLeave?: boolean; orgRoot?: string | null; orgChild1?: string | null; orgChild2?: string | null; orgChild3?: string | null; orgChild4?: string | null; officerOrg?: string | null; dateStart?: Date | string | null; dateEnd?: Date | 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; refId?: string | null; } /** * Context สำหรับ audit/log (เหมือน ExecuteOfficerProfileService) */ export interface SalaryExecutionContext { user: { sub: string; name: string }; req?: any; } /** * Service สำหรับสร้าง ProfileSalary ของข้าราชการ + handle leave/ออกจากราชการ/ช่วยราชการ * * ใช้กับ commandType: C-PM-13 (โอน), C-PM-15 (ช่วยราชการ), C-PM-16 (เกษียณ) * * - endpoint /org/command/excexute/salary เรียกผ่าน service นี้ (thin wrapper) * - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow) * * Behavior ทั้งหมด preserve จาก CommandController.newSalaryAndUpdate ต้นฉบับ */ export class ExecuteSalaryService { private commandRepository = AppDataSource.getRepository(Command); private profileRepository = AppDataSource.getRepository(Profile); private salaryRepo = AppDataSource.getRepository(ProfileSalary); private salaryHistoryRepo = AppDataSource.getRepository(ProfileSalaryHistory); private posMasterRepository = AppDataSource.getRepository(PosMaster); private orgRootRepository = AppDataSource.getRepository(OrgRoot); private assistanceRepository = AppDataSource.getRepository(ProfileAssistance); private assistanceHistoryRepository = AppDataSource.getRepository(ProfileAssistanceHistory); /** * ประมวลผลสร้าง ProfileSalary + handle leave/assistance */ async executeSalary(data: SalaryItem[], ctx: SalaryExecutionContext): Promise { console.log("[ExecuteSalaryService] Starting executeSalary"); console.log("[ExecuteSalaryService] 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.dateLeave = toDate(it.dateLeave); it.dateStart = toDate(it.dateStart); it.dateEnd = toDate(it.dateEnd); it.commandDateAffect = toDate(it.commandDateAffect); it.commandDateSign = toDate(it.commandDateSign); } let _posNumCodeSit: string = ""; let _posNumCodeSitAbb: string = ""; const _command = await this.commandRepository.findOne({ relations: ["commandType"], 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.profileRepository.findOne({ where: { id: item.profileId }, relations: { roleKeycloaks: true, posType: true, posLevel: true, }, }); if (!profile) { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); } const posMaster: any = await this.posMasterRepository.findOne({ where: { current_holderId: item.profileId, orgRevision: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false, }, }, relations: { orgRevision: true, orgRoot: true, orgChild1: true, orgChild2: true, orgChild3: true, orgChild4: true, }, }); const orgRevisionRef = posMaster ? posMaster.id : null; const orgRootRef = orgRevisionRef?.orgRoot ?? null; const orgChild1Ref = orgRevisionRef?.orgChild1 ?? null; const orgChild2Ref = orgRevisionRef?.orgChild2 ?? null; const orgChild3Ref = orgRevisionRef?.orgChild3 ?? null; const orgChild4Ref = orgRevisionRef?.orgChild4 ?? null; //ลบตำแหน่งที่รักษาการแทน const code = _command?.commandType?.code; if (code && ["C-PM-13"].includes(code)) { removePostMasterAct(profile.id); } let _commandYear = item.commandYear; if (item.commandYear) { _commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543; } const dest_item = await this.salaryRepo.findOne({ where: { profileId: 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(), }; if (item.isLeave != undefined && item.isLeave == true) { await CreatePosMasterHistoryOfficer(orgRevisionRef, req, "DELETE"); await removeProfileInOrganize(profile.id, "OFFICER"); } const clearProfile = await checkCommandType(String(item.commandId)); const _null: any = null; if (clearProfile.status) { if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) { const delUserKeycloak = await deleteUser(profile.keycloak); if (delUserKeycloak) { // Task #228 // profile.keycloak = _null; profile.roleKeycloaks = []; profile.isActive = false; profile.isDelete = true; } } profile.isLeave = item.isLeave; profile.leaveCommandId = item.commandId ?? _null; profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`; profile.leaveRemark = clearProfile.leaveRemark ?? _null; profile.leaveDate = item.commandDateAffect ?? _null; profile.leaveType = clearProfile.LeaveType ?? _null; //ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516) // profile.position = _null; // profile.posTypeId = _null; // profile.posLevelId = _null; profile.leaveReason = item.leaveReason ?? _null; profile.dateLeave = item.dateLeave ?? _null; profile.amount = item.amount ?? _null; profile.amountSpecial = item.amountSpecial ?? _null; await this.profileRepository.save(profile, { data: req }); // if (profile.id) { // await this.keycloakAttributeService.clearOrgDnaAttributes( // [profile.id], // "PROFILE", // ); // } } Object.assign(dataSalary, { ...item, ...meta }); 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 }); if (_command) { if (["C-PM-15", "C-PM-16"].includes(_command.commandType.code)) { // ประวัติคำสั่งให้ช่วยราชการ const dataAssis = new ProfileAssistance(); const metaAssis = { profileId: item.profileId, agency: item.officerOrg, dateStart: item.dateStart, dateEnd: item.dateEnd, commandNo: `${item.commandNo}/${_commandYear}`, commandName: item.commandName, refId: item.refId, refCommandDate: new Date(), commandId: item.commandId, createdUserId: ctx.user.sub, createdFullName: ctx.user.name, lastUpdateUserId: ctx.user.sub, lastUpdateFullName: ctx.user.name, createdAt: new Date(), lastUpdatedAt: new Date(), status: _command.commandType.code == "C-PM-15" ? "PENDING" : "DONE", }; Object.assign(dataAssis, metaAssis); const historyAssis = new ProfileAssistanceHistory(); Object.assign(historyAssis, { ...dataAssis, id: undefined }); await this.assistanceRepository.save(dataAssis); historyAssis.profileAssistanceId = dataAssis.id; await this.assistanceHistoryRepository.save(historyAssis); } // Task #2190 else if (_command.commandType.code == "C-PM-13") { let organizeName = ""; if (orgRootRef) { const names = [ orgChild4Ref?.orgChild4Name, orgChild3Ref?.orgChild3Name, orgChild2Ref?.orgChild2Name, orgChild1Ref?.orgChild1Name, orgRootRef?.orgRootName, ].filter(Boolean); organizeName = names.join(" "); } } } }), ); console.log("[ExecuteSalaryService] executeSalary completed successfully"); } }