add transaction #224
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m10s
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m10s
This commit is contained in:
parent
ecd0388eb0
commit
832c5d2cb3
10 changed files with 2322 additions and 1991 deletions
|
|
@ -106,7 +106,7 @@ import { reOrderCommandRecivesAndDelete } from "../services/CommandService";
|
|||
import { RetirementService } from "../services/RetirementService";
|
||||
import { ExecuteOfficerProfileService } from "../services/ExecuteOfficerProfileService";
|
||||
import { ExecuteSalaryService } from "../services/ExecuteSalaryService";
|
||||
import { ExecuteSalaryCurrentService, ExecuteSalaryResult } from "../services/ExecuteSalaryCurrentService";
|
||||
import { ExecuteSalaryCurrentService } from "../services/ExecuteSalaryCurrentService";
|
||||
import { ExecuteSalaryEmployeeCurrentService } from "../services/ExecuteSalaryEmployeeCurrentService";
|
||||
import { ExecuteSalaryLeaveService } from "../services/ExecuteSalaryLeaveService";
|
||||
import { ExecuteSalaryEmployeeLeaveService } from "../services/ExecuteSalaryEmployeeLeaveService";
|
||||
|
|
@ -3702,14 +3702,11 @@ export class CommandController extends Controller {
|
|||
}[];
|
||||
},
|
||||
) {
|
||||
const result: ExecuteSalaryResult = await new ExecuteSalaryCurrentService().executeSalaryCurrent(
|
||||
body.data,
|
||||
{
|
||||
user: { sub: req.user.sub, name: req.user.name },
|
||||
req,
|
||||
},
|
||||
);
|
||||
return new HttpSuccess(result);
|
||||
await new ExecuteSalaryCurrentService().executeSalaryCurrent(body.data, {
|
||||
user: { sub: req.user.sub, name: req.user.name },
|
||||
req,
|
||||
});
|
||||
return new HttpSuccess();
|
||||
}
|
||||
|
||||
@Post("excexute/salary-employee-current")
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { PosMaster } from "../entities/PosMaster";
|
|||
import { Position } from "../entities/Position";
|
||||
import { EmployeePosMaster } from "../entities/EmployeePosMaster";
|
||||
import { EmployeePosition } from "../entities/EmployeePosition";
|
||||
import { In, IsNull, MoreThan, Not } from "typeorm";
|
||||
import { EntityManager, In, IsNull, MoreThan, Not } from "typeorm";
|
||||
import { RequestWithUser } from "../middlewares/user";
|
||||
import { Command } from "../entities/Command";
|
||||
import { ProfileSalary } from "../entities/ProfileSalary";
|
||||
|
|
@ -254,14 +254,23 @@ export function calculateRetireYear(birthDate: Date) {
|
|||
|
||||
return yy + 61;
|
||||
}
|
||||
export async function removeProfileInOrganize(profileId: string, type: string) {
|
||||
const currentRevision = await AppDataSource.getRepository(OrgRevision)
|
||||
export async function removeProfileInOrganize(
|
||||
profileId: string,
|
||||
type: string,
|
||||
manager?: EntityManager,
|
||||
) {
|
||||
// ถ้าส่ง manager เข้ามา → ทุก query/update อยู่ใน transaction ของ caller (all-or-nothing)
|
||||
// ถ้าไม่ส่ง → ใช้ global DataSource เหมือนเดิม (backward compatible)
|
||||
const ds = manager ?? AppDataSource;
|
||||
const currentRevision = await ds
|
||||
.getRepository(OrgRevision)
|
||||
.createQueryBuilder("orgRevision")
|
||||
.where("orgRevision.orgRevisionIsDraft = false")
|
||||
.andWhere("orgRevision.orgRevisionIsCurrent = true")
|
||||
.getOne();
|
||||
|
||||
const draftRevision = await AppDataSource.getRepository(OrgRevision)
|
||||
const draftRevision = await ds
|
||||
.getRepository(OrgRevision)
|
||||
.createQueryBuilder("orgRevision")
|
||||
.where("orgRevision.orgRevisionIsDraft = true")
|
||||
.andWhere("orgRevision.orgRevisionIsCurrent = false")
|
||||
|
|
@ -271,26 +280,30 @@ export async function removeProfileInOrganize(profileId: string, type: string) {
|
|||
return;
|
||||
}
|
||||
if (type === "OFFICER") {
|
||||
const findProfileInposMaster = await AppDataSource.getRepository(PosMaster)
|
||||
const findProfileInposMaster = await ds
|
||||
.getRepository(PosMaster)
|
||||
.createQueryBuilder("posMaster")
|
||||
.where("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: currentRevision?.id })
|
||||
.andWhere("posMaster.current_holderId = :profileId", { profileId })
|
||||
.getOne();
|
||||
|
||||
await AppDataSource.getRepository(PosMaster)
|
||||
await ds
|
||||
.getRepository(PosMaster)
|
||||
.createQueryBuilder()
|
||||
.update(PosMaster)
|
||||
.set({ current_holderId: null, isSit: false })
|
||||
.where("id = :id", { id: findProfileInposMaster?.id })
|
||||
.execute();
|
||||
|
||||
const findProfileInposMasterDraft = await AppDataSource.getRepository(PosMaster)
|
||||
const findProfileInposMasterDraft = await ds
|
||||
.getRepository(PosMaster)
|
||||
.createQueryBuilder("posMaster")
|
||||
.where("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: draftRevision?.id })
|
||||
.andWhere("posMaster.next_holderId = :profileId", { profileId })
|
||||
.getOne();
|
||||
|
||||
await AppDataSource.getRepository(PosMaster)
|
||||
await ds
|
||||
.getRepository(PosMaster)
|
||||
.createQueryBuilder()
|
||||
.update(PosMaster)
|
||||
.set({ next_holderId: null, isSit: false })
|
||||
|
|
@ -300,7 +313,8 @@ export async function removeProfileInOrganize(profileId: string, type: string) {
|
|||
if (!findProfileInposMaster && !findProfileInposMasterDraft) {
|
||||
return;
|
||||
}
|
||||
const findPosition = await AppDataSource.getRepository(Position)
|
||||
const findPosition = await ds
|
||||
.getRepository(Position)
|
||||
.createQueryBuilder("position")
|
||||
.where("position.posMasterId = :posMasterId", { posMasterId: findProfileInposMaster?.id })
|
||||
.getMany();
|
||||
|
|
@ -308,7 +322,8 @@ export async function removeProfileInOrganize(profileId: string, type: string) {
|
|||
if (!findPosition) {
|
||||
return;
|
||||
}
|
||||
await AppDataSource.getRepository(Position)
|
||||
await ds
|
||||
.getRepository(Position)
|
||||
.createQueryBuilder()
|
||||
.update(Position)
|
||||
.set({ positionIsSelected: false })
|
||||
|
|
@ -316,14 +331,16 @@ export async function removeProfileInOrganize(profileId: string, type: string) {
|
|||
.execute();
|
||||
}
|
||||
if (type === "EMPLOYEE") {
|
||||
const findProfileInEmpPosMaster = await AppDataSource.getRepository(EmployeePosMaster)
|
||||
const findProfileInEmpPosMaster = await ds
|
||||
.getRepository(EmployeePosMaster)
|
||||
.createQueryBuilder("employeePosMaster")
|
||||
.where("employeePosMaster.orgRevisionId = :orgRevisionId", {
|
||||
orgRevisionId: currentRevision?.id,
|
||||
})
|
||||
.andWhere("employeePosMaster.current_holderId = :profileId", { profileId })
|
||||
.getOne();
|
||||
await AppDataSource.getRepository(EmployeePosMaster)
|
||||
await ds
|
||||
.getRepository(EmployeePosMaster)
|
||||
.createQueryBuilder()
|
||||
.update(EmployeePosMaster)
|
||||
.set({ current_holderId: null, isSit: false })
|
||||
|
|
@ -333,7 +350,8 @@ export async function removeProfileInOrganize(profileId: string, type: string) {
|
|||
if (!findProfileInEmpPosMaster) {
|
||||
return;
|
||||
}
|
||||
const findEmpPosition = await AppDataSource.getRepository(EmployeePosition)
|
||||
const findEmpPosition = await ds
|
||||
.getRepository(EmployeePosition)
|
||||
.createQueryBuilder("employeePosition")
|
||||
.where("employeePosition.posMasterId = :posMasterId", {
|
||||
posMasterId: findProfileInEmpPosMaster?.id,
|
||||
|
|
@ -344,7 +362,8 @@ export async function removeProfileInOrganize(profileId: string, type: string) {
|
|||
return;
|
||||
}
|
||||
|
||||
await AppDataSource.getRepository(EmployeePosition)
|
||||
await ds
|
||||
.getRepository(EmployeePosition)
|
||||
.createQueryBuilder()
|
||||
.update(EmployeePosition)
|
||||
.set({ positionIsSelected: false })
|
||||
|
|
@ -353,8 +372,10 @@ export async function removeProfileInOrganize(profileId: string, type: string) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function removePostMasterAct(profileId: string) {
|
||||
const currentRevision = await AppDataSource.getRepository(OrgRevision)
|
||||
export async function removePostMasterAct(profileId: string, manager?: EntityManager) {
|
||||
const ds = manager ?? AppDataSource;
|
||||
const currentRevision = await ds
|
||||
.getRepository(OrgRevision)
|
||||
.createQueryBuilder("orgRevision")
|
||||
.where("orgRevision.orgRevisionIsDraft = false")
|
||||
.andWhere("orgRevision.orgRevisionIsCurrent = true")
|
||||
|
|
@ -364,7 +385,8 @@ export async function removePostMasterAct(profileId: string) {
|
|||
return;
|
||||
}
|
||||
|
||||
const findProfileInposMaster = await AppDataSource.getRepository(PosMaster)
|
||||
const findProfileInposMaster = await ds
|
||||
.getRepository(PosMaster)
|
||||
.createQueryBuilder("posMaster")
|
||||
.where("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: currentRevision?.id })
|
||||
.andWhere("posMaster.current_holderId = :profileId", { profileId })
|
||||
|
|
@ -374,11 +396,12 @@ export async function removePostMasterAct(profileId: string) {
|
|||
return;
|
||||
}
|
||||
|
||||
const posMasterAct = await AppDataSource.getRepository(PosMasterAct)
|
||||
const posMasterAct = await ds
|
||||
.getRepository(PosMasterAct)
|
||||
.createQueryBuilder("posMasterAct")
|
||||
.where("posMasterAct.posMasterChildId = :posMasterChildId", { posMasterChildId: findProfileInposMaster.id })
|
||||
.getMany();
|
||||
await AppDataSource.getRepository(PosMasterAct).remove(posMasterAct);
|
||||
await ds.getRepository(PosMasterAct).remove(posMasterAct);
|
||||
}
|
||||
|
||||
export async function checkReturnCommandType(commandId: string) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { CommandRecive } from "../entities/CommandRecive";
|
|||
import { Command } from "../entities/Command";
|
||||
import { OrgRoot } from "../entities/OrgRoot";
|
||||
import { Profile } from "../entities/Profile";
|
||||
import { EntityManager } from "typeorm";
|
||||
|
||||
export interface PosNumCodeSitResult {
|
||||
posNumCodeSit: string;
|
||||
|
|
@ -17,43 +18,42 @@ export interface PosNumCodeSitResult {
|
|||
* เรียงลำดับผู้ได้รับคำสั่งใหม่หลังจากลบรายการ และอัพเดทสถานะคำสั่งถ้าไม่มีผู้ได้รับคำสั่งเหลือ
|
||||
* @param reciveId commandRecive.Id ของผู้ได้รับคำสั่ง
|
||||
* @param code ประเภทคำสั่ง
|
||||
* @param manager ถ้าส่งเข้ามา → ทุก operation อยู่ใน transaction ของ caller (all-or-nothing)
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
export async function reOrderCommandRecivesAndDelete(
|
||||
reciveId: string
|
||||
reciveId: string,
|
||||
manager?: EntityManager,
|
||||
): Promise<void> {
|
||||
const commandReciveRepo = AppDataSource.getRepository(CommandRecive);
|
||||
const commandRepo = AppDataSource.getRepository(Command);
|
||||
const ds = manager ?? AppDataSource;
|
||||
const commandReciveRepo = ds.getRepository(CommandRecive);
|
||||
const commandRepo = ds.getRepository(Command);
|
||||
|
||||
// ค้นหาข้อมูลผู้ได้รับคำสั่งตาม reciveId
|
||||
const commandRecive = await commandReciveRepo.findOne({
|
||||
where: { id: reciveId }
|
||||
where: { id: reciveId },
|
||||
});
|
||||
|
||||
if (commandRecive == null)
|
||||
return;
|
||||
if (commandRecive == null) return;
|
||||
|
||||
const commandId = commandRecive.commandId;
|
||||
// ลบตาม refId
|
||||
await commandReciveRepo.delete(commandRecive.id);
|
||||
|
||||
|
||||
const commandReciveList = await commandReciveRepo.find({
|
||||
where: { commandId: commandId },
|
||||
order: { order: "ASC" },
|
||||
});
|
||||
// ลำดับผู้ได้รับคำสั่งใหม่
|
||||
if (commandReciveList.length > 0) {
|
||||
await Promise.all(
|
||||
commandReciveList.map(async (p, i) => {
|
||||
p.order = i + 1;
|
||||
await commandReciveRepo.save(p);
|
||||
})
|
||||
);
|
||||
for (let i = 0; i < commandReciveList.length; i++) {
|
||||
commandReciveList[i].order = i + 1;
|
||||
await commandReciveRepo.save(commandReciveList[i]);
|
||||
}
|
||||
} else {
|
||||
// ถ้าไม่มีผู้ได้รับคำสั่งเหลือเลย ให้ยกเลิกคำสั่ง
|
||||
await commandRepo.update({ id: commandId }, { status: "CANCEL" });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -60,17 +60,6 @@ export interface SalaryCurrentExecutionContext {
|
|||
req?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* ผลลัพธ์การประมวลผล batch — all-or-nothing (single transaction ครอบทั้ง batch)
|
||||
* ถ้าทุกคนสำเร็จจะ return result; ถ้ามีคนใด throw จะ rollback ทั้ง batch
|
||||
* และ propagate error ออกไป (caller เห็นเป็น failure ทั้งหมด)
|
||||
*/
|
||||
export interface ExecuteSalaryResult {
|
||||
successCount: number;
|
||||
failureCount: number;
|
||||
failures: { profileId: string; reason: string }[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Service สำหรับสร้าง ProfileSalary ของข้าราชการ + อัปเดตตำแหน่งปัจจุบัน (เปลี่ยนตำแหน่ง)
|
||||
*
|
||||
|
|
@ -98,7 +87,7 @@ export class ExecuteSalaryCurrentService {
|
|||
async executeSalaryCurrent(
|
||||
data: SalaryCurrentItem[],
|
||||
ctx: SalaryCurrentExecutionContext,
|
||||
): Promise<ExecuteSalaryResult> {
|
||||
): Promise<void> {
|
||||
const commandId = data?.find((x) => x.commandId)?.commandId ?? "unknown";
|
||||
const commandCode = data?.find((x) => x.commandCode)?.commandCode ?? "unknown";
|
||||
console.log(
|
||||
|
|
@ -170,12 +159,10 @@ export class ExecuteSalaryCurrentService {
|
|||
// ทุกคนใช้ manager ตัวเดียวกัน — คนใด throw จะ rollback ทั้ง batch
|
||||
// และ propagate error ออกไป (ล้มเหลวทั้งหมด) โดย log error ของคนที่ทำให้ fail ก่อน rethrow
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
let successCount = 0;
|
||||
await AppDataSource.transaction(async (manager) => {
|
||||
for (const item of data ?? []) {
|
||||
try {
|
||||
await this.processOne(item, ctx, manager, _posNumCodeSit, _posNumCodeSitAbb);
|
||||
successCount++;
|
||||
} catch (err) {
|
||||
const reason =
|
||||
err instanceof HttpError
|
||||
|
|
@ -191,12 +178,6 @@ export class ExecuteSalaryCurrentService {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log(
|
||||
`[ExecuteSalaryCurrentService] executeSalaryCurrent completed — success: ${successCount}, failure: 0`,
|
||||
);
|
||||
|
||||
return { successCount, failureCount: 0, failures: [] };
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -373,10 +354,10 @@ export class ExecuteSalaryCurrentService {
|
|||
if (posMasterOld != null) {
|
||||
await posMasterRepository.save(posMasterOld);
|
||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
||||
await CreatePosMasterHistoryOfficer(posMasterOld.id, req, null, null, manager);
|
||||
console.log(
|
||||
`[ExecuteSalaryCurrentService] PosMasterOldId: ${posMasterOld.id}, profileId: ${item.profileId}`,
|
||||
`[ExecuteSalaryCurrentService] Creating PosMasterHistory — posMasterId: ${posMasterOld.id}, profileId: ${item.profileId} (old)`,
|
||||
);
|
||||
await CreatePosMasterHistoryOfficer(posMasterOld.id, req, null, null, manager);
|
||||
}
|
||||
await posMasterRepository.save(posMaster);
|
||||
|
||||
|
|
@ -504,11 +485,11 @@ export class ExecuteSalaryCurrentService {
|
|||
profile.amountSpecial = item.amountSpecial ?? null;
|
||||
await profileRepository.save(profile);
|
||||
await positionRepository.save(positionNew);
|
||||
console.log(
|
||||
`[ExecuteSalaryCurrentService] Applied new position — profileId: ${item.profileId}, positionId: ${positionNew.id}, posMasterId: ${posMaster.id}`,
|
||||
);
|
||||
}
|
||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
||||
console.log(
|
||||
`[ExecuteSalaryCurrentService] Creating PosMasterHistory — posMasterId: ${posMaster.id}, profileId: ${item.profileId}`,
|
||||
);
|
||||
await CreatePosMasterHistoryOfficer(posMaster.id, req, null, null, manager);
|
||||
|
||||
console.log(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Double } from "typeorm";
|
||||
import { Double, EntityManager } from "typeorm";
|
||||
import { AppDataSource } from "../database/data-source";
|
||||
import HttpError from "../interfaces/http-error";
|
||||
import HttpStatusCode from "../interfaces/http-status";
|
||||
|
|
@ -63,28 +63,29 @@ export interface SalaryEmployeeCurrentExecutionContext {
|
|||
* - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow)
|
||||
*
|
||||
* Behavior ทั้งหมด preserve จาก CommandController.newSalaryEmployeeAndUpdateCurrent ต้นฉบับ
|
||||
*
|
||||
* Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential)
|
||||
* ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด)
|
||||
* ถ้าทุกคนสำเร็จจะ return result รายงาน success count
|
||||
*/
|
||||
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 + อัปเดตตำแหน่งปัจจุบันของลูกจ้าง
|
||||
* ประมวลผลสร้าง ProfileSalary + อัปเดตตำแหน่งปัจจุบันของลูกจ้างทั้ง batch
|
||||
*/
|
||||
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;
|
||||
const commandId = data?.find((x) => x.commandId)?.commandId ?? "unknown";
|
||||
const commandCode = data?.find((x) => x.commandCode)?.commandCode ?? "unknown";
|
||||
console.log(
|
||||
`[ExecuteSalaryEmployeeCurrentService] Starting executeSalaryEmployeeCurrent — commandCode: ${commandCode}, commandId: ${commandId}`,
|
||||
);
|
||||
console.log(`[ExecuteSalaryEmployeeCurrentService] Request body count: ${data?.length ?? 0}`);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date)
|
||||
|
|
@ -144,119 +145,172 @@ export class ExecuteSalaryEmployeeCurrentService {
|
|||
.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 ดังกล่าว");
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Single transaction ครอบทั้ง batch (all-or-nothing)
|
||||
// ทุกคนใช้ manager ตัวเดียวกัน — คนใด throw จะ rollback ทั้ง batch
|
||||
// และ propagate error ออกไป (ล้มเหลวทั้งหมด) โดย log error ของคนที่ทำให้ fail ก่อน rethrow
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
await AppDataSource.transaction(async (manager) => {
|
||||
for (const item of data ?? []) {
|
||||
try {
|
||||
await this.processOne(item, ctx, manager, _posNumCodeSit, _posNumCodeSitAbb);
|
||||
} catch (err) {
|
||||
const reason =
|
||||
err instanceof HttpError
|
||||
? err.message
|
||||
: err instanceof Error
|
||||
? err.message
|
||||
: "unexpected error";
|
||||
console.error(
|
||||
`[ExecuteSalaryEmployeeCurrentService] Failed commandCode=${commandCode}, commandId=${commandId}, profileId=${item.profileId}: ${reason}`,
|
||||
err,
|
||||
);
|
||||
throw err; // → rollback ทั้ง transaction + propagate เป็น batch failure
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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(),
|
||||
};
|
||||
/**
|
||||
* ประมวลผล 1 คน ภายใน transaction เดียว (manager)
|
||||
* ทุก save ใช้ manager.getRepository(...) เพื่อให้อยู่ใน transaction เดียวกัน
|
||||
* ถ้า throw ระหว่างทาง → rollback ทั้งหมดของคนนี้ + ทั้ง batch (กัน partial commit)
|
||||
*/
|
||||
private async processOne(
|
||||
item: SalaryEmployeeCurrentItem,
|
||||
ctx: SalaryEmployeeCurrentExecutionContext,
|
||||
manager: EntityManager,
|
||||
_posNumCodeSit: string,
|
||||
_posNumCodeSitAbb: string,
|
||||
): Promise<void> {
|
||||
const req = ctx.req;
|
||||
|
||||
Object.assign(dataSalary, {
|
||||
...item,
|
||||
...meta,
|
||||
profileEmployeeId: item.profileId,
|
||||
profileId: undefined,
|
||||
});
|
||||
const history = new ProfileSalaryHistory();
|
||||
Object.assign(history, { ...dataSalary, id: undefined });
|
||||
const profileEmployeeRepository = manager.getRepository(ProfileEmployee);
|
||||
const salaryRepo = manager.getRepository(ProfileSalary);
|
||||
const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory);
|
||||
const employeePosMasterRepository = manager.getRepository(EmployeePosMaster);
|
||||
const employeePositionRepository = manager.getRepository(EmployeePosition);
|
||||
|
||||
await this.salaryRepo.save(dataSalary, { data: req });
|
||||
setLogDataDiff(req, { before, after: dataSalary });
|
||||
history.profileSalaryId = dataSalary.id;
|
||||
await this.salaryHistoryRepo.save(history, { data: req });
|
||||
const profile: any = await profileEmployeeRepository.findOneBy({ id: item.profileId });
|
||||
if (!profile) {
|
||||
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
|
||||
}
|
||||
|
||||
const posMaster = await this.employeePosMasterRepository.findOne({
|
||||
where: { id: item.posmasterId },
|
||||
relations: ["orgRoot"],
|
||||
});
|
||||
if (posMaster == null)
|
||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้");
|
||||
const dest_item = await 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(),
|
||||
};
|
||||
|
||||
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;
|
||||
Object.assign(dataSalary, {
|
||||
...item,
|
||||
...meta,
|
||||
profileEmployeeId: item.profileId,
|
||||
profileId: undefined,
|
||||
});
|
||||
const history = new ProfileSalaryHistory();
|
||||
Object.assign(history, { ...dataSalary, id: undefined });
|
||||
|
||||
const positionOld = await this.employeePositionRepository.findOne({
|
||||
where: {
|
||||
posMasterId: posMasterOld?.id,
|
||||
positionIsSelected: true,
|
||||
},
|
||||
});
|
||||
if (positionOld != null) {
|
||||
positionOld.positionIsSelected = false;
|
||||
await this.employeePositionRepository.save(positionOld);
|
||||
}
|
||||
await salaryRepo.save(dataSalary, { data: req });
|
||||
setLogDataDiff(req, { before, after: dataSalary });
|
||||
history.profileSalaryId = dataSalary.id;
|
||||
await salaryHistoryRepo.save(history, { data: req });
|
||||
|
||||
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);
|
||||
}
|
||||
const posMaster = await employeePosMasterRepository.findOne({
|
||||
where: { id: item.posmasterId },
|
||||
relations: ["orgRoot"],
|
||||
});
|
||||
if (posMaster == null)
|
||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้");
|
||||
|
||||
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);
|
||||
}),
|
||||
const posMasterOld = await 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 employeePositionRepository.findOne({
|
||||
where: {
|
||||
posMasterId: posMasterOld?.id,
|
||||
positionIsSelected: true,
|
||||
},
|
||||
});
|
||||
if (positionOld != null) {
|
||||
positionOld.positionIsSelected = false;
|
||||
await employeePositionRepository.save(positionOld);
|
||||
}
|
||||
|
||||
const checkPosition = await employeePositionRepository.find({
|
||||
where: {
|
||||
posMasterId: item.posmasterId,
|
||||
positionIsSelected: true,
|
||||
},
|
||||
});
|
||||
if (checkPosition.length > 0) {
|
||||
const clearPosition = checkPosition.map((positions) => ({
|
||||
...positions,
|
||||
positionIsSelected: false,
|
||||
}));
|
||||
await employeePositionRepository.save(clearPosition);
|
||||
}
|
||||
|
||||
posMaster.current_holderId = item.profileId;
|
||||
posMaster.lastUpdatedAt = new Date();
|
||||
posMaster.next_holderId = null;
|
||||
if (posMasterOld != null) {
|
||||
await employeePosMasterRepository.save(posMasterOld);
|
||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
||||
console.log(
|
||||
`[ExecuteSalaryEmployeeCurrentService] Creating PosMasterHistory — posMasterId: ${posMasterOld.id}, profileId: ${item.profileId} (old)`,
|
||||
);
|
||||
await CreatePosMasterHistoryEmployee(posMasterOld.id, req, null, manager);
|
||||
}
|
||||
await employeePosMasterRepository.save(posMaster);
|
||||
const positionNew = await 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 profileEmployeeRepository.save(profile);
|
||||
await employeePositionRepository.save(positionNew);
|
||||
}
|
||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
||||
console.log(
|
||||
`[ExecuteSalaryEmployeeCurrentService] Creating PosMasterHistory — posMasterId: ${posMaster.id}, profileId: ${item.profileId}`,
|
||||
);
|
||||
await CreatePosMasterHistoryEmployee(posMaster.id, req, null, manager);
|
||||
|
||||
console.log("[ExecuteSalaryEmployeeCurrentService] executeSalaryEmployeeCurrent completed successfully");
|
||||
console.log(
|
||||
`[ExecuteSalaryEmployeeCurrentService] Completed processOne — profileId: ${item.profileId}, posMasterId: ${posMaster.id}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Double } from "typeorm";
|
||||
import { Double, EntityManager } from "typeorm";
|
||||
import { AppDataSource } from "../database/data-source";
|
||||
import HttpError from "../interfaces/http-error";
|
||||
import HttpStatus from "../interfaces/http-status";
|
||||
|
|
@ -71,29 +71,30 @@ export interface SalaryEmployeeLeaveExecutionContext {
|
|||
* - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow)
|
||||
*
|
||||
* Behavior ทั้งหมด preserve จาก CommandController.newSalaryEmployeeAndUpdateLeave ต้นฉบับ
|
||||
*
|
||||
* Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential)
|
||||
* ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด)
|
||||
* ถ้าทุกคนสำเร็จจะ return result รายงาน success count
|
||||
*/
|
||||
export class ExecuteSalaryEmployeeLeaveService {
|
||||
private commandRepository = AppDataSource.getRepository(Command);
|
||||
private commandReciveRepository = AppDataSource.getRepository(CommandRecive);
|
||||
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 orgRootRepository = AppDataSource.getRepository(OrgRoot);
|
||||
private orgRevisionRepo = AppDataSource.getRepository(OrgRevision);
|
||||
|
||||
/**
|
||||
* ประมวลผลสร้าง ProfileSalary + handle leave ของลูกจ้าง
|
||||
* ประมวลผลสร้าง ProfileSalary + handle leave ของลูกจ้างทั้ง batch
|
||||
*/
|
||||
async executeSalaryEmployeeLeave(
|
||||
data: SalaryEmployeeLeaveItem[],
|
||||
ctx: SalaryEmployeeLeaveExecutionContext,
|
||||
): Promise<void> {
|
||||
console.log("[ExecuteSalaryEmployeeLeaveService] Starting executeSalaryEmployeeLeave");
|
||||
console.log("[ExecuteSalaryEmployeeLeaveService] Request body count:", data?.length);
|
||||
|
||||
const req = ctx.req;
|
||||
const commandId = data?.find((x) => x.commandId)?.commandId ?? "unknown";
|
||||
const commandCode = data?.find((x) => x.commandCode)?.commandCode ?? "unknown";
|
||||
console.log(
|
||||
`[ExecuteSalaryEmployeeLeaveService] Starting executeSalaryEmployeeLeave — commandCode: ${commandCode}, commandId: ${commandId}`,
|
||||
);
|
||||
console.log(`[ExecuteSalaryEmployeeLeaveService] Request body count: ${data?.length ?? 0}`);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date)
|
||||
|
|
@ -156,166 +157,222 @@ export class ExecuteSalaryEmployeeLeaveService {
|
|||
}
|
||||
}
|
||||
const today = new Date().setHours(0, 0, 0, 0);
|
||||
await Promise.all(
|
||||
data.map(async (item) => {
|
||||
const profile = await this.profileEmployeeRepository.findOne({
|
||||
where: { id: item.profileId },
|
||||
relations: {
|
||||
roleKeycloaks: true,
|
||||
posType: true,
|
||||
posLevel: true,
|
||||
},
|
||||
});
|
||||
if (!profile) {
|
||||
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
|
||||
}
|
||||
const code = _command?.commandType?.code;
|
||||
//ออกคำสั่งยกเลิกลาออก ลบเฉพาะคนที่ขอยกเลิกลาออก
|
||||
if (item.resignId && code && ["C-PM-42"].includes(code)) {
|
||||
const commandResign = await this.commandReciveRepository.findOne({
|
||||
where: { refId: item.resignId },
|
||||
relations: { command: true },
|
||||
});
|
||||
const executeDate = commandResign
|
||||
? new Date(commandResign.command.commandExcecuteDate).setHours(0, 0, 0, 0)
|
||||
: today;
|
||||
if (
|
||||
commandResign &&
|
||||
_command.status !== "REPORTED" &&
|
||||
(_command.status !== "WAITING" || today < executeDate)
|
||||
) {
|
||||
await reOrderCommandRecivesAndDelete(commandResign!.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: { 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 });
|
||||
dataSalary.dateGovernment = (item.commandDateAffect as Date) ?? meta.createdAt;
|
||||
await this.salaryRepo.save(dataSalary, { data: req });
|
||||
setLogDataDiff(req, { before, after: dataSalary });
|
||||
history.profileSalaryId = dataSalary.id;
|
||||
await this.salaryHistoryRepo.save(history, { data: req });
|
||||
|
||||
const _null: any = null;
|
||||
profile.isLeave = item.isLeave;
|
||||
profile.leaveReason = item.leaveReason ?? _null;
|
||||
profile.dateLeave = item.dateLeave ?? _null;
|
||||
profile.lastUpdateUserId = ctx.user.sub;
|
||||
profile.lastUpdateFullName = ctx.user.name;
|
||||
profile.lastUpdatedAt = new Date();
|
||||
// บันทึกประวัติก่อนลบตำแหน่ง
|
||||
const clearProfile = await checkCommandType(String(item.commandId));
|
||||
const curRevision = await this.orgRevisionRepo.findOne({
|
||||
where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false },
|
||||
});
|
||||
let orgRootRef = null;
|
||||
let orgChild1Ref = null;
|
||||
let orgChild2Ref = null;
|
||||
let orgChild3Ref = null;
|
||||
let orgChild4Ref = null;
|
||||
if (curRevision) {
|
||||
const curPosMaster = await this.employeePosMasterRepository.findOne({
|
||||
where: {
|
||||
current_holderId: profile.id,
|
||||
orgRevisionId: curRevision.id,
|
||||
},
|
||||
relations: {
|
||||
orgRoot: true,
|
||||
orgChild1: true,
|
||||
orgChild2: true,
|
||||
orgChild3: true,
|
||||
orgChild4: true,
|
||||
},
|
||||
});
|
||||
orgRootRef = curPosMaster?.orgRoot ?? null;
|
||||
orgChild1Ref = curPosMaster?.orgChild1 ?? null;
|
||||
orgChild2Ref = curPosMaster?.orgChild2 ?? null;
|
||||
orgChild3Ref = curPosMaster?.orgChild3 ?? null;
|
||||
orgChild4Ref = curPosMaster?.orgChild4 ?? null;
|
||||
if (curPosMaster) {
|
||||
await CreatePosMasterHistoryEmployee(curPosMaster.id, req, "DELETE");
|
||||
}
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Single transaction ครอบทั้ง batch (all-or-nothing)
|
||||
// ทุกคนใช้ manager ตัวเดียวกัน — คนใด throw จะ rollback ทั้ง batch
|
||||
// และ propagate error ออกไป (ล้มเหลวทั้งหมด) โดย log error ของคนที่ทำให้ fail ก่อน rethrow
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
await AppDataSource.transaction(async (manager) => {
|
||||
for (const item of data ?? []) {
|
||||
try {
|
||||
await this.processOne(item, ctx, manager, _command, _posNumCodeSit, _posNumCodeSitAbb, today);
|
||||
} catch (err) {
|
||||
const reason =
|
||||
err instanceof HttpError
|
||||
? err.message
|
||||
: err instanceof Error
|
||||
? err.message
|
||||
: "unexpected error";
|
||||
console.error(
|
||||
`[ExecuteSalaryEmployeeLeaveService] Failed commandCode=${commandCode}, commandId=${commandId}, profileId=${item.profileId}: ${reason}`,
|
||||
err,
|
||||
);
|
||||
throw err; // → rollback ทั้ง transaction + propagate เป็น batch failure
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ลบตำแหน่ง
|
||||
if (item.isLeave == true) {
|
||||
await removeProfileInOrganize(profile.id, "EMPLOYEE");
|
||||
/**
|
||||
* ประมวลผล 1 คน ภายใน transaction เดียว (manager)
|
||||
* ทุก save ใช้ manager.getRepository(...) เพื่อให้อยู่ใน transaction เดียวกัน
|
||||
* ถ้า throw ระหว่างทาง → rollback ทั้งหมดของคนนี้ + ทั้ง batch (กัน partial commit)
|
||||
*/
|
||||
private async processOne(
|
||||
item: SalaryEmployeeLeaveItem,
|
||||
ctx: SalaryEmployeeLeaveExecutionContext,
|
||||
manager: EntityManager,
|
||||
_command: Command | null,
|
||||
_posNumCodeSit: string,
|
||||
_posNumCodeSitAbb: string,
|
||||
today: number,
|
||||
): Promise<void> {
|
||||
const req = ctx.req;
|
||||
|
||||
const commandReciveRepository = manager.getRepository(CommandRecive);
|
||||
const profileEmployeeRepository = manager.getRepository(ProfileEmployee);
|
||||
const salaryRepo = manager.getRepository(ProfileSalary);
|
||||
const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory);
|
||||
const employeePosMasterRepository = manager.getRepository(EmployeePosMaster);
|
||||
const orgRevisionRepo = manager.getRepository(OrgRevision);
|
||||
|
||||
const profile = await profileEmployeeRepository.findOne({
|
||||
where: { id: item.profileId },
|
||||
relations: {
|
||||
roleKeycloaks: true,
|
||||
posType: true,
|
||||
posLevel: true,
|
||||
},
|
||||
});
|
||||
if (!profile) {
|
||||
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
|
||||
}
|
||||
const code = _command?.commandType?.code;
|
||||
//ออกคำสั่งยกเลิกลาออก ลบเฉพาะคนที่ขอยกเลิกลาออก
|
||||
if (item.resignId && code && ["C-PM-42"].includes(code)) {
|
||||
const commandResign = await commandReciveRepository.findOne({
|
||||
where: { refId: item.resignId },
|
||||
relations: { command: true },
|
||||
});
|
||||
const executeDate = commandResign
|
||||
? new Date(commandResign.command.commandExcecuteDate).setHours(0, 0, 0, 0)
|
||||
: today;
|
||||
if (
|
||||
commandResign &&
|
||||
_command.status !== "REPORTED" &&
|
||||
(_command.status !== "WAITING" || today < executeDate)
|
||||
) {
|
||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
||||
await reOrderCommandRecivesAndDelete(commandResign!.id, manager);
|
||||
}
|
||||
}
|
||||
let _commandYear = item.commandYear;
|
||||
if (item.commandYear) {
|
||||
_commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543;
|
||||
}
|
||||
const dest_item = await 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 });
|
||||
dataSalary.dateGovernment = (item.commandDateAffect as Date) ?? meta.createdAt;
|
||||
await salaryRepo.save(dataSalary, { data: req });
|
||||
setLogDataDiff(req, { before, after: dataSalary });
|
||||
history.profileSalaryId = dataSalary.id;
|
||||
await salaryHistoryRepo.save(history, { data: req });
|
||||
|
||||
const _null: any = null;
|
||||
profile.isLeave = item.isLeave;
|
||||
profile.leaveReason = item.leaveReason ?? _null;
|
||||
profile.dateLeave = item.dateLeave ?? _null;
|
||||
profile.lastUpdateUserId = ctx.user.sub;
|
||||
profile.lastUpdateFullName = ctx.user.name;
|
||||
profile.lastUpdatedAt = new Date();
|
||||
// บันทึกประวัติก่อนลบตำแหน่ง
|
||||
const clearProfile = await checkCommandType(String(item.commandId));
|
||||
const curRevision = await orgRevisionRepo.findOne({
|
||||
where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false },
|
||||
});
|
||||
let orgRootRef = null;
|
||||
let orgChild1Ref = null;
|
||||
let orgChild2Ref = null;
|
||||
let orgChild3Ref = null;
|
||||
let orgChild4Ref = null;
|
||||
if (curRevision) {
|
||||
const curPosMaster = await employeePosMasterRepository.findOne({
|
||||
where: {
|
||||
current_holderId: profile.id,
|
||||
orgRevisionId: curRevision.id,
|
||||
},
|
||||
relations: {
|
||||
orgRoot: true,
|
||||
orgChild1: true,
|
||||
orgChild2: true,
|
||||
orgChild3: true,
|
||||
orgChild4: true,
|
||||
},
|
||||
});
|
||||
orgRootRef = curPosMaster?.orgRoot ?? null;
|
||||
orgChild1Ref = curPosMaster?.orgChild1 ?? null;
|
||||
orgChild2Ref = curPosMaster?.orgChild2 ?? null;
|
||||
orgChild3Ref = curPosMaster?.orgChild3 ?? null;
|
||||
orgChild4Ref = curPosMaster?.orgChild4 ?? null;
|
||||
if (curPosMaster) {
|
||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
||||
console.log(
|
||||
`[ExecuteSalaryEmployeeLeaveService] Creating PosMasterHistory — posMasterId: ${curPosMaster.id}, profileId: ${item.profileId}, type: DELETE`,
|
||||
);
|
||||
await CreatePosMasterHistoryEmployee(curPosMaster.id, req, "DELETE", manager);
|
||||
}
|
||||
}
|
||||
|
||||
// ลบตำแหน่ง
|
||||
if (item.isLeave == true) {
|
||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
||||
await removeProfileInOrganize(profile.id, "EMPLOYEE", manager);
|
||||
}
|
||||
|
||||
if (clearProfile.status) {
|
||||
if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) {
|
||||
// Keycloak deleteUser ทำภายใน transaction — ถ้า DB rollback หลังจากนี้ Keycloak จะถูกลบไปแล้ว
|
||||
// (Keycloak ไม่สามารถ rollback ได้)
|
||||
const delUserKeycloak = await deleteUser(profile.keycloak);
|
||||
if (delUserKeycloak) {
|
||||
// Task #228
|
||||
// profile.keycloak = _null;
|
||||
profile.roleKeycloaks = [];
|
||||
profile.isActive = false;
|
||||
profile.isDelete = true;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
await profileEmployeeRepository.save(profile);
|
||||
|
||||
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.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;
|
||||
}
|
||||
await this.profileEmployeeRepository.save(profile);
|
||||
// if (profile.id) {
|
||||
// await this.keycloakAttributeService.clearOrgDnaAttributes(
|
||||
// [profile.id],
|
||||
// "PROFILE_EMPLOYEE",
|
||||
// );
|
||||
// }
|
||||
|
||||
// if (profile.id) {
|
||||
// await this.keycloakAttributeService.clearOrgDnaAttributes(
|
||||
// [profile.id],
|
||||
// "PROFILE_EMPLOYEE",
|
||||
// );
|
||||
// }
|
||||
// Task #2190
|
||||
if (code && ["C-PM-23", "C-PM-43"].includes(code)) {
|
||||
let organizeName = "";
|
||||
if (orgRootRef) {
|
||||
const names = [
|
||||
orgChild4Ref?.orgChild4Name,
|
||||
orgChild3Ref?.orgChild3Name,
|
||||
orgChild2Ref?.orgChild2Name,
|
||||
orgChild1Ref?.orgChild1Name,
|
||||
orgRootRef?.orgRootName,
|
||||
].filter(Boolean);
|
||||
organizeName = names.join(" ");
|
||||
}
|
||||
}
|
||||
|
||||
// Task #2190
|
||||
if (code && ["C-PM-23", "C-PM-43"].includes(code)) {
|
||||
let organizeName = "";
|
||||
if (orgRootRef) {
|
||||
const names = [
|
||||
orgChild4Ref?.orgChild4Name,
|
||||
orgChild3Ref?.orgChild3Name,
|
||||
orgChild2Ref?.orgChild2Name,
|
||||
orgChild1Ref?.orgChild1Name,
|
||||
orgRootRef?.orgRootName,
|
||||
].filter(Boolean);
|
||||
organizeName = names.join(" ");
|
||||
}
|
||||
}
|
||||
}),
|
||||
console.log(
|
||||
`[ExecuteSalaryEmployeeLeaveService] Completed processOne — profileId: ${item.profileId}`,
|
||||
);
|
||||
|
||||
console.log("[ExecuteSalaryEmployeeLeaveService] executeSalaryEmployeeLeave completed successfully");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Double, In, Like } from "typeorm";
|
||||
import { Double, EntityManager, In, Like } from "typeorm";
|
||||
import { AppDataSource } from "../database/data-source";
|
||||
import HttpError from "../interfaces/http-error";
|
||||
import HttpStatusCode from "../interfaces/http-status";
|
||||
|
|
@ -103,27 +103,34 @@ export interface SalaryLeaveExecutionContext {
|
|||
* - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow)
|
||||
*
|
||||
* Behavior ทั้งหมด preserve จาก CommandController.newSalaryAndUpdateLeave ต้นฉบับ
|
||||
*
|
||||
* Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential)
|
||||
* ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด)
|
||||
* ถ้าทุกคนสำเร็จจะ return result รายงาน success count
|
||||
*
|
||||
* ⚠️ หมายเหตุ Keycloak: operations (deleteUser/createUser/addUserRoles/updateUserAttributes)
|
||||
* ทำภายใน transaction เพื่อ preserve behavior เดิม — Keycloak ไม่สามารถ rollback ได้
|
||||
* ถ้า DB rollback หลังจาก Keycloak operation สำเร็จ → Keycloak จะถูกเปลี่ยนไปแล้ว
|
||||
*/
|
||||
export class ExecuteSalaryLeaveService {
|
||||
private commandRepository = AppDataSource.getRepository(Command);
|
||||
private commandReciveRepository = AppDataSource.getRepository(CommandRecive);
|
||||
private profileRepository = AppDataSource.getRepository(Profile);
|
||||
private salaryRepo = AppDataSource.getRepository(ProfileSalary);
|
||||
private salaryHistoryRepo = AppDataSource.getRepository(ProfileSalaryHistory);
|
||||
private posMasterRepository = AppDataSource.getRepository(PosMaster);
|
||||
private positionRepository = AppDataSource.getRepository(Position);
|
||||
private orgRootRepository = AppDataSource.getRepository(OrgRoot);
|
||||
private orgRevisionRepo = AppDataSource.getRepository(OrgRevision);
|
||||
private roleKeycloakRepo = AppDataSource.getRepository(RoleKeycloak);
|
||||
|
||||
/**
|
||||
* ประมวลผลสร้าง ProfileSalary + handle leave/กลับเข้าราชการ ของข้าราชการ
|
||||
* ประมวลผลสร้าง ProfileSalary + handle leave/กลับเข้าราชการ ของข้าราชการทั้ง batch
|
||||
*
|
||||
* @returns สรุปผล success/failure ต่อคน
|
||||
*/
|
||||
async executeSalaryLeave(data: SalaryLeaveItem[], ctx: SalaryLeaveExecutionContext): Promise<void> {
|
||||
console.log("[ExecuteSalaryLeaveService] Starting executeSalaryLeave");
|
||||
console.log("[ExecuteSalaryLeaveService] Request body count:", data?.length);
|
||||
|
||||
const req = ctx.req;
|
||||
const commandId = data?.find((x) => x.commandId)?.commandId ?? "unknown";
|
||||
const commandCode = data?.find((x) => x.commandCode)?.commandCode ?? "unknown";
|
||||
console.log(
|
||||
`[ExecuteSalaryLeaveService] Starting executeSalaryLeave — commandCode: ${commandCode}, commandId: ${commandId}`,
|
||||
);
|
||||
console.log(`[ExecuteSalaryLeaveService] Request body count: ${data?.length ?? 0}`);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date)
|
||||
|
|
@ -189,96 +196,242 @@ export class ExecuteSalaryLeaveService {
|
|||
}
|
||||
}
|
||||
const today = new Date().setHours(0, 0, 0, 0);
|
||||
await Promise.all(
|
||||
data.map(async (item) => {
|
||||
const profile = await this.profileRepository.findOne({
|
||||
where: { id: item.profileId },
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Single transaction ครอบทั้ง batch (all-or-nothing)
|
||||
// ทุกคนใช้ manager ตัวเดียวกัน — คนใด throw จะ rollback ทั้ง batch
|
||||
// และ propagate error ออกไป (ล้มเหลวทั้งหมด) โดย log error ของคนที่ทำให้ fail ก่อน rethrow
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
await AppDataSource.transaction(async (manager) => {
|
||||
for (const item of data ?? []) {
|
||||
try {
|
||||
await this.processOne(
|
||||
item,
|
||||
ctx,
|
||||
manager,
|
||||
_command,
|
||||
_posNumCodeSit,
|
||||
_posNumCodeSitAbb,
|
||||
today,
|
||||
roleKeycloak,
|
||||
);
|
||||
} catch (err) {
|
||||
const reason =
|
||||
err instanceof HttpError
|
||||
? err.message
|
||||
: err instanceof Error
|
||||
? err.message
|
||||
: "unexpected error";
|
||||
console.error(
|
||||
`[ExecuteSalaryLeaveService] Failed commandCode=${commandCode}, commandId=${commandId}, profileId=${item.profileId}: ${reason}`,
|
||||
err,
|
||||
);
|
||||
throw err; // → rollback ทั้ง transaction + propagate เป็น batch failure
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* ประมวลผล 1 คน ภายใน transaction เดียว (manager)
|
||||
* ทุก save ใช้ manager.getRepository(...) เพื่อให้อยู่ใน transaction เดียวกัน
|
||||
* ถ้า throw ระหว่างทาง → rollback ทั้งหมดของคนนี้ + ทั้ง batch (กัน partial commit)
|
||||
*/
|
||||
private async processOne(
|
||||
item: SalaryLeaveItem,
|
||||
ctx: SalaryLeaveExecutionContext,
|
||||
manager: EntityManager,
|
||||
_command: Command | null,
|
||||
_posNumCodeSit: string,
|
||||
_posNumCodeSitAbb: string,
|
||||
today: number,
|
||||
roleKeycloak: RoleKeycloak | null,
|
||||
): Promise<void> {
|
||||
const req = ctx.req;
|
||||
|
||||
const commandReciveRepository = manager.getRepository(CommandRecive);
|
||||
const profileRepository = manager.getRepository(Profile);
|
||||
const salaryRepo = manager.getRepository(ProfileSalary);
|
||||
const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory);
|
||||
const posMasterRepository = manager.getRepository(PosMaster);
|
||||
const positionRepository = manager.getRepository(Position);
|
||||
const orgRevisionRepo = manager.getRepository(OrgRevision);
|
||||
const roleKeycloakRepo = manager.getRepository(RoleKeycloak);
|
||||
|
||||
const profile = await profileRepository.findOne({
|
||||
where: { id: item.profileId },
|
||||
relations: {
|
||||
roleKeycloaks: true,
|
||||
},
|
||||
});
|
||||
if (!profile) {
|
||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้");
|
||||
}
|
||||
//ลบตำแหน่งที่รักษาการแทน
|
||||
const code = _command?.commandType?.code;
|
||||
if (code && ["C-PM-08", "C-PM-17", "C-PM-18", "C-PM-48"].includes(code)) {
|
||||
// await (เดิมไม่ await = fire-and-forget bug) + ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction
|
||||
await removePostMasterAct(profile.id, manager);
|
||||
}
|
||||
//ออกคำสั่งยกเลิกลาออก ลบเฉพาะคนที่ขอยกเลิกลาออก
|
||||
else if (item.resignId && code && ["C-PM-41"].includes(code)) {
|
||||
const commandResign = await commandReciveRepository.findOne({
|
||||
where: { refId: item.resignId },
|
||||
relations: { command: true },
|
||||
});
|
||||
const executeDate = commandResign
|
||||
? new Date(commandResign.command.commandExcecuteDate).setHours(0, 0, 0, 0)
|
||||
: today;
|
||||
if (
|
||||
commandResign &&
|
||||
_command.status !== "REPORTED" &&
|
||||
(_command.status !== "WAITING" || today < executeDate)
|
||||
) {
|
||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
||||
await reOrderCommandRecivesAndDelete(commandResign!.id, manager);
|
||||
}
|
||||
}
|
||||
let _commandYear = item.commandYear;
|
||||
if (item.commandYear) {
|
||||
_commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543;
|
||||
}
|
||||
const returnWork = await checkReturnCommandType(String(item.commandId));
|
||||
const dest_item = await salaryRepo.findOne({
|
||||
where: { profileId: item.profileId },
|
||||
order: { order: "DESC" },
|
||||
});
|
||||
const before = null;
|
||||
const dataSalary = new ProfileSalary();
|
||||
dataSalary.posNumCodeSit = _posNumCodeSit;
|
||||
dataSalary.posNumCodeSitAbb = _posNumCodeSitAbb;
|
||||
dataSalary.dateGovernment = (item.commandDateAffect as Date) ?? new Date();
|
||||
dataSalary.order = dest_item == null ? 1 : dest_item.order + 1;
|
||||
const meta = {
|
||||
createdUserId: ctx.user.sub,
|
||||
createdFullName: ctx.user.name,
|
||||
lastUpdateUserId: ctx.user.sub,
|
||||
lastUpdateFullName: ctx.user.name,
|
||||
createdAt: new Date(),
|
||||
lastUpdatedAt: new Date(),
|
||||
};
|
||||
if (!returnWork) {
|
||||
Object.assign(dataSalary, { ...item, ...meta });
|
||||
const history = new ProfileSalaryHistory();
|
||||
Object.assign(history, { ...dataSalary, id: undefined });
|
||||
await salaryRepo.save(dataSalary, { data: req });
|
||||
setLogDataDiff(req, { before, after: dataSalary });
|
||||
history.profileSalaryId = dataSalary.id;
|
||||
await salaryHistoryRepo.save(history, { data: req });
|
||||
}
|
||||
const _null: any = null;
|
||||
profile.isLeave = item.isLeave;
|
||||
profile.leaveReason = item.leaveReason ?? _null;
|
||||
profile.dateLeave = item.dateLeave ?? _null;
|
||||
profile.lastUpdateUserId = ctx.user.sub;
|
||||
profile.lastUpdateFullName = ctx.user.name;
|
||||
profile.lastUpdatedAt = new Date();
|
||||
const clearProfile = await checkCommandType(String(item.commandId));
|
||||
|
||||
//ปั๊มประวัติก่อนลบตำแหน่ง
|
||||
const curRevision = await orgRevisionRepo.findOne({
|
||||
where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false },
|
||||
});
|
||||
let orgRootRef = null;
|
||||
let orgChild1Ref = null;
|
||||
let orgChild2Ref = null;
|
||||
let orgChild3Ref = null;
|
||||
let orgChild4Ref = null;
|
||||
if (curRevision) {
|
||||
const curPosMaster = await posMasterRepository.findOne({
|
||||
where: {
|
||||
current_holderId: profile.id,
|
||||
orgRevisionId: curRevision.id,
|
||||
},
|
||||
relations: {
|
||||
orgRoot: true,
|
||||
orgChild1: true,
|
||||
orgChild2: true,
|
||||
orgChild3: true,
|
||||
orgChild4: true,
|
||||
},
|
||||
});
|
||||
orgRootRef = curPosMaster?.orgRoot ?? null;
|
||||
orgChild1Ref = curPosMaster?.orgChild1 ?? null;
|
||||
orgChild2Ref = curPosMaster?.orgChild2 ?? null;
|
||||
orgChild3Ref = curPosMaster?.orgChild3 ?? null;
|
||||
orgChild4Ref = curPosMaster?.orgChild4 ?? null;
|
||||
if (curPosMaster && clearProfile.LeaveType != "RETIRE_OUT_EMP") {
|
||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
||||
console.log(
|
||||
`[ExecuteSalaryLeaveService] Creating PosMasterHistory — posMasterId: ${curPosMaster.id}, profileId: ${item.profileId}, type: DELETE`,
|
||||
);
|
||||
await CreatePosMasterHistoryOfficer(curPosMaster.id, req, "DELETE", null, manager);
|
||||
}
|
||||
}
|
||||
|
||||
//ลบตำแหน่ง
|
||||
if (item.isLeave == true) {
|
||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
||||
await removeProfileInOrganize(profile.id, "OFFICER", manager);
|
||||
}
|
||||
if (clearProfile.status) {
|
||||
if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) {
|
||||
// Keycloak ทำภายใน transaction — ไม่สามารถ rollback ได้ (ดู docstring ของ class)
|
||||
const delUserKeycloak = await deleteUser(profile.keycloak);
|
||||
if (delUserKeycloak) {
|
||||
// Task #228
|
||||
// profile.keycloak = _null;
|
||||
profile.roleKeycloaks = [];
|
||||
profile.isActive = false;
|
||||
profile.isDelete = true;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
if (item.isGovernment == true) {
|
||||
if (returnWork) {
|
||||
//ปลดตำแหน่งเดิมที่ไม่ถูกปลดออกจากกิ่งครั้งเมื่อออกคำสั่งพักราชการหรือออกราชการไว้
|
||||
await removeProfileInOrganize(profile.id, "OFFICER", manager);
|
||||
//ปั๊มตำแหน่งใหม่
|
||||
// หา posMaster และเช็ค orgRevisionIsCurrent
|
||||
let posMaster = await posMasterRepository.findOne({
|
||||
where: { id: item.posmasterId?.toString() },
|
||||
relations: {
|
||||
roleKeycloaks: true,
|
||||
orgRevision: true,
|
||||
orgRoot: true,
|
||||
orgChild1: true,
|
||||
orgChild2: true,
|
||||
orgChild3: true,
|
||||
orgChild4: true,
|
||||
},
|
||||
});
|
||||
if (!profile) {
|
||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้");
|
||||
}
|
||||
//ลบตำแหน่งที่รักษาการแทน
|
||||
const code = _command?.commandType?.code;
|
||||
if (code && ["C-PM-08", "C-PM-17", "C-PM-18", "C-PM-48"].includes(code)) {
|
||||
removePostMasterAct(profile.id);
|
||||
}
|
||||
//ออกคำสั่งยกเลิกลาออก ลบเฉพาะคนที่ขอยกเลิกลาออก
|
||||
else if (item.resignId && code && ["C-PM-41"].includes(code)) {
|
||||
const commandResign = await this.commandReciveRepository.findOne({
|
||||
where: { refId: item.resignId },
|
||||
relations: { command: true },
|
||||
});
|
||||
const executeDate = commandResign
|
||||
? new Date(commandResign.command.commandExcecuteDate).setHours(0, 0, 0, 0)
|
||||
: today;
|
||||
if (
|
||||
commandResign &&
|
||||
_command.status !== "REPORTED" &&
|
||||
(_command.status !== "WAITING" || today < executeDate)
|
||||
) {
|
||||
await reOrderCommandRecivesAndDelete(commandResign!.id);
|
||||
}
|
||||
}
|
||||
let _commandYear = item.commandYear;
|
||||
if (item.commandYear) {
|
||||
_commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543;
|
||||
}
|
||||
const returnWork = await checkReturnCommandType(String(item.commandId));
|
||||
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;
|
||||
dataSalary.dateGovernment = (item.commandDateAffect as Date) ?? new Date();
|
||||
dataSalary.order = dest_item == null ? 1 : dest_item.order + 1;
|
||||
const meta = {
|
||||
createdUserId: ctx.user.sub,
|
||||
createdFullName: ctx.user.name,
|
||||
lastUpdateUserId: ctx.user.sub,
|
||||
lastUpdateFullName: ctx.user.name,
|
||||
createdAt: new Date(),
|
||||
lastUpdatedAt: new Date(),
|
||||
};
|
||||
if (!returnWork) {
|
||||
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 });
|
||||
}
|
||||
const _null: any = null;
|
||||
profile.isLeave = item.isLeave;
|
||||
profile.leaveReason = item.leaveReason ?? _null;
|
||||
profile.dateLeave = item.dateLeave ?? _null;
|
||||
profile.lastUpdateUserId = ctx.user.sub;
|
||||
profile.lastUpdateFullName = ctx.user.name;
|
||||
profile.lastUpdatedAt = new Date();
|
||||
const clearProfile = await checkCommandType(String(item.commandId));
|
||||
|
||||
//ปั๊มประวัติก่อนลบตำแหน่ง
|
||||
const curRevision = await this.orgRevisionRepo.findOne({
|
||||
where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false },
|
||||
});
|
||||
let orgRootRef = null;
|
||||
let orgChild1Ref = null;
|
||||
let orgChild2Ref = null;
|
||||
let orgChild3Ref = null;
|
||||
let orgChild4Ref = null;
|
||||
if (curRevision) {
|
||||
const curPosMaster = await this.posMasterRepository.findOne({
|
||||
// เช็คว่า posMaster ที่หามาอยู่ในโครงสร้างปัจจุบันหรือไม่
|
||||
const isCurrent =
|
||||
posMaster?.orgRevision?.orgRevisionIsCurrent === true &&
|
||||
posMaster?.orgRevision?.orgRevisionIsDraft === false;
|
||||
|
||||
// ถ้าไม่อยู่ในโครงสร้างปัจจุบัน ให้หาตัวใหม่จาก ancestorDNA
|
||||
if (!isCurrent && posMaster?.ancestorDNA) {
|
||||
posMaster = await posMasterRepository.findOne({
|
||||
where: {
|
||||
current_holderId: profile.id,
|
||||
orgRevisionId: curRevision.id,
|
||||
ancestorDNA: posMaster.ancestorDNA,
|
||||
orgRevision: {
|
||||
orgRevisionIsCurrent: true,
|
||||
orgRevisionIsDraft: false,
|
||||
},
|
||||
},
|
||||
relations: {
|
||||
orgRevision: true,
|
||||
orgRoot: true,
|
||||
orgChild1: true,
|
||||
orgChild2: true,
|
||||
|
|
@ -286,343 +439,270 @@ export class ExecuteSalaryLeaveService {
|
|||
orgChild4: true,
|
||||
},
|
||||
});
|
||||
orgRootRef = curPosMaster?.orgRoot ?? null;
|
||||
orgChild1Ref = curPosMaster?.orgChild1 ?? null;
|
||||
orgChild2Ref = curPosMaster?.orgChild2 ?? null;
|
||||
orgChild3Ref = curPosMaster?.orgChild3 ?? null;
|
||||
orgChild4Ref = curPosMaster?.orgChild4 ?? null;
|
||||
if (curPosMaster && clearProfile.LeaveType != "RETIRE_OUT_EMP") {
|
||||
await CreatePosMasterHistoryOfficer(curPosMaster.id, req, "DELETE");
|
||||
}
|
||||
}
|
||||
|
||||
//ลบตำแหน่ง
|
||||
if (item.isLeave == true) {
|
||||
await removeProfileInOrganize(profile.id, "OFFICER");
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (posMaster) {
|
||||
const checkPosition = await positionRepository.find({
|
||||
where: {
|
||||
posMasterId: posMaster.id,
|
||||
positionIsSelected: true,
|
||||
},
|
||||
});
|
||||
if (checkPosition.length > 0) {
|
||||
const clearPosition = checkPosition.map((positions) => ({
|
||||
...positions,
|
||||
positionIsSelected: false,
|
||||
}));
|
||||
await positionRepository.save(clearPosition);
|
||||
}
|
||||
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;
|
||||
}
|
||||
posMaster.current_holderId = profile.id;
|
||||
posMaster.lastUpdatedAt = new Date();
|
||||
// posMaster.conditionReason = _null;
|
||||
// posMaster.isCondition = false;
|
||||
await posMasterRepository.save(posMaster);
|
||||
|
||||
if (item.isGovernment == true) {
|
||||
if (returnWork) {
|
||||
//ปลดตำแหน่งเดิมที่ไม่ถูกปลดออกจากกิ่งครั้งเมื่อออกคำสั่งพักราชการหรือออกราชการไว้
|
||||
await removeProfileInOrganize(profile.id, "OFFICER");
|
||||
//ปั๊มตำแหน่งใหม่
|
||||
// หา posMaster และเช็ค orgRevisionIsCurrent
|
||||
let posMaster = await this.posMasterRepository.findOne({
|
||||
where: { id: item.posmasterId?.toString() },
|
||||
relations: {
|
||||
orgRevision: true,
|
||||
orgRoot: true,
|
||||
orgChild1: true,
|
||||
orgChild2: true,
|
||||
orgChild3: true,
|
||||
orgChild4: true,
|
||||
// Match position ตามลำดับ priority:
|
||||
// Condition 1: match จาก positionId
|
||||
// Condition 2: match 7 ฟิลด์ (positionName, posTypeId, posLevelId, positionField, positionArea, positionExecutiveField, posExecutiveId)
|
||||
// Condition 3: match 3 ฟิลด์ (positionName, posTypeId, posLevelId)
|
||||
// Fallback: เลือก position แรกใน posMaster
|
||||
|
||||
let positionNew: Position | null = null;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// CONDITION 1: เช็คจาก positionId ตรง
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
if (item.positionId) {
|
||||
const positionById = await positionRepository.findOne({
|
||||
where: {
|
||||
id: item.positionId,
|
||||
posMasterId: posMaster.id, // ต้องอยู่ใน posMaster ที่ถูกต้อง
|
||||
},
|
||||
relations: ["posExecutive"],
|
||||
});
|
||||
|
||||
// เช็คว่า posMaster ที่หามาอยู่ในโครงสร้างปัจจุบันหรือไม่
|
||||
const isCurrent =
|
||||
posMaster?.orgRevision?.orgRevisionIsCurrent === true &&
|
||||
posMaster?.orgRevision?.orgRevisionIsDraft === false;
|
||||
|
||||
// ถ้าไม่อยู่ในโครงสร้างปัจจุบัน ให้หาตัวใหม่จาก ancestorDNA
|
||||
if (!isCurrent && posMaster?.ancestorDNA) {
|
||||
posMaster = await this.posMasterRepository.findOne({
|
||||
where: {
|
||||
ancestorDNA: posMaster.ancestorDNA,
|
||||
orgRevision: {
|
||||
orgRevisionIsCurrent: true,
|
||||
orgRevisionIsDraft: false,
|
||||
},
|
||||
},
|
||||
relations: {
|
||||
orgRevision: true,
|
||||
orgRoot: true,
|
||||
orgChild1: true,
|
||||
orgChild2: true,
|
||||
orgChild3: true,
|
||||
orgChild4: true,
|
||||
},
|
||||
});
|
||||
if (positionById) {
|
||||
positionNew = positionById;
|
||||
}
|
||||
}
|
||||
|
||||
if (posMaster) {
|
||||
const checkPosition = await this.positionRepository.find({
|
||||
where: {
|
||||
posMasterId: posMaster.id,
|
||||
positionIsSelected: true,
|
||||
},
|
||||
});
|
||||
if (checkPosition.length > 0) {
|
||||
const clearPosition = checkPosition.map((positions) => ({
|
||||
...positions,
|
||||
positionIsSelected: false,
|
||||
}));
|
||||
await this.positionRepository.save(clearPosition);
|
||||
}
|
||||
posMaster.current_holderId = profile.id;
|
||||
posMaster.lastUpdatedAt = new Date();
|
||||
// posMaster.conditionReason = _null;
|
||||
// posMaster.isCondition = false;
|
||||
await this.posMasterRepository.save(posMaster);
|
||||
|
||||
// Match position ตามลำดับ priority:
|
||||
// Condition 1: match จาก positionId
|
||||
// Condition 2: match 7 ฟิลด์ (positionName, posTypeId, posLevelId, positionField, positionArea, positionExecutiveField, posExecutiveId)
|
||||
// Condition 3: match 3 ฟิลด์ (positionName, posTypeId, posLevelId)
|
||||
// Fallback: เลือก position แรกใน posMaster
|
||||
|
||||
let positionNew: Position | null = null;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// CONDITION 1: เช็คจาก positionId ตรง
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
if (item.positionId) {
|
||||
const positionById = await this.positionRepository.findOne({
|
||||
where: {
|
||||
id: item.positionId,
|
||||
posMasterId: posMaster.id, // ต้องอยู่ใน posMaster ที่ถูกต้อง
|
||||
},
|
||||
relations: ["posExecutive"],
|
||||
});
|
||||
|
||||
if (positionById) {
|
||||
positionNew = positionById;
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// CONDITION 2: Match 7 ฟิลด์ (ถ้า Condition 1 ไม่ match)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
if (!positionNew && item.positionNameNew && item.positionTypeNew && item.positionLevelNew) {
|
||||
// สร้าง where clause แบบ dynamic - ใส่เฉพาะฟิลด์ที่มีค่า
|
||||
const whereCondition: any = {
|
||||
posMasterId: posMaster.id,
|
||||
positionName: item.positionNameNew,
|
||||
posTypeId: item.positionTypeNew,
|
||||
posLevelId: item.positionLevelNew,
|
||||
};
|
||||
|
||||
if (item.positionField) {
|
||||
whereCondition.positionField = item.positionField;
|
||||
}
|
||||
if (item.posExecutiveId) {
|
||||
whereCondition.posExecutiveId = item.posExecutiveId;
|
||||
}
|
||||
if (item.positionExecutiveField) {
|
||||
whereCondition.positionExecutiveField = item.positionExecutiveField;
|
||||
}
|
||||
if (item.positionArea) {
|
||||
whereCondition.positionArea = item.positionArea;
|
||||
}
|
||||
|
||||
const positionBy7Fields = await this.positionRepository.findOne({
|
||||
where: whereCondition,
|
||||
relations: ["posExecutive"],
|
||||
order: { orderNo: "ASC" },
|
||||
});
|
||||
|
||||
if (positionBy7Fields) {
|
||||
positionNew = positionBy7Fields;
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// CONDITION 3: Match 3 ฟิลด์ (ถ้า Condition 2 ไม่ match)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
if (!positionNew && item.positionNameNew && item.positionTypeNew && item.positionLevelNew) {
|
||||
const positionBy3Fields = await this.positionRepository.findOne({
|
||||
where: {
|
||||
posMasterId: posMaster.id,
|
||||
positionName: item.positionNameNew,
|
||||
posTypeId: item.positionTypeNew,
|
||||
posLevelId: item.positionLevelNew,
|
||||
},
|
||||
relations: ["posExecutive"],
|
||||
order: { orderNo: "ASC" },
|
||||
});
|
||||
|
||||
if (positionBy3Fields) {
|
||||
positionNew = positionBy3Fields;
|
||||
}
|
||||
}
|
||||
|
||||
// // FALLBACK: เลือก position แรก (ถ้าไม่เจอทั้ง 2 condition)
|
||||
// if (!positionNew) {
|
||||
// const fallbackPositions = await this.positionRepository.find({
|
||||
// where: {
|
||||
// posMasterId: posMaster.id,
|
||||
// },
|
||||
// relations: ["posExecutive"],
|
||||
// order: {
|
||||
// orderNo: "ASC",
|
||||
// },
|
||||
// take: 1,
|
||||
// });
|
||||
|
||||
// if (fallbackPositions.length > 0) {
|
||||
// positionNew = fallbackPositions[0];
|
||||
// }
|
||||
// }
|
||||
|
||||
if (positionNew) {
|
||||
positionNew.positionIsSelected = true;
|
||||
await this.positionRepository.save(positionNew, { data: req });
|
||||
}
|
||||
await CreatePosMasterHistoryOfficer(posMaster.id, req);
|
||||
profile.posMasterNo = getPosMasterNo(posMaster);
|
||||
profile.org = getOrgFullName(posMaster);
|
||||
}
|
||||
const newMapProfileSalary = {
|
||||
profileId: profile.id,
|
||||
commandId: item.commandId,
|
||||
positionName: item.positionNameNew ?? null,
|
||||
positionType: item.posTypeNameNew ?? null,
|
||||
positionLevel: item.posLevelNameNew ?? null,
|
||||
amount: item.amount ? item.amount : null,
|
||||
positionSalaryAmount: item.positionSalaryAmount ? item.positionSalaryAmount : null,
|
||||
amountSpecial: item.amountSpecial ? item.amountSpecial : null,
|
||||
mouthSalaryAmount: item.mouthSalaryAmount ? item.mouthSalaryAmount : null,
|
||||
posNo: item.posNoNew,
|
||||
posNoAbb: item.posNoAbbNew,
|
||||
orgRoot: item.orgRootNew,
|
||||
orgChild1: item.orgChild1New,
|
||||
orgChild2: item.orgChild2New,
|
||||
orgChild3: item.orgChild3New,
|
||||
orgChild4: item.orgChild4New,
|
||||
isGovernment: item.isGovernment,
|
||||
commandNo: item.commandNo,
|
||||
commandYear: item.commandYear,
|
||||
commandDateAffect: item.commandDateAffect,
|
||||
commandDateSign: item.commandDateSign,
|
||||
commandCode: item.commandCode,
|
||||
commandName: item.commandName,
|
||||
remark: item.remark,
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// CONDITION 2: Match 7 ฟิลด์ (ถ้า Condition 1 ไม่ match)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
if (!positionNew && item.positionNameNew && item.positionTypeNew && item.positionLevelNew) {
|
||||
// สร้าง where clause แบบ dynamic - ใส่เฉพาะฟิลด์ที่มีค่า
|
||||
const whereCondition: any = {
|
||||
posMasterId: posMaster.id,
|
||||
positionName: item.positionNameNew,
|
||||
posTypeId: item.positionTypeNew,
|
||||
posLevelId: item.positionLevelNew,
|
||||
};
|
||||
Object.assign(dataSalary, { ...newMapProfileSalary, ...meta });
|
||||
const history = new ProfileSalaryHistory();
|
||||
Object.assign(history, { ...dataSalary, id: undefined });
|
||||
await this.salaryRepo.save(dataSalary);
|
||||
history.profileSalaryId = dataSalary.id;
|
||||
await this.salaryHistoryRepo.save(history);
|
||||
profile.leaveReason = _null;
|
||||
profile.leaveCommandId = _null;
|
||||
profile.leaveCommandNo = _null;
|
||||
profile.leaveRemark = _null;
|
||||
profile.leaveDate = _null;
|
||||
profile.leaveType = _null;
|
||||
profile.position = item.positionNameNew ?? _null;
|
||||
profile.posTypeId = item.positionTypeNew ?? _null;
|
||||
profile.posLevelId = item.positionLevelNew ?? _null;
|
||||
}
|
||||
let userKeycloakId;
|
||||
const checkUser = await getUserByUsername(profile.citizenId);
|
||||
//ถ้ายังไม่มี user keycloak ให้สร้างใหม่
|
||||
if (checkUser.length == 0) {
|
||||
let password = profile.citizenId;
|
||||
if (profile.birthDate != null) {
|
||||
const _date = new Date(profile.birthDate.toDateString())
|
||||
.getDate()
|
||||
.toString()
|
||||
.padStart(2, "0");
|
||||
const _month = (new Date(profile.birthDate.toDateString()).getMonth() + 1)
|
||||
.toString()
|
||||
.padStart(2, "0");
|
||||
const _year = new Date(profile.birthDate.toDateString()).getFullYear() + 543;
|
||||
password = `${_date}${_month}${_year}`;
|
||||
|
||||
if (item.positionField) {
|
||||
whereCondition.positionField = item.positionField;
|
||||
}
|
||||
// กรอง "." ออกจาก firstName ก่อนส่งไป keycloak
|
||||
const sanitizedFirstName = profile.firstName?.replace(/\./g, "") ?? "";
|
||||
userKeycloakId = await createUser(profile.citizenId, password, {
|
||||
firstName: sanitizedFirstName,
|
||||
lastName: profile.lastName,
|
||||
if (item.posExecutiveId) {
|
||||
whereCondition.posExecutiveId = item.posExecutiveId;
|
||||
}
|
||||
if (item.positionExecutiveField) {
|
||||
whereCondition.positionExecutiveField = item.positionExecutiveField;
|
||||
}
|
||||
if (item.positionArea) {
|
||||
whereCondition.positionArea = item.positionArea;
|
||||
}
|
||||
|
||||
const positionBy7Fields = await positionRepository.findOne({
|
||||
where: whereCondition,
|
||||
relations: ["posExecutive"],
|
||||
order: { orderNo: "ASC" },
|
||||
});
|
||||
const list = await getRoles();
|
||||
let result = false;
|
||||
if (Array.isArray(list) && userKeycloakId) {
|
||||
result = await addUserRoles(
|
||||
userKeycloakId,
|
||||
list
|
||||
.filter((v) => v.name === "USER")
|
||||
.map((x) => ({
|
||||
id: x.id,
|
||||
name: x.name,
|
||||
})),
|
||||
);
|
||||
|
||||
if (positionBy7Fields) {
|
||||
positionNew = positionBy7Fields;
|
||||
}
|
||||
profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : [];
|
||||
profile.keycloak =
|
||||
userKeycloakId && typeof userKeycloakId === "string" ? userKeycloakId : "";
|
||||
}
|
||||
//ถ้ามีอยู่แล้วให้ใช้อันเดิม
|
||||
else {
|
||||
const rolesData = await getRoleMappings(checkUser[0].id);
|
||||
if (rolesData) {
|
||||
const _roleKeycloak = await this.roleKeycloakRepo.find({
|
||||
where: { name: In(rolesData.map((x: any) => x.name)) },
|
||||
});
|
||||
profile.roleKeycloaks =
|
||||
_roleKeycloak && _roleKeycloak.length > 0 ? _roleKeycloak : [];
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// CONDITION 3: Match 3 ฟิลด์ (ถ้า Condition 2 ไม่ match)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
if (!positionNew && item.positionNameNew && item.positionTypeNew && item.positionLevelNew) {
|
||||
const positionBy3Fields = await positionRepository.findOne({
|
||||
where: {
|
||||
posMasterId: posMaster.id,
|
||||
positionName: item.positionNameNew,
|
||||
posTypeId: item.positionTypeNew,
|
||||
posLevelId: item.positionLevelNew,
|
||||
},
|
||||
relations: ["posExecutive"],
|
||||
order: { orderNo: "ASC" },
|
||||
});
|
||||
|
||||
if (positionBy3Fields) {
|
||||
positionNew = positionBy3Fields;
|
||||
}
|
||||
profile.keycloak = checkUser[0].id;
|
||||
}
|
||||
profile.amount = item.amount ?? _null;
|
||||
profile.amountSpecial = item.amountSpecial ?? _null;
|
||||
profile.isActive = true;
|
||||
profile.isDelete = false;
|
||||
|
||||
// // FALLBACK: เลือก position แรก (ถ้าไม่เจอทั้ง 2 condition)
|
||||
// if (!positionNew) {
|
||||
// const fallbackPositions = await positionRepository.find({
|
||||
// where: {
|
||||
// posMasterId: posMaster.id,
|
||||
// },
|
||||
// relations: ["posExecutive"],
|
||||
// order: {
|
||||
// orderNo: "ASC",
|
||||
// },
|
||||
// take: 1,
|
||||
// });
|
||||
|
||||
// if (fallbackPositions.length > 0) {
|
||||
// positionNew = fallbackPositions[0];
|
||||
// }
|
||||
// }
|
||||
|
||||
if (positionNew) {
|
||||
positionNew.positionIsSelected = true;
|
||||
await positionRepository.save(positionNew, { data: req });
|
||||
}
|
||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
||||
console.log(
|
||||
`[ExecuteSalaryLeaveService] Creating PosMasterHistory — posMasterId: ${posMaster.id}, profileId: ${item.profileId}`,
|
||||
);
|
||||
await CreatePosMasterHistoryOfficer(posMaster.id, req, null, null, manager);
|
||||
profile.posMasterNo = getPosMasterNo(posMaster);
|
||||
profile.org = getOrgFullName(posMaster);
|
||||
}
|
||||
await this.profileRepository.save(profile);
|
||||
|
||||
// if (profile.id) {
|
||||
// await this.keycloakAttributeService.clearOrgDnaAttributes(
|
||||
// [profile.id],
|
||||
// "PROFILE",
|
||||
// );
|
||||
// }
|
||||
|
||||
// update user attribute in keycloak
|
||||
await updateUserAttributes(profile.keycloak ?? "", {
|
||||
profileId: [profile.id],
|
||||
prefix: [profile.prefix || ""],
|
||||
const newMapProfileSalary = {
|
||||
profileId: profile.id,
|
||||
commandId: item.commandId,
|
||||
positionName: item.positionNameNew ?? null,
|
||||
positionType: item.posTypeNameNew ?? null,
|
||||
positionLevel: item.posLevelNameNew ?? null,
|
||||
amount: item.amount ? item.amount : null,
|
||||
positionSalaryAmount: item.positionSalaryAmount ? item.positionSalaryAmount : null,
|
||||
amountSpecial: item.amountSpecial ? item.amountSpecial : null,
|
||||
mouthSalaryAmount: item.mouthSalaryAmount ? item.mouthSalaryAmount : null,
|
||||
posNo: item.posNoNew,
|
||||
posNoAbb: item.posNoAbbNew,
|
||||
orgRoot: item.orgRootNew,
|
||||
orgChild1: item.orgChild1New,
|
||||
orgChild2: item.orgChild2New,
|
||||
orgChild3: item.orgChild3New,
|
||||
orgChild4: item.orgChild4New,
|
||||
isGovernment: item.isGovernment,
|
||||
commandNo: item.commandNo,
|
||||
commandYear: item.commandYear,
|
||||
commandDateAffect: item.commandDateAffect,
|
||||
commandDateSign: item.commandDateSign,
|
||||
commandCode: item.commandCode,
|
||||
commandName: item.commandName,
|
||||
remark: item.remark,
|
||||
};
|
||||
Object.assign(dataSalary, { ...newMapProfileSalary, ...meta });
|
||||
const history = new ProfileSalaryHistory();
|
||||
Object.assign(history, { ...dataSalary, id: undefined });
|
||||
await salaryRepo.save(dataSalary);
|
||||
history.profileSalaryId = dataSalary.id;
|
||||
await salaryHistoryRepo.save(history);
|
||||
profile.leaveReason = _null;
|
||||
profile.leaveCommandId = _null;
|
||||
profile.leaveCommandNo = _null;
|
||||
profile.leaveRemark = _null;
|
||||
profile.leaveDate = _null;
|
||||
profile.leaveType = _null;
|
||||
profile.position = item.positionNameNew ?? _null;
|
||||
profile.posTypeId = item.positionTypeNew ?? _null;
|
||||
profile.posLevelId = item.positionLevelNew ?? _null;
|
||||
}
|
||||
let userKeycloakId;
|
||||
const checkUser = await getUserByUsername(profile.citizenId);
|
||||
//ถ้ายังไม่มี user keycloak ให้สร้างใหม่
|
||||
if (checkUser.length == 0) {
|
||||
let password = profile.citizenId;
|
||||
if (profile.birthDate != null) {
|
||||
const _date = new Date(profile.birthDate.toDateString())
|
||||
.getDate()
|
||||
.toString()
|
||||
.padStart(2, "0");
|
||||
const _month = (new Date(profile.birthDate.toDateString()).getMonth() + 1)
|
||||
.toString()
|
||||
.padStart(2, "0");
|
||||
const _year = new Date(profile.birthDate.toDateString()).getFullYear() + 543;
|
||||
password = `${_date}${_month}${_year}`;
|
||||
}
|
||||
// กรอง "." ออกจาก firstName ก่อนส่งไป keycloak
|
||||
const sanitizedFirstName = profile.firstName?.replace(/\./g, "") ?? "";
|
||||
// Keycloak ทำภายใน transaction — ไม่สามารถ rollback ได้ (ดู docstring ของ class)
|
||||
userKeycloakId = await createUser(profile.citizenId, password, {
|
||||
firstName: sanitizedFirstName,
|
||||
lastName: profile.lastName,
|
||||
});
|
||||
|
||||
// Task #2190
|
||||
if (code && ["C-PM-17", "C-PM-18", "C-PM-48"].includes(code)) {
|
||||
let organizeName = "";
|
||||
if (orgRootRef) {
|
||||
const names = [
|
||||
orgChild4Ref?.orgChild4Name,
|
||||
orgChild3Ref?.orgChild3Name,
|
||||
orgChild2Ref?.orgChild2Name,
|
||||
orgChild1Ref?.orgChild1Name,
|
||||
orgRootRef?.orgRootName,
|
||||
].filter(Boolean);
|
||||
organizeName = names.join(" ");
|
||||
}
|
||||
const list = await getRoles();
|
||||
let result = false;
|
||||
if (Array.isArray(list) && userKeycloakId) {
|
||||
result = await addUserRoles(
|
||||
userKeycloakId,
|
||||
list
|
||||
.filter((v) => v.name === "USER")
|
||||
.map((x) => ({
|
||||
id: x.id,
|
||||
name: x.name,
|
||||
})),
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : [];
|
||||
profile.keycloak =
|
||||
userKeycloakId && typeof userKeycloakId === "string" ? userKeycloakId : "";
|
||||
}
|
||||
//ถ้ามีอยู่แล้วให้ใช้อันเดิม
|
||||
else {
|
||||
const rolesData = await getRoleMappings(checkUser[0].id);
|
||||
if (rolesData) {
|
||||
const _roleKeycloak = await roleKeycloakRepo.find({
|
||||
where: { name: In(rolesData.map((x: any) => x.name)) },
|
||||
});
|
||||
profile.roleKeycloaks =
|
||||
_roleKeycloak && _roleKeycloak.length > 0 ? _roleKeycloak : [];
|
||||
}
|
||||
profile.keycloak = checkUser[0].id;
|
||||
}
|
||||
profile.amount = item.amount ?? _null;
|
||||
profile.amountSpecial = item.amountSpecial ?? _null;
|
||||
profile.isActive = true;
|
||||
profile.isDelete = false;
|
||||
}
|
||||
await profileRepository.save(profile);
|
||||
|
||||
console.log("[ExecuteSalaryLeaveService] executeSalaryLeave completed successfully");
|
||||
// if (profile.id) {
|
||||
// await this.keycloakAttributeService.clearOrgDnaAttributes(
|
||||
// [profile.id],
|
||||
// "PROFILE",
|
||||
// );
|
||||
// }
|
||||
|
||||
// update user attribute in keycloak
|
||||
await updateUserAttributes(profile.keycloak ?? "", {
|
||||
profileId: [profile.id],
|
||||
prefix: [profile.prefix || ""],
|
||||
});
|
||||
|
||||
// Task #2190
|
||||
if (code && ["C-PM-17", "C-PM-18", "C-PM-48"].includes(code)) {
|
||||
let organizeName = "";
|
||||
if (orgRootRef) {
|
||||
const names = [
|
||||
orgChild4Ref?.orgChild4Name,
|
||||
orgChild3Ref?.orgChild3Name,
|
||||
orgChild2Ref?.orgChild2Name,
|
||||
orgChild1Ref?.orgChild1Name,
|
||||
orgRootRef?.orgRootName,
|
||||
].filter(Boolean);
|
||||
organizeName = names.join(" ");
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[ExecuteSalaryLeaveService] Completed processOne — profileId: ${item.profileId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Double } from "typeorm";
|
||||
import { Double, EntityManager } from "typeorm";
|
||||
import { AppDataSource } from "../database/data-source";
|
||||
import HttpError from "../interfaces/http-error";
|
||||
import HttpStatusCode from "../interfaces/http-status";
|
||||
|
|
@ -76,25 +76,30 @@ export interface SalaryExecutionContext {
|
|||
* - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow)
|
||||
*
|
||||
* Behavior ทั้งหมด preserve จาก CommandController.newSalaryAndUpdate ต้นฉบับ
|
||||
*
|
||||
* Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential)
|
||||
* ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด)
|
||||
* ถ้าทุกคนสำเร็จจะ return result รายงาน success count
|
||||
*
|
||||
* Keycloak operations (deleteUser) ทำก่อนเข้า transaction เพราะไม่สามารถ rollback ได้
|
||||
*/
|
||||
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
|
||||
* ประมวลผลสร้าง ProfileSalary + handle leave/assistance ทั้ง batch
|
||||
*
|
||||
* @returns สรุปผล success/failure ต่อคน
|
||||
*/
|
||||
async executeSalary(data: SalaryItem[], ctx: SalaryExecutionContext): Promise<void> {
|
||||
console.log("[ExecuteSalaryService] Starting executeSalary");
|
||||
console.log("[ExecuteSalaryService] Request body count:", data?.length);
|
||||
|
||||
const req = ctx.req;
|
||||
const commandId = data?.find((x) => x.commandId)?.commandId ?? "unknown";
|
||||
const commandCode = data?.find((x) => x.commandCode)?.commandCode ?? "unknown";
|
||||
console.log(
|
||||
`[ExecuteSalaryService] Starting executeSalary — commandCode: ${commandCode}, commandId: ${commandId}`,
|
||||
);
|
||||
console.log(`[ExecuteSalaryService] Request body count: ${data?.length ?? 0}`);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date)
|
||||
|
|
@ -158,170 +163,227 @@ export class ExecuteSalaryService {
|
|||
.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);
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Single transaction ครอบทั้ง batch (all-or-nothing)
|
||||
// ทุกคนใช้ manager ตัวเดียวกัน — คนใด throw จะ rollback ทั้ง batch
|
||||
// และ propagate error ออกไป (ล้มเหลวทั้งหมด) โดย log error ของคนที่ทำให้ fail ก่อน rethrow
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
let successCount = 0;
|
||||
await AppDataSource.transaction(async (manager) => {
|
||||
for (const item of data ?? []) {
|
||||
try {
|
||||
await this.processOne(item, ctx, manager, _command, _posNumCodeSit, _posNumCodeSitAbb);
|
||||
} catch (err) {
|
||||
const reason =
|
||||
err instanceof HttpError
|
||||
? err.message
|
||||
: err instanceof Error
|
||||
? err.message
|
||||
: "unexpected error";
|
||||
console.error(
|
||||
`[ExecuteSalaryService] Failed commandCode=${commandCode}, commandId=${commandId}, profileId=${item.profileId}: ${reason}`,
|
||||
err,
|
||||
);
|
||||
throw err; // → rollback ทั้ง transaction + propagate เป็น batch failure
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let _commandYear = item.commandYear;
|
||||
if (item.commandYear) {
|
||||
_commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543;
|
||||
/**
|
||||
* ประมวลผล 1 คน ภายใน transaction เดียว (manager)
|
||||
* ทุก save ใช้ manager.getRepository(...) เพื่อให้อยู่ใน transaction เดียวกัน
|
||||
* ถ้า throw ระหว่างทาง → rollback ทั้งหมดของคนนี้ + ทั้ง batch (กัน partial commit)
|
||||
*
|
||||
* หมายเหตุ: Keycloak deleteUser ทำก่อนเข้า transaction เพราะไม่สามารถ rollback ได้
|
||||
*/
|
||||
private async processOne(
|
||||
item: SalaryItem,
|
||||
ctx: SalaryExecutionContext,
|
||||
manager: EntityManager,
|
||||
_command: Command | null,
|
||||
_posNumCodeSit: string,
|
||||
_posNumCodeSitAbb: string,
|
||||
): Promise<void> {
|
||||
const req = ctx.req;
|
||||
|
||||
const profileRepository = manager.getRepository(Profile);
|
||||
const salaryRepo = manager.getRepository(ProfileSalary);
|
||||
const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory);
|
||||
const posMasterRepository = manager.getRepository(PosMaster);
|
||||
const assistanceRepository = manager.getRepository(ProfileAssistance);
|
||||
const assistanceHistoryRepository = manager.getRepository(ProfileAssistanceHistory);
|
||||
|
||||
const profile: any = await 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 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)) {
|
||||
// await (เดิมไม่ await = fire-and-forget bug) + ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction
|
||||
await removePostMasterAct(profile.id, manager);
|
||||
}
|
||||
|
||||
let _commandYear = item.commandYear;
|
||||
if (item.commandYear) {
|
||||
_commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543;
|
||||
}
|
||||
const dest_item = await 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) {
|
||||
console.log(
|
||||
`[ExecuteSalaryService] Creating PosMasterHistory — posMasterId: ${orgRevisionRef}, profileId: ${item.profileId}, type: DELETE`,
|
||||
);
|
||||
await CreatePosMasterHistoryOfficer(orgRevisionRef, req, "DELETE", null, manager);
|
||||
await removeProfileInOrganize(profile.id, "OFFICER", manager);
|
||||
}
|
||||
const clearProfile = await checkCommandType(String(item.commandId));
|
||||
const _null: any = null;
|
||||
if (clearProfile.status) {
|
||||
// Keycloak deleteUser ทำก่อนเข้า transaction-bound save ด้านล่าง
|
||||
// (ทำภายใน transaction เดียวกัน เพราะถ้า fail ต้อง rollback DB ด้วย)
|
||||
// หมายเหตุ: Keycloak ไม่สามารถ rollback ได้ → ถ้า DB rollback หลังจากนี้ Keycloak จะถูกลบไปแล้ว
|
||||
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;
|
||||
}
|
||||
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,
|
||||
}
|
||||
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 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 salaryRepo.save(dataSalary, { data: req });
|
||||
setLogDataDiff(req, { before, after: dataSalary });
|
||||
history.profileSalaryId = dataSalary.id;
|
||||
await 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",
|
||||
};
|
||||
if (item.isLeave != undefined && item.isLeave == true) {
|
||||
await CreatePosMasterHistoryOfficer(orgRevisionRef, req, "DELETE");
|
||||
await removeProfileInOrganize(profile.id, "OFFICER");
|
||||
|
||||
Object.assign(dataAssis, metaAssis);
|
||||
const historyAssis = new ProfileAssistanceHistory();
|
||||
Object.assign(historyAssis, { ...dataAssis, id: undefined });
|
||||
|
||||
await assistanceRepository.save(dataAssis);
|
||||
historyAssis.profileAssistanceId = dataAssis.id;
|
||||
await 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(" ");
|
||||
}
|
||||
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] Completed processOne — profileId: ${item.profileId}`,
|
||||
);
|
||||
|
||||
console.log("[ExecuteSalaryService] executeSalary completed successfully");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -217,92 +217,105 @@ export async function CreatePosMasterHistoryEmployee(
|
|||
posMasterId: string,
|
||||
request: RequestWithUser | null,
|
||||
type?: string | null,
|
||||
manager?: EntityManager,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
await AppDataSource.transaction(async (manager) => {
|
||||
const repoPosmaster = manager.getRepository(EmployeePosMaster);
|
||||
const repoHistory = manager.getRepository(PosMasterEmployeeHistory);
|
||||
const repoProfileEmployee = manager.getRepository(ProfileEmployee);
|
||||
const execute = async (transactionManager: EntityManager) => {
|
||||
const repoPosmaster = transactionManager.getRepository(EmployeePosMaster);
|
||||
const repoHistory = transactionManager.getRepository(PosMasterEmployeeHistory);
|
||||
const repoProfileEmployee = transactionManager.getRepository(ProfileEmployee);
|
||||
|
||||
const pm = await repoPosmaster.findOne({
|
||||
where: { id: posMasterId },
|
||||
relations: [
|
||||
"positions",
|
||||
"positions.posLevel",
|
||||
"positions.posType",
|
||||
// "positions.posExecutive",
|
||||
"orgRoot",
|
||||
"orgChild1",
|
||||
"orgChild2",
|
||||
"orgChild3",
|
||||
"orgChild4",
|
||||
"current_holder",
|
||||
],
|
||||
});
|
||||
if (!pm) return false;
|
||||
if (!pm.ancestorDNA) return false;
|
||||
const _null: any = null;
|
||||
const h = new PosMasterEmployeeHistory();
|
||||
const selectedPosition =
|
||||
pm.positions.length > 0
|
||||
? pm.positions.find((p) => p.positionIsSelected === true) ?? null
|
||||
: null;
|
||||
|
||||
let position = selectedPosition?.positionName ?? _null;
|
||||
let posTypeName = selectedPosition?.posType?.posTypeName ?? _null;
|
||||
let posLevelName = selectedPosition?.posType && selectedPosition?.posLevel
|
||||
? `${selectedPosition?.posType?.posTypeShortName ?? ""} ${selectedPosition?.posLevel?.posLevelName ?? ""}`.trim()
|
||||
: _null;
|
||||
if (pm.isSit && pm.current_holderId) {
|
||||
const profile = await repoProfileEmployee.findOne({
|
||||
where: { id: pm.current_holderId },
|
||||
relations: ["posType", "posLevel"]
|
||||
});
|
||||
position = profile?.position ?? _null;
|
||||
posTypeName = profile?.posType?.posTypeName ?? _null;
|
||||
posLevelName = profile?.posType && profile?.posLevel
|
||||
? `${profile?.posType?.posTypeShortName ?? ""} ${profile?.posLevel?.posLevelName ?? ""}`.trim()
|
||||
: _null;
|
||||
}
|
||||
h.ancestorDNA = pm.ancestorDNA;
|
||||
if (!type || type != "DELETE") {
|
||||
h.profileEmployeeId = pm.current_holder?.id || _null;
|
||||
h.prefix = pm.current_holder?.prefix || _null;
|
||||
h.firstName = pm.current_holder?.firstName || _null;
|
||||
h.lastName = pm.current_holder?.lastName || _null;
|
||||
h.position = position;
|
||||
h.posType = posTypeName;
|
||||
h.posLevel = posLevelName;
|
||||
}
|
||||
h.rootDnaId = pm.orgRoot?.ancestorDNA || _null;
|
||||
h.child1DnaId = pm.orgChild1?.ancestorDNA || _null;
|
||||
h.child2DnaId = pm.orgChild2?.ancestorDNA || _null;
|
||||
h.child3DnaId = pm.orgChild3?.ancestorDNA || _null;
|
||||
h.child4DnaId = pm.orgChild4?.ancestorDNA || _null;
|
||||
h.posMasterNoPrefix = pm.posMasterNoPrefix ?? _null;
|
||||
h.posMasterNo = pm.posMasterNo ?? _null;
|
||||
h.posMasterNoSuffix = pm.posMasterNoSuffix ?? _null;
|
||||
h.shortName =
|
||||
[
|
||||
pm.orgChild4?.orgChild4ShortName,
|
||||
pm.orgChild3?.orgChild3ShortName,
|
||||
pm.orgChild2?.orgChild2ShortName,
|
||||
pm.orgChild1?.orgChild1ShortName,
|
||||
pm.orgRoot?.orgRootShortName,
|
||||
].find((s) => typeof s === "string" && s.trim().length > 0) ?? _null;
|
||||
const userId = request?.user?.sub ?? "";
|
||||
const userName = request?.user?.name ?? "system";
|
||||
h.createdUserId = userId;
|
||||
h.createdFullName = userName;
|
||||
h.lastUpdateUserId = userId;
|
||||
h.lastUpdateFullName = userName;
|
||||
h.createdAt = new Date();
|
||||
h.lastUpdatedAt = new Date();
|
||||
await repoHistory.save(h);
|
||||
const pm = await repoPosmaster.findOne({
|
||||
where: { id: posMasterId },
|
||||
relations: [
|
||||
"positions",
|
||||
"positions.posLevel",
|
||||
"positions.posType",
|
||||
// "positions.posExecutive",
|
||||
"orgRoot",
|
||||
"orgChild1",
|
||||
"orgChild2",
|
||||
"orgChild3",
|
||||
"orgChild4",
|
||||
"current_holder",
|
||||
],
|
||||
});
|
||||
if (!pm) return;
|
||||
if (!pm.ancestorDNA) return;
|
||||
const _null: any = null;
|
||||
const h = new PosMasterEmployeeHistory();
|
||||
const selectedPosition =
|
||||
pm.positions.length > 0
|
||||
? pm.positions.find((p) => p.positionIsSelected === true) ?? null
|
||||
: null;
|
||||
|
||||
let position = selectedPosition?.positionName ?? _null;
|
||||
let posTypeName = selectedPosition?.posType?.posTypeName ?? _null;
|
||||
let posLevelName = selectedPosition?.posType && selectedPosition?.posLevel
|
||||
? `${selectedPosition?.posType?.posTypeShortName ?? ""} ${selectedPosition?.posLevel?.posLevelName ?? ""}`.trim()
|
||||
: _null;
|
||||
if (pm.isSit && pm.current_holderId) {
|
||||
const profile = await repoProfileEmployee.findOne({
|
||||
where: { id: pm.current_holderId },
|
||||
relations: ["posType", "posLevel"]
|
||||
});
|
||||
position = profile?.position ?? _null;
|
||||
posTypeName = profile?.posType?.posTypeName ?? _null;
|
||||
posLevelName = profile?.posType && profile?.posLevel
|
||||
? `${profile?.posType?.posTypeShortName ?? ""} ${profile?.posLevel?.posLevelName ?? ""}`.trim()
|
||||
: _null;
|
||||
}
|
||||
h.ancestorDNA = pm.ancestorDNA;
|
||||
if (!type || type != "DELETE") {
|
||||
h.profileEmployeeId = pm.current_holder?.id || _null;
|
||||
h.prefix = pm.current_holder?.prefix || _null;
|
||||
h.firstName = pm.current_holder?.firstName || _null;
|
||||
h.lastName = pm.current_holder?.lastName || _null;
|
||||
h.position = position;
|
||||
h.posType = posTypeName;
|
||||
h.posLevel = posLevelName;
|
||||
}
|
||||
h.rootDnaId = pm.orgRoot?.ancestorDNA || _null;
|
||||
h.child1DnaId = pm.orgChild1?.ancestorDNA || _null;
|
||||
h.child2DnaId = pm.orgChild2?.ancestorDNA || _null;
|
||||
h.child3DnaId = pm.orgChild3?.ancestorDNA || _null;
|
||||
h.child4DnaId = pm.orgChild4?.ancestorDNA || _null;
|
||||
h.posMasterNoPrefix = pm.posMasterNoPrefix ?? _null;
|
||||
h.posMasterNo = pm.posMasterNo ?? _null;
|
||||
h.posMasterNoSuffix = pm.posMasterNoSuffix ?? _null;
|
||||
h.shortName =
|
||||
[
|
||||
pm.orgChild4?.orgChild4ShortName,
|
||||
pm.orgChild3?.orgChild3ShortName,
|
||||
pm.orgChild2?.orgChild2ShortName,
|
||||
pm.orgChild1?.orgChild1ShortName,
|
||||
pm.orgRoot?.orgRootShortName,
|
||||
].find((s) => typeof s === "string" && s.trim().length > 0) ?? _null;
|
||||
const userId = request?.user?.sub ?? "";
|
||||
const userName = request?.user?.name ?? "system";
|
||||
h.createdUserId = userId;
|
||||
h.createdFullName = userName;
|
||||
h.lastUpdateUserId = userId;
|
||||
h.lastUpdateFullName = userName;
|
||||
h.createdAt = new Date();
|
||||
h.lastUpdatedAt = new Date();
|
||||
await repoHistory.save(h);
|
||||
};
|
||||
|
||||
try {
|
||||
if (manager) {
|
||||
await execute(manager);
|
||||
return true;
|
||||
}
|
||||
|
||||
await AppDataSource.transaction(async (transactionManager) => {
|
||||
await execute(transactionManager);
|
||||
});
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (manager) {
|
||||
console.error("CreatePosMasterHistoryEmployee error (external transaction):", err);
|
||||
throw err;
|
||||
}
|
||||
console.error("CreatePosMasterHistoryEmployee transaction error:", err);
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue