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 { RetirementService } from "../services/RetirementService";
|
||||||
import { ExecuteOfficerProfileService } from "../services/ExecuteOfficerProfileService";
|
import { ExecuteOfficerProfileService } from "../services/ExecuteOfficerProfileService";
|
||||||
import { ExecuteSalaryService } from "../services/ExecuteSalaryService";
|
import { ExecuteSalaryService } from "../services/ExecuteSalaryService";
|
||||||
import { ExecuteSalaryCurrentService, ExecuteSalaryResult } from "../services/ExecuteSalaryCurrentService";
|
import { ExecuteSalaryCurrentService } from "../services/ExecuteSalaryCurrentService";
|
||||||
import { ExecuteSalaryEmployeeCurrentService } from "../services/ExecuteSalaryEmployeeCurrentService";
|
import { ExecuteSalaryEmployeeCurrentService } from "../services/ExecuteSalaryEmployeeCurrentService";
|
||||||
import { ExecuteSalaryLeaveService } from "../services/ExecuteSalaryLeaveService";
|
import { ExecuteSalaryLeaveService } from "../services/ExecuteSalaryLeaveService";
|
||||||
import { ExecuteSalaryEmployeeLeaveService } from "../services/ExecuteSalaryEmployeeLeaveService";
|
import { ExecuteSalaryEmployeeLeaveService } from "../services/ExecuteSalaryEmployeeLeaveService";
|
||||||
|
|
@ -3702,14 +3702,11 @@ export class CommandController extends Controller {
|
||||||
}[];
|
}[];
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const result: ExecuteSalaryResult = await new ExecuteSalaryCurrentService().executeSalaryCurrent(
|
await new ExecuteSalaryCurrentService().executeSalaryCurrent(body.data, {
|
||||||
body.data,
|
|
||||||
{
|
|
||||||
user: { sub: req.user.sub, name: req.user.name },
|
user: { sub: req.user.sub, name: req.user.name },
|
||||||
req,
|
req,
|
||||||
},
|
});
|
||||||
);
|
return new HttpSuccess();
|
||||||
return new HttpSuccess(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post("excexute/salary-employee-current")
|
@Post("excexute/salary-employee-current")
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { PosMaster } from "../entities/PosMaster";
|
||||||
import { Position } from "../entities/Position";
|
import { Position } from "../entities/Position";
|
||||||
import { EmployeePosMaster } from "../entities/EmployeePosMaster";
|
import { EmployeePosMaster } from "../entities/EmployeePosMaster";
|
||||||
import { EmployeePosition } from "../entities/EmployeePosition";
|
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 { RequestWithUser } from "../middlewares/user";
|
||||||
import { Command } from "../entities/Command";
|
import { Command } from "../entities/Command";
|
||||||
import { ProfileSalary } from "../entities/ProfileSalary";
|
import { ProfileSalary } from "../entities/ProfileSalary";
|
||||||
|
|
@ -254,14 +254,23 @@ export function calculateRetireYear(birthDate: Date) {
|
||||||
|
|
||||||
return yy + 61;
|
return yy + 61;
|
||||||
}
|
}
|
||||||
export async function removeProfileInOrganize(profileId: string, type: string) {
|
export async function removeProfileInOrganize(
|
||||||
const currentRevision = await AppDataSource.getRepository(OrgRevision)
|
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")
|
.createQueryBuilder("orgRevision")
|
||||||
.where("orgRevision.orgRevisionIsDraft = false")
|
.where("orgRevision.orgRevisionIsDraft = false")
|
||||||
.andWhere("orgRevision.orgRevisionIsCurrent = true")
|
.andWhere("orgRevision.orgRevisionIsCurrent = true")
|
||||||
.getOne();
|
.getOne();
|
||||||
|
|
||||||
const draftRevision = await AppDataSource.getRepository(OrgRevision)
|
const draftRevision = await ds
|
||||||
|
.getRepository(OrgRevision)
|
||||||
.createQueryBuilder("orgRevision")
|
.createQueryBuilder("orgRevision")
|
||||||
.where("orgRevision.orgRevisionIsDraft = true")
|
.where("orgRevision.orgRevisionIsDraft = true")
|
||||||
.andWhere("orgRevision.orgRevisionIsCurrent = false")
|
.andWhere("orgRevision.orgRevisionIsCurrent = false")
|
||||||
|
|
@ -271,26 +280,30 @@ export async function removeProfileInOrganize(profileId: string, type: string) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (type === "OFFICER") {
|
if (type === "OFFICER") {
|
||||||
const findProfileInposMaster = await AppDataSource.getRepository(PosMaster)
|
const findProfileInposMaster = await ds
|
||||||
|
.getRepository(PosMaster)
|
||||||
.createQueryBuilder("posMaster")
|
.createQueryBuilder("posMaster")
|
||||||
.where("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: currentRevision?.id })
|
.where("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: currentRevision?.id })
|
||||||
.andWhere("posMaster.current_holderId = :profileId", { profileId })
|
.andWhere("posMaster.current_holderId = :profileId", { profileId })
|
||||||
.getOne();
|
.getOne();
|
||||||
|
|
||||||
await AppDataSource.getRepository(PosMaster)
|
await ds
|
||||||
|
.getRepository(PosMaster)
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
.update(PosMaster)
|
.update(PosMaster)
|
||||||
.set({ current_holderId: null, isSit: false })
|
.set({ current_holderId: null, isSit: false })
|
||||||
.where("id = :id", { id: findProfileInposMaster?.id })
|
.where("id = :id", { id: findProfileInposMaster?.id })
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
const findProfileInposMasterDraft = await AppDataSource.getRepository(PosMaster)
|
const findProfileInposMasterDraft = await ds
|
||||||
|
.getRepository(PosMaster)
|
||||||
.createQueryBuilder("posMaster")
|
.createQueryBuilder("posMaster")
|
||||||
.where("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: draftRevision?.id })
|
.where("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: draftRevision?.id })
|
||||||
.andWhere("posMaster.next_holderId = :profileId", { profileId })
|
.andWhere("posMaster.next_holderId = :profileId", { profileId })
|
||||||
.getOne();
|
.getOne();
|
||||||
|
|
||||||
await AppDataSource.getRepository(PosMaster)
|
await ds
|
||||||
|
.getRepository(PosMaster)
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
.update(PosMaster)
|
.update(PosMaster)
|
||||||
.set({ next_holderId: null, isSit: false })
|
.set({ next_holderId: null, isSit: false })
|
||||||
|
|
@ -300,7 +313,8 @@ export async function removeProfileInOrganize(profileId: string, type: string) {
|
||||||
if (!findProfileInposMaster && !findProfileInposMasterDraft) {
|
if (!findProfileInposMaster && !findProfileInposMasterDraft) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const findPosition = await AppDataSource.getRepository(Position)
|
const findPosition = await ds
|
||||||
|
.getRepository(Position)
|
||||||
.createQueryBuilder("position")
|
.createQueryBuilder("position")
|
||||||
.where("position.posMasterId = :posMasterId", { posMasterId: findProfileInposMaster?.id })
|
.where("position.posMasterId = :posMasterId", { posMasterId: findProfileInposMaster?.id })
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
@ -308,7 +322,8 @@ export async function removeProfileInOrganize(profileId: string, type: string) {
|
||||||
if (!findPosition) {
|
if (!findPosition) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await AppDataSource.getRepository(Position)
|
await ds
|
||||||
|
.getRepository(Position)
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
.update(Position)
|
.update(Position)
|
||||||
.set({ positionIsSelected: false })
|
.set({ positionIsSelected: false })
|
||||||
|
|
@ -316,14 +331,16 @@ export async function removeProfileInOrganize(profileId: string, type: string) {
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
if (type === "EMPLOYEE") {
|
if (type === "EMPLOYEE") {
|
||||||
const findProfileInEmpPosMaster = await AppDataSource.getRepository(EmployeePosMaster)
|
const findProfileInEmpPosMaster = await ds
|
||||||
|
.getRepository(EmployeePosMaster)
|
||||||
.createQueryBuilder("employeePosMaster")
|
.createQueryBuilder("employeePosMaster")
|
||||||
.where("employeePosMaster.orgRevisionId = :orgRevisionId", {
|
.where("employeePosMaster.orgRevisionId = :orgRevisionId", {
|
||||||
orgRevisionId: currentRevision?.id,
|
orgRevisionId: currentRevision?.id,
|
||||||
})
|
})
|
||||||
.andWhere("employeePosMaster.current_holderId = :profileId", { profileId })
|
.andWhere("employeePosMaster.current_holderId = :profileId", { profileId })
|
||||||
.getOne();
|
.getOne();
|
||||||
await AppDataSource.getRepository(EmployeePosMaster)
|
await ds
|
||||||
|
.getRepository(EmployeePosMaster)
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
.update(EmployeePosMaster)
|
.update(EmployeePosMaster)
|
||||||
.set({ current_holderId: null, isSit: false })
|
.set({ current_holderId: null, isSit: false })
|
||||||
|
|
@ -333,7 +350,8 @@ export async function removeProfileInOrganize(profileId: string, type: string) {
|
||||||
if (!findProfileInEmpPosMaster) {
|
if (!findProfileInEmpPosMaster) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const findEmpPosition = await AppDataSource.getRepository(EmployeePosition)
|
const findEmpPosition = await ds
|
||||||
|
.getRepository(EmployeePosition)
|
||||||
.createQueryBuilder("employeePosition")
|
.createQueryBuilder("employeePosition")
|
||||||
.where("employeePosition.posMasterId = :posMasterId", {
|
.where("employeePosition.posMasterId = :posMasterId", {
|
||||||
posMasterId: findProfileInEmpPosMaster?.id,
|
posMasterId: findProfileInEmpPosMaster?.id,
|
||||||
|
|
@ -344,7 +362,8 @@ export async function removeProfileInOrganize(profileId: string, type: string) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await AppDataSource.getRepository(EmployeePosition)
|
await ds
|
||||||
|
.getRepository(EmployeePosition)
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
.update(EmployeePosition)
|
.update(EmployeePosition)
|
||||||
.set({ positionIsSelected: false })
|
.set({ positionIsSelected: false })
|
||||||
|
|
@ -353,8 +372,10 @@ export async function removeProfileInOrganize(profileId: string, type: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removePostMasterAct(profileId: string) {
|
export async function removePostMasterAct(profileId: string, manager?: EntityManager) {
|
||||||
const currentRevision = await AppDataSource.getRepository(OrgRevision)
|
const ds = manager ?? AppDataSource;
|
||||||
|
const currentRevision = await ds
|
||||||
|
.getRepository(OrgRevision)
|
||||||
.createQueryBuilder("orgRevision")
|
.createQueryBuilder("orgRevision")
|
||||||
.where("orgRevision.orgRevisionIsDraft = false")
|
.where("orgRevision.orgRevisionIsDraft = false")
|
||||||
.andWhere("orgRevision.orgRevisionIsCurrent = true")
|
.andWhere("orgRevision.orgRevisionIsCurrent = true")
|
||||||
|
|
@ -364,7 +385,8 @@ export async function removePostMasterAct(profileId: string) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const findProfileInposMaster = await AppDataSource.getRepository(PosMaster)
|
const findProfileInposMaster = await ds
|
||||||
|
.getRepository(PosMaster)
|
||||||
.createQueryBuilder("posMaster")
|
.createQueryBuilder("posMaster")
|
||||||
.where("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: currentRevision?.id })
|
.where("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: currentRevision?.id })
|
||||||
.andWhere("posMaster.current_holderId = :profileId", { profileId })
|
.andWhere("posMaster.current_holderId = :profileId", { profileId })
|
||||||
|
|
@ -374,11 +396,12 @@ export async function removePostMasterAct(profileId: string) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const posMasterAct = await AppDataSource.getRepository(PosMasterAct)
|
const posMasterAct = await ds
|
||||||
|
.getRepository(PosMasterAct)
|
||||||
.createQueryBuilder("posMasterAct")
|
.createQueryBuilder("posMasterAct")
|
||||||
.where("posMasterAct.posMasterChildId = :posMasterChildId", { posMasterChildId: findProfileInposMaster.id })
|
.where("posMasterAct.posMasterChildId = :posMasterChildId", { posMasterChildId: findProfileInposMaster.id })
|
||||||
.getMany();
|
.getMany();
|
||||||
await AppDataSource.getRepository(PosMasterAct).remove(posMasterAct);
|
await ds.getRepository(PosMasterAct).remove(posMasterAct);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkReturnCommandType(commandId: string) {
|
export async function checkReturnCommandType(commandId: string) {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { CommandRecive } from "../entities/CommandRecive";
|
||||||
import { Command } from "../entities/Command";
|
import { Command } from "../entities/Command";
|
||||||
import { OrgRoot } from "../entities/OrgRoot";
|
import { OrgRoot } from "../entities/OrgRoot";
|
||||||
import { Profile } from "../entities/Profile";
|
import { Profile } from "../entities/Profile";
|
||||||
|
import { EntityManager } from "typeorm";
|
||||||
|
|
||||||
export interface PosNumCodeSitResult {
|
export interface PosNumCodeSitResult {
|
||||||
posNumCodeSit: string;
|
posNumCodeSit: string;
|
||||||
|
|
@ -17,21 +18,23 @@ export interface PosNumCodeSitResult {
|
||||||
* เรียงลำดับผู้ได้รับคำสั่งใหม่หลังจากลบรายการ และอัพเดทสถานะคำสั่งถ้าไม่มีผู้ได้รับคำสั่งเหลือ
|
* เรียงลำดับผู้ได้รับคำสั่งใหม่หลังจากลบรายการ และอัพเดทสถานะคำสั่งถ้าไม่มีผู้ได้รับคำสั่งเหลือ
|
||||||
* @param reciveId commandRecive.Id ของผู้ได้รับคำสั่ง
|
* @param reciveId commandRecive.Id ของผู้ได้รับคำสั่ง
|
||||||
* @param code ประเภทคำสั่ง
|
* @param code ประเภทคำสั่ง
|
||||||
|
* @param manager ถ้าส่งเข้ามา → ทุก operation อยู่ใน transaction ของ caller (all-or-nothing)
|
||||||
* @returns Promise<void>
|
* @returns Promise<void>
|
||||||
*/
|
*/
|
||||||
export async function reOrderCommandRecivesAndDelete(
|
export async function reOrderCommandRecivesAndDelete(
|
||||||
reciveId: string
|
reciveId: string,
|
||||||
|
manager?: EntityManager,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const commandReciveRepo = AppDataSource.getRepository(CommandRecive);
|
const ds = manager ?? AppDataSource;
|
||||||
const commandRepo = AppDataSource.getRepository(Command);
|
const commandReciveRepo = ds.getRepository(CommandRecive);
|
||||||
|
const commandRepo = ds.getRepository(Command);
|
||||||
|
|
||||||
// ค้นหาข้อมูลผู้ได้รับคำสั่งตาม reciveId
|
// ค้นหาข้อมูลผู้ได้รับคำสั่งตาม reciveId
|
||||||
const commandRecive = await commandReciveRepo.findOne({
|
const commandRecive = await commandReciveRepo.findOne({
|
||||||
where: { id: reciveId }
|
where: { id: reciveId },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (commandRecive == null)
|
if (commandRecive == null) return;
|
||||||
return;
|
|
||||||
|
|
||||||
const commandId = commandRecive.commandId;
|
const commandId = commandRecive.commandId;
|
||||||
// ลบตาม refId
|
// ลบตาม refId
|
||||||
|
|
@ -43,17 +46,14 @@ export async function reOrderCommandRecivesAndDelete(
|
||||||
});
|
});
|
||||||
// ลำดับผู้ได้รับคำสั่งใหม่
|
// ลำดับผู้ได้รับคำสั่งใหม่
|
||||||
if (commandReciveList.length > 0) {
|
if (commandReciveList.length > 0) {
|
||||||
await Promise.all(
|
for (let i = 0; i < commandReciveList.length; i++) {
|
||||||
commandReciveList.map(async (p, i) => {
|
commandReciveList[i].order = i + 1;
|
||||||
p.order = i + 1;
|
await commandReciveRepo.save(commandReciveList[i]);
|
||||||
await commandReciveRepo.save(p);
|
}
|
||||||
})
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// ถ้าไม่มีผู้ได้รับคำสั่งเหลือเลย ให้ยกเลิกคำสั่ง
|
// ถ้าไม่มีผู้ได้รับคำสั่งเหลือเลย ให้ยกเลิกคำสั่ง
|
||||||
await commandRepo.update({ id: commandId }, { status: "CANCEL" });
|
await commandRepo.update({ id: commandId }, { status: "CANCEL" });
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { In, Like } from "typeorm";
|
import { EntityManager, In, Like } from "typeorm";
|
||||||
import { AppDataSource } from "../database/data-source";
|
import { AppDataSource } from "../database/data-source";
|
||||||
import HttpError from "../interfaces/http-error";
|
import HttpError from "../interfaces/http-error";
|
||||||
import HttpStatusCode from "../interfaces/http-status";
|
import HttpStatusCode from "../interfaces/http-status";
|
||||||
|
|
@ -177,8 +177,12 @@ export class ExecuteOfficerProfileService {
|
||||||
data: OfficerProfileItem[],
|
data: OfficerProfileItem[],
|
||||||
ctx: ExecutionContext,
|
ctx: ExecutionContext,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
console.log("[ExecuteOfficerProfileService] Starting executeCreateOfficerProfile");
|
const commandId =
|
||||||
console.log("[ExecuteOfficerProfileService] Request body count:", data?.length);
|
data?.find((x: any) => x.bodySalarys?.commandId)?.bodySalarys?.commandId ?? "unknown";
|
||||||
|
console.log(
|
||||||
|
`[ExecuteOfficerProfileService] Starting executeCreateOfficerProfile — commandId: ${commandId}`,
|
||||||
|
);
|
||||||
|
console.log(`[ExecuteOfficerProfileService] Request body count: ${data?.length ?? 0}`);
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
// Normalize date fields
|
// Normalize date fields
|
||||||
|
|
@ -326,20 +330,101 @@ export class ExecuteOfficerProfileService {
|
||||||
data.length,
|
data.length,
|
||||||
"profile(s)",
|
"profile(s)",
|
||||||
);
|
);
|
||||||
await Promise.all(
|
|
||||||
data.map(async (item, index) => {
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
// 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,
|
||||||
|
roleKeycloak,
|
||||||
|
list,
|
||||||
|
_posNumCodeSit,
|
||||||
|
_posNumCodeSitAbb,
|
||||||
|
meta,
|
||||||
|
before,
|
||||||
|
commandId,
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
const reason =
|
||||||
|
err instanceof HttpError
|
||||||
|
? err.message
|
||||||
|
: err instanceof Error
|
||||||
|
? err.message
|
||||||
|
: "unexpected error";
|
||||||
|
console.error(
|
||||||
|
`[ExecuteOfficerProfileService] Failed commandId=${commandId}, citizenId=${item.bodyProfile?.citizenId}: ${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: OfficerProfileItem,
|
||||||
|
ctx: ExecutionContext,
|
||||||
|
manager: EntityManager,
|
||||||
|
roleKeycloak: RoleKeycloak | null,
|
||||||
|
list: any[],
|
||||||
|
_posNumCodeSit: string,
|
||||||
|
_posNumCodeSitAbb: string,
|
||||||
|
meta: any,
|
||||||
|
before: any,
|
||||||
|
commandId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const req = ctx.req;
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
// repo ทั้งหมดสร้างจาก manager เพื่อให้อยู่ใน transaction เดียวกัน
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
const profileRepository = manager.getRepository(Profile);
|
||||||
|
const profileEmployeeRepository = manager.getRepository(ProfileEmployee);
|
||||||
|
const salaryRepo = manager.getRepository(ProfileSalary);
|
||||||
|
const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory);
|
||||||
|
const posLevelRepo = manager.getRepository(PosLevel);
|
||||||
|
const posTypeRepo = manager.getRepository(PosType);
|
||||||
|
const provinceRepo = manager.getRepository(Province);
|
||||||
|
const districtRepo = manager.getRepository(District);
|
||||||
|
const subDistrictRepo = manager.getRepository(SubDistrict);
|
||||||
|
const posMasterRepository = manager.getRepository(PosMaster);
|
||||||
|
const positionRepository = manager.getRepository(Position);
|
||||||
|
const profileEducationRepo = manager.getRepository(ProfileEducation);
|
||||||
|
const profileEducationHistoryRepo = manager.getRepository(ProfileEducationHistory);
|
||||||
|
const certificateRepo = manager.getRepository(ProfileCertificate);
|
||||||
|
const certificateHistoryRepo = manager.getRepository(ProfileCertificateHistory);
|
||||||
|
const profileFamilyCoupleRepo = manager.getRepository(ProfileFamilyCouple);
|
||||||
|
const profileFamilyCoupleHistoryRepo = manager.getRepository(ProfileFamilyCoupleHistory);
|
||||||
|
const profileFamilyFatherRepo = manager.getRepository(ProfileFamilyFather);
|
||||||
|
const profileFamilyFatherHistoryRepo = manager.getRepository(ProfileFamilyFatherHistory);
|
||||||
|
const profileFamilyMotherRepo = manager.getRepository(ProfileFamilyMother);
|
||||||
|
const profileFamilyMotherHistoryRepo = manager.getRepository(ProfileFamilyMotherHistory);
|
||||||
|
const insigniaRepo = manager.getRepository(ProfileInsignia);
|
||||||
|
const insigniaHistoryRepo = manager.getRepository(ProfileInsigniaHistory);
|
||||||
|
const avatarRepository = manager.getRepository(ProfileAvatar);
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
"[ExecuteOfficerProfileService] Processing item",
|
"[ExecuteOfficerProfileService] Processing citizenId:",
|
||||||
index + 1,
|
item.bodyProfile.citizenId,
|
||||||
"of",
|
|
||||||
data.length,
|
|
||||||
);
|
);
|
||||||
const _null: any = null;
|
const _null: any = null;
|
||||||
if (item.bodyProfile.posLevelId === "") item.bodyProfile.posLevelId = null;
|
if (item.bodyProfile.posLevelId === "") item.bodyProfile.posLevelId = null;
|
||||||
if (item.bodyProfile.posTypeId === "") item.bodyProfile.posTypeId = null;
|
if (item.bodyProfile.posTypeId === "") item.bodyProfile.posTypeId = null;
|
||||||
if (
|
if (
|
||||||
item.bodyProfile.posLevelId &&
|
item.bodyProfile.posLevelId &&
|
||||||
!(await this.posLevelRepo.findOneBy({ id: item.bodyProfile.posLevelId }))
|
!(await posLevelRepo.findOneBy({ id: item.bodyProfile.posLevelId }))
|
||||||
) {
|
) {
|
||||||
console.error(
|
console.error(
|
||||||
"[ExecuteOfficerProfileService] ไม่พบข้อมูลระดับตำแหน่งนี้ posLevelId:",
|
"[ExecuteOfficerProfileService] ไม่พบข้อมูลระดับตำแหน่งนี้ posLevelId:",
|
||||||
|
|
@ -349,7 +434,7 @@ export class ExecuteOfficerProfileService {
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
item.bodyProfile.posTypeId &&
|
item.bodyProfile.posTypeId &&
|
||||||
!(await this.posTypeRepo.findOneBy({ id: item.bodyProfile.posTypeId }))
|
!(await posTypeRepo.findOneBy({ id: item.bodyProfile.posTypeId }))
|
||||||
) {
|
) {
|
||||||
console.error(
|
console.error(
|
||||||
"[ExecuteOfficerProfileService] ไม่พบข้อมูลประเภทตำแหน่งนี้ posTypeId:",
|
"[ExecuteOfficerProfileService] ไม่พบข้อมูลประเภทตำแหน่งนี้ posTypeId:",
|
||||||
|
|
@ -362,22 +447,22 @@ export class ExecuteOfficerProfileService {
|
||||||
"[ExecuteOfficerProfileService] Processing citizenId:",
|
"[ExecuteOfficerProfileService] Processing citizenId:",
|
||||||
item.bodyProfile.citizenId,
|
item.bodyProfile.citizenId,
|
||||||
);
|
);
|
||||||
let registrationProvinceId = await this.provinceRepo.findOneBy({
|
let registrationProvinceId = await provinceRepo.findOneBy({
|
||||||
id: item.bodyProfile.registrationProvinceId ?? "",
|
id: item.bodyProfile.registrationProvinceId ?? "",
|
||||||
});
|
});
|
||||||
let registrationDistrictId = await this.districtRepo.findOneBy({
|
let registrationDistrictId = await districtRepo.findOneBy({
|
||||||
id: item.bodyProfile.registrationDistrictId ?? "",
|
id: item.bodyProfile.registrationDistrictId ?? "",
|
||||||
});
|
});
|
||||||
let registrationSubDistrictId = await this.subDistrictRepo.findOneBy({
|
let registrationSubDistrictId = await subDistrictRepo.findOneBy({
|
||||||
id: item.bodyProfile.registrationSubDistrictId ?? "",
|
id: item.bodyProfile.registrationSubDistrictId ?? "",
|
||||||
});
|
});
|
||||||
let currentProvinceId = await this.provinceRepo.findOneBy({
|
let currentProvinceId = await provinceRepo.findOneBy({
|
||||||
id: item.bodyProfile.currentProvinceId ?? "",
|
id: item.bodyProfile.currentProvinceId ?? "",
|
||||||
});
|
});
|
||||||
let currentDistrictId = await this.districtRepo.findOneBy({
|
let currentDistrictId = await districtRepo.findOneBy({
|
||||||
id: item.bodyProfile.currentDistrictId ?? "",
|
id: item.bodyProfile.currentDistrictId ?? "",
|
||||||
});
|
});
|
||||||
let currentSubDistrictId = await this.subDistrictRepo.findOneBy({
|
let currentSubDistrictId = await subDistrictRepo.findOneBy({
|
||||||
id: item.bodyProfile.currentSubDistrictId ?? "",
|
id: item.bodyProfile.currentSubDistrictId ?? "",
|
||||||
});
|
});
|
||||||
console.log("[ExecuteOfficerProfileService] Address validation completed");
|
console.log("[ExecuteOfficerProfileService] Address validation completed");
|
||||||
|
|
@ -502,7 +587,7 @@ export class ExecuteOfficerProfileService {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let profile: any = await this.profileRepository.findOne({
|
let profile: any = await profileRepository.findOne({
|
||||||
where: { citizenId: item.bodyProfile.citizenId /*, isActive: true */ },
|
where: { citizenId: item.bodyProfile.citizenId /*, isActive: true */ },
|
||||||
relations: ["roleKeycloaks", "profileInsignias", "profileAvatars"],
|
relations: ["roleKeycloaks", "profileInsignias", "profileAvatars"],
|
||||||
});
|
});
|
||||||
|
|
@ -520,7 +605,7 @@ export class ExecuteOfficerProfileService {
|
||||||
"[ExecuteOfficerProfileService] No existing profile found, creating new profile",
|
"[ExecuteOfficerProfileService] No existing profile found, creating new profile",
|
||||||
);
|
);
|
||||||
//กรณีลูกจ้างประจำมาสอบเป็นข้าราชการ ต้อง update สถานะโปรไฟล์เดิม
|
//กรณีลูกจ้างประจำมาสอบเป็นข้าราชการ ต้อง update สถานะโปรไฟล์เดิม
|
||||||
let profileEmployee: any = await this.profileEmployeeRepository.findOne({
|
let profileEmployee: any = await profileEmployeeRepository.findOne({
|
||||||
where: { citizenId: item.bodyProfile.citizenId },
|
where: { citizenId: item.bodyProfile.citizenId },
|
||||||
relations: ["profileInsignias", "roleKeycloaks"],
|
relations: ["profileInsignias", "roleKeycloaks"],
|
||||||
});
|
});
|
||||||
|
|
@ -532,7 +617,7 @@ export class ExecuteOfficerProfileService {
|
||||||
console.log(
|
console.log(
|
||||||
"[ExecuteOfficerProfileService] Converting employee profile to officer profile",
|
"[ExecuteOfficerProfileService] Converting employee profile to officer profile",
|
||||||
);
|
);
|
||||||
const _order = await this.salaryRepo.findOne({
|
const _order = await salaryRepo.findOne({
|
||||||
where: { profileEmployeeId: profileEmployee.id },
|
where: { profileEmployeeId: profileEmployee.id },
|
||||||
order: { order: "DESC" },
|
order: { order: "DESC" },
|
||||||
});
|
});
|
||||||
|
|
@ -550,15 +635,15 @@ export class ExecuteOfficerProfileService {
|
||||||
Object.assign(history, { ...profileEmpSalary, id: undefined });
|
Object.assign(history, { ...profileEmpSalary, id: undefined });
|
||||||
profileEmpSalary.dateGovernment = item.bodySalarys?.commandDateAffect ?? meta.createdAt;
|
profileEmpSalary.dateGovernment = item.bodySalarys?.commandDateAffect ?? meta.createdAt;
|
||||||
(profileEmpSalary.profileId = _null),
|
(profileEmpSalary.profileId = _null),
|
||||||
await this.salaryRepo.save(profileEmpSalary, { data: req });
|
await salaryRepo.save(profileEmpSalary, { data: req });
|
||||||
setLogDataDiff(req, { before, after: profileEmpSalary });
|
setLogDataDiff(req, { before, after: profileEmpSalary });
|
||||||
history.profileSalaryId = profileEmpSalary.id;
|
history.profileSalaryId = profileEmpSalary.id;
|
||||||
await this.salaryHistoryRepo.save(history, { data: req });
|
await salaryHistoryRepo.save(history, { data: req });
|
||||||
|
|
||||||
if (profileEmployee.profileInsignias.length > 0) {
|
if (profileEmployee.profileInsignias.length > 0) {
|
||||||
_oldInsigniaIds = profileEmployee.profileInsignias?.map((x: any) => x.id) ?? [];
|
_oldInsigniaIds = profileEmployee.profileInsignias?.map((x: any) => x.id) ?? [];
|
||||||
}
|
}
|
||||||
await removeProfileInOrganize(profileEmployee.id, "EMPLOYEE");
|
await removeProfileInOrganize(profileEmployee.id, "EMPLOYEE", manager);
|
||||||
if (profileEmployee.keycloak != null) {
|
if (profileEmployee.keycloak != null) {
|
||||||
// const delUserKeycloak = await deleteUser(profileEmployee.keycloak);
|
// const delUserKeycloak = await deleteUser(profileEmployee.keycloak);
|
||||||
// if (delUserKeycloak) {
|
// if (delUserKeycloak) {
|
||||||
|
|
@ -573,7 +658,7 @@ export class ExecuteOfficerProfileService {
|
||||||
profileEmployee.lastUpdateUserId = ctx.user.sub;
|
profileEmployee.lastUpdateUserId = ctx.user.sub;
|
||||||
profileEmployee.lastUpdateFullName = ctx.user.name;
|
profileEmployee.lastUpdateFullName = ctx.user.name;
|
||||||
profileEmployee.lastUpdatedAt = new Date();
|
profileEmployee.lastUpdatedAt = new Date();
|
||||||
await this.profileEmployeeRepository.save(profileEmployee);
|
await profileEmployeeRepository.save(profileEmployee);
|
||||||
setLogDataDiff(req, { before, after: profileEmployee });
|
setLogDataDiff(req, { before, after: profileEmployee });
|
||||||
}
|
}
|
||||||
profile = Object.assign({ ...item.bodyProfile, ...meta });
|
profile = Object.assign({ ...item.bodyProfile, ...meta });
|
||||||
|
|
@ -619,7 +704,7 @@ export class ExecuteOfficerProfileService {
|
||||||
profile.phone = item.bodyProfile.phone ?? null;
|
profile.phone = item.bodyProfile.phone ?? null;
|
||||||
|
|
||||||
console.log("[ExecuteOfficerProfileService] Saving new profile");
|
console.log("[ExecuteOfficerProfileService] Saving new profile");
|
||||||
await this.profileRepository.save(profile);
|
await profileRepository.save(profile);
|
||||||
console.log(
|
console.log(
|
||||||
"[ExecuteOfficerProfileService] New profile saved, profileId:",
|
"[ExecuteOfficerProfileService] New profile saved, profileId:",
|
||||||
profile.id,
|
profile.id,
|
||||||
|
|
@ -649,7 +734,7 @@ export class ExecuteOfficerProfileService {
|
||||||
"[ExecuteOfficerProfileService] Profile is leaving with eligible leave type, creating new profile record",
|
"[ExecuteOfficerProfileService] Profile is leaving with eligible leave type, creating new profile record",
|
||||||
);
|
);
|
||||||
//ดึง profileSalary เดิม
|
//ดึง profileSalary เดิม
|
||||||
_oldSalaries = await this.salaryRepo.find({
|
_oldSalaries = await salaryRepo.find({
|
||||||
where: { profileId: profile.id },
|
where: { profileId: profile.id },
|
||||||
order: { order: "ASC" },
|
order: { order: "ASC" },
|
||||||
});
|
});
|
||||||
|
|
@ -696,7 +781,7 @@ export class ExecuteOfficerProfileService {
|
||||||
profile.nationality = item.bodyProfile.nationality ?? null;
|
profile.nationality = item.bodyProfile.nationality ?? null;
|
||||||
profile.bloodGroup = item.bodyProfile.bloodGroup ?? null;
|
profile.bloodGroup = item.bodyProfile.bloodGroup ?? null;
|
||||||
profile.phone = item.bodyProfile.phone ?? null;
|
profile.phone = item.bodyProfile.phone ?? null;
|
||||||
await this.profileRepository.save(profile);
|
await profileRepository.save(profile);
|
||||||
console.log(
|
console.log(
|
||||||
"[ExecuteOfficerProfileService] New profile record saved for leaving officer, profileId:",
|
"[ExecuteOfficerProfileService] New profile record saved for leaving officer, profileId:",
|
||||||
profile.id,
|
profile.id,
|
||||||
|
|
@ -791,7 +876,7 @@ export class ExecuteOfficerProfileService {
|
||||||
item.bodyProfile.phone && item.bodyProfile.phone != ""
|
item.bodyProfile.phone && item.bodyProfile.phone != ""
|
||||||
? item.bodyProfile.phone
|
? item.bodyProfile.phone
|
||||||
: profile.phone;
|
: profile.phone;
|
||||||
await this.profileRepository.save(profile);
|
await profileRepository.save(profile);
|
||||||
console.log(
|
console.log(
|
||||||
"[ExecuteOfficerProfileService] Existing active profile updated, profileId:",
|
"[ExecuteOfficerProfileService] Existing active profile updated, profileId:",
|
||||||
profile.id,
|
profile.id,
|
||||||
|
|
@ -811,25 +896,23 @@ export class ExecuteOfficerProfileService {
|
||||||
"[ExecuteOfficerProfileService] Processing educations, count:",
|
"[ExecuteOfficerProfileService] Processing educations, count:",
|
||||||
item.bodyEducations.length,
|
item.bodyEducations.length,
|
||||||
);
|
);
|
||||||
await Promise.all(
|
for (const education of item.bodyEducations) {
|
||||||
item.bodyEducations.map(async (education) => {
|
|
||||||
const profileEdu = new ProfileEducation();
|
const profileEdu = new ProfileEducation();
|
||||||
Object.assign(profileEdu, { ...education, ...meta });
|
Object.assign(profileEdu, { ...education, ...meta });
|
||||||
const eduHistory = new ProfileEducationHistory();
|
const eduHistory = new ProfileEducationHistory();
|
||||||
Object.assign(eduHistory, { ...profileEdu, id: undefined });
|
Object.assign(eduHistory, { ...profileEdu, id: undefined });
|
||||||
profileEdu.profileId = profile.id;
|
profileEdu.profileId = profile.id;
|
||||||
const educationLevel = await this.profileEducationRepo.findOne({
|
const educationLevel = await profileEducationRepo.findOne({
|
||||||
select: ["id", "level", "profileId"],
|
select: ["id", "level", "profileId"],
|
||||||
where: { profileId: profile.id, isDeleted: false },
|
where: { profileId: profile.id, isDeleted: false },
|
||||||
order: { level: "DESC" },
|
order: { level: "DESC" },
|
||||||
});
|
});
|
||||||
profileEdu.level = educationLevel == null ? 1 : educationLevel.level + 1;
|
profileEdu.level = educationLevel == null ? 1 : educationLevel.level + 1;
|
||||||
await this.profileEducationRepo.save(profileEdu, { data: req });
|
await profileEducationRepo.save(profileEdu, { data: req });
|
||||||
setLogDataDiff(req, { before, after: profileEdu });
|
setLogDataDiff(req, { before, after: profileEdu });
|
||||||
eduHistory.profileEducationId = profileEdu.id;
|
eduHistory.profileEducationId = profileEdu.id;
|
||||||
await this.profileEducationHistoryRepo.save(eduHistory, { data: req });
|
await profileEducationHistoryRepo.save(eduHistory, { data: req });
|
||||||
}),
|
}
|
||||||
);
|
|
||||||
}
|
}
|
||||||
//Certificates
|
//Certificates
|
||||||
if (item.bodyCertificates && item.bodyCertificates.length > 0) {
|
if (item.bodyCertificates && item.bodyCertificates.length > 0) {
|
||||||
|
|
@ -837,19 +920,17 @@ export class ExecuteOfficerProfileService {
|
||||||
"[ExecuteOfficerProfileService] Processing certificates, count:",
|
"[ExecuteOfficerProfileService] Processing certificates, count:",
|
||||||
item.bodyCertificates.length,
|
item.bodyCertificates.length,
|
||||||
);
|
);
|
||||||
await Promise.all(
|
for (const cer of item.bodyCertificates) {
|
||||||
item.bodyCertificates.map(async (cer) => {
|
|
||||||
const profileCer = new ProfileCertificate();
|
const profileCer = new ProfileCertificate();
|
||||||
Object.assign(profileCer, { ...cer, ...meta });
|
Object.assign(profileCer, { ...cer, ...meta });
|
||||||
const cerHistory = new ProfileCertificateHistory();
|
const cerHistory = new ProfileCertificateHistory();
|
||||||
Object.assign(cerHistory, { ...profileCer, id: undefined });
|
Object.assign(cerHistory, { ...profileCer, id: undefined });
|
||||||
profileCer.profileId = profile.id;
|
profileCer.profileId = profile.id;
|
||||||
await this.certificateRepo.save(profileCer, { data: req });
|
await certificateRepo.save(profileCer, { data: req });
|
||||||
setLogDataDiff(req, { before, after: profileCer });
|
setLogDataDiff(req, { before, after: profileCer });
|
||||||
cerHistory.profileCertificateId = profileCer.id;
|
cerHistory.profileCertificateId = profileCer.id;
|
||||||
await this.certificateHistoryRepo.save(cerHistory, { data: req });
|
await certificateHistoryRepo.save(cerHistory, { data: req });
|
||||||
}),
|
}
|
||||||
);
|
|
||||||
}
|
}
|
||||||
//FamilyCouple
|
//FamilyCouple
|
||||||
if (item.bodyMarry != null) {
|
if (item.bodyMarry != null) {
|
||||||
|
|
@ -868,10 +949,10 @@ export class ExecuteOfficerProfileService {
|
||||||
const coupleHistory = new ProfileFamilyCoupleHistory();
|
const coupleHistory = new ProfileFamilyCoupleHistory();
|
||||||
Object.assign(coupleHistory, { ...profileCouple, id: undefined });
|
Object.assign(coupleHistory, { ...profileCouple, id: undefined });
|
||||||
profileCouple.profileId = profile.id;
|
profileCouple.profileId = profile.id;
|
||||||
await this.profileFamilyCoupleRepo.save(profileCouple, { data: req });
|
await profileFamilyCoupleRepo.save(profileCouple, { data: req });
|
||||||
setLogDataDiff(req, { before, after: profileCouple });
|
setLogDataDiff(req, { before, after: profileCouple });
|
||||||
coupleHistory.profileFamilyCoupleId = profileCouple.id;
|
coupleHistory.profileFamilyCoupleId = profileCouple.id;
|
||||||
await this.profileFamilyCoupleHistoryRepo.save(coupleHistory, { data: req });
|
await profileFamilyCoupleHistoryRepo.save(coupleHistory, { data: req });
|
||||||
}
|
}
|
||||||
//FamilyFather
|
//FamilyFather
|
||||||
if (item.bodyFather != null) {
|
if (item.bodyFather != null) {
|
||||||
|
|
@ -889,10 +970,10 @@ export class ExecuteOfficerProfileService {
|
||||||
const fatherHistory = new ProfileFamilyFatherHistory();
|
const fatherHistory = new ProfileFamilyFatherHistory();
|
||||||
Object.assign(fatherHistory, { ...profileFather, id: undefined });
|
Object.assign(fatherHistory, { ...profileFather, id: undefined });
|
||||||
profileFather.profileId = profile.id;
|
profileFather.profileId = profile.id;
|
||||||
await this.profileFamilyFatherRepo.save(profileFather, { data: req });
|
await profileFamilyFatherRepo.save(profileFather, { data: req });
|
||||||
setLogDataDiff(req, { before, after: profileFather });
|
setLogDataDiff(req, { before, after: profileFather });
|
||||||
fatherHistory.profileFamilyFatherId = profileFather.id;
|
fatherHistory.profileFamilyFatherId = profileFather.id;
|
||||||
await this.profileFamilyFatherHistoryRepo.save(fatherHistory, { data: req });
|
await profileFamilyFatherHistoryRepo.save(fatherHistory, { data: req });
|
||||||
}
|
}
|
||||||
//FamilyMother
|
//FamilyMother
|
||||||
if (item.bodyMother != null) {
|
if (item.bodyMother != null) {
|
||||||
|
|
@ -910,10 +991,10 @@ export class ExecuteOfficerProfileService {
|
||||||
const motherHistory = new ProfileFamilyMotherHistory();
|
const motherHistory = new ProfileFamilyMotherHistory();
|
||||||
Object.assign(motherHistory, { ...profileMother, id: undefined });
|
Object.assign(motherHistory, { ...profileMother, id: undefined });
|
||||||
profileMother.profileId = profile.id;
|
profileMother.profileId = profile.id;
|
||||||
await this.profileFamilyMotherRepo.save(profileMother, { data: req });
|
await profileFamilyMotherRepo.save(profileMother, { data: req });
|
||||||
setLogDataDiff(req, { before, after: profileMother });
|
setLogDataDiff(req, { before, after: profileMother });
|
||||||
motherHistory.profileFamilyMotherId = profileMother.id;
|
motherHistory.profileFamilyMotherId = profileMother.id;
|
||||||
await this.profileFamilyMotherHistoryRepo.save(motherHistory, { data: req });
|
await profileFamilyMotherHistoryRepo.save(motherHistory, { data: req });
|
||||||
}
|
}
|
||||||
//Salary
|
//Salary
|
||||||
//insert profileSalary อันเก่า กรณีพ้นราชการแล้วกลับมาบรรจุ
|
//insert profileSalary อันเก่า กรณีพ้นราชการแล้วกลับมาบรรจุ
|
||||||
|
|
@ -922,24 +1003,22 @@ export class ExecuteOfficerProfileService {
|
||||||
"[ExecuteOfficerProfileService] Restoring old salaries, count:",
|
"[ExecuteOfficerProfileService] Restoring old salaries, count:",
|
||||||
_oldSalaries.length,
|
_oldSalaries.length,
|
||||||
);
|
);
|
||||||
await Promise.all(
|
for (const oldSal of _oldSalaries) {
|
||||||
_oldSalaries.map(async (oldSal) => {
|
|
||||||
const profileSal: any = new ProfileSalary();
|
const profileSal: any = new ProfileSalary();
|
||||||
Object.assign(profileSal, { ...oldSal, ...meta });
|
Object.assign(profileSal, { ...oldSal, ...meta });
|
||||||
const salaryHistory = new ProfileSalaryHistory();
|
const salaryHistory = new ProfileSalaryHistory();
|
||||||
Object.assign(salaryHistory, { ...profileSal, id: undefined });
|
Object.assign(salaryHistory, { ...profileSal, id: undefined });
|
||||||
profileSal.profileId = profile.id;
|
profileSal.profileId = profile.id;
|
||||||
await this.salaryRepo.save(profileSal, { data: req });
|
await salaryRepo.save(profileSal, { data: req });
|
||||||
setLogDataDiff(req, { before, after: profileSal });
|
setLogDataDiff(req, { before, after: profileSal });
|
||||||
salaryHistory.profileSalaryId = profileSal.id;
|
salaryHistory.profileSalaryId = profileSal.id;
|
||||||
await this.salaryHistoryRepo.save(salaryHistory, { data: req });
|
await salaryHistoryRepo.save(salaryHistory, { data: req });
|
||||||
}),
|
}
|
||||||
);
|
|
||||||
}
|
}
|
||||||
//insert item.bodySalarys ต่อจากที่ insert เดิมไปแล้ว
|
//insert item.bodySalarys ต่อจากที่ insert เดิมไปแล้ว
|
||||||
if (item.bodySalarys && item.bodySalarys != null) {
|
if (item.bodySalarys && item.bodySalarys != null) {
|
||||||
console.log("[ExecuteOfficerProfileService] Processing new salary data");
|
console.log("[ExecuteOfficerProfileService] Processing new salary data");
|
||||||
const dest_item = await this.salaryRepo.findOne({
|
const dest_item = await salaryRepo.findOne({
|
||||||
where: { profileId: profile.id },
|
where: { profileId: profile.id },
|
||||||
order: { order: "DESC" },
|
order: { order: "DESC" },
|
||||||
});
|
});
|
||||||
|
|
@ -956,10 +1035,10 @@ export class ExecuteOfficerProfileService {
|
||||||
profileSal.amountSpecial = item.bodySalarys.amountSpecial ?? null;
|
profileSal.amountSpecial = item.bodySalarys.amountSpecial ?? null;
|
||||||
profileSal.positionSalaryAmount = item.bodySalarys.positionSalaryAmount ?? null;
|
profileSal.positionSalaryAmount = item.bodySalarys.positionSalaryAmount ?? null;
|
||||||
profileSal.mouthSalaryAmount = item.bodySalarys.mouthSalaryAmount ?? null;
|
profileSal.mouthSalaryAmount = item.bodySalarys.mouthSalaryAmount ?? null;
|
||||||
await this.salaryRepo.save(profileSal, { data: req });
|
await salaryRepo.save(profileSal, { data: req });
|
||||||
setLogDataDiff(req, { before, after: profileSal });
|
setLogDataDiff(req, { before, after: profileSal });
|
||||||
salaryHistory.profileSalaryId = profileSal.id;
|
salaryHistory.profileSalaryId = profileSal.id;
|
||||||
await this.salaryHistoryRepo.save(salaryHistory, { data: req });
|
await salaryHistoryRepo.save(salaryHistory, { data: req });
|
||||||
}
|
}
|
||||||
//Position
|
//Position
|
||||||
if (item.bodyPosition && item.bodyPosition != null) {
|
if (item.bodyPosition && item.bodyPosition != null) {
|
||||||
|
|
@ -969,7 +1048,7 @@ export class ExecuteOfficerProfileService {
|
||||||
"[ExecuteOfficerProfileService] STEP 1: Finding posMaster, posmasterId:",
|
"[ExecuteOfficerProfileService] STEP 1: Finding posMaster, posmasterId:",
|
||||||
item.bodyPosition.posmasterId,
|
item.bodyPosition.posmasterId,
|
||||||
);
|
);
|
||||||
let posMaster = await this.posMasterRepository.findOne({
|
let posMaster = await posMasterRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
id: item.bodyPosition.posmasterId,
|
id: item.bodyPosition.posmasterId,
|
||||||
},
|
},
|
||||||
|
|
@ -995,7 +1074,7 @@ export class ExecuteOfficerProfileService {
|
||||||
console.log(
|
console.log(
|
||||||
"[ExecuteOfficerProfileService] Finding current posMaster from ancestorDNA",
|
"[ExecuteOfficerProfileService] Finding current posMaster from ancestorDNA",
|
||||||
);
|
);
|
||||||
posMaster = await this.posMasterRepository.findOne({
|
posMaster = await posMasterRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
ancestorDNA: posMaster.ancestorDNA,
|
ancestorDNA: posMaster.ancestorDNA,
|
||||||
orgRevision: {
|
orgRevision: {
|
||||||
|
|
@ -1027,7 +1106,7 @@ export class ExecuteOfficerProfileService {
|
||||||
|
|
||||||
// STEP 2: เคลียร์ข้อมูลตำแหน่งเก่าที่ครองอยู่ ในโครงสร้างปัจจุบัน
|
// STEP 2: เคลียร์ข้อมูลตำแหน่งเก่าที่ครองอยู่ ในโครงสร้างปัจจุบัน
|
||||||
console.log("[ExecuteOfficerProfileService] STEP 2: Clearing old position data");
|
console.log("[ExecuteOfficerProfileService] STEP 2: Clearing old position data");
|
||||||
const posMasterOld = await this.posMasterRepository.findOne({
|
const posMasterOld = await posMasterRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
current_holderId: profile.id,
|
current_holderId: profile.id,
|
||||||
orgRevisionId: posMaster.orgRevisionId,
|
orgRevisionId: posMaster.orgRevisionId,
|
||||||
|
|
@ -1040,7 +1119,7 @@ export class ExecuteOfficerProfileService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// หา position เก่าที่เลือกไว้ แล้วเคลียร์การเลือก
|
// หา position เก่าที่เลือกไว้ แล้วเคลียร์การเลือก
|
||||||
const positionOld = await this.positionRepository.findOne({
|
const positionOld = await positionRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
posMasterId: posMasterOld?.id,
|
posMasterId: posMasterOld?.id,
|
||||||
positionIsSelected: true,
|
positionIsSelected: true,
|
||||||
|
|
@ -1048,14 +1127,14 @@ export class ExecuteOfficerProfileService {
|
||||||
});
|
});
|
||||||
if (positionOld != null) {
|
if (positionOld != null) {
|
||||||
positionOld.positionIsSelected = false;
|
positionOld.positionIsSelected = false;
|
||||||
await this.positionRepository.save(positionOld);
|
await positionRepository.save(positionOld);
|
||||||
}
|
}
|
||||||
|
|
||||||
// STEP 3: เคลียร์ position ที่เลือกไว้อื่นๆ ใน posMaster ตัวใหม่
|
// STEP 3: เคลียร์ position ที่เลือกไว้อื่นๆ ใน posMaster ตัวใหม่
|
||||||
console.log(
|
console.log(
|
||||||
"[ExecuteOfficerProfileService] STEP 3: Clearing other selected positions in new posMaster",
|
"[ExecuteOfficerProfileService] STEP 3: Clearing other selected positions in new posMaster",
|
||||||
);
|
);
|
||||||
const checkPosition = await this.positionRepository.find({
|
const checkPosition = await positionRepository.find({
|
||||||
where: {
|
where: {
|
||||||
posMasterId: posMaster.id,
|
posMasterId: posMaster.id,
|
||||||
positionIsSelected: true,
|
positionIsSelected: true,
|
||||||
|
|
@ -1066,7 +1145,7 @@ export class ExecuteOfficerProfileService {
|
||||||
...positions,
|
...positions,
|
||||||
positionIsSelected: false,
|
positionIsSelected: false,
|
||||||
}));
|
}));
|
||||||
await this.positionRepository.save(clearPosition);
|
await positionRepository.save(clearPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
// STEP 4: กำหนดคนครองใหม่ให้กับ posMaster
|
// STEP 4: กำหนดคนครองใหม่ให้กับ posMaster
|
||||||
|
|
@ -1078,10 +1157,13 @@ export class ExecuteOfficerProfileService {
|
||||||
// posMaster.conditionReason = _null;
|
// posMaster.conditionReason = _null;
|
||||||
// posMaster.isCondition = false;
|
// posMaster.isCondition = false;
|
||||||
if (posMasterOld != null) {
|
if (posMasterOld != null) {
|
||||||
await this.posMasterRepository.save(posMasterOld);
|
await posMasterRepository.save(posMasterOld);
|
||||||
await CreatePosMasterHistoryOfficer(posMasterOld.id, req);
|
console.log(
|
||||||
|
`[ExecuteOfficerProfileService] Creating PosMasterHistory — posMasterId: ${posMasterOld.id}, citizenId: ${item.bodyProfile?.citizenId} (old)`,
|
||||||
|
);
|
||||||
|
await CreatePosMasterHistoryOfficer(posMasterOld.id, req, null, null, manager);
|
||||||
}
|
}
|
||||||
await this.posMasterRepository.save(posMaster);
|
await posMasterRepository.save(posMaster);
|
||||||
console.log("[ExecuteOfficerProfileService] posMaster saved with new holder");
|
console.log("[ExecuteOfficerProfileService] posMaster saved with new holder");
|
||||||
|
|
||||||
// STEP 5: กำหนด position ใหม่
|
// STEP 5: กำหนด position ใหม่
|
||||||
|
|
@ -1104,7 +1186,7 @@ export class ExecuteOfficerProfileService {
|
||||||
item.bodyPosition?.positionId,
|
item.bodyPosition?.positionId,
|
||||||
);
|
);
|
||||||
if (item.bodyPosition?.positionId) {
|
if (item.bodyPosition?.positionId) {
|
||||||
const positionById = await this.positionRepository.findOne({
|
const positionById = await positionRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
id: item.bodyPosition.positionId,
|
id: item.bodyPosition.positionId,
|
||||||
posMasterId: posMaster.id, // ต้องอยู่ใน posMaster ที่ถูกต้อง
|
posMasterId: posMaster.id, // ต้องอยู่ใน posMaster ที่ถูกต้อง
|
||||||
|
|
@ -1149,7 +1231,7 @@ export class ExecuteOfficerProfileService {
|
||||||
whereCondition.positionArea = item.bodyPosition.positionArea;
|
whereCondition.positionArea = item.bodyPosition.positionArea;
|
||||||
}
|
}
|
||||||
|
|
||||||
const positionBy7Fields = await this.positionRepository.findOne({
|
const positionBy7Fields = await positionRepository.findOne({
|
||||||
where: whereCondition,
|
where: whereCondition,
|
||||||
relations: ["posExecutive"],
|
relations: ["posExecutive"],
|
||||||
order: { orderNo: "ASC" },
|
order: { orderNo: "ASC" },
|
||||||
|
|
@ -1171,7 +1253,7 @@ export class ExecuteOfficerProfileService {
|
||||||
console.log(
|
console.log(
|
||||||
"[ExecuteOfficerProfileService] CONDITION 2 not matched, trying CONDITION 3: Match 3 fields",
|
"[ExecuteOfficerProfileService] CONDITION 2 not matched, trying CONDITION 3: Match 3 fields",
|
||||||
);
|
);
|
||||||
const positionBy3Fields = await this.positionRepository.findOne({
|
const positionBy3Fields = await positionRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
posMasterId: posMaster.id,
|
posMasterId: posMaster.id,
|
||||||
positionName: item.bodyPosition.positionName,
|
positionName: item.bodyPosition.positionName,
|
||||||
|
|
@ -1196,26 +1278,6 @@ export class ExecuteOfficerProfileService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// // ═══════════════════════════════════════════════════════════
|
|
||||||
// // FALLBACK: ถ้าทั้ง 3 ไม่ match ให้เลือก position แรกใน posMaster
|
|
||||||
// // ═══════════════════════════════════════════════════════════
|
|
||||||
// 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];
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// อัพเดท org และ posMasterNo ตลอดไม่ต้องดัก isSit
|
// อัพเดท org และ posMasterNo ตลอดไม่ต้องดัก isSit
|
||||||
profile.posMasterNo = getPosMasterNo(posMaster);
|
profile.posMasterNo = getPosMasterNo(posMaster);
|
||||||
profile.org = getOrgFullName(posMaster);
|
profile.org = getOrgFullName(posMaster);
|
||||||
|
|
@ -1238,7 +1300,7 @@ export class ExecuteOfficerProfileService {
|
||||||
profile.positionExecutiveField = positionNew.positionExecutiveField ?? null;
|
profile.positionExecutiveField = positionNew.positionExecutiveField ?? null;
|
||||||
// profile.dateStart = new Date();
|
// profile.dateStart = new Date();
|
||||||
}
|
}
|
||||||
await this.positionRepository.save(positionNew, { data: req });
|
await positionRepository.save(positionNew, { data: req });
|
||||||
} else if (!posMaster.isSit) {
|
} else if (!posMaster.isSit) {
|
||||||
// fallback: ตำแหน่งในโครงสร้างถูกแก้ไข ใช้ข้อมูลตำแหน่งที่สมัครสอบมา
|
// fallback: ตำแหน่งในโครงสร้างถูกแก้ไข ใช้ข้อมูลตำแหน่งที่สมัครสอบมา
|
||||||
console.log(
|
console.log(
|
||||||
|
|
@ -1251,12 +1313,15 @@ export class ExecuteOfficerProfileService {
|
||||||
profile.positionArea = item.bodyPosition.positionArea ?? null;
|
profile.positionArea = item.bodyPosition.positionArea ?? null;
|
||||||
profile.positionExecutiveField = item.bodyPosition.positionExecutiveField ?? null;
|
profile.positionExecutiveField = item.bodyPosition.positionExecutiveField ?? null;
|
||||||
}
|
}
|
||||||
await this.profileRepository.save(profile, { data: req });
|
await profileRepository.save(profile, { data: req });
|
||||||
setLogDataDiff(req, { before, after: profile });
|
setLogDataDiff(req, { before, after: profile });
|
||||||
// await CreatePosMasterHistoryOfficer(posMaster.id, req);
|
// await CreatePosMasterHistoryOfficer(posMaster.id, req);
|
||||||
|
console.log(
|
||||||
|
`[ExecuteOfficerProfileService] Creating PosMasterHistory — posMasterId: ${posMaster.id}, citizenId: ${item.bodyProfile?.citizenId}`,
|
||||||
|
);
|
||||||
await CreatePosMasterHistoryOfficer(posMaster.id, req, null, {
|
await CreatePosMasterHistoryOfficer(posMaster.id, req, null, {
|
||||||
positionId: positionNew?.id,
|
positionId: positionNew?.id,
|
||||||
});
|
}, manager);
|
||||||
}
|
}
|
||||||
// Insignia
|
// Insignia
|
||||||
if (_oldInsigniaIds.length > 0) {
|
if (_oldInsigniaIds.length > 0) {
|
||||||
|
|
@ -1264,7 +1329,7 @@ export class ExecuteOfficerProfileService {
|
||||||
"[ExecuteOfficerProfileService] Processing old insignias, count:",
|
"[ExecuteOfficerProfileService] Processing old insignias, count:",
|
||||||
_oldInsigniaIds.length,
|
_oldInsigniaIds.length,
|
||||||
);
|
);
|
||||||
const _insignias = await this.insigniaRepo.find({
|
const _insignias = await insigniaRepo.find({
|
||||||
where: { id: In(_oldInsigniaIds), isDeleted: false },
|
where: { id: In(_oldInsigniaIds), isDeleted: false },
|
||||||
order: { createdAt: "ASC" },
|
order: { createdAt: "ASC" },
|
||||||
});
|
});
|
||||||
|
|
@ -1290,10 +1355,10 @@ export class ExecuteOfficerProfileService {
|
||||||
Object.assign(insignia, { ...newInsigniaData, ...meta });
|
Object.assign(insignia, { ...newInsigniaData, ...meta });
|
||||||
const history = new ProfileInsigniaHistory();
|
const history = new ProfileInsigniaHistory();
|
||||||
Object.assign(history, { ...insignia, id: undefined });
|
Object.assign(history, { ...insignia, id: undefined });
|
||||||
await this.insigniaRepo.save(insignia, { data: req });
|
await insigniaRepo.save(insignia, { data: req });
|
||||||
setLogDataDiff(req, { before, after: insignia });
|
setLogDataDiff(req, { before, after: insignia });
|
||||||
history.profileInsigniaId = insignia.id;
|
history.profileInsigniaId = insignia.id;
|
||||||
await this.insigniaHistoryRepo.save(history, { data: req });
|
await insigniaHistoryRepo.save(history, { data: req });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// เพิ่มรูปภาพโปรไฟล์
|
// เพิ่มรูปภาพโปรไฟล์
|
||||||
|
|
@ -1309,29 +1374,27 @@ export class ExecuteOfficerProfileService {
|
||||||
profileEmployeeId: undefined,
|
profileEmployeeId: undefined,
|
||||||
});
|
});
|
||||||
if (profile.profileAvatars && profile.profileAvatars.length > 0) {
|
if (profile.profileAvatars && profile.profileAvatars.length > 0) {
|
||||||
await Promise.all(
|
for (const avatarItem of profile.profileAvatars) {
|
||||||
profile.profileAvatars.map(async (item: any) => {
|
avatarItem.isActive = false;
|
||||||
item.isActive = false;
|
await avatarRepository.save(avatarItem);
|
||||||
await this.avatarRepository.save(item);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
await this.avatarRepository.save(_profileAvatar);
|
}
|
||||||
|
await avatarRepository.save(_profileAvatar);
|
||||||
let avatar = `ทะเบียนประวัติ/โปรไฟล์/${profile.id}`;
|
let avatar = `ทะเบียนประวัติ/โปรไฟล์/${profile.id}`;
|
||||||
let fileName = `profile-${_profileAvatar.id}`;
|
let fileName = `profile-${_profileAvatar.id}`;
|
||||||
_profileAvatar.isActive = true;
|
_profileAvatar.isActive = true;
|
||||||
_profileAvatar.avatar = avatar;
|
_profileAvatar.avatar = avatar;
|
||||||
_profileAvatar.avatarName = fileName;
|
_profileAvatar.avatarName = fileName;
|
||||||
await this.avatarRepository.save(_profileAvatar, { data: req });
|
await avatarRepository.save(_profileAvatar, { data: req });
|
||||||
profile.avatar = avatar;
|
profile.avatar = avatar;
|
||||||
profile.avatarName = fileName;
|
profile.avatarName = fileName;
|
||||||
await this.profileRepository.save(profile, { data: req });
|
await profileRepository.save(profile, { data: req });
|
||||||
const checkAvatar = await this.avatarRepository.findOne({
|
const checkAvatar = await avatarRepository.findOne({
|
||||||
where: { avatar: avatar, avatarName: fileName },
|
where: { avatar: avatar, avatarName: fileName },
|
||||||
});
|
});
|
||||||
if (checkAvatar && checkAvatar.profileId == null) {
|
if (checkAvatar && checkAvatar.profileId == null) {
|
||||||
checkAvatar.profileId = profile.id;
|
checkAvatar.profileId = profile.id;
|
||||||
await this.avatarRepository.save(checkAvatar);
|
await avatarRepository.save(checkAvatar);
|
||||||
}
|
}
|
||||||
//duplicate รูปภาพโปรไฟล์โดยอิงจากรูปภาพเดิม
|
//duplicate รูปภาพโปรไฟล์โดยอิงจากรูปภาพเดิม
|
||||||
await new CallAPI()
|
await new CallAPI()
|
||||||
|
|
@ -1343,8 +1406,9 @@ export class ExecuteOfficerProfileService {
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
|
||||||
|
console.log(
|
||||||
|
`[ExecuteOfficerProfileService] Completed processOne — citizenId: ${item.bodyProfile?.citizenId}`,
|
||||||
);
|
);
|
||||||
console.log("[ExecuteOfficerProfileService] executeCreateOfficerProfile completed successfully");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,17 +60,6 @@ export interface SalaryCurrentExecutionContext {
|
||||||
req?: any;
|
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 ของข้าราชการ + อัปเดตตำแหน่งปัจจุบัน (เปลี่ยนตำแหน่ง)
|
* Service สำหรับสร้าง ProfileSalary ของข้าราชการ + อัปเดตตำแหน่งปัจจุบัน (เปลี่ยนตำแหน่ง)
|
||||||
*
|
*
|
||||||
|
|
@ -98,7 +87,7 @@ export class ExecuteSalaryCurrentService {
|
||||||
async executeSalaryCurrent(
|
async executeSalaryCurrent(
|
||||||
data: SalaryCurrentItem[],
|
data: SalaryCurrentItem[],
|
||||||
ctx: SalaryCurrentExecutionContext,
|
ctx: SalaryCurrentExecutionContext,
|
||||||
): Promise<ExecuteSalaryResult> {
|
): Promise<void> {
|
||||||
const commandId = data?.find((x) => x.commandId)?.commandId ?? "unknown";
|
const commandId = data?.find((x) => x.commandId)?.commandId ?? "unknown";
|
||||||
const commandCode = data?.find((x) => x.commandCode)?.commandCode ?? "unknown";
|
const commandCode = data?.find((x) => x.commandCode)?.commandCode ?? "unknown";
|
||||||
console.log(
|
console.log(
|
||||||
|
|
@ -170,12 +159,10 @@ export class ExecuteSalaryCurrentService {
|
||||||
// ทุกคนใช้ manager ตัวเดียวกัน — คนใด throw จะ rollback ทั้ง batch
|
// ทุกคนใช้ manager ตัวเดียวกัน — คนใด throw จะ rollback ทั้ง batch
|
||||||
// และ propagate error ออกไป (ล้มเหลวทั้งหมด) โดย log error ของคนที่ทำให้ fail ก่อน rethrow
|
// และ propagate error ออกไป (ล้มเหลวทั้งหมด) โดย log error ของคนที่ทำให้ fail ก่อน rethrow
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
let successCount = 0;
|
|
||||||
await AppDataSource.transaction(async (manager) => {
|
await AppDataSource.transaction(async (manager) => {
|
||||||
for (const item of data ?? []) {
|
for (const item of data ?? []) {
|
||||||
try {
|
try {
|
||||||
await this.processOne(item, ctx, manager, _posNumCodeSit, _posNumCodeSitAbb);
|
await this.processOne(item, ctx, manager, _posNumCodeSit, _posNumCodeSitAbb);
|
||||||
successCount++;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const reason =
|
const reason =
|
||||||
err instanceof HttpError
|
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) {
|
if (posMasterOld != null) {
|
||||||
await posMasterRepository.save(posMasterOld);
|
await posMasterRepository.save(posMasterOld);
|
||||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
||||||
await CreatePosMasterHistoryOfficer(posMasterOld.id, req, null, null, manager);
|
|
||||||
console.log(
|
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);
|
await posMasterRepository.save(posMaster);
|
||||||
|
|
||||||
|
|
@ -504,11 +485,11 @@ export class ExecuteSalaryCurrentService {
|
||||||
profile.amountSpecial = item.amountSpecial ?? null;
|
profile.amountSpecial = item.amountSpecial ?? null;
|
||||||
await profileRepository.save(profile);
|
await profileRepository.save(profile);
|
||||||
await positionRepository.save(positionNew);
|
await positionRepository.save(positionNew);
|
||||||
console.log(
|
|
||||||
`[ExecuteSalaryCurrentService] Applied new position — profileId: ${item.profileId}, positionId: ${positionNew.id}, posMasterId: ${posMaster.id}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
||||||
|
console.log(
|
||||||
|
`[ExecuteSalaryCurrentService] Creating PosMasterHistory — posMasterId: ${posMaster.id}, profileId: ${item.profileId}`,
|
||||||
|
);
|
||||||
await CreatePosMasterHistoryOfficer(posMaster.id, req, null, null, manager);
|
await CreatePosMasterHistoryOfficer(posMaster.id, req, null, null, manager);
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Double } from "typeorm";
|
import { Double, EntityManager } from "typeorm";
|
||||||
import { AppDataSource } from "../database/data-source";
|
import { AppDataSource } from "../database/data-source";
|
||||||
import HttpError from "../interfaces/http-error";
|
import HttpError from "../interfaces/http-error";
|
||||||
import HttpStatusCode from "../interfaces/http-status";
|
import HttpStatusCode from "../interfaces/http-status";
|
||||||
|
|
@ -63,28 +63,29 @@ export interface SalaryEmployeeCurrentExecutionContext {
|
||||||
* - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow)
|
* - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow)
|
||||||
*
|
*
|
||||||
* Behavior ทั้งหมด preserve จาก CommandController.newSalaryEmployeeAndUpdateCurrent ต้นฉบับ
|
* Behavior ทั้งหมด preserve จาก CommandController.newSalaryEmployeeAndUpdateCurrent ต้นฉบับ
|
||||||
|
*
|
||||||
|
* Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential)
|
||||||
|
* ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด)
|
||||||
|
* ถ้าทุกคนสำเร็จจะ return result รายงาน success count
|
||||||
*/
|
*/
|
||||||
export class ExecuteSalaryEmployeeCurrentService {
|
export class ExecuteSalaryEmployeeCurrentService {
|
||||||
private commandRepository = AppDataSource.getRepository(Command);
|
private commandRepository = AppDataSource.getRepository(Command);
|
||||||
private profileRepository = AppDataSource.getRepository(Profile);
|
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);
|
private orgRootRepository = AppDataSource.getRepository(OrgRoot);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ประมวลผลสร้าง ProfileSalary + อัปเดตตำแหน่งปัจจุบันของลูกจ้าง
|
* ประมวลผลสร้าง ProfileSalary + อัปเดตตำแหน่งปัจจุบันของลูกจ้างทั้ง batch
|
||||||
*/
|
*/
|
||||||
async executeSalaryEmployeeCurrent(
|
async executeSalaryEmployeeCurrent(
|
||||||
data: SalaryEmployeeCurrentItem[],
|
data: SalaryEmployeeCurrentItem[],
|
||||||
ctx: SalaryEmployeeCurrentExecutionContext,
|
ctx: SalaryEmployeeCurrentExecutionContext,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
console.log("[ExecuteSalaryEmployeeCurrentService] Starting executeSalaryEmployeeCurrent");
|
const commandId = data?.find((x) => x.commandId)?.commandId ?? "unknown";
|
||||||
console.log("[ExecuteSalaryEmployeeCurrentService] Request body count:", data?.length);
|
const commandCode = data?.find((x) => x.commandCode)?.commandCode ?? "unknown";
|
||||||
|
console.log(
|
||||||
const req = ctx.req;
|
`[ExecuteSalaryEmployeeCurrentService] Starting executeSalaryEmployeeCurrent — commandCode: ${commandCode}, commandId: ${commandId}`,
|
||||||
|
);
|
||||||
|
console.log(`[ExecuteSalaryEmployeeCurrentService] Request body count: ${data?.length ?? 0}`);
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
// Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date)
|
// Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date)
|
||||||
|
|
@ -144,14 +145,59 @@ export class ExecuteSalaryEmployeeCurrentService {
|
||||||
.orgRootShortName ?? "";
|
.orgRootShortName ?? "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await Promise.all(
|
|
||||||
data.map(async (item) => {
|
// ─────────────────────────────────────────────────────────────
|
||||||
const profile: any = await this.profileEmployeeRepository.findOneBy({ 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, _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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ประมวลผล 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;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
const profile: any = await profileEmployeeRepository.findOneBy({ id: item.profileId });
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
|
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
|
||||||
}
|
}
|
||||||
|
|
||||||
const dest_item = await this.salaryRepo.findOne({
|
const dest_item = await salaryRepo.findOne({
|
||||||
where: { profileEmployeeId: item.profileId },
|
where: { profileEmployeeId: item.profileId },
|
||||||
order: { order: "DESC" },
|
order: { order: "DESC" },
|
||||||
});
|
});
|
||||||
|
|
@ -178,19 +224,19 @@ export class ExecuteSalaryEmployeeCurrentService {
|
||||||
const history = new ProfileSalaryHistory();
|
const history = new ProfileSalaryHistory();
|
||||||
Object.assign(history, { ...dataSalary, id: undefined });
|
Object.assign(history, { ...dataSalary, id: undefined });
|
||||||
|
|
||||||
await this.salaryRepo.save(dataSalary, { data: req });
|
await salaryRepo.save(dataSalary, { data: req });
|
||||||
setLogDataDiff(req, { before, after: dataSalary });
|
setLogDataDiff(req, { before, after: dataSalary });
|
||||||
history.profileSalaryId = dataSalary.id;
|
history.profileSalaryId = dataSalary.id;
|
||||||
await this.salaryHistoryRepo.save(history, { data: req });
|
await salaryHistoryRepo.save(history, { data: req });
|
||||||
|
|
||||||
const posMaster = await this.employeePosMasterRepository.findOne({
|
const posMaster = await employeePosMasterRepository.findOne({
|
||||||
where: { id: item.posmasterId },
|
where: { id: item.posmasterId },
|
||||||
relations: ["orgRoot"],
|
relations: ["orgRoot"],
|
||||||
});
|
});
|
||||||
if (posMaster == null)
|
if (posMaster == null)
|
||||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้");
|
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้");
|
||||||
|
|
||||||
const posMasterOld = await this.employeePosMasterRepository.findOne({
|
const posMasterOld = await employeePosMasterRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
current_holderId: item.profileId,
|
current_holderId: item.profileId,
|
||||||
orgRevisionId: posMaster.orgRevisionId,
|
orgRevisionId: posMaster.orgRevisionId,
|
||||||
|
|
@ -202,7 +248,7 @@ export class ExecuteSalaryEmployeeCurrentService {
|
||||||
}
|
}
|
||||||
// if (posMasterOld != null) posMasterOld.next_holderId = null;
|
// if (posMasterOld != null) posMasterOld.next_holderId = null;
|
||||||
|
|
||||||
const positionOld = await this.employeePositionRepository.findOne({
|
const positionOld = await employeePositionRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
posMasterId: posMasterOld?.id,
|
posMasterId: posMasterOld?.id,
|
||||||
positionIsSelected: true,
|
positionIsSelected: true,
|
||||||
|
|
@ -210,10 +256,10 @@ export class ExecuteSalaryEmployeeCurrentService {
|
||||||
});
|
});
|
||||||
if (positionOld != null) {
|
if (positionOld != null) {
|
||||||
positionOld.positionIsSelected = false;
|
positionOld.positionIsSelected = false;
|
||||||
await this.employeePositionRepository.save(positionOld);
|
await employeePositionRepository.save(positionOld);
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkPosition = await this.employeePositionRepository.find({
|
const checkPosition = await employeePositionRepository.find({
|
||||||
where: {
|
where: {
|
||||||
posMasterId: item.posmasterId,
|
posMasterId: item.posmasterId,
|
||||||
positionIsSelected: true,
|
positionIsSelected: true,
|
||||||
|
|
@ -224,18 +270,22 @@ export class ExecuteSalaryEmployeeCurrentService {
|
||||||
...positions,
|
...positions,
|
||||||
positionIsSelected: false,
|
positionIsSelected: false,
|
||||||
}));
|
}));
|
||||||
await this.employeePositionRepository.save(clearPosition);
|
await employeePositionRepository.save(clearPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
posMaster.current_holderId = item.profileId;
|
posMaster.current_holderId = item.profileId;
|
||||||
posMaster.lastUpdatedAt = new Date();
|
posMaster.lastUpdatedAt = new Date();
|
||||||
posMaster.next_holderId = null;
|
posMaster.next_holderId = null;
|
||||||
if (posMasterOld != null) {
|
if (posMasterOld != null) {
|
||||||
await this.employeePosMasterRepository.save(posMasterOld);
|
await employeePosMasterRepository.save(posMasterOld);
|
||||||
await CreatePosMasterHistoryEmployee(posMasterOld.id, req);
|
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
||||||
|
console.log(
|
||||||
|
`[ExecuteSalaryEmployeeCurrentService] Creating PosMasterHistory — posMasterId: ${posMasterOld.id}, profileId: ${item.profileId} (old)`,
|
||||||
|
);
|
||||||
|
await CreatePosMasterHistoryEmployee(posMasterOld.id, req, null, manager);
|
||||||
}
|
}
|
||||||
await this.employeePosMasterRepository.save(posMaster);
|
await employeePosMasterRepository.save(posMaster);
|
||||||
const positionNew = await this.employeePositionRepository.findOne({
|
const positionNew = await employeePositionRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
id: item.positionId,
|
id: item.positionId,
|
||||||
posMasterId: item.posmasterId,
|
posMasterId: item.posmasterId,
|
||||||
|
|
@ -250,13 +300,17 @@ export class ExecuteSalaryEmployeeCurrentService {
|
||||||
profile.positionEmployeePositionId = positionNew.positionName;
|
profile.positionEmployeePositionId = positionNew.positionName;
|
||||||
profile.amount = item.amount ?? null;
|
profile.amount = item.amount ?? null;
|
||||||
profile.amountSpecial = item.amountSpecial ?? null;
|
profile.amountSpecial = item.amountSpecial ?? null;
|
||||||
await this.profileEmployeeRepository.save(profile);
|
await profileEmployeeRepository.save(profile);
|
||||||
await this.employeePositionRepository.save(positionNew);
|
await employeePositionRepository.save(positionNew);
|
||||||
}
|
}
|
||||||
await CreatePosMasterHistoryEmployee(posMaster.id, req);
|
// ส่ง 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 { AppDataSource } from "../database/data-source";
|
||||||
import HttpError from "../interfaces/http-error";
|
import HttpError from "../interfaces/http-error";
|
||||||
import HttpStatus from "../interfaces/http-status";
|
import HttpStatus from "../interfaces/http-status";
|
||||||
|
|
@ -71,29 +71,30 @@ export interface SalaryEmployeeLeaveExecutionContext {
|
||||||
* - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow)
|
* - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow)
|
||||||
*
|
*
|
||||||
* Behavior ทั้งหมด preserve จาก CommandController.newSalaryEmployeeAndUpdateLeave ต้นฉบับ
|
* Behavior ทั้งหมด preserve จาก CommandController.newSalaryEmployeeAndUpdateLeave ต้นฉบับ
|
||||||
|
*
|
||||||
|
* Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential)
|
||||||
|
* ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด)
|
||||||
|
* ถ้าทุกคนสำเร็จจะ return result รายงาน success count
|
||||||
*/
|
*/
|
||||||
export class ExecuteSalaryEmployeeLeaveService {
|
export class ExecuteSalaryEmployeeLeaveService {
|
||||||
private commandRepository = AppDataSource.getRepository(Command);
|
private commandRepository = AppDataSource.getRepository(Command);
|
||||||
private commandReciveRepository = AppDataSource.getRepository(CommandRecive);
|
private commandReciveRepository = AppDataSource.getRepository(CommandRecive);
|
||||||
private profileRepository = AppDataSource.getRepository(Profile);
|
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 orgRootRepository = AppDataSource.getRepository(OrgRoot);
|
||||||
private orgRevisionRepo = AppDataSource.getRepository(OrgRevision);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ประมวลผลสร้าง ProfileSalary + handle leave ของลูกจ้าง
|
* ประมวลผลสร้าง ProfileSalary + handle leave ของลูกจ้างทั้ง batch
|
||||||
*/
|
*/
|
||||||
async executeSalaryEmployeeLeave(
|
async executeSalaryEmployeeLeave(
|
||||||
data: SalaryEmployeeLeaveItem[],
|
data: SalaryEmployeeLeaveItem[],
|
||||||
ctx: SalaryEmployeeLeaveExecutionContext,
|
ctx: SalaryEmployeeLeaveExecutionContext,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
console.log("[ExecuteSalaryEmployeeLeaveService] Starting executeSalaryEmployeeLeave");
|
const commandId = data?.find((x) => x.commandId)?.commandId ?? "unknown";
|
||||||
console.log("[ExecuteSalaryEmployeeLeaveService] Request body count:", data?.length);
|
const commandCode = data?.find((x) => x.commandCode)?.commandCode ?? "unknown";
|
||||||
|
console.log(
|
||||||
const req = ctx.req;
|
`[ExecuteSalaryEmployeeLeaveService] Starting executeSalaryEmployeeLeave — commandCode: ${commandCode}, commandId: ${commandId}`,
|
||||||
|
);
|
||||||
|
console.log(`[ExecuteSalaryEmployeeLeaveService] Request body count: ${data?.length ?? 0}`);
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
// Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date)
|
// Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date)
|
||||||
|
|
@ -156,9 +157,57 @@ export class ExecuteSalaryEmployeeLeaveService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const today = new Date().setHours(0, 0, 0, 0);
|
const today = new Date().setHours(0, 0, 0, 0);
|
||||||
await Promise.all(
|
|
||||||
data.map(async (item) => {
|
// ─────────────────────────────────────────────────────────────
|
||||||
const profile = await this.profileEmployeeRepository.findOne({
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ประมวลผล 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 },
|
where: { id: item.profileId },
|
||||||
relations: {
|
relations: {
|
||||||
roleKeycloaks: true,
|
roleKeycloaks: true,
|
||||||
|
|
@ -172,7 +221,7 @@ export class ExecuteSalaryEmployeeLeaveService {
|
||||||
const code = _command?.commandType?.code;
|
const code = _command?.commandType?.code;
|
||||||
//ออกคำสั่งยกเลิกลาออก ลบเฉพาะคนที่ขอยกเลิกลาออก
|
//ออกคำสั่งยกเลิกลาออก ลบเฉพาะคนที่ขอยกเลิกลาออก
|
||||||
if (item.resignId && code && ["C-PM-42"].includes(code)) {
|
if (item.resignId && code && ["C-PM-42"].includes(code)) {
|
||||||
const commandResign = await this.commandReciveRepository.findOne({
|
const commandResign = await commandReciveRepository.findOne({
|
||||||
where: { refId: item.resignId },
|
where: { refId: item.resignId },
|
||||||
relations: { command: true },
|
relations: { command: true },
|
||||||
});
|
});
|
||||||
|
|
@ -184,14 +233,15 @@ export class ExecuteSalaryEmployeeLeaveService {
|
||||||
_command.status !== "REPORTED" &&
|
_command.status !== "REPORTED" &&
|
||||||
(_command.status !== "WAITING" || today < executeDate)
|
(_command.status !== "WAITING" || today < executeDate)
|
||||||
) {
|
) {
|
||||||
await reOrderCommandRecivesAndDelete(commandResign!.id);
|
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
||||||
|
await reOrderCommandRecivesAndDelete(commandResign!.id, manager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let _commandYear = item.commandYear;
|
let _commandYear = item.commandYear;
|
||||||
if (item.commandYear) {
|
if (item.commandYear) {
|
||||||
_commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543;
|
_commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543;
|
||||||
}
|
}
|
||||||
const dest_item = await this.salaryRepo.findOne({
|
const dest_item = await salaryRepo.findOne({
|
||||||
where: { profileEmployeeId: item.profileId },
|
where: { profileEmployeeId: item.profileId },
|
||||||
order: { order: "DESC" },
|
order: { order: "DESC" },
|
||||||
});
|
});
|
||||||
|
|
@ -218,10 +268,10 @@ export class ExecuteSalaryEmployeeLeaveService {
|
||||||
const history = new ProfileSalaryHistory();
|
const history = new ProfileSalaryHistory();
|
||||||
Object.assign(history, { ...dataSalary, id: undefined });
|
Object.assign(history, { ...dataSalary, id: undefined });
|
||||||
dataSalary.dateGovernment = (item.commandDateAffect as Date) ?? meta.createdAt;
|
dataSalary.dateGovernment = (item.commandDateAffect as Date) ?? meta.createdAt;
|
||||||
await this.salaryRepo.save(dataSalary, { data: req });
|
await salaryRepo.save(dataSalary, { data: req });
|
||||||
setLogDataDiff(req, { before, after: dataSalary });
|
setLogDataDiff(req, { before, after: dataSalary });
|
||||||
history.profileSalaryId = dataSalary.id;
|
history.profileSalaryId = dataSalary.id;
|
||||||
await this.salaryHistoryRepo.save(history, { data: req });
|
await salaryHistoryRepo.save(history, { data: req });
|
||||||
|
|
||||||
const _null: any = null;
|
const _null: any = null;
|
||||||
profile.isLeave = item.isLeave;
|
profile.isLeave = item.isLeave;
|
||||||
|
|
@ -232,7 +282,7 @@ export class ExecuteSalaryEmployeeLeaveService {
|
||||||
profile.lastUpdatedAt = new Date();
|
profile.lastUpdatedAt = new Date();
|
||||||
// บันทึกประวัติก่อนลบตำแหน่ง
|
// บันทึกประวัติก่อนลบตำแหน่ง
|
||||||
const clearProfile = await checkCommandType(String(item.commandId));
|
const clearProfile = await checkCommandType(String(item.commandId));
|
||||||
const curRevision = await this.orgRevisionRepo.findOne({
|
const curRevision = await orgRevisionRepo.findOne({
|
||||||
where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false },
|
where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false },
|
||||||
});
|
});
|
||||||
let orgRootRef = null;
|
let orgRootRef = null;
|
||||||
|
|
@ -241,7 +291,7 @@ export class ExecuteSalaryEmployeeLeaveService {
|
||||||
let orgChild3Ref = null;
|
let orgChild3Ref = null;
|
||||||
let orgChild4Ref = null;
|
let orgChild4Ref = null;
|
||||||
if (curRevision) {
|
if (curRevision) {
|
||||||
const curPosMaster = await this.employeePosMasterRepository.findOne({
|
const curPosMaster = await employeePosMasterRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
current_holderId: profile.id,
|
current_holderId: profile.id,
|
||||||
orgRevisionId: curRevision.id,
|
orgRevisionId: curRevision.id,
|
||||||
|
|
@ -260,17 +310,24 @@ export class ExecuteSalaryEmployeeLeaveService {
|
||||||
orgChild3Ref = curPosMaster?.orgChild3 ?? null;
|
orgChild3Ref = curPosMaster?.orgChild3 ?? null;
|
||||||
orgChild4Ref = curPosMaster?.orgChild4 ?? null;
|
orgChild4Ref = curPosMaster?.orgChild4 ?? null;
|
||||||
if (curPosMaster) {
|
if (curPosMaster) {
|
||||||
await CreatePosMasterHistoryEmployee(curPosMaster.id, req, "DELETE");
|
// ส่ง 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) {
|
if (item.isLeave == true) {
|
||||||
await removeProfileInOrganize(profile.id, "EMPLOYEE");
|
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
||||||
|
await removeProfileInOrganize(profile.id, "EMPLOYEE", manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clearProfile.status) {
|
if (clearProfile.status) {
|
||||||
if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) {
|
if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) {
|
||||||
|
// Keycloak deleteUser ทำภายใน transaction — ถ้า DB rollback หลังจากนี้ Keycloak จะถูกลบไปแล้ว
|
||||||
|
// (Keycloak ไม่สามารถ rollback ได้)
|
||||||
const delUserKeycloak = await deleteUser(profile.keycloak);
|
const delUserKeycloak = await deleteUser(profile.keycloak);
|
||||||
if (delUserKeycloak) {
|
if (delUserKeycloak) {
|
||||||
// Task #228
|
// Task #228
|
||||||
|
|
@ -290,7 +347,7 @@ export class ExecuteSalaryEmployeeLeaveService {
|
||||||
// profile.posTypeId = _null;
|
// profile.posTypeId = _null;
|
||||||
// profile.posLevelId = _null;
|
// profile.posLevelId = _null;
|
||||||
}
|
}
|
||||||
await this.profileEmployeeRepository.save(profile);
|
await profileEmployeeRepository.save(profile);
|
||||||
|
|
||||||
// if (profile.id) {
|
// if (profile.id) {
|
||||||
// await this.keycloakAttributeService.clearOrgDnaAttributes(
|
// await this.keycloakAttributeService.clearOrgDnaAttributes(
|
||||||
|
|
@ -313,9 +370,9 @@ export class ExecuteSalaryEmployeeLeaveService {
|
||||||
organizeName = names.join(" ");
|
organizeName = names.join(" ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log("[ExecuteSalaryEmployeeLeaveService] executeSalaryEmployeeLeave completed successfully");
|
console.log(
|
||||||
|
`[ExecuteSalaryEmployeeLeaveService] Completed processOne — profileId: ${item.profileId}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Double, In, Like } from "typeorm";
|
import { Double, EntityManager, In, Like } from "typeorm";
|
||||||
import { AppDataSource } from "../database/data-source";
|
import { AppDataSource } from "../database/data-source";
|
||||||
import HttpError from "../interfaces/http-error";
|
import HttpError from "../interfaces/http-error";
|
||||||
import HttpStatusCode from "../interfaces/http-status";
|
import HttpStatusCode from "../interfaces/http-status";
|
||||||
|
|
@ -103,27 +103,34 @@ export interface SalaryLeaveExecutionContext {
|
||||||
* - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow)
|
* - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow)
|
||||||
*
|
*
|
||||||
* Behavior ทั้งหมด preserve จาก CommandController.newSalaryAndUpdateLeave ต้นฉบับ
|
* 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 {
|
export class ExecuteSalaryLeaveService {
|
||||||
private commandRepository = AppDataSource.getRepository(Command);
|
private commandRepository = AppDataSource.getRepository(Command);
|
||||||
private commandReciveRepository = AppDataSource.getRepository(CommandRecive);
|
private commandReciveRepository = AppDataSource.getRepository(CommandRecive);
|
||||||
private profileRepository = AppDataSource.getRepository(Profile);
|
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 orgRootRepository = AppDataSource.getRepository(OrgRoot);
|
||||||
private orgRevisionRepo = AppDataSource.getRepository(OrgRevision);
|
|
||||||
private roleKeycloakRepo = AppDataSource.getRepository(RoleKeycloak);
|
private roleKeycloakRepo = AppDataSource.getRepository(RoleKeycloak);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ประมวลผลสร้าง ProfileSalary + handle leave/กลับเข้าราชการ ของข้าราชการ
|
* ประมวลผลสร้าง ProfileSalary + handle leave/กลับเข้าราชการ ของข้าราชการทั้ง batch
|
||||||
|
*
|
||||||
|
* @returns สรุปผล success/failure ต่อคน
|
||||||
*/
|
*/
|
||||||
async executeSalaryLeave(data: SalaryLeaveItem[], ctx: SalaryLeaveExecutionContext): Promise<void> {
|
async executeSalaryLeave(data: SalaryLeaveItem[], ctx: SalaryLeaveExecutionContext): Promise<void> {
|
||||||
console.log("[ExecuteSalaryLeaveService] Starting executeSalaryLeave");
|
const commandId = data?.find((x) => x.commandId)?.commandId ?? "unknown";
|
||||||
console.log("[ExecuteSalaryLeaveService] Request body count:", data?.length);
|
const commandCode = data?.find((x) => x.commandCode)?.commandCode ?? "unknown";
|
||||||
|
console.log(
|
||||||
const req = ctx.req;
|
`[ExecuteSalaryLeaveService] Starting executeSalaryLeave — commandCode: ${commandCode}, commandId: ${commandId}`,
|
||||||
|
);
|
||||||
|
console.log(`[ExecuteSalaryLeaveService] Request body count: ${data?.length ?? 0}`);
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
// Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date)
|
// Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date)
|
||||||
|
|
@ -189,9 +196,69 @@ export class ExecuteSalaryLeaveService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const today = new Date().setHours(0, 0, 0, 0);
|
const today = new Date().setHours(0, 0, 0, 0);
|
||||||
await Promise.all(
|
|
||||||
data.map(async (item) => {
|
// ─────────────────────────────────────────────────────────────
|
||||||
const profile = await this.profileRepository.findOne({
|
// 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 },
|
where: { id: item.profileId },
|
||||||
relations: {
|
relations: {
|
||||||
roleKeycloaks: true,
|
roleKeycloaks: true,
|
||||||
|
|
@ -203,11 +270,12 @@ export class ExecuteSalaryLeaveService {
|
||||||
//ลบตำแหน่งที่รักษาการแทน
|
//ลบตำแหน่งที่รักษาการแทน
|
||||||
const code = _command?.commandType?.code;
|
const code = _command?.commandType?.code;
|
||||||
if (code && ["C-PM-08", "C-PM-17", "C-PM-18", "C-PM-48"].includes(code)) {
|
if (code && ["C-PM-08", "C-PM-17", "C-PM-18", "C-PM-48"].includes(code)) {
|
||||||
removePostMasterAct(profile.id);
|
// await (เดิมไม่ await = fire-and-forget bug) + ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction
|
||||||
|
await removePostMasterAct(profile.id, manager);
|
||||||
}
|
}
|
||||||
//ออกคำสั่งยกเลิกลาออก ลบเฉพาะคนที่ขอยกเลิกลาออก
|
//ออกคำสั่งยกเลิกลาออก ลบเฉพาะคนที่ขอยกเลิกลาออก
|
||||||
else if (item.resignId && code && ["C-PM-41"].includes(code)) {
|
else if (item.resignId && code && ["C-PM-41"].includes(code)) {
|
||||||
const commandResign = await this.commandReciveRepository.findOne({
|
const commandResign = await commandReciveRepository.findOne({
|
||||||
where: { refId: item.resignId },
|
where: { refId: item.resignId },
|
||||||
relations: { command: true },
|
relations: { command: true },
|
||||||
});
|
});
|
||||||
|
|
@ -219,7 +287,8 @@ export class ExecuteSalaryLeaveService {
|
||||||
_command.status !== "REPORTED" &&
|
_command.status !== "REPORTED" &&
|
||||||
(_command.status !== "WAITING" || today < executeDate)
|
(_command.status !== "WAITING" || today < executeDate)
|
||||||
) {
|
) {
|
||||||
await reOrderCommandRecivesAndDelete(commandResign!.id);
|
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
||||||
|
await reOrderCommandRecivesAndDelete(commandResign!.id, manager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let _commandYear = item.commandYear;
|
let _commandYear = item.commandYear;
|
||||||
|
|
@ -227,7 +296,7 @@ export class ExecuteSalaryLeaveService {
|
||||||
_commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543;
|
_commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543;
|
||||||
}
|
}
|
||||||
const returnWork = await checkReturnCommandType(String(item.commandId));
|
const returnWork = await checkReturnCommandType(String(item.commandId));
|
||||||
const dest_item = await this.salaryRepo.findOne({
|
const dest_item = await salaryRepo.findOne({
|
||||||
where: { profileId: item.profileId },
|
where: { profileId: item.profileId },
|
||||||
order: { order: "DESC" },
|
order: { order: "DESC" },
|
||||||
});
|
});
|
||||||
|
|
@ -249,10 +318,10 @@ export class ExecuteSalaryLeaveService {
|
||||||
Object.assign(dataSalary, { ...item, ...meta });
|
Object.assign(dataSalary, { ...item, ...meta });
|
||||||
const history = new ProfileSalaryHistory();
|
const history = new ProfileSalaryHistory();
|
||||||
Object.assign(history, { ...dataSalary, id: undefined });
|
Object.assign(history, { ...dataSalary, id: undefined });
|
||||||
await this.salaryRepo.save(dataSalary, { data: req });
|
await salaryRepo.save(dataSalary, { data: req });
|
||||||
setLogDataDiff(req, { before, after: dataSalary });
|
setLogDataDiff(req, { before, after: dataSalary });
|
||||||
history.profileSalaryId = dataSalary.id;
|
history.profileSalaryId = dataSalary.id;
|
||||||
await this.salaryHistoryRepo.save(history, { data: req });
|
await salaryHistoryRepo.save(history, { data: req });
|
||||||
}
|
}
|
||||||
const _null: any = null;
|
const _null: any = null;
|
||||||
profile.isLeave = item.isLeave;
|
profile.isLeave = item.isLeave;
|
||||||
|
|
@ -264,7 +333,7 @@ export class ExecuteSalaryLeaveService {
|
||||||
const clearProfile = await checkCommandType(String(item.commandId));
|
const clearProfile = await checkCommandType(String(item.commandId));
|
||||||
|
|
||||||
//ปั๊มประวัติก่อนลบตำแหน่ง
|
//ปั๊มประวัติก่อนลบตำแหน่ง
|
||||||
const curRevision = await this.orgRevisionRepo.findOne({
|
const curRevision = await orgRevisionRepo.findOne({
|
||||||
where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false },
|
where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false },
|
||||||
});
|
});
|
||||||
let orgRootRef = null;
|
let orgRootRef = null;
|
||||||
|
|
@ -273,7 +342,7 @@ export class ExecuteSalaryLeaveService {
|
||||||
let orgChild3Ref = null;
|
let orgChild3Ref = null;
|
||||||
let orgChild4Ref = null;
|
let orgChild4Ref = null;
|
||||||
if (curRevision) {
|
if (curRevision) {
|
||||||
const curPosMaster = await this.posMasterRepository.findOne({
|
const curPosMaster = await posMasterRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
current_holderId: profile.id,
|
current_holderId: profile.id,
|
||||||
orgRevisionId: curRevision.id,
|
orgRevisionId: curRevision.id,
|
||||||
|
|
@ -292,16 +361,22 @@ export class ExecuteSalaryLeaveService {
|
||||||
orgChild3Ref = curPosMaster?.orgChild3 ?? null;
|
orgChild3Ref = curPosMaster?.orgChild3 ?? null;
|
||||||
orgChild4Ref = curPosMaster?.orgChild4 ?? null;
|
orgChild4Ref = curPosMaster?.orgChild4 ?? null;
|
||||||
if (curPosMaster && clearProfile.LeaveType != "RETIRE_OUT_EMP") {
|
if (curPosMaster && clearProfile.LeaveType != "RETIRE_OUT_EMP") {
|
||||||
await CreatePosMasterHistoryOfficer(curPosMaster.id, req, "DELETE");
|
// ส่ง 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) {
|
if (item.isLeave == true) {
|
||||||
await removeProfileInOrganize(profile.id, "OFFICER");
|
// ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน
|
||||||
|
await removeProfileInOrganize(profile.id, "OFFICER", manager);
|
||||||
}
|
}
|
||||||
if (clearProfile.status) {
|
if (clearProfile.status) {
|
||||||
if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) {
|
if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) {
|
||||||
|
// Keycloak ทำภายใน transaction — ไม่สามารถ rollback ได้ (ดู docstring ของ class)
|
||||||
const delUserKeycloak = await deleteUser(profile.keycloak);
|
const delUserKeycloak = await deleteUser(profile.keycloak);
|
||||||
if (delUserKeycloak) {
|
if (delUserKeycloak) {
|
||||||
// Task #228
|
// Task #228
|
||||||
|
|
@ -325,10 +400,10 @@ export class ExecuteSalaryLeaveService {
|
||||||
if (item.isGovernment == true) {
|
if (item.isGovernment == true) {
|
||||||
if (returnWork) {
|
if (returnWork) {
|
||||||
//ปลดตำแหน่งเดิมที่ไม่ถูกปลดออกจากกิ่งครั้งเมื่อออกคำสั่งพักราชการหรือออกราชการไว้
|
//ปลดตำแหน่งเดิมที่ไม่ถูกปลดออกจากกิ่งครั้งเมื่อออกคำสั่งพักราชการหรือออกราชการไว้
|
||||||
await removeProfileInOrganize(profile.id, "OFFICER");
|
await removeProfileInOrganize(profile.id, "OFFICER", manager);
|
||||||
//ปั๊มตำแหน่งใหม่
|
//ปั๊มตำแหน่งใหม่
|
||||||
// หา posMaster และเช็ค orgRevisionIsCurrent
|
// หา posMaster และเช็ค orgRevisionIsCurrent
|
||||||
let posMaster = await this.posMasterRepository.findOne({
|
let posMaster = await posMasterRepository.findOne({
|
||||||
where: { id: item.posmasterId?.toString() },
|
where: { id: item.posmasterId?.toString() },
|
||||||
relations: {
|
relations: {
|
||||||
orgRevision: true,
|
orgRevision: true,
|
||||||
|
|
@ -347,7 +422,7 @@ export class ExecuteSalaryLeaveService {
|
||||||
|
|
||||||
// ถ้าไม่อยู่ในโครงสร้างปัจจุบัน ให้หาตัวใหม่จาก ancestorDNA
|
// ถ้าไม่อยู่ในโครงสร้างปัจจุบัน ให้หาตัวใหม่จาก ancestorDNA
|
||||||
if (!isCurrent && posMaster?.ancestorDNA) {
|
if (!isCurrent && posMaster?.ancestorDNA) {
|
||||||
posMaster = await this.posMasterRepository.findOne({
|
posMaster = await posMasterRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
ancestorDNA: posMaster.ancestorDNA,
|
ancestorDNA: posMaster.ancestorDNA,
|
||||||
orgRevision: {
|
orgRevision: {
|
||||||
|
|
@ -367,7 +442,7 @@ export class ExecuteSalaryLeaveService {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (posMaster) {
|
if (posMaster) {
|
||||||
const checkPosition = await this.positionRepository.find({
|
const checkPosition = await positionRepository.find({
|
||||||
where: {
|
where: {
|
||||||
posMasterId: posMaster.id,
|
posMasterId: posMaster.id,
|
||||||
positionIsSelected: true,
|
positionIsSelected: true,
|
||||||
|
|
@ -378,13 +453,13 @@ export class ExecuteSalaryLeaveService {
|
||||||
...positions,
|
...positions,
|
||||||
positionIsSelected: false,
|
positionIsSelected: false,
|
||||||
}));
|
}));
|
||||||
await this.positionRepository.save(clearPosition);
|
await positionRepository.save(clearPosition);
|
||||||
}
|
}
|
||||||
posMaster.current_holderId = profile.id;
|
posMaster.current_holderId = profile.id;
|
||||||
posMaster.lastUpdatedAt = new Date();
|
posMaster.lastUpdatedAt = new Date();
|
||||||
// posMaster.conditionReason = _null;
|
// posMaster.conditionReason = _null;
|
||||||
// posMaster.isCondition = false;
|
// posMaster.isCondition = false;
|
||||||
await this.posMasterRepository.save(posMaster);
|
await posMasterRepository.save(posMaster);
|
||||||
|
|
||||||
// Match position ตามลำดับ priority:
|
// Match position ตามลำดับ priority:
|
||||||
// Condition 1: match จาก positionId
|
// Condition 1: match จาก positionId
|
||||||
|
|
@ -398,7 +473,7 @@ export class ExecuteSalaryLeaveService {
|
||||||
// CONDITION 1: เช็คจาก positionId ตรง
|
// CONDITION 1: เช็คจาก positionId ตรง
|
||||||
// ═══════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════
|
||||||
if (item.positionId) {
|
if (item.positionId) {
|
||||||
const positionById = await this.positionRepository.findOne({
|
const positionById = await positionRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
id: item.positionId,
|
id: item.positionId,
|
||||||
posMasterId: posMaster.id, // ต้องอยู่ใน posMaster ที่ถูกต้อง
|
posMasterId: posMaster.id, // ต้องอยู่ใน posMaster ที่ถูกต้อง
|
||||||
|
|
@ -436,7 +511,7 @@ export class ExecuteSalaryLeaveService {
|
||||||
whereCondition.positionArea = item.positionArea;
|
whereCondition.positionArea = item.positionArea;
|
||||||
}
|
}
|
||||||
|
|
||||||
const positionBy7Fields = await this.positionRepository.findOne({
|
const positionBy7Fields = await positionRepository.findOne({
|
||||||
where: whereCondition,
|
where: whereCondition,
|
||||||
relations: ["posExecutive"],
|
relations: ["posExecutive"],
|
||||||
order: { orderNo: "ASC" },
|
order: { orderNo: "ASC" },
|
||||||
|
|
@ -451,7 +526,7 @@ export class ExecuteSalaryLeaveService {
|
||||||
// CONDITION 3: Match 3 ฟิลด์ (ถ้า Condition 2 ไม่ match)
|
// CONDITION 3: Match 3 ฟิลด์ (ถ้า Condition 2 ไม่ match)
|
||||||
// ═══════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════
|
||||||
if (!positionNew && item.positionNameNew && item.positionTypeNew && item.positionLevelNew) {
|
if (!positionNew && item.positionNameNew && item.positionTypeNew && item.positionLevelNew) {
|
||||||
const positionBy3Fields = await this.positionRepository.findOne({
|
const positionBy3Fields = await positionRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
posMasterId: posMaster.id,
|
posMasterId: posMaster.id,
|
||||||
positionName: item.positionNameNew,
|
positionName: item.positionNameNew,
|
||||||
|
|
@ -469,7 +544,7 @@ export class ExecuteSalaryLeaveService {
|
||||||
|
|
||||||
// // FALLBACK: เลือก position แรก (ถ้าไม่เจอทั้ง 2 condition)
|
// // FALLBACK: เลือก position แรก (ถ้าไม่เจอทั้ง 2 condition)
|
||||||
// if (!positionNew) {
|
// if (!positionNew) {
|
||||||
// const fallbackPositions = await this.positionRepository.find({
|
// const fallbackPositions = await positionRepository.find({
|
||||||
// where: {
|
// where: {
|
||||||
// posMasterId: posMaster.id,
|
// posMasterId: posMaster.id,
|
||||||
// },
|
// },
|
||||||
|
|
@ -487,9 +562,13 @@ export class ExecuteSalaryLeaveService {
|
||||||
|
|
||||||
if (positionNew) {
|
if (positionNew) {
|
||||||
positionNew.positionIsSelected = true;
|
positionNew.positionIsSelected = true;
|
||||||
await this.positionRepository.save(positionNew, { data: req });
|
await positionRepository.save(positionNew, { data: req });
|
||||||
}
|
}
|
||||||
await CreatePosMasterHistoryOfficer(posMaster.id, 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.posMasterNo = getPosMasterNo(posMaster);
|
||||||
profile.org = getOrgFullName(posMaster);
|
profile.org = getOrgFullName(posMaster);
|
||||||
}
|
}
|
||||||
|
|
@ -522,9 +601,9 @@ export class ExecuteSalaryLeaveService {
|
||||||
Object.assign(dataSalary, { ...newMapProfileSalary, ...meta });
|
Object.assign(dataSalary, { ...newMapProfileSalary, ...meta });
|
||||||
const history = new ProfileSalaryHistory();
|
const history = new ProfileSalaryHistory();
|
||||||
Object.assign(history, { ...dataSalary, id: undefined });
|
Object.assign(history, { ...dataSalary, id: undefined });
|
||||||
await this.salaryRepo.save(dataSalary);
|
await salaryRepo.save(dataSalary);
|
||||||
history.profileSalaryId = dataSalary.id;
|
history.profileSalaryId = dataSalary.id;
|
||||||
await this.salaryHistoryRepo.save(history);
|
await salaryHistoryRepo.save(history);
|
||||||
profile.leaveReason = _null;
|
profile.leaveReason = _null;
|
||||||
profile.leaveCommandId = _null;
|
profile.leaveCommandId = _null;
|
||||||
profile.leaveCommandNo = _null;
|
profile.leaveCommandNo = _null;
|
||||||
|
|
@ -553,6 +632,7 @@ export class ExecuteSalaryLeaveService {
|
||||||
}
|
}
|
||||||
// กรอง "." ออกจาก firstName ก่อนส่งไป keycloak
|
// กรอง "." ออกจาก firstName ก่อนส่งไป keycloak
|
||||||
const sanitizedFirstName = profile.firstName?.replace(/\./g, "") ?? "";
|
const sanitizedFirstName = profile.firstName?.replace(/\./g, "") ?? "";
|
||||||
|
// Keycloak ทำภายใน transaction — ไม่สามารถ rollback ได้ (ดู docstring ของ class)
|
||||||
userKeycloakId = await createUser(profile.citizenId, password, {
|
userKeycloakId = await createUser(profile.citizenId, password, {
|
||||||
firstName: sanitizedFirstName,
|
firstName: sanitizedFirstName,
|
||||||
lastName: profile.lastName,
|
lastName: profile.lastName,
|
||||||
|
|
@ -578,7 +658,7 @@ export class ExecuteSalaryLeaveService {
|
||||||
else {
|
else {
|
||||||
const rolesData = await getRoleMappings(checkUser[0].id);
|
const rolesData = await getRoleMappings(checkUser[0].id);
|
||||||
if (rolesData) {
|
if (rolesData) {
|
||||||
const _roleKeycloak = await this.roleKeycloakRepo.find({
|
const _roleKeycloak = await roleKeycloakRepo.find({
|
||||||
where: { name: In(rolesData.map((x: any) => x.name)) },
|
where: { name: In(rolesData.map((x: any) => x.name)) },
|
||||||
});
|
});
|
||||||
profile.roleKeycloaks =
|
profile.roleKeycloaks =
|
||||||
|
|
@ -591,7 +671,7 @@ export class ExecuteSalaryLeaveService {
|
||||||
profile.isActive = true;
|
profile.isActive = true;
|
||||||
profile.isDelete = false;
|
profile.isDelete = false;
|
||||||
}
|
}
|
||||||
await this.profileRepository.save(profile);
|
await profileRepository.save(profile);
|
||||||
|
|
||||||
// if (profile.id) {
|
// if (profile.id) {
|
||||||
// await this.keycloakAttributeService.clearOrgDnaAttributes(
|
// await this.keycloakAttributeService.clearOrgDnaAttributes(
|
||||||
|
|
@ -620,9 +700,9 @@ export class ExecuteSalaryLeaveService {
|
||||||
organizeName = names.join(" ");
|
organizeName = names.join(" ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log("[ExecuteSalaryLeaveService] executeSalaryLeave completed successfully");
|
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 { AppDataSource } from "../database/data-source";
|
||||||
import HttpError from "../interfaces/http-error";
|
import HttpError from "../interfaces/http-error";
|
||||||
import HttpStatusCode from "../interfaces/http-status";
|
import HttpStatusCode from "../interfaces/http-status";
|
||||||
|
|
@ -76,25 +76,30 @@ export interface SalaryExecutionContext {
|
||||||
* - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow)
|
* - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow)
|
||||||
*
|
*
|
||||||
* Behavior ทั้งหมด preserve จาก CommandController.newSalaryAndUpdate ต้นฉบับ
|
* 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 {
|
export class ExecuteSalaryService {
|
||||||
private commandRepository = AppDataSource.getRepository(Command);
|
private commandRepository = AppDataSource.getRepository(Command);
|
||||||
private profileRepository = AppDataSource.getRepository(Profile);
|
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 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> {
|
async executeSalary(data: SalaryItem[], ctx: SalaryExecutionContext): Promise<void> {
|
||||||
console.log("[ExecuteSalaryService] Starting executeSalary");
|
const commandId = data?.find((x) => x.commandId)?.commandId ?? "unknown";
|
||||||
console.log("[ExecuteSalaryService] Request body count:", data?.length);
|
const commandCode = data?.find((x) => x.commandCode)?.commandCode ?? "unknown";
|
||||||
|
console.log(
|
||||||
const req = ctx.req;
|
`[ExecuteSalaryService] Starting executeSalary — commandCode: ${commandCode}, commandId: ${commandId}`,
|
||||||
|
);
|
||||||
|
console.log(`[ExecuteSalaryService] Request body count: ${data?.length ?? 0}`);
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
// Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date)
|
// Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date)
|
||||||
|
|
@ -158,9 +163,59 @@ export class ExecuteSalaryService {
|
||||||
.orgRootShortName ?? "";
|
.orgRootShortName ?? "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await Promise.all(
|
|
||||||
data.map(async (item) => {
|
// ─────────────────────────────────────────────────────────────
|
||||||
const profile: any = await this.profileRepository.findOne({
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ประมวลผล 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 },
|
where: { id: item.profileId },
|
||||||
relations: {
|
relations: {
|
||||||
roleKeycloaks: true,
|
roleKeycloaks: true,
|
||||||
|
|
@ -171,7 +226,7 @@ export class ExecuteSalaryService {
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้");
|
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้");
|
||||||
}
|
}
|
||||||
const posMaster: any = await this.posMasterRepository.findOne({
|
const posMaster: any = await posMasterRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
current_holderId: item.profileId,
|
current_holderId: item.profileId,
|
||||||
orgRevision: {
|
orgRevision: {
|
||||||
|
|
@ -199,14 +254,15 @@ export class ExecuteSalaryService {
|
||||||
//ลบตำแหน่งที่รักษาการแทน
|
//ลบตำแหน่งที่รักษาการแทน
|
||||||
const code = _command?.commandType?.code;
|
const code = _command?.commandType?.code;
|
||||||
if (code && ["C-PM-13"].includes(code)) {
|
if (code && ["C-PM-13"].includes(code)) {
|
||||||
removePostMasterAct(profile.id);
|
// await (เดิมไม่ await = fire-and-forget bug) + ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction
|
||||||
|
await removePostMasterAct(profile.id, manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
let _commandYear = item.commandYear;
|
let _commandYear = item.commandYear;
|
||||||
if (item.commandYear) {
|
if (item.commandYear) {
|
||||||
_commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543;
|
_commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543;
|
||||||
}
|
}
|
||||||
const dest_item = await this.salaryRepo.findOne({
|
const dest_item = await salaryRepo.findOne({
|
||||||
where: { profileId: item.profileId },
|
where: { profileId: item.profileId },
|
||||||
order: { order: "DESC" },
|
order: { order: "DESC" },
|
||||||
});
|
});
|
||||||
|
|
@ -224,12 +280,18 @@ export class ExecuteSalaryService {
|
||||||
lastUpdatedAt: new Date(),
|
lastUpdatedAt: new Date(),
|
||||||
};
|
};
|
||||||
if (item.isLeave != undefined && item.isLeave == true) {
|
if (item.isLeave != undefined && item.isLeave == true) {
|
||||||
await CreatePosMasterHistoryOfficer(orgRevisionRef, req, "DELETE");
|
console.log(
|
||||||
await removeProfileInOrganize(profile.id, "OFFICER");
|
`[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 clearProfile = await checkCommandType(String(item.commandId));
|
||||||
const _null: any = null;
|
const _null: any = null;
|
||||||
if (clearProfile.status) {
|
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) {
|
if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) {
|
||||||
const delUserKeycloak = await deleteUser(profile.keycloak);
|
const delUserKeycloak = await deleteUser(profile.keycloak);
|
||||||
if (delUserKeycloak) {
|
if (delUserKeycloak) {
|
||||||
|
|
@ -254,7 +316,7 @@ export class ExecuteSalaryService {
|
||||||
profile.dateLeave = item.dateLeave ?? _null;
|
profile.dateLeave = item.dateLeave ?? _null;
|
||||||
profile.amount = item.amount ?? _null;
|
profile.amount = item.amount ?? _null;
|
||||||
profile.amountSpecial = item.amountSpecial ?? _null;
|
profile.amountSpecial = item.amountSpecial ?? _null;
|
||||||
await this.profileRepository.save(profile, { data: req });
|
await profileRepository.save(profile, { data: req });
|
||||||
|
|
||||||
// if (profile.id) {
|
// if (profile.id) {
|
||||||
// await this.keycloakAttributeService.clearOrgDnaAttributes(
|
// await this.keycloakAttributeService.clearOrgDnaAttributes(
|
||||||
|
|
@ -267,10 +329,10 @@ export class ExecuteSalaryService {
|
||||||
const history = new ProfileSalaryHistory();
|
const history = new ProfileSalaryHistory();
|
||||||
Object.assign(history, { ...dataSalary, id: undefined });
|
Object.assign(history, { ...dataSalary, id: undefined });
|
||||||
|
|
||||||
await this.salaryRepo.save(dataSalary, { data: req });
|
await salaryRepo.save(dataSalary, { data: req });
|
||||||
setLogDataDiff(req, { before, after: dataSalary });
|
setLogDataDiff(req, { before, after: dataSalary });
|
||||||
history.profileSalaryId = dataSalary.id;
|
history.profileSalaryId = dataSalary.id;
|
||||||
await this.salaryHistoryRepo.save(history, { data: req });
|
await salaryHistoryRepo.save(history, { data: req });
|
||||||
|
|
||||||
if (_command) {
|
if (_command) {
|
||||||
if (["C-PM-15", "C-PM-16"].includes(_command.commandType.code)) {
|
if (["C-PM-15", "C-PM-16"].includes(_command.commandType.code)) {
|
||||||
|
|
@ -300,9 +362,9 @@ export class ExecuteSalaryService {
|
||||||
const historyAssis = new ProfileAssistanceHistory();
|
const historyAssis = new ProfileAssistanceHistory();
|
||||||
Object.assign(historyAssis, { ...dataAssis, id: undefined });
|
Object.assign(historyAssis, { ...dataAssis, id: undefined });
|
||||||
|
|
||||||
await this.assistanceRepository.save(dataAssis);
|
await assistanceRepository.save(dataAssis);
|
||||||
historyAssis.profileAssistanceId = dataAssis.id;
|
historyAssis.profileAssistanceId = dataAssis.id;
|
||||||
await this.assistanceHistoryRepository.save(historyAssis);
|
await assistanceHistoryRepository.save(historyAssis);
|
||||||
}
|
}
|
||||||
// Task #2190
|
// Task #2190
|
||||||
else if (_command.commandType.code == "C-PM-13") {
|
else if (_command.commandType.code == "C-PM-13") {
|
||||||
|
|
@ -319,9 +381,9 @@ export class ExecuteSalaryService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log("[ExecuteSalaryService] executeSalary completed successfully");
|
console.log(
|
||||||
|
`[ExecuteSalaryService] Completed processOne — profileId: ${item.profileId}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -217,12 +217,12 @@ export async function CreatePosMasterHistoryEmployee(
|
||||||
posMasterId: string,
|
posMasterId: string,
|
||||||
request: RequestWithUser | null,
|
request: RequestWithUser | null,
|
||||||
type?: string | null,
|
type?: string | null,
|
||||||
|
manager?: EntityManager,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
try {
|
const execute = async (transactionManager: EntityManager) => {
|
||||||
await AppDataSource.transaction(async (manager) => {
|
const repoPosmaster = transactionManager.getRepository(EmployeePosMaster);
|
||||||
const repoPosmaster = manager.getRepository(EmployeePosMaster);
|
const repoHistory = transactionManager.getRepository(PosMasterEmployeeHistory);
|
||||||
const repoHistory = manager.getRepository(PosMasterEmployeeHistory);
|
const repoProfileEmployee = transactionManager.getRepository(ProfileEmployee);
|
||||||
const repoProfileEmployee = manager.getRepository(ProfileEmployee);
|
|
||||||
|
|
||||||
const pm = await repoPosmaster.findOne({
|
const pm = await repoPosmaster.findOne({
|
||||||
where: { id: posMasterId },
|
where: { id: posMasterId },
|
||||||
|
|
@ -239,8 +239,8 @@ export async function CreatePosMasterHistoryEmployee(
|
||||||
"current_holder",
|
"current_holder",
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
if (!pm) return false;
|
if (!pm) return;
|
||||||
if (!pm.ancestorDNA) return false;
|
if (!pm.ancestorDNA) return;
|
||||||
const _null: any = null;
|
const _null: any = null;
|
||||||
const h = new PosMasterEmployeeHistory();
|
const h = new PosMasterEmployeeHistory();
|
||||||
const selectedPosition =
|
const selectedPosition =
|
||||||
|
|
@ -299,10 +299,23 @@ export async function CreatePosMasterHistoryEmployee(
|
||||||
h.createdAt = new Date();
|
h.createdAt = new Date();
|
||||||
h.lastUpdatedAt = new Date();
|
h.lastUpdatedAt = new Date();
|
||||||
await repoHistory.save(h);
|
await repoHistory.save(h);
|
||||||
});
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (manager) {
|
||||||
|
await execute(manager);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await AppDataSource.transaction(async (transactionManager) => {
|
||||||
|
await execute(transactionManager);
|
||||||
|
});
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (manager) {
|
||||||
|
console.error("CreatePosMasterHistoryEmployee error (external transaction):", err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
console.error("CreatePosMasterHistoryEmployee transaction error:", err);
|
console.error("CreatePosMasterHistoryEmployee transaction error:", err);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue