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 { 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"); } }