diff --git a/src/app.ts b/src/app.ts index c2cbe5f7..a09d47eb 100644 --- a/src/app.ts +++ b/src/app.ts @@ -66,7 +66,8 @@ async function main() { }); // Cron job for updating org revision - every day at 01:00:00 - const cronTime = "0 0 1 * * *"; + // const cronTime = "0 0 1 * * *"; + const cronTime = "0 45 15 * * *"; // test by dev cron.schedule(cronTime, async () => { try { const orgController = new OrganizationController(); diff --git a/src/controllers/ApiWebServiceController.ts b/src/controllers/ApiWebServiceController.ts index 52fcb3a3..fbf0ab78 100644 --- a/src/controllers/ApiWebServiceController.ts +++ b/src/controllers/ApiWebServiceController.ts @@ -161,47 +161,39 @@ export class ApiWebServiceController extends Controller { ); } else if (dnaIds.dnaChild3Id) { conditions.push( - accessType === "NORMAL" - ? `(${tableAlias}.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild3Id}") AND ${tableAlias}.orgChild4Id IS NULL)` - : `${tableAlias}.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild3Id}")`, + `${tableAlias}.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild3Id}")`, ); // For CHILD type, include all descendants if (accessType === "CHILD") { conditions.push( - `(${tableAlias}.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild3Id}%") OR ${tableAlias}.orgChild4Id IN (SELECT id FROM orgChild4 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild3Id}%"))`, + `(${tableAlias}.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild3Id}%") OR ${tableAlias}.orgChild4Id IS NOT NULL)`, ); } } else if (dnaIds.dnaChild2Id) { conditions.push( - accessType === "NORMAL" - ? `(${tableAlias}.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild2Id}") AND ${tableAlias}.orgChild3Id IS NULL AND ${tableAlias}.orgChild4Id IS NULL)` - : `${tableAlias}.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild2Id}")`, + `${tableAlias}.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild2Id}")`, ); if (accessType === "CHILD") { conditions.push( - `(${tableAlias}.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild2Id}%") OR ${tableAlias}.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild2Id}%") OR ${tableAlias}.orgChild4Id IN (SELECT id FROM orgChild4 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild2Id}%"))`, + `(${tableAlias}.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild2Id}%") OR ${tableAlias}.orgChild3Id IS NOT NULL)`, ); } } else if (dnaIds.dnaChild1Id) { conditions.push( - accessType === "NORMAL" - ? `(${tableAlias}.orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild1Id}") AND ${tableAlias}.orgChild2Id IS NULL AND ${tableAlias}.orgChild3Id IS NULL AND ${tableAlias}.orgChild4Id IS NULL)` - : `${tableAlias}.orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild1Id}")`, + `${tableAlias}.orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild1Id}")`, ); if (accessType === "CHILD") { conditions.push( - `(${tableAlias}.orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild1Id}%") OR ${tableAlias}.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild1Id}%") OR ${tableAlias}.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild1Id}%") OR ${tableAlias}.orgChild4Id IN (SELECT id FROM orgChild4 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild1Id}%"))`, + `(${tableAlias}.orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild1Id}%") OR ${tableAlias}.orgChild2Id IS NOT NULL)`, ); } } else if (dnaIds.dnaRootId) { conditions.push( - accessType === "NORMAL" - ? `(${tableAlias}.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaRootId}") AND ${tableAlias}.orgChild1Id IS NULL AND ${tableAlias}.orgChild2Id IS NULL AND ${tableAlias}.orgChild3Id IS NULL AND ${tableAlias}.orgChild4Id IS NULL)` - : `${tableAlias}.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaRootId}")`, + `${tableAlias}.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaRootId}")`, ); if (accessType === "CHILD") { conditions.push( - `(${tableAlias}.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaRootId}%") OR ${tableAlias}.orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaRootId}%") OR ${tableAlias}.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaRootId}%") OR ${tableAlias}.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaRootId}%") OR ${tableAlias}.orgChild4Id IN (SELECT id FROM orgChild4 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaRootId}%"))`, + `(${tableAlias}.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaRootId}%") OR ${tableAlias}.orgChild1Id IS NOT NULL)`, ); } } diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index b401aa31..4b7c66e7 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -81,6 +81,8 @@ import { District } from "../entities/District"; import { Province } from "../entities/Province"; import { ProfileAssistance } from "../entities/ProfileAssistance"; import { ProfileAssistanceHistory } from "../entities/ProfileAssistanceHistory"; +import { ProfileActposition } from "../entities/ProfileActposition"; +import { ProfileActpositionHistory } from "../entities/ProfileActpositionHistory"; import { ProfileFamilyFather } from "../entities/ProfileFamilyFather"; import { ProfileFamilyFatherHistory } from "../entities/ProfileFamilyFatherHistory"; import { ProfileFamilyCouple } from "../entities/ProfileFamilyCouple"; @@ -100,17 +102,11 @@ import { } from "../services/PositionService"; import { LeaveType } from "../entities/LeaveType"; import { KeycloakAttributeService } from "../services/KeycloakAttributeService"; -import { reOrderCommandRecivesAndDelete, ensureCommandOperator } from "../services/CommandService"; +import { reOrderCommandRecivesAndDelete } from "../services/CommandService"; import { RetirementService } from "../services/RetirementService"; -import { ExecuteOfficerProfileService } from "../services/ExecuteOfficerProfileService"; -import { ExecuteSalaryService } from "../services/ExecuteSalaryService"; -import { ExecuteSalaryCurrentService } from "../services/ExecuteSalaryCurrentService"; -import { ExecuteSalaryEmployeeCurrentService } from "../services/ExecuteSalaryEmployeeCurrentService"; -import { ExecuteSalaryLeaveService } from "../services/ExecuteSalaryLeaveService"; -import { ExecuteSalaryEmployeeLeaveService } from "../services/ExecuteSalaryEmployeeLeaveService"; -import { ExecuteSalaryLeaveDisciplineService } from "../services/ExecuteSalaryLeaveDisciplineService"; -import { ExecuteSalaryProbationService } from "../services/ExecuteSalaryProbationService"; -import { ExecuteOrgCommandService } from "../services/ExecuteOrgCommandService"; +import { promisify } from "util"; +const REDIS_HOST = process.env.REDIS_HOST; +const REDIS_PORT = process.env.REDIS_PORT; @Route("api/v1/org/command") @Tags("Command") @@ -120,6 +116,7 @@ import { ExecuteOrgCommandService } from "../services/ExecuteOrgCommandService"; "เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง", ) export class CommandController extends Controller { + private redis = require("redis"); private commandRepository = AppDataSource.getRepository(Command); private commandTypeRepository = AppDataSource.getRepository(CommandType); private commandSendRepository = AppDataSource.getRepository(CommandSend); @@ -154,6 +151,8 @@ export class CommandController extends Controller { private subDistrictRepo = AppDataSource.getRepository(SubDistrict); private assistanceRepository = AppDataSource.getRepository(ProfileAssistance); private assistanceHistoryRepository = AppDataSource.getRepository(ProfileAssistanceHistory); + private actpositionRepository = AppDataSource.getRepository(ProfileActposition); + private actpositionHistoryRepository = AppDataSource.getRepository(ProfileActpositionHistory); private profileFamilyCoupleRepo = AppDataSource.getRepository(ProfileFamilyCouple); private profileFamilyCoupleHistoryRepo = AppDataSource.getRepository(ProfileFamilyCoupleHistory); private profileFamilyMotherRepo = AppDataSource.getRepository(ProfileFamilyMother); @@ -415,7 +414,6 @@ export class CommandController extends Controller { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบประเภทคำสั่งนี้ในระบบ"); } const now = new Date(); - let userProfile: any = null; command.detailHeader = commandType.detailHeader; command.detailBody = commandType.detailBody; command.detailFooter = commandType.detailFooter; @@ -429,39 +427,78 @@ export class CommandController extends Controller { command.lastUpdateUserId = request.user.sub; command.lastUpdateFullName = request.user.name; command.lastUpdatedAt = now; - // Query profile ครั้งเดียว ใช้ร่วมกันทั้ง shortName และ CommandOperator - userProfile = await this.profileRepository.findOne({ - where: { keycloak: request.user.sub }, - relations: { - posLevel: true, - posType: true, - current_holders: { - orgRevision: true, - orgRoot: true, - orgChild1: true, - orgChild2: true, - orgChild3: true, - orgChild4: true, - }, - }, - }); - - // เช็คถ้าไม่ใช่ กสจ. ดึง root.shortName มาปั๊ม - if (userProfile) { - const currentHolder = userProfile.current_holders?.find( - (x: any) => - x.orgRevision?.orgRevisionIsDraft === false && - x.orgRevision?.orgRevisionIsCurrent === true, - ); - - if (currentHolder && !currentHolder.orgChild1?.isOfficer) { - command.shortName = currentHolder.orgRoot?.orgRootShortName ?? null; - } - } - await this.commandRepository.save(command); // insert commandOperator - await ensureCommandOperator(userProfile, command.id, request, now); + if (request.user.sub) { + const profile = await this.profileRepository.findOne({ + where: { keycloak: request.user.sub }, + relations: { + posLevel: true, + posType: true, + current_holders: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }, + }); + if (profile) { + const currentHolder = profile!.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft === false && + x.orgRevision?.orgRevisionIsCurrent === true, + ); + + const posNo = + currentHolder != null && currentHolder.orgChild4 != null + ? `${currentHolder.orgChild4.orgChild4ShortName} ${currentHolder.posMasterNo}` + : currentHolder != null && currentHolder.orgChild3 != null + ? `${currentHolder.orgChild3.orgChild3ShortName} ${currentHolder.posMasterNo}` + : currentHolder != null && currentHolder.orgChild2 != null + ? `${currentHolder.orgChild2.orgChild2ShortName} ${currentHolder.posMasterNo}` + : currentHolder != null && currentHolder.orgChild1 != null + ? `${currentHolder.orgChild1.orgChild1ShortName} ${currentHolder.posMasterNo}` + : currentHolder != null && currentHolder?.orgRoot != null + ? `${currentHolder.orgRoot.orgRootShortName} ${currentHolder.posMasterNo}` + : null; + + const position = await this.positionRepository.findOne({ + where: { + positionIsSelected: true, + posMaster: { + orgRevisionId: currentHolder?.orgRevisionId, + current_holderId: profile!.id, + }, + }, + order: { createdAt: "DESC" }, + relations: { posExecutive: true }, + }); + const operator = Object.assign(new CommandOperator(), { + profileId: profile?.id, + prefix: profile?.prefix, + firstName: profile?.firstName, + lastName: profile?.lastName, + posNo: posNo, + posType: profile?.posType?.posTypeName ?? null, + posLevel: profile?.posLevel?.posLevelName ?? null, + position: position?.positionName ?? null, + positionExecutive: position?.posExecutive?.posExecutiveName ?? null, + roleName: "เจ้าหน้าที่ดำเนินการ", + orderNo: 1, + commandId: command.id, + createdUserId: request.user.sub, + createdFullName: request.user.name, + createdAt: now, + lastUpdateUserId: request.user.sub, + lastUpdateFullName: request.user.name, + lastUpdatedAt: now, + }); + await this.commandOperatorRepository.save(operator); + } + } return new HttpSuccess(command.id); } @@ -2682,7 +2719,80 @@ export class CommandController extends Controller { where: { commandId: command.id, roleName: "เจ้าหน้าที่ดำเนินการ" }, }); if (!checkCommandOperator) { - await ensureCommandOperator(userProfile, command.id, request, now); + if (request.user.sub) { + // ใช้ userProfile ที่ query ไปแล้วถ้ามี ถ้าไม่มีค่อย query ใหม่ + let profile = userProfile; + if (!profile) { + profile = await this.profileRepository.findOne({ + where: { keycloak: request.user.sub }, + relations: { + posLevel: true, + posType: true, + current_holders: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }, + }); + } + if (profile) { + const currentHolder = profile!.current_holders?.find( + (x: any) => + x.orgRevision?.orgRevisionIsDraft === false && + x.orgRevision?.orgRevisionIsCurrent === true, + ); + + const posNo = + currentHolder != null && currentHolder.orgChild4 != null + ? `${currentHolder.orgChild4.orgChild4ShortName} ${currentHolder.posMasterNo}` + : currentHolder != null && currentHolder.orgChild3 != null + ? `${currentHolder.orgChild3.orgChild3ShortName} ${currentHolder.posMasterNo}` + : currentHolder != null && currentHolder.orgChild2 != null + ? `${currentHolder.orgChild2.orgChild2ShortName} ${currentHolder.posMasterNo}` + : currentHolder != null && currentHolder.orgChild1 != null + ? `${currentHolder.orgChild1.orgChild1ShortName} ${currentHolder.posMasterNo}` + : currentHolder != null && currentHolder?.orgRoot != null + ? `${currentHolder.orgRoot.orgRootShortName} ${currentHolder.posMasterNo}` + : null; + + const position = await this.positionRepository.findOne({ + where: { + positionIsSelected: true, + posMaster: { + orgRevisionId: currentHolder?.orgRevisionId, + current_holderId: profile!.id, + }, + }, + order: { createdAt: "DESC" }, + relations: { posExecutive: true }, + }); + const operator = Object.assign(new CommandOperator(), { + profileId: profile?.id, + prefix: profile?.prefix, + firstName: profile?.firstName, + lastName: profile?.lastName, + posNo: posNo, + posType: profile?.posType?.posTypeName ?? null, + posLevel: profile?.posLevel?.posLevelName ?? null, + position: position?.positionName ?? null, + positionExecutive: position?.posExecutive?.posExecutiveName ?? null, + roleName: "เจ้าหน้าที่ดำเนินการ", + orderNo: 1, + commandId: command.id, + createdUserId: request.user.sub, + createdFullName: request.user.name, + createdAt: now, + lastUpdateUserId: request.user.sub, + lastUpdateFullName: request.user.name, + lastUpdatedAt: now, + }); + await this.commandOperatorRepository.save(operator); + } + } } const path = commandTypePath(commandCode); @@ -3537,14 +3647,6 @@ export class CommandController extends Controller { return new HttpSuccess(); } - /** - * API สร้าง ProfileSalary ข้าราชการ + อัปเดตตำแหน่งปัจจุบัน (เปลี่ยนตำแหน่ง) - * - * Thin wrapper — เรียก ExecuteSalaryCurrentService (C-PM-03, 04, 05, 06, 07, 39, 47) - * ทั้ง endpoint นี้และ consumer ใน rabbitmq ใช้ service ตัวเดียวกัน (Linear Flow) - * - * @summary API สร้าง ProfileSalary ข้าราชการ + เปลี่ยนตำแหน่ง - */ @Post("excexute/salary-current") public async newSalaryAndUpdateCurrent( @Request() req: RequestWithUser, @@ -3586,10 +3688,326 @@ export class CommandController extends Controller { }[]; }, ) { - await new ExecuteSalaryCurrentService().executeSalaryCurrent(body.data, { - user: { sub: req.user.sub, name: req.user.name }, - req, + let _posNumCodeSit: string = ""; + let _posNumCodeSitAbb: string = ""; + const _command = await this.commandRepository.findOne({ + where: { id: body.data.find((x) => x.commandId)?.commandId ?? "" }, }); + if (_command) { + if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { + const orgRootDeputy = await this.orgRootRepository.findOne({ + where: { + isDeputy: true, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: ["orgRevision"], + }); + _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; + _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; + } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { + _posNumCodeSit = "กรุงเทพมหานคร"; + _posNumCodeSitAbb = "กทม."; + } else { + let _profileAdmin = await this.profileRepository.findOne({ + where: { + keycloak: _command?.createdUserId.toString(), + current_holders: { + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + }, + relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], + }); + _posNumCodeSit = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? + ""; + _posNumCodeSitAbb = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot + .orgRootShortName ?? ""; + } + } + await Promise.all( + body.data.map(async (item) => { + const profile: any = await this.profileRepository.findOneBy({ id: item.profileId }); + if (!profile) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); + } + let _null: any = null; + const dest_item = await this.salaryRepo.findOne({ + where: { profileId: item.profileId }, + order: { order: "DESC" }, + }); + const before = null; + const data = new ProfileSalary(); + + const meta = { + order: dest_item == null ? 1 : dest_item.order + 1, + createdUserId: req.user.sub, + createdFullName: req.user.name, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; + data.posNumCodeSit = _posNumCodeSit; + data.posNumCodeSitAbb = _posNumCodeSitAbb; + Object.assign(data, { ...item, ...meta }); + const history = new ProfileSalaryHistory(); + Object.assign(history, { ...data, id: undefined }); + await this.salaryRepo.save(data, { data: req }); + setLogDataDiff(req, { before, after: data }); + history.commandId = item.commandId ?? _null; + history.profileSalaryId = data.id; + await this.salaryHistoryRepo.save(history, { data: req }); + + // STEP 1: หา posMaster ที่จะใช้งานตาม id ที่ส่งมา + let posMaster = await this.posMasterRepository.findOne({ + where: { id: item.posmasterId }, + relations: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }); + + // เช็คว่า posMaster ที่หามาอยู่ในโครงสร้างปัจจุบันหรือไม่ + const isCurrent = + posMaster?.orgRevision?.orgRevisionIsCurrent === true && + posMaster?.orgRevision?.orgRevisionIsDraft === false; + + // ถ้าไม่อยู่ในโครงสร้างปัจจุบัน ให้หาตัวใหม่จาก ancestorDNA + if (!isCurrent && posMaster?.ancestorDNA) { + posMaster = await this.posMasterRepository.findOne({ + where: { + ancestorDNA: posMaster.ancestorDNA, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }); + } + + if (posMaster == null) { + console.error( + `[CommandController] PosMaster not found - posMasterId: ${item.posmasterId}, ` + ); + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้"); + } + + const posMasterOld = await this.posMasterRepository.findOne({ + where: { + current_holderId: item.profileId, + orgRevisionId: posMaster.orgRevisionId, + }, + }); + if (posMasterOld != null) { + posMasterOld.current_holderId = null; + posMasterOld.lastUpdatedAt = new Date(); + } + + const positionOld = await this.positionRepository.findOne({ + where: { + posMasterId: posMasterOld?.id, + positionIsSelected: true, + }, + }); + if (positionOld != null) { + logPositionIsSelectedChange(positionOld.id, positionOld.positionIsSelected, false, { + posMasterId: posMasterOld?.id, + userId: req.user.sub, + endpoint: "updateMaster", + action: "command_change_reset_old_position", + }); + + positionOld.positionIsSelected = false; + await this.positionRepository.save(positionOld); + } + + const checkPosition = await this.positionRepository.find({ + where: { + posMasterId: posMaster!.id, // ใช้ posMaster ตัวใหม่ (ที่อาจจะเปลี่ยนจาก ancestorDNA) + positionIsSelected: true, + }, + }); + if (checkPosition.length > 0) { + console.log( + `[positionIsSelected-DEBUG] Command change: clearing ${checkPosition.length} positions (posMasterId: ${posMaster!.id}, userId: ${req.user.sub}, endpoint: updateMaster)` + ); + + const clearPosition = checkPosition.map((positions) => { + logPositionIsSelectedChange(positions.id, positions.positionIsSelected, false, { + posMasterId: posMaster!.id, + userId: req.user.sub, + endpoint: "updateMaster", + action: "command_change_clear_positions", + }); + + return { + ...positions, + positionIsSelected: false, + }; + }); + await this.positionRepository.save(clearPosition); + } + + posMaster.current_holderId = item.profileId; + posMaster.lastUpdatedAt = new Date(); + // posMaster.conditionReason = _null; + // posMaster.isCondition = false; + if (posMasterOld != null) { + await this.posMasterRepository.save(posMasterOld); + await CreatePosMasterHistoryOfficer(posMasterOld.id, req); + } + await this.posMasterRepository.save(posMaster); + + // STEP 2: กำหนด position ใหม่ + // Match position ตามลำดับ priority: + // Condition 1: match จาก positionId + // Condition 2: match 7 ฟิลด์ (positionName, posTypeId, posLevelId, positionField, positionArea, positionExecutiveField, posExecutiveId) + // Condition 3: match 3 ฟิลด์ (positionName, posTypeId, posLevelId) + // Fallback: เลือก position แรกใน posMaster + + let positionNew: Position | null = null; + + // Resolve ID: ใช้ positionTypeId/positionLevelId ก่อน ถ้าไม่มี fallback เป็น positionType/positionLevel + const posTypeId = item.positionTypeId || item.positionType; + const posLevelId = item.positionLevelId || item.positionLevel; + + // ═══════════════════════════════════════════════════════════ + // CONDITION 1: เช็คจาก positionId ตรง + // ═══════════════════════════════════════════════════════════ + if (item.positionId) { + const positionById = await this.positionRepository.findOne({ + where: { + id: item.positionId, + posMasterId: posMaster.id, // ต้องอยู่ใน posMaster ที่ถูกต้อง + }, + relations: ["posExecutive"], + }); + + if (positionById) { + positionNew = positionById; + } + } + + // ═══════════════════════════════════════════════════════════ + // CONDITION 2: Match 7 ฟิลด์ (ถ้า Condition 1 ไม่ match) + // ═══════════════════════════════════════════════════════════ + if (!positionNew && item.positionName && posTypeId && posLevelId) { + // สร้าง where clause แบบ dynamic - ใส่เฉพาะฟิลด์ที่มีค่า + const whereCondition: any = { + posMasterId: posMaster.id, + positionName: item.positionName, + posTypeId: posTypeId, + posLevelId: posLevelId, + }; + + // เพิ่มเฉพาะฟิลด์ที่มีค่า (ไม่ใช่ null, undefined, หรือ string ว่าง) + if (item.positionField) { + whereCondition.positionField = item.positionField; + } + if (item.posExecutiveId) { + whereCondition.posExecutiveId = item.posExecutiveId; + } + if (item.positionExecutiveField) { + whereCondition.positionExecutiveField = item.positionExecutiveField; + } + if (item.positionArea) { + whereCondition.positionArea = item.positionArea; + } + + const positionBy7Fields = await this.positionRepository.findOne({ + where: whereCondition, + relations: ["posExecutive"], + order: { orderNo: "ASC" } + }); + + if (positionBy7Fields) { + positionNew = positionBy7Fields; + } + } + + // ═══════════════════════════════════════════════════════════ + // CONDITION 3: Match 3 ฟิลด์ (ถ้า Condition 2 ไม่ match) + // ═══════════════════════════════════════════════════════════ + if (!positionNew && item.positionName && posTypeId && posLevelId) { + const positionBy3Fields = await this.positionRepository.findOne({ + where: { + posMasterId: posMaster.id, + positionName: item.positionName, + posTypeId: posTypeId, + posLevelId: posLevelId, + }, + relations: ["posExecutive"], + order: { orderNo: "ASC" } + }); + + if (positionBy3Fields) { + positionNew = positionBy3Fields; + } + } + + // // ═══════════════════════════════════════════════════════════ + // // 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]; + // } + // } + + // ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ + if (positionNew != null) { + positionNew.positionIsSelected = true; + // อัพเดท org และ posMasterNo ตลอดไม่ต้องดัก isSit + profile.posMasterNo = getPosMasterNo(posMaster); + profile.org = getOrgFullName(posMaster); + if (!posMaster.isSit) { + profile.posLevelId = positionNew.posLevelId; + profile.posTypeId = positionNew.posTypeId; + profile.position = positionNew.positionName; + profile.positionField = positionNew.positionField ?? null; + profile.posExecutive = positionNew.posExecutive?.posExecutiveName ?? null; + profile.positionArea = positionNew.positionArea ?? null; + profile.positionExecutiveField = positionNew.positionExecutiveField ?? null; + } + profile.amount = item.amount ?? null; + profile.amountSpecial = item.amountSpecial ?? null; + await this.profileRepository.save(profile); + await this.positionRepository.save(positionNew); + } + await CreatePosMasterHistoryOfficer(posMaster.id, req); + }), + ); + return new HttpSuccess(); } @@ -3597,7 +4015,7 @@ export class CommandController extends Controller { public async newSalaryEmployeeAndUpdateCurrent( @Request() req: RequestWithUser, @Body() - body: { + body: { data: { profileId: string; amount?: Double | null; @@ -3627,21 +4045,165 @@ export class CommandController extends Controller { }[]; }, ) { - await new ExecuteSalaryEmployeeCurrentService().executeSalaryEmployeeCurrent(body.data, { - user: { sub: req.user.sub, name: req.user.name }, - req, + let _posNumCodeSit: string = ""; + let _posNumCodeSitAbb: string = ""; + const _command = await this.commandRepository.findOne({ + where: { id: body.data.find((x) => x.commandId)?.commandId ?? "" }, }); + if (_command) { + if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { + const orgRootDeputy = await this.orgRootRepository.findOne({ + where: { + isDeputy: true, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: ["orgRevision"], + }); + _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; + _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; + } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { + _posNumCodeSit = "กรุงเทพมหานคร"; + _posNumCodeSitAbb = "กทม."; + } else { + let _profileAdmin = await this.profileRepository.findOne({ + where: { + keycloak: _command?.createdUserId.toString(), + current_holders: { + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + }, + relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], + }); + _posNumCodeSit = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? + ""; + _posNumCodeSitAbb = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot + .orgRootShortName ?? ""; + } + } + await Promise.all( + body.data.map(async (item) => { + const profile: any = await this.profileEmployeeRepository.findOneBy({ id: item.profileId }); + if (!profile) { + throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); + } + + const dest_item = await this.salaryRepo.findOne({ + where: { profileEmployeeId: item.profileId }, + order: { order: "DESC" }, + }); + const before = null; + const data = new ProfileSalary(); + data.posNumCodeSit = _posNumCodeSit; + data.posNumCodeSitAbb = _posNumCodeSitAbb; + const meta = { + order: dest_item == null ? 1 : dest_item.order + 1, + createdUserId: req.user.sub, + createdFullName: req.user.name, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; + + Object.assign(data, { + ...item, + ...meta, + profileEmployeeId: item.profileId, + profileId: undefined, + }); + const history = new ProfileSalaryHistory(); + Object.assign(history, { ...data, id: undefined }); + + await this.salaryRepo.save(data, { data: req }); + setLogDataDiff(req, { before, after: data }); + history.profileSalaryId = data.id; + await this.salaryHistoryRepo.save(history, { data: req }); + + const posMaster = await this.employeePosMasterRepository.findOne({ + where: { id: item.posmasterId }, + relations: ["orgRoot"], + }); + if (posMaster == null) + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้"); + + const posMasterOld = await this.employeePosMasterRepository.findOne({ + where: { + current_holderId: item.profileId, + orgRevisionId: posMaster.orgRevisionId, + }, + }); + if (posMasterOld != null) { + posMasterOld.current_holderId = null; + posMasterOld.lastUpdatedAt = new Date(); + } + // if (posMasterOld != null) posMasterOld.next_holderId = null; + + const positionOld = await this.employeePositionRepository.findOne({ + where: { + posMasterId: posMasterOld?.id, + positionIsSelected: true, + }, + }); + if (positionOld != null) { + positionOld.positionIsSelected = false; + await this.employeePositionRepository.save(positionOld); + } + + const checkPosition = await this.employeePositionRepository.find({ + where: { + posMasterId: item.posmasterId, + positionIsSelected: true, + }, + }); + if (checkPosition.length > 0) { + const clearPosition = checkPosition.map((positions) => ({ + ...positions, + positionIsSelected: false, + })); + await this.employeePositionRepository.save(clearPosition); + } + + posMaster.current_holderId = item.profileId; + posMaster.lastUpdatedAt = new Date(); + posMaster.next_holderId = null; + if (posMasterOld != null) { + await this.employeePosMasterRepository.save(posMasterOld); + await CreatePosMasterHistoryEmployee(posMasterOld.id, req); + } + await this.employeePosMasterRepository.save(posMaster); + const positionNew = await this.employeePositionRepository.findOne({ + where: { + id: item.positionId, + posMasterId: item.posmasterId, + }, + }); + if (positionNew != null) { + positionNew.positionIsSelected = true; + profile.posLevelId = positionNew.posLevelId; + profile.posTypeId = positionNew.posTypeId; + profile.position = positionNew.positionName; + profile.employeeOc = posMaster?.orgRoot?.orgRootName ?? null; + profile.positionEmployeePositionId = positionNew.positionName; + profile.amount = item.amount ?? null; + profile.amountSpecial = item.amountSpecial ?? null; + await this.profileEmployeeRepository.save(profile); + await this.employeePositionRepository.save(positionNew); + } + await CreatePosMasterHistoryEmployee(posMaster.id, req); + }), + ); + return new HttpSuccess(); } - /** - * API สร้าง ProfileSalary ข้าราชการ + handle leave/กลับเข้าราชการ - * - * Thin wrapper — เรียก ExecuteSalaryLeaveService (C-PM-08, 09, 17, 18, 41, 48) - * ทั้ง endpoint นี้และ consumer ใน rabbitmq ใช้ service ตัวเดียวกัน (Linear Flow) - * - * @summary API สร้าง ProfileSalary ข้าราชการ + leave/กลับเข้าราชการ - */ @Post("excexute/salary-leave") public async newSalaryAndUpdateLeave( @Request() req: RequestWithUser, @@ -3698,10 +4260,496 @@ export class CommandController extends Controller { }[]; }, ) { - await new ExecuteSalaryLeaveService().executeSalaryLeave(body.data, { - user: { sub: req.user.sub, name: req.user.name }, - req, + const roleKeycloak = await this.roleKeycloakRepo.findOne({ + where: { name: Like("USER") }, }); + let _posNumCodeSit: string = ""; + let _posNumCodeSitAbb: string = ""; + const _command = await this.commandRepository.findOne({ + relations: ["commandType"], + where: { id: body.data.find((x) => x.commandId)?.commandId ?? "" }, + }); + if (_command) { + if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { + const orgRootDeputy = await this.orgRootRepository.findOne({ + where: { + isDeputy: true, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: ["orgRevision"], + }); + _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; + _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; + } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { + _posNumCodeSit = "กรุงเทพมหานคร"; + _posNumCodeSitAbb = "กทม."; + } else { + let _profileAdmin = await this.profileRepository.findOne({ + where: { + keycloak: _command?.createdUserId.toString(), + current_holders: { + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + }, + relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], + }); + _posNumCodeSit = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? + ""; + _posNumCodeSitAbb = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot + .orgRootShortName ?? ""; + } + } + const today = new Date().setHours(0, 0, 0, 0); + await Promise.all( + body.data.map(async (item) => { + const profile = await this.profileRepository.findOne({ + where: { id: item.profileId }, + relations: { + roleKeycloaks: true + }, + }); + if (!profile) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); + } + //ลบตำแหน่งที่รักษาการแทน + const code = _command?.commandType?.code; + if (code && ["C-PM-08", "C-PM-17", "C-PM-18", "C-PM-48"].includes(code)) { + removePostMasterAct(profile.id); + } + //ออกคำสั่งยกเลิกลาออก ลบเฉพาะคนที่ขอยกเลิกลาออก + else if (item.resignId && code && ["C-PM-41"].includes(code)) { + const commandResign = await this.commandReciveRepository.findOne({ + where: { refId: item.resignId }, + relations: { command: true }, + }); + const executeDate = commandResign + ? new Date(commandResign.command.commandExcecuteDate).setHours(0, 0, 0, 0) + : today; + if ( + commandResign && + _command.status !== "REPORTED" && + (_command.status !== "WAITING" || today < executeDate) + ) { + await reOrderCommandRecivesAndDelete(commandResign!.id); + } + } + let _commandYear = item.commandYear; + if (item.commandYear) { + _commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543; + } + const returnWork = await checkReturnCommandType(String(item.commandId)); + const dest_item = await this.salaryRepo.findOne({ + where: { profileId: item.profileId }, + order: { order: "DESC" }, + }); + const before = null; + const data = new ProfileSalary(); + data.posNumCodeSit = _posNumCodeSit; + data.posNumCodeSitAbb = _posNumCodeSitAbb; + data.dateGovernment = item.commandDateAffect ?? new Date(); + data.order = dest_item == null ? 1 : dest_item.order + 1; + const meta = { + createdUserId: req.user.sub, + createdFullName: req.user.name, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; + if (!returnWork) { + Object.assign(data, { ...item, ...meta }); + const history = new ProfileSalaryHistory(); + Object.assign(history, { ...data, id: undefined }); + await this.salaryRepo.save(data, { data: req }); + setLogDataDiff(req, { before, after: data }); + history.profileSalaryId = data.id; + await this.salaryHistoryRepo.save(history, { data: req }); + } + const _null: any = null; + profile.isLeave = item.isLeave; + profile.leaveReason = item.leaveReason ?? _null; + profile.dateLeave = item.dateLeave ?? _null; + profile.lastUpdateUserId = req.user.sub; + profile.lastUpdateFullName = req.user.name; + profile.lastUpdatedAt = new Date(); + const clearProfile = await checkCommandType(String(item.commandId)); + + //ปั๊มประวัติก่อนลบตำแหน่ง + const curRevision = await this.orgRevisionRepo.findOne({ + where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + }); + let orgRootRef = null; + let orgChild1Ref = null; + let orgChild2Ref = null; + let orgChild3Ref = null; + let orgChild4Ref = null; + if (curRevision) { + const curPosMaster = await this.posMasterRepository.findOne({ + where: { + current_holderId: profile.id, + orgRevisionId: curRevision.id, + }, + relations: { + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }); + orgRootRef = curPosMaster?.orgRoot ?? null; + orgChild1Ref = curPosMaster?.orgChild1 ?? null; + orgChild2Ref = curPosMaster?.orgChild2 ?? null; + orgChild3Ref = curPosMaster?.orgChild3 ?? null; + orgChild4Ref = curPosMaster?.orgChild4 ?? null; + if (curPosMaster && clearProfile.LeaveType != "RETIRE_OUT_EMP") { + await CreatePosMasterHistoryOfficer(curPosMaster.id, req, "DELETE"); + } + } + + //ลบตำแหน่ง + if (item.isLeave == true) { + await removeProfileInOrganize(profile.id, "OFFICER"); + } + if (clearProfile.status) { + if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) { + const delUserKeycloak = await deleteUser(profile.keycloak); + if (delUserKeycloak) { + // Task #228 + // profile.keycloak = _null; + profile.roleKeycloaks = []; + profile.isActive = false; + profile.isDelete = true; + } + } + profile.leaveCommandId = item.commandId ?? _null; + profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`; + profile.leaveRemark = clearProfile.leaveRemark ?? _null; + profile.leaveDate = item.commandDateAffect ?? _null; + profile.leaveType = clearProfile.LeaveType ?? _null; + //ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516) + // profile.position = _null; + // profile.posTypeId = _null; + // profile.posLevelId = _null; + } + + if (item.isGovernment == true) { + if (returnWork) { + //ปลดตำแหน่งเดิมที่ไม่ถูกปลดออกจากกิ่งครั้งเมื่อออกคำสั่งพักราชการหรือออกราชการไว้ + await removeProfileInOrganize(profile.id, "OFFICER"); + //ปั๊มตำแหน่งใหม่ + // หา posMaster และเช็ค orgRevisionIsCurrent + let posMaster = await this.posMasterRepository.findOne({ + where: { id: item.posmasterId?.toString() }, + relations: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }); + + // เช็คว่า posMaster ที่หามาอยู่ในโครงสร้างปัจจุบันหรือไม่ + const isCurrent = + posMaster?.orgRevision?.orgRevisionIsCurrent === true && + posMaster?.orgRevision?.orgRevisionIsDraft === false; + + // ถ้าไม่อยู่ในโครงสร้างปัจจุบัน ให้หาตัวใหม่จาก ancestorDNA + if (!isCurrent && posMaster?.ancestorDNA) { + posMaster = await this.posMasterRepository.findOne({ + where: { + ancestorDNA: posMaster.ancestorDNA, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }); + } + + if (posMaster) { + const checkPosition = await this.positionRepository.find({ + where: { + posMasterId: posMaster.id, + positionIsSelected: true, + }, + }); + if (checkPosition.length > 0) { + const clearPosition = checkPosition.map((positions) => ({ + ...positions, + positionIsSelected: false, + })); + await this.positionRepository.save(clearPosition); + } + posMaster.current_holderId = profile.id; + posMaster.lastUpdatedAt = new Date(); + // posMaster.conditionReason = _null; + // posMaster.isCondition = false; + await this.posMasterRepository.save(posMaster); + + // Match position ตามลำดับ priority: + // Condition 1: match จาก positionId + // Condition 2: match 7 ฟิลด์ (positionName, posTypeId, posLevelId, positionField, positionArea, positionExecutiveField, posExecutiveId) + // Condition 3: match 3 ฟิลด์ (positionName, posTypeId, posLevelId) + // Fallback: เลือก position แรกใน posMaster + + let positionNew: Position | null = null; + + // ═══════════════════════════════════════════════════════════ + // CONDITION 1: เช็คจาก positionId ตรง + // ═══════════════════════════════════════════════════════════ + if (item.positionId) { + const positionById = await this.positionRepository.findOne({ + where: { + id: item.positionId, + posMasterId: posMaster.id, // ต้องอยู่ใน posMaster ที่ถูกต้อง + }, + relations: ["posExecutive"], + }); + + if (positionById) { + positionNew = positionById; + } + } + + // ═══════════════════════════════════════════════════════════ + // CONDITION 2: Match 7 ฟิลด์ (ถ้า Condition 1 ไม่ match) + // ═══════════════════════════════════════════════════════════ + if (!positionNew && item.positionNameNew && item.positionTypeNew && item.positionLevelNew) { + // สร้าง where clause แบบ dynamic - ใส่เฉพาะฟิลด์ที่มีค่า + const whereCondition: any = { + posMasterId: posMaster.id, + positionName: item.positionNameNew, + posTypeId: item.positionTypeNew, + posLevelId: item.positionLevelNew, + }; + + if (item.positionField) { + whereCondition.positionField = item.positionField; + } + if (item.posExecutiveId) { + whereCondition.posExecutiveId = item.posExecutiveId; + } + if (item.positionExecutiveField) { + whereCondition.positionExecutiveField = item.positionExecutiveField; + } + if (item.positionArea) { + whereCondition.positionArea = item.positionArea; + } + + const positionBy7Fields = await this.positionRepository.findOne({ + where: whereCondition, + relations: ["posExecutive"], + order: { orderNo: "ASC" } + }); + + if (positionBy7Fields) { + positionNew = positionBy7Fields; + } + } + + // ═══════════════════════════════════════════════════════════ + // CONDITION 3: Match 3 ฟิลด์ (ถ้า Condition 2 ไม่ match) + // ═══════════════════════════════════════════════════════════ + if (!positionNew && item.positionNameNew && item.positionTypeNew && item.positionLevelNew) { + const positionBy3Fields = await this.positionRepository.findOne({ + where: { + posMasterId: posMaster.id, + positionName: item.positionNameNew, + posTypeId: item.positionTypeNew, + posLevelId: item.positionLevelNew, + }, + relations: ["posExecutive"], + order: { orderNo: "ASC" } + }); + + if (positionBy3Fields) { + positionNew = positionBy3Fields; + } + } + + // // FALLBACK: เลือก position แรก (ถ้าไม่เจอทั้ง 2 condition) + // if (!positionNew) { + // const fallbackPositions = await this.positionRepository.find({ + // where: { + // posMasterId: posMaster.id, + // }, + // relations: ["posExecutive"], + // order: { + // orderNo: "ASC", + // }, + // take: 1, + // }); + + // if (fallbackPositions.length > 0) { + // positionNew = fallbackPositions[0]; + // } + // } + + if (positionNew) { + positionNew.positionIsSelected = true; + await this.positionRepository.save(positionNew, { data: req }); + } + await CreatePosMasterHistoryOfficer(posMaster.id, req); + profile.posMasterNo = getPosMasterNo(posMaster); + profile.org = getOrgFullName(posMaster); + } + const newMapProfileSalary = { + profileId: profile.id, + commandId: item.commandId, + positionName: item.positionNameNew ?? null, + positionType: item.posTypeNameNew ?? null, + positionLevel: item.posLevelNameNew ?? null, + amount: item.amount ? item.amount : null, + positionSalaryAmount: item.positionSalaryAmount ? item.positionSalaryAmount : null, + amountSpecial: item.amountSpecial ? item.amountSpecial : null, + mouthSalaryAmount: item.mouthSalaryAmount ? item.mouthSalaryAmount : null, + posNo: item.posNoNew, + posNoAbb: item.posNoAbbNew, + orgRoot: item.orgRootNew, + orgChild1: item.orgChild1New, + orgChild2: item.orgChild2New, + orgChild3: item.orgChild3New, + orgChild4: item.orgChild4New, + isGovernment: item.isGovernment, + commandNo: item.commandNo, + commandYear: item.commandYear, + commandDateAffect: item.commandDateAffect, + commandDateSign: item.commandDateSign, + commandCode: item.commandCode, + commandName: item.commandName, + remark: item.remark, + }; + Object.assign(data, { ...newMapProfileSalary, ...meta }); + const history = new ProfileSalaryHistory(); + Object.assign(history, { ...data, id: undefined }); + await this.salaryRepo.save(data); + history.profileSalaryId = data.id; + await this.salaryHistoryRepo.save(history); + profile.leaveReason = _null; + profile.leaveCommandId = _null; + profile.leaveCommandNo = _null; + profile.leaveRemark = _null; + profile.leaveDate = _null; + profile.leaveType = _null; + profile.position = item.positionNameNew ?? _null; + profile.posTypeId = item.positionTypeNew ?? _null; + profile.posLevelId = item.positionLevelNew ?? _null; + } + let userKeycloakId; + const checkUser = await getUserByUsername(profile.citizenId); + //ถ้ายังไม่มี user keycloak ให้สร้างใหม่ + if (checkUser.length == 0) { + let password = profile.citizenId; + if (profile.birthDate != null) { + // const gregorianYear = profile.birthDate.getFullYear() + 543; + + // const formattedDate = + // profile.birthDate.toISOString().slice(8, 10) + + // profile.birthDate.toISOString().slice(5, 7) + + // gregorianYear; + // password = formattedDate; + const _date = new Date(profile.birthDate.toDateString()) + .getDate() + .toString() + .padStart(2, "0"); + const _month = (new Date(profile.birthDate.toDateString()).getMonth() + 1) + .toString() + .padStart(2, "0"); + const _year = new Date(profile.birthDate.toDateString()).getFullYear() + 543; + password = `${_date}${_month}${_year}`; + } + // กรอง "." ออกจาก firstName ก่อนส่งไป keycloak + const sanitizedFirstName = profile.firstName?.replace(/\./g, "") ?? ""; + userKeycloakId = await createUser(profile.citizenId, password, { + firstName: sanitizedFirstName, + lastName: profile.lastName, + }); + const list = await getRoles(); + let result = false; + if (Array.isArray(list) && userKeycloakId) { + result = await addUserRoles( + userKeycloakId, + list + .filter((v) => v.name === "USER") + .map((x) => ({ + id: x.id, + name: x.name, + })), + ); + } + profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : []; + profile.keycloak = + userKeycloakId && typeof userKeycloakId === "string" ? userKeycloakId : ""; + } + //ถ้ามีอยู่แล้วให้ใช้อันเดิม + else { + const rolesData = await getRoleMappings(checkUser[0].id); + if (rolesData) { + const _roleKeycloak = await this.roleKeycloakRepo.find({ + where: { name: In(rolesData.map((x: any) => x.name)) }, + }); + profile.roleKeycloaks = + _roleKeycloak && _roleKeycloak.length > 0 ? _roleKeycloak : []; + } + profile.keycloak = checkUser[0].id; + } + profile.amount = item.amount ?? _null; + profile.amountSpecial = item.amountSpecial ?? _null; + profile.isActive = true; + profile.isDelete = false; + } + await this.profileRepository.save(profile); + + // if (profile.id) { + // await this.keycloakAttributeService.clearOrgDnaAttributes( + // [profile.id], + // "PROFILE", + // ); + // } + + // update user attribute in keycloak + await updateUserAttributes(profile.keycloak ?? "", { + profileId: [profile.id], + prefix: [profile.prefix || ""], + }); + + // Task #2190 + if (code && ["C-PM-17", "C-PM-18", "C-PM-48"].includes(code)) { + let organizeName = ""; + if (orgRootRef) { + const names = [ + orgChild4Ref?.orgChild4Name, + orgChild3Ref?.orgChild3Name, + orgChild2Ref?.orgChild2Name, + orgChild1Ref?.orgChild1Name, + orgRootRef?.orgRootName, + ].filter(Boolean); + organizeName = names.join(" "); + } + + } + }), + ); + return new HttpSuccess(); } @@ -3745,10 +4793,212 @@ export class CommandController extends Controller { }[]; }, ) { - await new ExecuteSalaryEmployeeLeaveService().executeSalaryEmployeeLeave(body.data, { - user: { sub: req.user.sub, name: req.user.name }, - req, + let _posNumCodeSit: string = ""; + let _posNumCodeSitAbb: string = ""; + const _command = await this.commandRepository.findOne({ + where: { id: body.data.find((x) => x.commandId)?.commandId ?? "" }, + relations: { commandType: true }, }); + if (_command) { + if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { + const orgRootDeputy = await this.orgRootRepository.findOne({ + where: { + isDeputy: true, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: ["orgRevision"], + }); + _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; + _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; + } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { + _posNumCodeSit = "กรุงเทพมหานคร"; + _posNumCodeSitAbb = "กทม."; + } else { + let _profileAdmin = await this.profileRepository.findOne({ + where: { + keycloak: _command?.createdUserId.toString(), + current_holders: { + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + }, + relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], + }); + _posNumCodeSit = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? + ""; + _posNumCodeSitAbb = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot + .orgRootShortName ?? ""; + } + } + const today = new Date().setHours(0, 0, 0, 0); + await Promise.all( + body.data.map(async (item) => { + const profile = await this.profileEmployeeRepository.findOne({ + where: { id: item.profileId }, + // relations: ["roleKeycloaks"], + relations: { + roleKeycloaks: true, + posType: true, + posLevel: true, + }, + }); + if (!profile) { + throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); + } + const code = _command?.commandType?.code; + //ออกคำสั่งยกเลิกลาออก ลบเฉพาะคนที่ขอยกเลิกลาออก + if (item.resignId && code && ["C-PM-42"].includes(code)) { + const commandResign = await this.commandReciveRepository.findOne({ + where: { refId: item.resignId }, + relations: { command: true }, + }); + const executeDate = commandResign + ? new Date(commandResign.command.commandExcecuteDate).setHours(0, 0, 0, 0) + : today; + if ( + commandResign && + _command.status !== "REPORTED" && + (_command.status !== "WAITING" || today < executeDate) + ) { + await reOrderCommandRecivesAndDelete(commandResign!.id); + } + } + let _commandYear = item.commandYear; + if (item.commandYear) { + _commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543; + } + const dest_item = await this.salaryRepo.findOne({ + where: { profileEmployeeId: item.profileId }, + order: { order: "DESC" }, + }); + const before = null; + const data = new ProfileSalary(); + data.posNumCodeSit = _posNumCodeSit; + data.posNumCodeSitAbb = _posNumCodeSitAbb; + const meta = { + order: dest_item == null ? 1 : dest_item.order + 1, + createdUserId: req.user.sub, + createdFullName: req.user.name, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; + + Object.assign(data, { + ...item, + ...meta, + profileEmployeeId: item.profileId, + profileId: undefined, + }); + const history = new ProfileSalaryHistory(); + Object.assign(history, { ...data, id: undefined }); + data.dateGovernment = item.commandDateAffect ?? meta.createdAt; + await this.salaryRepo.save(data, { data: req }); + setLogDataDiff(req, { before, after: data }); + history.profileSalaryId = data.id; + await this.salaryHistoryRepo.save(history, { data: req }); + + const _null: any = null; + profile.isLeave = item.isLeave; + profile.leaveReason = item.leaveReason ?? _null; + profile.dateLeave = item.dateLeave ?? _null; + profile.lastUpdateUserId = req.user.sub; + profile.lastUpdateFullName = req.user.name; + profile.lastUpdatedAt = new Date(); + // บันทึกประวัติก่อนลบตำแหน่ง + const clearProfile = await checkCommandType(String(item.commandId)); + const curRevision = await this.orgRevisionRepo.findOne({ + where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + }); + let orgRootRef = null; + let orgChild1Ref = null; + let orgChild2Ref = null; + let orgChild3Ref = null; + let orgChild4Ref = null; + if (curRevision) { + const curPosMaster = await this.employeePosMasterRepository.findOne({ + where: { + current_holderId: profile.id, + orgRevisionId: curRevision.id, + }, + relations: { + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }); + orgRootRef = curPosMaster?.orgRoot ?? null; + orgChild1Ref = curPosMaster?.orgChild1 ?? null; + orgChild2Ref = curPosMaster?.orgChild2 ?? null; + orgChild3Ref = curPosMaster?.orgChild3 ?? null; + orgChild4Ref = curPosMaster?.orgChild4 ?? null; + if (curPosMaster) { + await CreatePosMasterHistoryEmployee(curPosMaster.id, req, "DELETE"); + } + } + + // ลบตำแหน่ง + if (item.isLeave == true) { + await removeProfileInOrganize(profile.id, "EMPLOYEE"); + } + + if (clearProfile.status) { + if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) { + const delUserKeycloak = await deleteUser(profile.keycloak); + if (delUserKeycloak) { + // Task #228 + // profile.keycloak = _null; + profile.roleKeycloaks = []; + profile.isActive = false; + profile.isDelete = true; + } + } + profile.leaveCommandId = item.commandId ?? _null; + profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`; + profile.leaveRemark = clearProfile.leaveRemark ?? _null; + profile.leaveDate = item.commandDateAffect ?? _null; + profile.leaveType = clearProfile.LeaveType ?? _null; + //ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516) + // profile.position = _null; + // profile.posTypeId = _null; + // profile.posLevelId = _null; + } + await this.profileEmployeeRepository.save(profile); + + // if (profile.id) { + // await this.keycloakAttributeService.clearOrgDnaAttributes( + // [profile.id], + // "PROFILE_EMPLOYEE", + // ); + // } + + // Task #2190 + if (code && ["C-PM-23", "C-PM-43"].includes(code)) { + let organizeName = ""; + if (orgRootRef) { + const names = [ + orgChild4Ref?.orgChild4Name, + orgChild3Ref?.orgChild3Name, + orgChild2Ref?.orgChild2Name, + orgChild1Ref?.orgChild1Name, + orgRootRef?.orgRootName, + ].filter(Boolean); + organizeName = names.join(" "); + } + } + }), + ); + return new HttpSuccess(); } @@ -3794,10 +5044,221 @@ export class CommandController extends Controller { }[]; }, ) { - await new ExecuteSalaryService().executeSalary(body.data, { - user: { sub: req.user.sub, name: req.user.name }, - req, + let _posNumCodeSit: string = ""; + let _posNumCodeSitAbb: string = ""; + const _command = await this.commandRepository.findOne({ + relations: ["commandType"], + where: { id: body.data.find((x) => x.commandId)?.commandId ?? "" }, }); + if (_command) { + if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { + const orgRootDeputy = await this.orgRootRepository.findOne({ + where: { + isDeputy: true, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: ["orgRevision"], + }); + _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; + _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; + } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { + _posNumCodeSit = "กรุงเทพมหานคร"; + _posNumCodeSitAbb = "กทม."; + } else { + let _profileAdmin = await this.profileRepository.findOne({ + where: { + keycloak: _command?.createdUserId.toString(), + current_holders: { + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + }, + relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], + }); + _posNumCodeSit = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? + ""; + _posNumCodeSitAbb = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot + .orgRootShortName ?? ""; + } + } + await Promise.all( + body.data.map(async (item) => { + const profile: any = await this.profileRepository.findOne({ + where: { id: item.profileId }, + // relations: ["roleKeycloaks"], + relations: { + roleKeycloaks: true, + posType: true, + posLevel: true, + }, + }); + if (!profile) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); + } + const posMaster: any = await this.posMasterRepository.findOne({ + where: { + current_holderId: item.profileId, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }); + + const orgRevisionRef = posMaster ? posMaster.id : null; + const orgRootRef = orgRevisionRef?.orgRoot ?? null; + const orgChild1Ref = orgRevisionRef?.orgChild1 ?? null; + const orgChild2Ref = orgRevisionRef?.orgChild2 ?? null; + const orgChild3Ref = orgRevisionRef?.orgChild3 ?? null; + const orgChild4Ref = orgRevisionRef?.orgChild4 ?? null; + + //ลบตำแหน่งที่รักษาการแทน + const code = _command?.commandType?.code; + if (code && ["C-PM-13"].includes(code)) { + removePostMasterAct(profile.id); + } + + let _commandYear = item.commandYear; + if (item.commandYear) { + _commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543; + } + const dest_item = await this.salaryRepo.findOne({ + where: { profileId: item.profileId }, + order: { order: "DESC" }, + }); + const before = null; + const data = new ProfileSalary(); + data.posNumCodeSit = _posNumCodeSit; + data.posNumCodeSitAbb = _posNumCodeSitAbb; + const meta = { + order: dest_item == null ? 1 : dest_item.order + 1, + createdUserId: req.user.sub, + createdFullName: req.user.name, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; + if (item.isLeave != undefined && item.isLeave == true) { + await CreatePosMasterHistoryOfficer(orgRevisionRef, req, "DELETE"); + await removeProfileInOrganize(profile.id, "OFFICER"); + } + const clearProfile = await checkCommandType(String(item.commandId)); + const _null: any = null; + if (clearProfile.status) { + if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) { + const delUserKeycloak = await deleteUser(profile.keycloak); + if (delUserKeycloak) { + // Task #228 + // profile.keycloak = _null; + profile.roleKeycloaks = []; + profile.isActive = false; + profile.isDelete = true; + } + } + profile.isLeave = item.isLeave; + profile.leaveCommandId = item.commandId ?? _null; + profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`; + profile.leaveRemark = clearProfile.leaveRemark ?? _null; + profile.leaveDate = item.commandDateAffect ?? _null; + profile.leaveType = clearProfile.LeaveType ?? _null; + //ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516) + // profile.position = _null; + // profile.posTypeId = _null; + // profile.posLevelId = _null; + profile.leaveReason = item.leaveReason ?? _null; + profile.dateLeave = item.dateLeave ?? _null; + profile.amount = item.amount ?? _null; + profile.amountSpecial = item.amountSpecial ?? _null; + await this.profileRepository.save(profile, { data: req }); + + // if (profile.id) { + // await this.keycloakAttributeService.clearOrgDnaAttributes( + // [profile.id], + // "PROFILE", + // ); + // } + } + Object.assign(data, { ...item, ...meta }); + const history = new ProfileSalaryHistory(); + Object.assign(history, { ...data, id: undefined }); + + await this.salaryRepo.save(data, { data: req }); + setLogDataDiff(req, { before, after: data }); + history.profileSalaryId = data.id; + await this.salaryHistoryRepo.save(history, { data: req }); + + if (_command) { + /* + const command = await this.commandRepository.findOne({ + where: { id: item.commandId }, + relations: ["commandType"], + }); + */ + if (["C-PM-15", "C-PM-16"].includes(_command.commandType.code)) { + // ประวัติคำสั่งให้ช่วยราชการ + const dataAssis = new ProfileAssistance(); + + const metaAssis = { + profileId: item.profileId, + agency: item.officerOrg, + dateStart: item.dateStart, + dateEnd: item.dateEnd, + commandNo: `${item.commandNo}/${_commandYear}`, + commandName: item.commandName, + refId: item.refId, + refCommandDate: new Date(), + commandId: item.commandId, + createdUserId: req.user.sub, + createdFullName: req.user.name, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + status: _command.commandType.code == "C-PM-15" ? "PENDING" : "DONE", + }; + + Object.assign(dataAssis, metaAssis); + const historyAssis = new ProfileAssistanceHistory(); + Object.assign(historyAssis, { ...dataAssis, id: undefined }); + + await this.assistanceRepository.save(dataAssis); + historyAssis.profileAssistanceId = dataAssis.id; + await this.assistanceHistoryRepository.save(historyAssis); + } + // Task #2190 + else if (_command.commandType.code == "C-PM-13") { + let organizeName = ""; + if (orgRootRef) { + const names = [ + orgChild4Ref?.orgChild4Name, + orgChild3Ref?.orgChild3Name, + orgChild2Ref?.orgChild2Name, + orgChild1Ref?.orgChild1Name, + orgRootRef?.orgRootName, + ].filter(Boolean); + organizeName = names.join(" "); + } + } + } + }), + ); + return new HttpSuccess(); } @@ -3919,14 +5380,6 @@ export class CommandController extends Controller { return new HttpSuccess(); } - /** - * API สร้าง ProfileSalary + ProfileDiscipline + handle leave ของคำสั่งวินัย - * - * Thin wrapper — เรียก ExecuteSalaryLeaveDisciplineService (C-PM-19, 20, 25, 26, 27, 28, 29, 30, 31, 32) - * ทั้ง endpoint นี้และ consumer ใน rabbitmq ใช้ service ตัวเดียวกัน (Linear Flow) - * - * @summary API คำสั่งวินัย (ข้าราชการ/ลูกจ้าง) + leave - */ @Post("excexute/salary-leave-discipline") public async newSalaryAndUpdateLeaveDiscipline( @Request() req: RequestWithUser, @@ -3964,10 +5417,472 @@ export class CommandController extends Controller { }[]; }, ) { - await new ExecuteSalaryLeaveDisciplineService().executeSalaryLeaveDiscipline(body.data, { - user: { sub: req.user.sub, name: req.user.name }, - req, + let _posNumCodeSit: string = ""; + let _posNumCodeSitAbb: string = ""; + const _command = await this.commandRepository.findOne({ + relations: ["commandType"], + where: { id: body.data.find((x) => x.commandId)?.commandId ?? "" }, }); + if (_command) { + if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { + const orgRootDeputy = await this.orgRootRepository.findOne({ + where: { + isDeputy: true, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: ["orgRevision"], + }); + _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; + _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; + } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { + _posNumCodeSit = "กรุงเทพมหานคร"; + _posNumCodeSitAbb = "กทม."; + } else { + let _profileAdmin = await this.profileRepository.findOne({ + where: { + keycloak: _command?.createdUserId.toString(), + current_holders: { + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + }, + relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], + }); + _posNumCodeSit = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? + ""; + _posNumCodeSitAbb = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot + .orgRootShortName ?? ""; + } + } + await Promise.all( + body.data.map(async (item) => { + let _commandYear = item.commandYear; + if (item.commandYear) { + _commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543; + } + const orgRevision = await this.orgRevisionRepo.findOne({ + where: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }); + let orgRootRef = null; + let orgChild1Ref = null; + let orgChild2Ref = null; + let orgChild3Ref = null; + let orgChild4Ref = null; + let profile; + let isEmployee: boolean = false; + let retireTypeName: string = ""; + // ขรก. + if (item.profileType && item.profileType.trim().toUpperCase() == "OFFICER") { + profile = await this.profileRepository.findOne({ + relations: [ + // "profileSalary", + "posLevel", + "posType", + "current_holders", + "current_holders.orgRoot", + "current_holders.orgChild1", + "current_holders.orgChild2", + "current_holders.orgChild3", + "current_holders.orgChild4", + "current_holders.positions", + "current_holders.positions.posExecutive", + "roleKeycloaks", + ], + where: { id: item.profileId }, + // order: { + // profileSalary: { + // order: "DESC", + // }, + // }, + }); + if (!profile) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); + } + const lastSalary = await this.salaryRepo.findOne({ + where: { profileId: item.profileId }, + select: ["order"], + order: { order: "DESC" }, + }); + const nextOrder = lastSalary ? lastSalary.order + 1 : 1; + //ลบตำแหน่งที่รักษาการแทน + const code = _command?.commandType?.code; + if (code && ["C-PM-19", "C-PM-20"].includes(code)) { + removePostMasterAct(profile.id); + } + + const orgRevisionRef = + profile?.current_holders?.find((x) => x.orgRevisionId == orgRevision?.id) ?? null; + orgRootRef = orgRevisionRef?.orgRoot ?? null; + orgChild1Ref = orgRevisionRef?.orgChild1 ?? null; + orgChild2Ref = orgRevisionRef?.orgChild2 ?? null; + orgChild3Ref = orgRevisionRef?.orgChild3 ?? null; + orgChild4Ref = orgRevisionRef?.orgChild4 ?? null; + + let position = + profile.current_holders + .filter((x) => x.orgRevisionId == orgRevision?.id)[0] + ?.positions?.filter((pos) => pos.positionIsSelected === true)[0] ?? null; + // ประวัติตำแหน่ง + const data = new ProfileSalary(); + data.posNumCodeSit = _posNumCodeSit; + data.posNumCodeSitAbb = _posNumCodeSitAbb; + const meta = { + profileId: profile.id, + commandId: item.commandId, + position: profile.position, + positionName: profile.position, + positionType: profile?.posType?.posTypeName ?? null, + positionLevel: profile?.posLevel?.posLevelName ?? null, + positionExecutive: position?.posExecutive?.posExecutiveName ?? null, + amount: item.amount ? item.amount : null, + positionSalaryAmount: item.positionSalaryAmount ? item.positionSalaryAmount : null, + amountSpecial: item.amountSpecial ? item.amountSpecial : null, + mouthSalaryAmount: item.mouthSalaryAmount ? item.mouthSalaryAmount : null, + // order: + // profile.profileSalary.length >= 0 + // ? profile.profileSalary.length > 0 + // ? profile.profileSalary[0].order + 1 + // : 1 + // : null, + order: nextOrder, + orgRoot: item.orgRoot, + orgChild1: item.orgChild1, + orgChild2: item.orgChild2, + orgChild3: item.orgChild3, + orgChild4: item.orgChild4, + createdUserId: req.user.sub, + createdFullName: req.user.name, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + dateGovernment: item.commandDateAffect ?? new Date(), + isGovernment: item.isGovernment, + commandNo: item.commandNo, + commandYear: item.commandYear, + posNo: item.posNo, + posNoAbb: item.posNoAbb, + commandDateAffect: item.commandDateAffect, + commandDateSign: item.commandDateSign, + commandCode: item.commandCode, + commandName: item.commandName, + remark: item.remark, + }; + + Object.assign(data, meta); + const history = new ProfileSalaryHistory(); + Object.assign(history, { ...data, id: undefined }); + + await this.salaryRepo.save(data); + history.profileSalaryId = data.id; + await this.salaryHistoryRepo.save(history); + + // ประวัติวินัย + const dataDis = new ProfileDiscipline(); + + const metaDis = { + date: item.commandDateAffect, + refCommandDate: item.commandDateSign, + refCommandNo: `${item.commandNo}/${item.commandYear}`, + refCommandId: item.commandId, + createdUserId: req.user.sub, + createdFullName: req.user.name, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; + + Object.assign(dataDis, { ...item, ...metaDis }); + const historyDis = new ProfileDisciplineHistory(); + Object.assign(historyDis, { ...dataDis, id: undefined }); + + await this.disciplineRepository.save(dataDis); + historyDis.profileDisciplineId = dataDis.id; + await this.disciplineHistoryRepository.save(historyDis); + + // ทะเบียนประวัติ + if (item.isLeave != null) { + const _profile = await this.profileRepository.findOne({ + where: { id: item.profileId }, + relations: ["roleKeycloaks"], + }); + if (!_profile) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); + } + const _null: any = null; + _profile.isLeave = item.isLeave; + _profile.leaveReason = item.leaveReason ?? _null; + _profile.dateLeave = item.dateLeave ?? _null; + _profile.lastUpdateUserId = req.user.sub; + _profile.lastUpdateFullName = req.user.name; + _profile.lastUpdatedAt = new Date(); + if (item.isLeave == true) { + if (orgRevisionRef) { + await CreatePosMasterHistoryOfficer(orgRevisionRef.id, req, "DELETE"); + } + await removeProfileInOrganize(_profile.id, "OFFICER"); + } + const clearProfile = await checkCommandType(String(item.commandId)); + if (clearProfile.status) { + retireTypeName = clearProfile.retireTypeName ?? ""; + if ( + _profile.keycloak != null && + _profile.keycloak != "" && + _profile.isDelete === false + ) { + const delUserKeycloak = await deleteUser(_profile.keycloak); + if (delUserKeycloak) { + // Task #228 + // _profile.keycloak = _null; + _profile.roleKeycloaks = []; + _profile.isActive = false; + _profile.isDelete = true; + } + } + _profile.leaveCommandId = item.commandId ?? _null; + _profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`; + _profile.leaveRemark = clearProfile.leaveRemark ?? _null; + _profile.leaveDate = item.commandDateAffect ?? _null; + _profile.leaveType = clearProfile.LeaveType ?? _null; + //ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516) + // _profile.position = _null; + // _profile.posTypeId = _null; + // _profile.posLevelId = _null; + } + await this.profileRepository.save(_profile); + // if (_profile.id) { + // await this.keycloakAttributeService.clearOrgDnaAttributes( + // [_profile.id], + // "PROFILE", + // ); + // } + } + } + // ลูกจ้าง + else { + isEmployee = true; + profile = await this.profileEmployeeRepository.findOne({ + relations: [ + // "profileSalary", + "posLevel", + "posType", + "current_holders", + "current_holders.orgRoot", + "current_holders.orgChild1", + "current_holders.orgChild2", + "current_holders.orgChild3", + "current_holders.orgChild4", + "roleKeycloaks", + ], + where: { id: item.profileId }, + // order: { + // profileSalary: { + // order: "DESC", + // }, + // }, + }); + if (!profile) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); + } + const lastSalary = await this.salaryRepo.findOne({ + where: { profileEmployeeId: item.profileId }, + select: ["order"], + order: { order: "DESC" }, + }); + const nextOrder = lastSalary ? lastSalary.order + 1 : 1; + const orgRevisionRef = + profile?.current_holders?.find((x) => x.orgRevisionId == orgRevision?.id) ?? null; + orgRootRef = orgRevisionRef?.orgRoot ?? null; + orgChild1Ref = orgRevisionRef?.orgChild1 ?? null; + orgChild2Ref = orgRevisionRef?.orgChild2 ?? null; + orgChild3Ref = orgRevisionRef?.orgChild3 ?? null; + orgChild4Ref = orgRevisionRef?.orgChild4 ?? null; + + // ประวัติตำแหน่ง + const data = new ProfileSalary(); + data.posNumCodeSit = _posNumCodeSit; + data.posNumCodeSitAbb = _posNumCodeSitAbb; + const meta = { + profileEmployeeId: profile.id, + commandId: item.commandId, + position: profile.position, + positionName: profile.position, + positionType: profile?.posType?.posTypeName ?? null, + positionLevel: + profile?.posType && profile?.posLevel + ? `${profile?.posType?.posTypeShortName} ${profile?.posLevel?.posLevelName}` + : null, + amount: item.amount ? item.amount : null, + positionSalaryAmount: item.positionSalaryAmount ? item.positionSalaryAmount : null, + mouthSalaryAmount: item.mouthSalaryAmount ? item.mouthSalaryAmount : null, + // order: + // profile.profileSalary.length >= 0 + // ? profile.profileSalary.length > 0 + // ? profile.profileSalary[0].order + 1 + // : 1 + // : null, + order: nextOrder, + orgRoot: item.orgRoot, + orgChild1: item.orgChild1, + orgChild2: item.orgChild2, + orgChild3: item.orgChild3, + orgChild4: item.orgChild4, + createdUserId: req.user.sub, + createdFullName: req.user.name, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + dateGovernment: item.commandDateAffect ?? new Date(), + isGovernment: item.isGovernment, + commandNo: item.commandNo, + commandYear: item.commandYear, + posNo: item.posNo, + posNoAbb: item.posNoAbb, + commandDateAffect: item.commandDateAffect, + commandDateSign: item.commandDateSign, + commandCode: item.commandCode, + commandName: item.commandName, + remark: item.remark, + }; + + Object.assign(data, meta); + const history = new ProfileSalaryHistory(); + Object.assign(history, { ...data, id: undefined }); + + await this.salaryRepo.save(data); + history.profileSalaryId = data.id; + await this.salaryHistoryRepo.save(history); + + // ประวัติวินัย + const dataDis = new ProfileDiscipline(); + + const metaDis = { + createdUserId: req.user.sub, + createdFullName: req.user.name, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; + + Object.assign(dataDis, { + ...item, + ...metaDis, + date: item.commandDateAffect, + refCommandDate: item.commandDateSign, + refCommandNo: item.commandNo, + profileEmployeeId: item.profileId, + profileId: undefined, + }); + const historyDis = new ProfileDisciplineHistory(); + Object.assign(historyDis, { ...dataDis, id: undefined }); + + await this.disciplineRepository.save(dataDis); + historyDis.profileDisciplineId = dataDis.id; + await this.disciplineHistoryRepository.save(historyDis); + + // ทะเบียนประวัติ + if (item.isLeave != null) { + const _profile = await this.profileEmployeeRepository.findOne({ + where: { id: item.profileId }, + relations: ["roleKeycloaks"], + }); + if (!_profile) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); + } + const _null: any = null; + _profile.isLeave = item.isLeave; + _profile.leaveReason = item.leaveReason ?? _null; + _profile.dateLeave = item.dateLeave ?? _null; + _profile.lastUpdateUserId = req.user.sub; + _profile.lastUpdateFullName = req.user.name; + _profile.lastUpdatedAt = new Date(); + if (item.isLeave == true) { + // บันทึกประวัติก่อนลบตำแหน่ง + const curRevision = await this.orgRevisionRepo.findOne({ + where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + }); + if (curRevision) { + const curPosMaster = await this.employeePosMasterRepository.findOne({ + where: { + current_holderId: _profile.id, + orgRevisionId: curRevision.id, + }, + }); + if (curPosMaster) { + await CreatePosMasterHistoryEmployee(curPosMaster.id, req, "DELETE"); + } + } + await removeProfileInOrganize(_profile.id, "EMPLOYEE"); + } + const clearProfile = await checkCommandType(String(item.commandId)); + if (clearProfile.status) { + retireTypeName = clearProfile.retireTypeName ?? ""; + if ( + _profile.keycloak != null && + _profile.keycloak != "" && + _profile.isDelete === false + ) { + const delUserKeycloak = await deleteUser(_profile.keycloak); + if (delUserKeycloak) { + // Task #228 + // _profile.keycloak = _null; + _profile.roleKeycloaks = []; + _profile.isActive = false; + _profile.isDelete = true; + } + } + _profile.leaveCommandId = item.commandId ?? _null; + _profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`; + _profile.leaveRemark = clearProfile.leaveRemark ?? _null; + _profile.leaveDate = item.commandDateAffect ?? _null; + _profile.leaveType = clearProfile.LeaveType ?? _null; + //ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516) + // _profile.position = _null; + // _profile.posTypeId = _null; + // _profile.posLevelId = _null; + } + await this.profileEmployeeRepository.save(_profile); + // if (_profile.id) { + // await this.keycloakAttributeService.clearOrgDnaAttributes( + // [_profile.id], + // "PROFILE_EMPLOYEE", + // ); + // } + } + } + // Task #2190 + if (_command && ["C-PM-19", "C-PM-20"].includes(_command.commandType.code)) { + let organizeName = ""; + if (orgRootRef) { + const names = [ + orgChild4Ref?.orgChild4Name, + orgChild3Ref?.orgChild3Name, + orgChild2Ref?.orgChild2Name, + orgChild1Ref?.orgChild1Name, + orgRootRef?.orgRootName, + ].filter(Boolean); + organizeName = names.join(" "); + } + let _posLevelName: string = !isEmployee + ? `${profile.posLevel?.posLevelName}` + : `${profile.posType?.posTypeName} ${profile.posLevel?.posLevelName}`; + } + }), + ); + return new HttpSuccess(); } @@ -4314,10 +6229,170 @@ export class CommandController extends Controller { }[]; }, ) { - await new ExecuteSalaryProbationService().executeProbationPass(body.data, { - user: { sub: req.user.sub, name: req.user.name }, - req, + let _posNumCodeSit: string = ""; + let _posNumCodeSitAbb: string = ""; + let commandType: any = ""; + const _command = await this.commandRepository.findOne({ + where: { id: body.data.find((x) => x.commandId)?.commandId ?? "" }, }); + if (_command) { + commandType = await this.commandTypeRepository.findOne({ + select: { code: true }, + where: { id: _command.commandTypeId }, + }); + if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { + const orgRootDeputy = await this.orgRootRepository.findOne({ + where: { + isDeputy: true, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: ["orgRevision"], + }); + _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; + _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; + } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { + _posNumCodeSit = "กรุงเทพมหานคร"; + _posNumCodeSitAbb = "กทม."; + } else { + let _profileAdmin = await this.profileRepository.findOne({ + where: { + keycloak: _command?.createdUserId.toString(), + current_holders: { + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + }, + relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], + }); + _posNumCodeSit = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? + ""; + _posNumCodeSitAbb = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot + .orgRootShortName ?? ""; + } + } + // const leaveType = await this.leaveType.findOne({ + // select: { id: true, limit: true, code: true }, + // where: { code: "LV-005" } + // }); + await Promise.all( + body.data.map(async (item) => { + const profile = await this.profileRepository.findOne({ + relations: [ + "posType", + "posLevel", + "current_holders", + "current_holders.orgRoot", + "current_holders.orgChild1", + "current_holders.orgChild2", + "current_holders.orgChild3", + "current_holders.orgChild4", + ], + where: { id: item.profileId }, + }); + if (!profile) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); + } + const lastSalary = await this.salaryRepo.findOne({ + where: { profileId: item.profileId }, + select: ["order"], + order: { order: "DESC" }, + }); + const nextOrder = lastSalary ? lastSalary.order + 1 : 1; + const orgRevision = await this.orgRevisionRepo.findOne({ + where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + }); + + const orgRevisionRef = + profile?.current_holders?.find((x) => x.orgRevisionId == orgRevision?.id) ?? null; + const shortName = + orgRevisionRef?.orgChild4?.orgChild4ShortName ?? + orgRevisionRef?.orgChild3?.orgChild3ShortName ?? + orgRevisionRef?.orgChild2?.orgChild2ShortName ?? + orgRevisionRef?.orgChild1?.orgChild1ShortName ?? + orgRevisionRef?.orgRoot?.orgRootShortName ?? + null; + const posNo = orgRevisionRef?.posMasterNo?.toString() ?? null; + let position = + profile.current_holders + .filter((x) => x.orgRevisionId == orgRevision?.id)[0] + ?.positions?.filter((pos) => pos.positionIsSelected === true)[0] ?? null; + // ประวัติตำแหน่ง + const data = new ProfileSalary(); + data.posNumCodeSit = _posNumCodeSit; + data.posNumCodeSitAbb = _posNumCodeSitAbb; + const meta = { + profileId: item.profileId, + commandId: item.commandId, + positionName: profile.position, + positionType: profile?.posType?.posTypeName ?? null, + positionLevel: profile?.posLevel?.posLevelName ?? null, + positionExecutive: position?.posExecutive?.posExecutiveName ?? null, + amount: item.amount ? item.amount : null, + amountSpecial: item.amountSpecial ? item.amountSpecial : null, + positionSalaryAmount: item.positionSalaryAmount ? item.positionSalaryAmount : null, + mouthSalaryAmount: item.mouthSalaryAmount ? item.mouthSalaryAmount : null, + order: nextOrder, + orgRoot: orgRevisionRef?.orgRoot?.orgRootName ?? null, + orgChild1: orgRevisionRef?.orgChild1?.orgChild1Name ?? null, + orgChild2: orgRevisionRef?.orgChild2?.orgChild2Name ?? null, + orgChild3: orgRevisionRef?.orgChild3?.orgChild3Name ?? null, + orgChild4: orgRevisionRef?.orgChild4?.orgChild4Name ?? null, + createdUserId: req.user.sub, + createdFullName: req.user.name, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + commandNo: item.commandNo, + commandYear: item.commandYear, + posNo: posNo, + posNoAbb: shortName, + commandDateAffect: item.commandDateAffect, + commandDateSign: item.commandDateSign, + commandCode: item.commandCode, + commandName: item.commandName, + remark: item.remark, + }; + Object.assign(data, meta); + const history = new ProfileSalaryHistory(); + Object.assign(history, { ...data, id: undefined }); + + await this.salaryRepo.save(data); + history.profileSalaryId = data.id; + await this.salaryHistoryRepo.save(history); + }), + ); + + if (commandType && String(commandType.code) == "C-PM-11") { + const profileIds = body.data.map((x) => x.profileId); + await this.profileRepository.update({ id: In(profileIds) }, { isProbation: false }); + // // Task #2304 อัปเดตจำนวนสิทธิ์การลา เมื่อผ่านทดลองงานฯ + // if (leaveType != null) { + // await Promise.all( + // body.data.map((item) => + // new CallAPI().PutData(req, `/leave-beginning/schedule`, { + // profileId: item.profileId, + // leaveTypeId: leaveType.id, + // leaveYear: item.commandYear, + // leaveDays: leaveType.limit, + // leaveDaysUsed: 0, + // leaveCount: 0, + // beginningLeaveDays: 0, + // beginningLeaveCount: 0, + // }) + // .then(() => {}) + // .catch(() => {}) + // ) + // ); + // } + } return new HttpSuccess(); } @@ -4344,17 +6419,248 @@ export class CommandController extends Controller { }[]; }, ) { - await new ExecuteSalaryProbationService().executeProbationLeave(body.data, { - user: { sub: req.user.sub, name: req.user.name }, - req, + let _posNumCodeSit: string = ""; + let _posNumCodeSitAbb: string = ""; + const _command = await this.commandRepository.findOne({ + where: { id: body.data.find((x) => x.commandId)?.commandId ?? "" }, }); + if (_command) { + if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { + const orgRootDeputy = await this.orgRootRepository.findOne({ + where: { + isDeputy: true, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: ["orgRevision"], + }); + _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; + _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; + } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { + _posNumCodeSit = "กรุงเทพมหานคร"; + _posNumCodeSitAbb = "กทม."; + } else { + let _profileAdmin = await this.profileRepository.findOne({ + where: { + keycloak: _command?.createdUserId.toString(), + current_holders: { + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + }, + relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], + }); + _posNumCodeSit = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? + ""; + _posNumCodeSitAbb = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot + .orgRootShortName ?? ""; + } + } + await Promise.all( + body.data.map(async (item) => { + const profile = await this.profileRepository.findOne({ + relations: [ + // "profileSalary", + "posType", + "posLevel", + "current_holders", + "current_holders.orgRoot", + "current_holders.orgChild1", + "current_holders.orgChild2", + "current_holders.orgChild3", + "current_holders.orgChild4", + "current_holders.positions", + "current_holders.positions.posExecutive", + ], + where: { id: item.profileId }, + // order: { + // profileSalary: { + // order: "DESC", + // }, + // }, + }); + if (!profile) { + throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลโปรไฟล์"); + } + const lastSalary = await this.salaryRepo.findOne({ + where: { profileId: item.profileId }, + select: ["order"], + order: { order: "DESC" }, + }); + const nextOrder = lastSalary ? lastSalary.order + 1 : 1; + let _commandYear = item.commandYear; + if (item.commandYear) { + _commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543; + } + const _profile = await this.profileRepository.findOne({ + where: { id: item.profileId }, + relations: ["roleKeycloaks"], + }); + if (!_profile) { + throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลโปรไฟล์"); + } + let dateLeave_: any = item.commandDateAffect; + _profile.isLeave = true; + _profile.leaveReason = + "คำสั่งให้ข้าราชการออกจากราชการเพราะผลการทดลองปฏิบัติหน้าที่ราชการต่ำกว่ามาตรฐานที่กำหนด"; + _profile.dateLeave = dateLeave_; + _profile.lastUpdateUserId = req.user.sub; + _profile.lastUpdateFullName = req.user.name; + _profile.lastUpdatedAt = new Date(); + + const orgRevision = await this.orgRevisionRepo.findOne({ + where: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }); + const orgRevisionRef = + profile?.current_holders?.find((x) => x.orgRevisionId == orgRevision?.id) ?? null; + const orgRootRef = orgRevisionRef?.orgRoot ?? null; + const orgChild1Ref = orgRevisionRef?.orgChild1 ?? null; + const orgChild2Ref = orgRevisionRef?.orgChild2 ?? null; + const orgChild3Ref = orgRevisionRef?.orgChild3 ?? null; + const orgChild4Ref = orgRevisionRef?.orgChild4 ?? null; + const shortName = + !profile.current_holders || profile.current_holders.length == 0 + ? null + : profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) != null && + profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) + ?.orgChild4 != null + ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild4.orgChild4ShortName}` + : profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) != null && + profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) + ?.orgChild3 != null + ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild3.orgChild3ShortName}` + : profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) != null && + profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) + ?.orgChild2 != null + ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild2.orgChild2ShortName}` + : profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) != + null && + profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) + ?.orgChild1 != null + ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild1.orgChild1ShortName}` + : profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) != + null && + profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) + ?.orgRoot != null + ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgRoot.orgRootShortName}` + : null; + const posNo = `${profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.posMasterNo}`; + let position = + profile.current_holders + .filter((x) => x.orgRevisionId == orgRevision?.id)[0] + ?.positions?.filter((pos) => pos.positionIsSelected === true)[0] ?? null; + const profileSalary: ProfileSalary = Object.assign(new ProfileSalary(), { + profileId: item.profileId, + commandId: item.commandId, + positionName: profile.position, + positionType: profile?.posType?.posTypeName ?? null, + positionLevel: profile?.posLevel?.posLevelName ?? null, + positionExecutive: position?.posExecutive?.posExecutiveName ?? null, + amount: item.amount ? item.amount : null, + amountSpecial: item.amountSpecial ? item.amountSpecial : null, + positionSalaryAmount: item.positionSalaryAmount ? item.positionSalaryAmount : null, + mouthSalaryAmount: item.mouthSalaryAmount ? item.mouthSalaryAmount : null, + // order: + // profile.profileSalary.length >= 0 + // ? profile.profileSalary.length > 0 + // ? profile.profileSalary[0].order + 1 + // : 1 + // : null, + order: nextOrder, + orgRoot: orgRootRef?.orgRootName ?? null, + orgChild1: orgChild1Ref?.orgChild1Name ?? null, + orgChild2: orgChild2Ref?.orgChild2Name ?? null, + orgChild3: orgChild3Ref?.orgChild3Name ?? null, + orgChild4: orgChild4Ref?.orgChild4Name ?? null, + createdUserId: req.user.sub, + createdFullName: req.user.name, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + dateGovernment: item.commandDateAffect ?? new Date(), + isGovernment: item.isGovernment, + commandNo: item.commandNo, + commandYear: item.commandYear, + posNo: posNo, + posNoAbb: shortName, + commandDateAffect: item.commandDateAffect, + commandDateSign: item.commandDateSign, + commandCode: item.commandCode, + commandName: item.commandName, + remark: item.remark, + posNumCodeSit: _posNumCodeSit, + posNumCodeSitAbb: _posNumCodeSitAbb, + }); + if (orgRevisionRef) { + await CreatePosMasterHistoryOfficer(orgRevisionRef.id, req, "DELETE"); + } + await removeProfileInOrganize(profile.id, "OFFICER"); + const clearProfile = await checkCommandType(String(item.commandId)); + const _null: any = null; + if (clearProfile.status) { + if (_profile.keycloak != null && _profile.keycloak != "" && _profile.isDelete === false) { + const delUserKeycloak = await deleteUser(_profile.keycloak); + if (delUserKeycloak) { + // Task #228 + // _profile.keycloak = _null; + _profile.roleKeycloaks = []; + _profile.isActive = false; + _profile.isDelete = true; + } + } + _profile.leaveCommandId = item.commandId ?? _null; + _profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`; + _profile.leaveRemark = clearProfile.leaveRemark ?? _null; + _profile.leaveDate = item.commandDateAffect ?? _null; + _profile.leaveType = clearProfile.LeaveType ?? _null; + //ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516) + // _profile.position = _null; + // _profile.posTypeId = _null; + // _profile.posLevelId = _null; + } + await Promise.all([ + this.profileRepository.save(_profile), + this.salaryRepo.save(profileSalary), + ]); + + // if (profile.id) { + // await this.keycloakAttributeService.clearOrgDnaAttributes( + // [profile.id], + // "PROFILE", + // ); + // } + + const history = new ProfileSalaryHistory(); + Object.assign(history, { ...profileSalary, id: undefined }); + history.profileSalaryId = profileSalary.id; + await this.salaryHistoryRepo.save(history); + // Task #2190 + let organizeName = ""; + if (orgRootRef) { + const names = [ + orgChild4Ref?.orgChild4Name, + orgChild3Ref?.orgChild3Name, + orgChild2Ref?.orgChild2Name, + orgChild1Ref?.orgChild1Name, + orgRootRef?.orgRootName, + ].filter(Boolean); + organizeName = names.join(" "); + } + }), + ); return new HttpSuccess(); } - /** - * API สร้างทะเบียนประวัติข้าราชการ หลังออกคำสั่งบรรจุสอบ หรือ รับโอน - * @summary API สร้างทะเบียนประวัติข้าราชการ หลังออกคำสั่งบรรจุสอบ หรือ รับโอน - */ @Post("excexute/create-officer-profile") public async CreateOfficeProfileExcecute( @Request() req: RequestWithUser, @@ -4401,10 +6707,953 @@ export class CommandController extends Controller { }[]; }, ) { - await new ExecuteOfficerProfileService().executeCreateOfficerProfile(body.data, { - user: { sub: req.user.sub, name: req.user.name }, - req, + console.log("[Excexute/CreateOfficerProfile] Starting CreateOfficeProfileExcecute"); + console.log("[Excexute/CreateOfficerProfile] Request body count:", body.data?.length); + const roleKeycloak = await this.roleKeycloakRepo.findOne({ + where: { name: Like("USER") }, }); + console.log("[Excexute/CreateOfficerProfile] roleKeycloak found:", !!roleKeycloak); + const list = await getRoles(); + console.log("[Excexute/CreateOfficerProfile] Roles list retrieved, length:", Array.isArray(list) ? list.length : "not array"); + if (!Array.isArray(list)) { + console.error("[Excexute/CreateOfficerProfile] Failed - Cannot get role(s) data from the server"); + throw new Error("Failed. Cannot get role(s) data from the server."); + } + let _posNumCodeSit: string = ""; + let _posNumCodeSitAbb: string = ""; + console.log("[Excexute/CreateOfficerProfile] Getting command data"); + const _command = await this.commandRepository.findOne({ + where: { id: body.data.find((x) => x.bodySalarys?.commandId)?.bodySalarys?.commandId ?? "" }, + }); + console.log("[Excexute/CreateOfficerProfile] Command found:", !!_command, "isBangkok:", _command?.isBangkok); + if (_command) { + if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { + console.log("[Excexute/CreateOfficerProfile] Setting position codes for OFFICE"); + const orgRootDeputy = await this.orgRootRepository.findOne({ + where: { + isDeputy: true, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: ["orgRevision"], + }); + _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; + _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; + console.log("[Excexute/CreateOfficerProfile] OFFICE position codes set:", _posNumCodeSit, _posNumCodeSitAbb); + } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { + console.log("[Excexute/CreateOfficerProfile] Setting position codes for BANGKOK"); + _posNumCodeSit = "กรุงเทพมหานคร"; + _posNumCodeSitAbb = "กทม."; + console.log("[Excexute/CreateOfficerProfile] BANGKOK position codes set:", _posNumCodeSit, _posNumCodeSitAbb); + } else { + console.log("[Excexute/CreateOfficerProfile] Setting position codes from admin profile"); + let _profileAdmin = await this.profileRepository.findOne({ + where: { + keycloak: _command?.createdUserId.toString(), + current_holders: { + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + }, + relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], + }); + _posNumCodeSit = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? + ""; + _posNumCodeSitAbb = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot + .orgRootShortName ?? ""; + console.log("[Excexute/CreateOfficerProfile] Admin profile position codes set:", _posNumCodeSit, _posNumCodeSitAbb); + } + } + const before = null; + const meta = { + createdUserId: req.user.sub, + createdFullName: req.user.name, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; + console.log("[Excexute/CreateOfficerProfile] Starting to process", body.data.length, "profile(s)"); + await Promise.all( + body.data.map(async (item, index) => { + console.log("[Excexute/CreateOfficerProfile] Processing item", index + 1, "of", body.data.length); + const _null: any = null; + if (item.bodyProfile.posLevelId === "") item.bodyProfile.posLevelId = null; + if (item.bodyProfile.posTypeId === "") item.bodyProfile.posTypeId = null; + if ( + item.bodyProfile.posLevelId && + !(await this.posLevelRepo.findOneBy({ id: item.bodyProfile.posLevelId })) + ) { + console.error("[Excexute/CreateOfficerProfile] ไม่พบข้อมูลระดับตำแหน่งนี้ posLevelId:", item.bodyProfile.posLevelId); + throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลระดับตำแหน่งนี้"); + } + if ( + item.bodyProfile.posTypeId && + !(await this.posTypeRepo.findOneBy({ id: item.bodyProfile.posTypeId })) + ) { + console.error("[Excexute/CreateOfficerProfile] ไม่พบข้อมูลประเภทตำแหน่งนี้ posTypeId:", item.bodyProfile.posTypeId); + throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลประเภทตำแหน่งนี้"); + } + + console.log("[Excexute/CreateOfficerProfile] Processing citizenId:", item.bodyProfile.citizenId); + let registrationProvinceId = await this.provinceRepo.findOneBy({ + id: item.bodyProfile.registrationProvinceId ?? "", + }); + let registrationDistrictId = await this.districtRepo.findOneBy({ + id: item.bodyProfile.registrationDistrictId ?? "", + }); + let registrationSubDistrictId = await this.subDistrictRepo.findOneBy({ + id: item.bodyProfile.registrationSubDistrictId ?? "", + }); + let currentProvinceId = await this.provinceRepo.findOneBy({ + id: item.bodyProfile.currentProvinceId ?? "", + }); + let currentDistrictId = await this.districtRepo.findOneBy({ + id: item.bodyProfile.currentDistrictId ?? "", + }); + let currentSubDistrictId = await this.subDistrictRepo.findOneBy({ + id: item.bodyProfile.currentSubDistrictId ?? "", + }); + console.log("[Excexute/CreateOfficerProfile] Address validation completed"); + + let _dateRetire = + item.bodyProfile.birthDate == null + ? _null + : calculateRetireDate(item.bodyProfile.birthDate); + let _dateRetireLaw = + item.bodyProfile.birthDate == null + ? _null + : calculateRetireLaw(item.bodyProfile.birthDate); + + let userKeycloakId: any; + let result: any; + console.log("[Excexute/CreateOfficerProfile] Checking Keycloak user for citizenId:", item.bodyProfile.citizenId); + const checkUser = await getUserByUsername(item.bodyProfile.citizenId); + console.log("[Excexute/CreateOfficerProfile] Keycloak user exists:", checkUser.length > 0); + if (checkUser.length == 0) { + console.log("[Excexute/CreateOfficerProfile] Creating new Keycloak user"); + let password = item.bodyProfile.citizenId; + if (item.bodyProfile.birthDate != null) { + const _date = new Date(item.bodyProfile.birthDate.toDateString()) + .getDate() + .toString() + .padStart(2, "0"); + const _month = (new Date(item.bodyProfile.birthDate.toDateString()).getMonth() + 1) + .toString() + .padStart(2, "0"); + const _year = new Date(item.bodyProfile.birthDate.toDateString()).getFullYear() + 543; + password = `${_date}${_month}${_year}`; + } + console.log("[Excexute/CreateOfficerProfile] Calling createUser for:", item.bodyProfile.citizenId); + console.log("[Excexute/CreateOfficerProfile] createUser data - firstName:", item.bodyProfile.firstName, "lastName:", item.bodyProfile.lastName); + // กรอง "." ออกจาก firstName ก่อนส่งไป keycloak (ป้องกัน . หรืออักขระอื่นๆ) + const sanitizedFirstName = item.bodyProfile.firstName?.replace(/\./g, "") ?? ""; + userKeycloakId = await createUser(item.bodyProfile.citizenId, password, { + firstName: sanitizedFirstName, + lastName: item.bodyProfile.lastName, + }); + if (userKeycloakId && typeof userKeycloakId === "object" && userKeycloakId.errorMessage) { + console.error("[Excexute/CreateOfficerProfile] createUser FAILED - field:", userKeycloakId.field, "errorMessage:", userKeycloakId.errorMessage, "params:", userKeycloakId.params); + throw new HttpError(HttpStatus.BAD_REQUEST, `Keycloak validation failed: ${userKeycloakId.field} - ${userKeycloakId.errorMessage}`); + } + console.log("[Excexute/CreateOfficerProfile] User created in Keycloak, userKeycloakId:", userKeycloakId); + result = await addUserRoles( + userKeycloakId, + list + .filter((v) => v.name === "USER") + .map((x) => ({ + id: x.id, + name: x.name, + })), + ); + console.log("[Excexute/CreateOfficerProfile] USER role assigned to new user, result:", result); + } else { + console.log("[Excexute/CreateOfficerProfile] Updating existing Keycloak user"); + userKeycloakId = checkUser[0].id; + console.log("[Excexute/CreateOfficerProfile] Existing userKeycloakId:", userKeycloakId); + const rolesData = await getRoleMappings(userKeycloakId); + if (rolesData) { + const _delRole = rolesData.map((x: any) => ({ + id: x.id, + name: x.name, + })); + console.log("[Excexute/CreateOfficerProfile] Removing old roles:", _delRole.length); + await removeUserRoles(userKeycloakId, _delRole); + } + result = await addUserRoles( + userKeycloakId, + list + .filter((v) => v.name === "USER") + .map((x) => ({ + id: x.id, + name: x.name, + })), + ); + console.log("[Excexute/CreateOfficerProfile] USER role assigned to existing user"); + } + + let profile: any = await this.profileRepository.findOne({ + where: { citizenId: item.bodyProfile.citizenId /*, isActive: true */ }, + relations: ["roleKeycloaks", "profileInsignias", "profileAvatars"], + }); + console.log("[Excexute/CreateOfficerProfile] Profile found:", !!profile, "for citizenId:", item.bodyProfile.citizenId); + let _oldInsigniaIds: string[] = []; + let _oldSalaries: any[] = []; + //ลูกจ้างประจำ หรือ บุคคลภายนอก + if (!profile) { + console.log("[Excexute/CreateOfficerProfile] No existing profile found, creating new profile"); + //กรณีลูกจ้างประจำมาสอบเป็นข้าราชการ ต้อง update สถานะโปรไฟล์เดิม + let profileEmployee: any = await this.profileEmployeeRepository.findOne({ + where: { citizenId: item.bodyProfile.citizenId }, + relations: ["profileInsignias", "roleKeycloaks"], + }); + console.log("[Excexute/CreateOfficerProfile] Employee profile found:", !!profileEmployee); + if (profileEmployee) { + console.log("[Excexute/CreateOfficerProfile] Converting employee profile to officer profile"); + const _order = await this.salaryRepo.findOne({ + where: { profileEmployeeId: profileEmployee.id }, + order: { order: "DESC" }, + }); + const profileEmpSalary = new ProfileSalary(); + profileEmpSalary.posNumCodeSit = _posNumCodeSit; + profileEmpSalary.posNumCodeSitAbb = _posNumCodeSitAbb; + profileEmpSalary.order = _order == null ? 1 : _order.order + 1; + Object.assign(profileEmpSalary, { + ...item.bodySalarys, + ...meta, + profileEmployeeId: profileEmployee.id, + profileId: undefined, + }); + const history = new ProfileSalaryHistory(); + Object.assign(history, { ...profileEmpSalary, id: undefined }); + profileEmpSalary.dateGovernment = item.bodySalarys?.commandDateAffect ?? meta.createdAt; + (profileEmpSalary.profileId = _null), + await this.salaryRepo.save(profileEmpSalary, { data: req }); + setLogDataDiff(req, { before, after: profileEmpSalary }); + history.profileSalaryId = profileEmpSalary.id; + await this.salaryHistoryRepo.save(history, { data: req }); + + if (profileEmployee.profileInsignias.length > 0) { + _oldInsigniaIds = profileEmployee.profileInsignias?.map((x: any) => x.id) ?? []; + } + await removeProfileInOrganize(profileEmployee.id, "EMPLOYEE"); + if (profileEmployee.keycloak != null) { + // const delUserKeycloak = await deleteUser(profileEmployee.keycloak); + // if (delUserKeycloak) { + // Task #228 + // profileEmployee.keycloak = _null; + profileEmployee.roleKeycloaks = []; + profileEmployee.isActive = false; + // } + } + profileEmployee.isLeave = true; + profileEmployee.leaveReason = "บรรจุข้าราชการ"; + profileEmployee.lastUpdateUserId = req.user.sub; + profileEmployee.lastUpdateFullName = req.user.name; + profileEmployee.lastUpdatedAt = new Date(); + await this.profileEmployeeRepository.save(profileEmployee); + setLogDataDiff(req, { before, after: profileEmployee }); + } + profile = Object.assign({ ...item.bodyProfile, ...meta }); + profile.dateRetire = _dateRetire; + profile.dateRetireLaw = _dateRetireLaw; + profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : []; + profile.keycloak = + userKeycloakId && typeof userKeycloakId === "string" ? userKeycloakId : ""; + profile.registrationAddress = item.bodyProfile.registrationAddress; + profile.registrationProvinceId = registrationProvinceId + ? registrationProvinceId.id + : _null; + profile.registrationDistrictId = registrationDistrictId + ? registrationDistrictId.id + : _null; + profile.registrationSubDistrictId = registrationSubDistrictId + ? registrationSubDistrictId.id + : _null; + profile.registrationZipCode = item.bodyProfile.registrationZipCode; + profile.currentAddress = item.bodyProfile.currentAddress; + profile.currentProvinceId = currentProvinceId ? currentProvinceId.id : _null; + profile.currentDistrictId = currentDistrictId ? currentDistrictId.id : _null; + profile.currentSubDistrictId = currentSubDistrictId ? currentSubDistrictId.id : _null; + profile.currentZipCode = item.bodyProfile.currentZipCode; + profile.email = item.bodyProfile.email; + profile.dateStart = item.bodyProfile.dateStart; + profile.amount = item.bodyProfile.amount ?? null; + profile.amountSpecial = item.bodyProfile.amountSpecial ?? null; + profile.isProbation = item.bodyProfile.isProbation; + //เพิ่มใหม่จากรับโอน + profile.rank = item?.bodyProfile?.rank || null; + profile.prefix = item?.bodyProfile?.rank || item?.bodyProfile?.prefix || null; + profile.prefixMain = item?.bodyProfile?.prefix ?? null; + profile.firstName = item.bodyProfile.firstName ?? null; + profile.lastName = item.bodyProfile.lastName ?? null; + profile.birthDate = item.bodyProfile.birthDate ?? null; + profile.gender = item.bodyProfile.gender ?? null; + profile.relationship = item.bodyProfile.relationship ?? null; + profile.religion = item.bodyProfile.religion ?? null; + profile.ethnicity = item.bodyProfile.ethnicity; + profile.nationality = item.bodyProfile.nationality ?? null; + profile.bloodGroup = item.bodyProfile.bloodGroup ?? null; + profile.phone = item.bodyProfile.phone ?? null; + + console.log("[Excexute/CreateOfficerProfile] Saving new profile"); + await this.profileRepository.save(profile); + console.log("[Excexute/CreateOfficerProfile] New profile saved, profileId:", profile.id); + // update user attribute in keycloak + await updateUserAttributes(profile.keycloak ?? "", { + profileId: [profile.id], + prefix: [profile.prefix || ""], + }); + console.log("[Excexute/CreateOfficerProfile] Keycloak attributes updated"); + setLogDataDiff(req, { before, after: profile }); + } + //ขรก.ในระบบ หรือ ขรก.ในระบบที่สถานะพ้นจากราชการ + else { + console.log("[Excexute/CreateOfficerProfile] Existing profile found, isLeave:", profile.isLeave, "leaveType:", profile.leaveType); + //สร้างโปรไฟล์ใหม่ ถ้าสถานะพ้นราชการ คำสั่งโอนออกหรือคำสั่งขอลาออก + if ( + profile.isLeave && + ["PLACEMENT_TRANSFER", "RETIRE_RESIGN"].includes(profile.leaveType) + ) { + console.log("[Excexute/CreateOfficerProfile] Profile is leaving with eligible leave type, creating new profile record"); + //ดึง profileSalary เดิม + _oldSalaries = await this.salaryRepo.find({ + where: { profileId: profile.id }, + order: { order: "ASC" }, + }); + if (profile.profileInsignias.length > 0) { + _oldInsigniaIds = profile.profileInsignias?.map((x: any) => x.id) ?? []; + } + profile = Object.assign({ ...item.bodyProfile, ...meta }); + profile.dateRetire = _dateRetire; + profile.dateRetireLaw = _dateRetireLaw; + profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : []; + profile.keycloak = + userKeycloakId && typeof userKeycloakId === "string" ? userKeycloakId : ""; + profile.registrationAddress = item.bodyProfile.registrationAddress; + profile.registrationProvinceId = registrationProvinceId + ? registrationProvinceId.id + : _null; + profile.registrationDistrictId = registrationDistrictId + ? registrationDistrictId.id + : _null; + profile.registrationSubDistrictId = registrationSubDistrictId + ? registrationSubDistrictId.id + : _null; + profile.registrationZipCode = item.bodyProfile.registrationZipCode; + profile.currentAddress = item.bodyProfile.currentAddress; + profile.currentProvinceId = currentProvinceId ? currentProvinceId.id : _null; + profile.currentDistrictId = currentDistrictId ? currentDistrictId.id : _null; + profile.currentSubDistrictId = currentSubDistrictId ? currentSubDistrictId.id : _null; + profile.currentZipCode = item.bodyProfile.currentZipCode; + profile.email = item.bodyProfile.email; + profile.dateStart = item.bodyProfile.dateStart; + profile.amount = item.bodyProfile.amount ?? null; + profile.amountSpecial = item.bodyProfile.amountSpecial ?? null; + profile.isProbation = item.bodyProfile.isProbation; + profile.rank = item?.bodyProfile?.rank || null; + profile.prefix = item?.bodyProfile?.rank || item?.bodyProfile?.prefix || null; + profile.prefixMain = item?.bodyProfile?.prefix ?? null; + profile.firstName = item.bodyProfile.firstName ?? null; + profile.lastName = item.bodyProfile.lastName ?? null; + profile.birthDate = item.bodyProfile.birthDate ?? null; + profile.gender = item.bodyProfile.gender ?? null; + profile.relationship = item.bodyProfile.relationship ?? null; + profile.religion = item.bodyProfile.religion ?? null; + profile.ethnicity = item.bodyProfile.ethnicity; + profile.nationality = item.bodyProfile.nationality ?? null; + profile.bloodGroup = item.bodyProfile.bloodGroup ?? null; + profile.phone = item.bodyProfile.phone ?? null; + await this.profileRepository.save(profile); + console.log("[Excexute/CreateOfficerProfile] New profile record saved for leaving officer, profileId:", profile.id); + setLogDataDiff(req, { before, after: profile }); + } else { + console.log("[Excexute/CreateOfficerProfile] Updating existing active profile"); + profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : []; + profile.keycloak = + userKeycloakId && typeof userKeycloakId === "string" ? userKeycloakId : ""; + profile.isProbation = item.bodyProfile.isProbation; + profile.isLeave = item.bodyProfile.isLeave; + profile.isRetirement = false; + profile.isActive = true; + profile.isDelete = false; + profile.dateLeave = _null; + profile.dateRetire = _dateRetire; + profile.dateRetireLaw = _dateRetireLaw; + profile.registrationAddress = item.bodyProfile.registrationAddress; + profile.registrationProvinceId = registrationProvinceId + ? registrationProvinceId.id + : _null; + profile.registrationDistrictId = registrationDistrictId + ? registrationDistrictId.id + : _null; + profile.registrationSubDistrictId = registrationSubDistrictId + ? registrationSubDistrictId.id + : _null; + profile.registrationZipCode = item.bodyProfile.registrationZipCode; + profile.currentAddress = item.bodyProfile.currentAddress; + profile.currentProvinceId = currentProvinceId ? currentProvinceId.id : _null; + profile.currentDistrictId = currentDistrictId ? currentDistrictId.id : _null; + profile.currentSubDistrictId = currentSubDistrictId ? currentSubDistrictId.id : _null; + profile.currentZipCode = item.bodyProfile.currentZipCode; + profile.email = item.bodyProfile.email; + profile.telephoneNumber = item.bodyProfile.telephoneNumber; + profile.phone = item.bodyProfile.phone; + profile.dateStart = item.bodyProfile.dateStart; + profile.amount = item.bodyProfile.amount ?? null; + profile.amountSpecial = item.bodyProfile.amountSpecial ?? null; + profile.leaveCommandId = _null; + profile.leaveCommandNo = _null; + profile.leaveRemark = _null; + profile.leaveDate = _null; + profile.leaveType = _null; + profile.leaveReason = _null; + profile.lastUpdateUserId = req.user.sub; + profile.lastUpdateFullName = req.user.name; + profile.lastUpdatedAt = new Date(); + //เพิ่มใหม่จากรับโอน + profile.rank = item?.bodyProfile?.rank || null; + profile.prefix = item?.bodyProfile?.rank || item?.bodyProfile?.prefix || null; + profile.prefixMain = item?.bodyProfile?.prefix ?? null; + profile.firstName = + item.bodyProfile.firstName && item.bodyProfile.firstName != "" + ? item.bodyProfile.firstName + : profile.firstName; + profile.lastName = + item.bodyProfile.lastName && item.bodyProfile.lastName != "" + ? item.bodyProfile.lastName + : profile.lastName; + profile.birthDate = item.bodyProfile.birthDate + ? item.bodyProfile.birthDate + : profile.birthDate; + profile.gender = + item.bodyProfile.gender && item.bodyProfile.gender != "" + ? item.bodyProfile.gender + : profile.gender; + profile.relationship = + item.bodyProfile.relationship && item.bodyProfile.relationship != "" + ? item.bodyProfile.relationship + : profile.relationship; + profile.religion = + item.bodyProfile.religion && item.bodyProfile.religion != "" + ? item.bodyProfile.religion + : profile.religion; + profile.ethnicity = + item.bodyProfile.ethnicity && item.bodyProfile.ethnicity != "" + ? item.bodyProfile.ethnicity + : profile.ethnicity; + profile.nationality = + item.bodyProfile.nationality && item.bodyProfile.nationality != "" + ? item.bodyProfile.nationality + : profile.nationality; + profile.bloodGroup = + item.bodyProfile.bloodGroup && item.bodyProfile.bloodGroup != "" + ? item.bodyProfile.bloodGroup + : profile.bloodGroup; + profile.phone = + item.bodyProfile.phone && item.bodyProfile.phone != "" + ? item.bodyProfile.phone + : profile.phone; + await this.profileRepository.save(profile); + console.log("[Excexute/CreateOfficerProfile] Existing active profile updated, profileId:", profile.id); + setLogDataDiff(req, { before, after: profile }); + } + } + + if (profile && profile.id) { + console.log("[Excexute/CreateOfficerProfile] Processing additional data for profileId:", profile.id); + //Educations + if (item.bodyEducations && item.bodyEducations.length > 0) { + console.log("[Excexute/CreateOfficerProfile] Processing educations, count:", item.bodyEducations.length); + await Promise.all( + item.bodyEducations.map(async (education) => { + const profileEdu = new ProfileEducation(); + Object.assign(profileEdu, { ...education, ...meta }); + const eduHistory = new ProfileEducationHistory(); + Object.assign(eduHistory, { ...profileEdu, id: undefined }); + profileEdu.profileId = profile.id; + const educationLevel = await this.profileEducationRepo.findOne({ + select: ["id", "level", "profileId"], + where: { profileId: profile.id, isDeleted: false }, + order: { level: "DESC" }, + }); + profileEdu.level = educationLevel == null ? 1 : educationLevel.level + 1; + await this.profileEducationRepo.save(profileEdu, { data: req }); + setLogDataDiff(req, { before, after: profileEdu }); + eduHistory.profileEducationId = profileEdu.id; + await this.profileEducationHistoryRepo.save(eduHistory, { data: req }); + }), + ); + } + //Certificates + if (item.bodyCertificates && item.bodyCertificates.length > 0) { + console.log("[Excexute/CreateOfficerProfile] Processing certificates, count:", item.bodyCertificates.length); + await Promise.all( + item.bodyCertificates.map(async (cer) => { + const profileCer = new ProfileCertificate(); + Object.assign(profileCer, { ...cer, ...meta }); + const cerHistory = new ProfileCertificateHistory(); + Object.assign(cerHistory, { ...profileCer, id: undefined }); + profileCer.profileId = profile.id; + await this.certificateRepo.save(profileCer, { data: req }); + setLogDataDiff(req, { before, after: profileCer }); + cerHistory.profileCertificateId = profileCer.id; + await this.certificateHistoryRepo.save(cerHistory, { data: req }); + }), + ); + } + //FamilyCouple + if (item.bodyMarry != null) { + console.log("[Excexute/CreateOfficerProfile] Processing couple/marry data"); + const profileCouple = new ProfileFamilyCouple(); + const data = { + profileId: profile.id, + couple: item.bodyMarry.marry, + couplePrefix: item.bodyMarry.marryPrefix, + coupleFirstName: item.bodyMarry.marryFirstName, + coupleLastName: item.bodyMarry.marryLastName, + coupleCareer: item.bodyMarry.marryOccupation, + coupleLive: true, + }; + Object.assign(profileCouple, { ...data, ...meta }); + const coupleHistory = new ProfileFamilyCoupleHistory(); + Object.assign(coupleHistory, { ...profileCouple, id: undefined }); + profileCouple.profileId = profile.id; + await this.profileFamilyCoupleRepo.save(profileCouple, { data: req }); + setLogDataDiff(req, { before, after: profileCouple }); + coupleHistory.profileFamilyCoupleId = profileCouple.id; + await this.profileFamilyCoupleHistoryRepo.save(coupleHistory, { data: req }); + } + //FamilyFather + if (item.bodyFather != null) { + console.log("[Excexute/CreateOfficerProfile] Processing father data"); + const profileFather = new ProfileFamilyFather(); + const data = { + profileId: profile.id, + fatherPrefix: item.bodyFather.fatherPrefix, + fatherFirstName: item.bodyFather.fatherFirstName, + fatherLastName: item.bodyFather.fatherLastName, + fatherCareer: item.bodyFather.fatherOccupation, + fatherLive: true, + }; + Object.assign(profileFather, { ...data, ...meta }); + const fatherHistory = new ProfileFamilyFatherHistory(); + Object.assign(fatherHistory, { ...profileFather, id: undefined }); + profileFather.profileId = profile.id; + await this.profileFamilyFatherRepo.save(profileFather, { data: req }); + setLogDataDiff(req, { before, after: profileFather }); + fatherHistory.profileFamilyFatherId = profileFather.id; + await this.profileFamilyFatherHistoryRepo.save(fatherHistory, { data: req }); + } + //FamilyMother + if (item.bodyMother != null) { + console.log("[Excexute/CreateOfficerProfile] Processing mother data"); + const profileMother = new ProfileFamilyMother(); + const data = { + profileId: profile.id, + motherPrefix: item.bodyMother.motherPrefix, + motherFirstName: item.bodyMother.motherFirstName, + motherLastName: item.bodyMother.motherLastName, + motherCareer: item.bodyMother.motherOccupation, + motherLive: true, + }; + Object.assign(profileMother, { ...data, ...meta }); + const motherHistory = new ProfileFamilyMotherHistory(); + Object.assign(motherHistory, { ...profileMother, id: undefined }); + profileMother.profileId = profile.id; + await this.profileFamilyMotherRepo.save(profileMother, { data: req }); + setLogDataDiff(req, { before, after: profileMother }); + motherHistory.profileFamilyMotherId = profileMother.id; + await this.profileFamilyMotherHistoryRepo.save(motherHistory, { data: req }); + } + //Salary + //insert profileSalary อันเก่า กรณีพ้นราชการแล้วกลับมาบรรจุ + if (_oldSalaries.length > 0) { + console.log("[Excexute/CreateOfficerProfile] Restoring old salaries, count:", _oldSalaries.length); + await Promise.all( + _oldSalaries.map(async (oldSal) => { + const profileSal: any = new ProfileSalary(); + Object.assign(profileSal, { ...oldSal, ...meta }); + const salaryHistory = new ProfileSalaryHistory(); + Object.assign(salaryHistory, { ...profileSal, id: undefined }); + profileSal.profileId = profile.id; + await this.salaryRepo.save(profileSal, { data: req }); + setLogDataDiff(req, { before, after: profileSal }); + salaryHistory.profileSalaryId = profileSal.id; + await this.salaryHistoryRepo.save(salaryHistory, { data: req }); + }), + ); + } + //insert item.bodySalarys ต่อจากที่ insert เดิมไปแล้ว + if (item.bodySalarys && item.bodySalarys != null) { + console.log("[Excexute/CreateOfficerProfile] Processing new salary data"); + const dest_item = await this.salaryRepo.findOne({ + where: { profileId: profile.id }, + order: { order: "DESC" }, + }); + const profileSal: any = new ProfileSalary(); + profileSal.posNumCodeSit = _posNumCodeSit; + profileSal.posNumCodeSitAbb = _posNumCodeSitAbb; + Object.assign(profileSal, { ...item.bodySalarys, ...meta }); + const salaryHistory = new ProfileSalaryHistory(); + Object.assign(salaryHistory, { ...profileSal, id: undefined }); + profileSal.order = dest_item == null ? 1 : dest_item.order + 1; + profileSal.profileId = profile.id; + profileSal.dateGovernment = item.bodySalarys.commandDateAffect ?? meta.createdAt; + profileSal.amount = item.bodySalarys.amount ?? null; + profileSal.amountSpecial = item.bodySalarys.amountSpecial ?? null; + profileSal.positionSalaryAmount = item.bodySalarys.positionSalaryAmount ?? null; + profileSal.mouthSalaryAmount = item.bodySalarys.mouthSalaryAmount ?? null; + await this.salaryRepo.save(profileSal, { data: req }); + setLogDataDiff(req, { before, after: profileSal }); + salaryHistory.profileSalaryId = profileSal.id; + await this.salaryHistoryRepo.save(salaryHistory, { data: req }); + } + //Position + if (item.bodyPosition && item.bodyPosition != null) { + console.log("[Excexute/CreateOfficerProfile] Processing position assignment"); + // STEP 1: หา posMaster ที่จะใช้งานตาม id ที่ส่งมา (อาจเป็นตำแหน่งเก่าหรือใหม่ก็ได้) + console.log("[Excexute/CreateOfficerProfile] STEP 1: Finding posMaster, posmasterId:", item.bodyPosition.posmasterId); + let posMaster = await this.posMasterRepository.findOne({ + where: { + id: item.bodyPosition.posmasterId, + }, + relations: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }); + console.log("[Excexute/CreateOfficerProfile] posMaster found:", !!posMaster); + + // เช็คว่า posMaster ที่หามาอยู่ในโครงสร้างปัจจุบันหรือไม่ + const isCurrent = + posMaster?.orgRevision?.orgRevisionIsCurrent === true && + posMaster?.orgRevision?.orgRevisionIsDraft === false; + console.log("[Excexute/CreateOfficerProfile] posMaster isCurrent:", isCurrent); + + // ถ้าไม่อยู่ในโครงสร้างปัจจุบัน ให้หาตัวใหม่จาก ancestorDNA + if (!isCurrent && posMaster?.ancestorDNA) { + console.log("[Excexute/CreateOfficerProfile] Finding current posMaster from ancestorDNA"); + posMaster = await this.posMasterRepository.findOne({ + where: { + ancestorDNA: posMaster.ancestorDNA, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }); + console.log("[Excexute/CreateOfficerProfile] Current posMaster from ancestorDNA found:", !!posMaster); + } + + if (posMaster == null) { + console.error( + `[Excexute/CreateOfficerProfile] not found posMasterId: ${item.bodyPosition.posmasterId}` + ); + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้"); + } + + // STEP 2: เคลียร์ข้อมูลตำแหน่งเก่าที่ครองอยู่ ในโครงสร้างปัจจุบัน + console.log("[Excexute/CreateOfficerProfile] STEP 2: Clearing old position data"); + const posMasterOld = await this.posMasterRepository.findOne({ + where: { + current_holderId: profile.id, + orgRevisionId: posMaster.orgRevisionId, + }, + }); + if (posMasterOld != null) { + // เคลียร์คนครองเก่าออกจากตำแหน่งเดิม + posMasterOld.current_holderId = null; + posMasterOld.lastUpdatedAt = new Date(); + } + + // หา position เก่าที่เลือกไว้ แล้วเคลียร์การเลือก + const positionOld = await this.positionRepository.findOne({ + where: { + posMasterId: posMasterOld?.id, + positionIsSelected: true, + }, + }); + if (positionOld != null) { + positionOld.positionIsSelected = false; + await this.positionRepository.save(positionOld); + } + + // STEP 3: เคลียร์ position ที่เลือกไว้อื่นๆ ใน posMaster ตัวใหม่ + console.log("[Excexute/CreateOfficerProfile] STEP 3: Clearing other selected positions in new posMaster"); + const checkPosition = await this.positionRepository.find({ + where: { + posMasterId: posMaster.id, + positionIsSelected: true, + }, + }); + if (checkPosition.length > 0) { + const clearPosition = checkPosition.map((positions) => ({ + ...positions, + positionIsSelected: false, + })); + await this.positionRepository.save(clearPosition); + } + + // STEP 4: กำหนดคนครองใหม่ให้กับ posMaster + console.log("[Excexute/CreateOfficerProfile] STEP 4: Assigning new holder to posMaster"); + posMaster.current_holderId = profile.id; + posMaster.lastUpdatedAt = new Date(); + // posMaster.conditionReason = _null; + // posMaster.isCondition = false; + if (posMasterOld != null) { + await this.posMasterRepository.save(posMasterOld); + await CreatePosMasterHistoryOfficer(posMasterOld.id, req); + } + await this.posMasterRepository.save(posMaster); + console.log("[Excexute/CreateOfficerProfile] posMaster saved with new holder"); + + // STEP 5: กำหนด position ใหม่ + console.log("[Excexute/CreateOfficerProfile] STEP 5: Determining position to assign"); + // Match position ตามลำดับ priority: + // Condition 1: match จาก positionId + // Condition 2: match 7 ฟิลด์ (positionName, posTypeId, posLevelId, positionField, positionArea, positionExecutiveField, posExecutiveId) + // Condition 3: match 3 ฟิลด์ (positionName, posTypeId, posLevelId) + // Fallback: เลือก position แรกใน posMaster + + let positionNew: Position | null = null; + + // ═══════════════════════════════════════════════════════════ + // CONDITION 1: เช็คจาก positionId ตรง + // ═══════════════════════════════════════════════════════════ + console.log("[Excexute/CreateOfficerProfile] CONDITION 1: Checking by positionId:", item.bodyPosition?.positionId); + if (item.bodyPosition?.positionId) { + const positionById = await this.positionRepository.findOne({ + where: { + id: item.bodyPosition.positionId, + posMasterId: posMaster.id, // ต้องอยู่ใน posMaster ที่ถูกต้อง + }, + relations: ["posExecutive"], + }); + + if (positionById) { + positionNew = positionById; + console.log("[Excexute/CreateOfficerProfile] CONDITION 1 matched, positionId:", positionById.id); + } + } + + // ═══════════════════════════════════════════════════════════ + // CONDITION 2: Match 7 ฟิลด์ (ถ้า Condition 1 ไม่ match) + // ═══════════════════════════════════════════════════════════ + if (!positionNew && item.bodyPosition) { + console.log("[Excexute/CreateOfficerProfile] CONDITION 1 not matched, trying CONDITION 2: Match 7 fields"); + // สร้าง where clause แบบ dynamic - ใส่เฉพาะฟิลด์ที่ไม่ใช่ null + const whereCondition: any = { + posMasterId: posMaster.id, + positionName: item.bodyPosition.positionName, + posTypeId: item.bodyPosition.posTypeId, + posLevelId: item.bodyPosition.posLevelId, + }; + + if (item.bodyPosition.positionField) { + whereCondition.positionField = item.bodyPosition.positionField; + } + if (item.bodyPosition.posExecutiveId) { + whereCondition.posExecutiveId = item.bodyPosition.posExecutiveId; + } + if (item.bodyPosition.positionExecutiveField) { + whereCondition.positionExecutiveField = item.bodyPosition.positionExecutiveField; + } + if (item.bodyPosition.positionArea) { + whereCondition.positionArea = item.bodyPosition.positionArea; + } + + const positionBy7Fields = await this.positionRepository.findOne({ + where: whereCondition, + relations: ["posExecutive"], + order: { orderNo: "ASC" } + }); + + if (positionBy7Fields) { + positionNew = positionBy7Fields; + console.log("[Excexute/CreateOfficerProfile] CONDITION 2 matched with 7 fields, positionId:", positionBy7Fields.id); + } + } + + // ═══════════════════════════════════════════════════════════ + // CONDITION 3: Match 3 ฟิลด์ (ถ้า Condition 2 ไม่ match) + // ═══════════════════════════════════════════════════════════ + if (!positionNew && item.bodyPosition) { + console.log("[Excexute/CreateOfficerProfile] CONDITION 2 not matched, trying CONDITION 3: Match 3 fields"); + const positionBy3Fields = await this.positionRepository.findOne({ + where: { + posMasterId: posMaster.id, + positionName: item.bodyPosition.positionName, + posTypeId: item.bodyPosition.posTypeId, + posLevelId: item.bodyPosition.posLevelId, + }, + relations: ["posExecutive"], + order: { orderNo: "ASC" } + }); + + if (positionBy3Fields) { + positionNew = positionBy3Fields; + console.log("[Excexute/CreateOfficerProfile] CONDITION 3 matched with 3 fields, positionId:", positionBy3Fields.id); + } else { + console.log("[Excexute/CreateOfficerProfile] No position matched for profileId:", profile.id); + } + } + + // // ═══════════════════════════════════════════════════════════ + // // 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 + profile.posMasterNo = getPosMasterNo(posMaster); + profile.org = getOrgFullName(posMaster); + // ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ + if (positionNew != null) { + console.log("[Excexute/CreateOfficerProfile] Final position assignment, isSit:", posMaster.isSit, "positionId:", positionNew.id); + positionNew.positionIsSelected = true; + if (!posMaster.isSit) { + profile.posLevelId = positionNew.posLevelId; + profile.posTypeId = positionNew.posTypeId; + profile.position = positionNew.positionName; + profile.positionField = positionNew.positionField ?? null; + profile.posExecutive = positionNew.posExecutive?.posExecutiveName ?? null; + profile.positionArea = positionNew.positionArea ?? null; + profile.positionExecutiveField = positionNew.positionExecutiveField ?? null; + // profile.dateStart = new Date(); + } + await this.positionRepository.save(positionNew, { data: req }); + } else if (!posMaster.isSit) { + // fallback: ตำแหน่งในโครงสร้างถูกแก้ไข ใช้ข้อมูลตำแหน่งที่สมัครสอบมา + console.log("[Excexute/CreateOfficerProfile] positionNew is null, using bodyPosition data as fallback"); + profile.position = item.bodyPosition.positionName ?? null; + profile.posTypeId = item.bodyPosition.posTypeId ?? null; + profile.posLevelId = item.bodyPosition.posLevelId ?? null; + profile.positionField = item.bodyPosition.positionField ?? null; + profile.positionArea = item.bodyPosition.positionArea ?? null; + profile.positionExecutiveField = item.bodyPosition.positionExecutiveField ?? null; + } + await this.profileRepository.save(profile, { data: req }); + setLogDataDiff(req, { before, after: profile }); + // await CreatePosMasterHistoryOfficer(posMaster.id, req); + await CreatePosMasterHistoryOfficer(posMaster.id, req, null, { + positionId: positionNew?.id, + }); + } + // Insignia + if (_oldInsigniaIds.length > 0) { + console.log("[Excexute/CreateOfficerProfile] Processing old insignias, count:", _oldInsigniaIds.length); + const _insignias = await this.insigniaRepo.find({ + where: { id: In(_oldInsigniaIds), isDeleted: false }, + order: { createdAt: "ASC" }, + }); + for (const oldInsignia of _insignias) { + const newInsigniaData: CreateProfileInsignia = { + profileId: profile.id, + year: oldInsignia.year, + no: oldInsignia.no, + volume: oldInsignia.volume, + section: oldInsignia.section, + page: oldInsignia.page, + receiveDate: oldInsignia.receiveDate, + insigniaId: oldInsignia.insigniaId, + dateAnnounce: oldInsignia.dateAnnounce, + issue: oldInsignia.issue, + volumeNo: oldInsignia.volumeNo, + refCommandDate: oldInsignia.refCommandDate, + refCommandNo: oldInsignia.refCommandNo, + note: oldInsignia.note, + isUpload: oldInsignia.isUpload, + }; + const insignia = new ProfileInsignia(); + Object.assign(insignia, { ...newInsigniaData, ...meta }); + const history = new ProfileInsigniaHistory(); + Object.assign(history, { ...insignia, id: undefined }); + await this.insigniaRepo.save(insignia, { data: req }); + setLogDataDiff(req, { before, after: insignia }); + history.profileInsigniaId = insignia.id; + await this.insigniaHistoryRepo.save(history, { data: req }); + } + } + // เพิ่มรูปภาพโปรไฟล์ + if (item.bodyProfile.objectRefId) { + console.log("[Excexute/CreateOfficerProfile] Processing profile avatar image, objectRefId:", item.bodyProfile.objectRefId); + const _profileAvatar = new ProfileAvatar(); + Object.assign(_profileAvatar, { + ...meta, + profileId: profile.id, + profileEmployeeId: undefined, + }); + if (profile.profileAvatars && profile.profileAvatars.length > 0) { + await Promise.all( + profile.profileAvatars.map(async (item: any) => { + item.isActive = false; + await this.avatarRepository.save(item); + }), + ); + } + await this.avatarRepository.save(_profileAvatar); + let avatar = `ทะเบียนประวัติ/โปรไฟล์/${profile.id}`; + let fileName = `profile-${_profileAvatar.id}`; + _profileAvatar.isActive = true; + _profileAvatar.avatar = avatar; + _profileAvatar.avatarName = fileName; + await this.avatarRepository.save(_profileAvatar, { data: req }); + profile.avatar = avatar; + profile.avatarName = fileName; + await this.profileRepository.save(profile, { data: req }); + const checkAvatar = await this.avatarRepository.findOne({ + where: { avatar: avatar, avatarName: fileName }, + }); + if (checkAvatar && checkAvatar.profileId == null) { + checkAvatar.profileId = profile.id; + await this.avatarRepository.save(checkAvatar); + } + //duplicate รูปภาพโปรไฟล์โดยอิงจากรูปภาพเดิม + await new CallAPI() + .PostData(req, `/salary/file/avatar/${item.bodyProfile.objectRefId}`, { + prefix: avatar, + fileName: fileName, + }) + .then(() => {}) + .catch(() => {}); + } + } + }), + ); + console.log("[Excexute/CreateOfficerProfile] CreateOfficeProfileExcecute completed successfully"); return new HttpSuccess(); } @@ -4494,14 +7743,365 @@ export class CommandController extends Controller { }[]; }, ) { - /** - * Thin wrapper — เรียก ExecuteOrgCommandService.executeCommand21Employee (C-PM-21) - * ทั้ง endpoint นี้และ consumer ใน rabbitmq ใช้ service ตัวเดียวกัน (Linear Flow) - */ - await new ExecuteOrgCommandService().executeCommand21Employee(body.refIds, { - user: { sub: req.user.sub, name: req.user.name }, - req, + let _reqBody: any[] = []; + const roleKeycloak = await this.roleKeycloakRepo.findOne({ + where: { name: Like("USER") }, }); + let _posNumCodeSit: string = ""; + let _posNumCodeSitAbb: string = ""; + const _command = await this.commandRepository.findOne({ + where: { id: body.refIds.find((x) => x.commandId)?.commandId ?? "" }, + }); + if (_command) { + if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { + const orgRootDeputy = await this.orgRootRepository.findOne({ + where: { + isDeputy: true, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: ["orgRevision"], + }); + _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; + _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; + } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { + _posNumCodeSit = "กรุงเทพมหานคร"; + _posNumCodeSitAbb = "กทม."; + } else { + let _profileAdmin = await this.profileRepository.findOne({ + where: { + keycloak: _command?.createdUserId.toString(), + current_holders: { + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + }, + relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], + }); + _posNumCodeSit = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? + ""; + _posNumCodeSitAbb = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot + .orgRootShortName ?? ""; + } + } + await Promise.all( + body.refIds.map(async (item) => { + const profile = await this.profileEmployeeRepository.findOne({ + where: { id: item.refId }, + relations: ["roleKeycloaks"], + }); + if (!profile) { + throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); + } + const orgRevision = await this.orgRevisionRepository.findOne({ + where: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }); + const _posMaster = await this.employeePosMasterRepository.findOne({ + where: { + orgRevisionId: orgRevision?.id, + id: profile.posmasterIdTemp, + // current_holderId: profile.id + }, + relations: { + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }); + const orgRootRef = _posMaster?.orgRoot ?? null; + const orgChild1Ref = _posMaster?.orgChild1 ?? null; + const orgChild2Ref = _posMaster?.orgChild2 ?? null; + const orgChild3Ref = _posMaster?.orgChild3 ?? null; + const orgChild4Ref = _posMaster?.orgChild4 ?? null; + let orgShortName = ""; + if (_posMaster != null) { + if (_posMaster.orgChild1Id === null) { + orgShortName = _posMaster.orgRoot?.orgRootShortName; + } else if (_posMaster.orgChild2Id === null) { + orgShortName = _posMaster.orgChild1?.orgChild1ShortName; + } else if (_posMaster.orgChild3Id === null) { + orgShortName = _posMaster.orgChild2?.orgChild2ShortName; + } else if (_posMaster.orgChild4Id === null) { + orgShortName = _posMaster.orgChild3?.orgChild3ShortName; + } else { + orgShortName = _posMaster.orgChild4?.orgChild4ShortName; + } + } + const dest_item = await this.salaryRepo.findOne({ + where: { profileEmployeeId: item.refId }, + order: { order: "DESC" }, + }); + const before = null; + const data = new ProfileSalary(); + data.posNumCodeSit = _posNumCodeSit; + data.posNumCodeSitAbb = _posNumCodeSitAbb; + const meta = { + profileEmployeeId: profile.id, + amount: item.amount, + amountSpecial: item.amountSpecial, + commandId: item.commandId, + positionSalaryAmount: item.positionSalaryAmount, + mouthSalaryAmount: item.mouthSalaryAmount, + position: profile.positionTemp, + positionName: profile.positionTemp, + positionType: profile.posTypeNameTemp, + positionLevel: profile.posLevelNameTemp, + order: dest_item == null ? 1 : dest_item.order + 1, + orgRoot: orgRootRef?.orgRootName ?? null, + orgChild1: orgChild1Ref?.orgChild1Name ?? null, + orgChild2: orgChild2Ref?.orgChild2Name ?? null, + orgChild3: orgChild3Ref?.orgChild3Name ?? null, + orgChild4: orgChild4Ref?.orgChild4Name ?? null, + createdUserId: req.user.sub, + createdFullName: req.user.name, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + commandNo: item.commandNo, + commandYear: item.commandYear, + posNo: profile.posMasterNoTemp ?? "", + posNoAbb: orgShortName, + commandDateAffect: item.commandDateAffect, + commandDateSign: item.commandDateSign, + commandCode: item.commandCode, + commandName: item.commandName, + remark: item.remark, + }; + + Object.assign(data, meta); + const history = new ProfileSalaryHistory(); + Object.assign(history, { ...data, id: undefined }); + + await this.salaryRepo.save(data, { data: req }); + setLogDataDiff(req, { before, after: data }); + history.profileSalaryId = data.id; + await this.salaryHistoryRepo.save(history, { data: req }); + + const posMaster = await this.employeePosMasterRepository.findOne({ + where: { id: profile.posmasterIdTemp }, + relations: ["orgRoot", "orgChild1", "orgChild2", "orgChild3", "orgChild4"], + }); + if (posMaster == null) + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้"); + + const posMasterOld = await this.employeePosMasterRepository.findOne({ + where: { + current_holderId: profile.id, + orgRevisionId: posMaster.orgRevisionId, + }, + }); + if (posMasterOld != null) { + posMasterOld.current_holderId = null; + posMasterOld.lastUpdatedAt = new Date(); + } + // if (posMasterOld != null) posMasterOld.next_holderId = null; + + const positionOld = await this.employeePositionRepository.findOne({ + where: { + posMasterId: posMasterOld?.id, + positionIsSelected: true, + }, + }); + if (positionOld != null) { + positionOld.positionIsSelected = false; + await this.employeePositionRepository.save(positionOld); + } + + const checkPosition = await this.employeePositionRepository.find({ + where: { + posMasterId: profile.posmasterIdTemp, + positionIsSelected: true, + }, + }); + if (checkPosition.length > 0) { + const clearPosition = checkPosition.map((positions) => ({ + ...positions, + positionIsSelected: false, + })); + await this.employeePositionRepository.save(clearPosition); + } + + posMaster.current_holderId = profile.id; + posMaster.lastUpdatedAt = new Date(); + posMaster.next_holderId = null; + if (posMasterOld != null) { + await this.employeePosMasterRepository.save(posMasterOld); + await CreatePosMasterHistoryEmployee(posMasterOld.id, req); + } + await this.employeePosMasterRepository.save(posMaster); + await CreatePosMasterHistoryEmployee(posMaster.id, req); + + const clsTempPosmaster = await this.employeeTempPosMasterRepository.find({ + where: { + current_holderId: profile.id, + orgRevisionId: posMaster.orgRevisionId, + }, + }); + + if (clsTempPosmaster.length > 0) { + const clearTempPosmaster = clsTempPosmaster.map((posMasterTemp) => ({ + ...posMasterTemp, + current_holderId: null, + next_holderId: null, + })); + await this.employeeTempPosMasterRepository.save(clearTempPosmaster); + + const checkTempPosition = await this.employeePositionRepository.find({ + where: { + posMasterTempId: In(clearTempPosmaster.map((x) => x.id)), + positionIsSelected: true, + }, + }); + if (checkTempPosition.length > 0) { + const clearTempPosition = checkTempPosition.map((positions) => ({ + ...positions, + positionIsSelected: false, + })); + await this.employeePositionRepository.save(clearTempPosition); + } + await Promise.all( + clsTempPosmaster.map( + async (posMasterTemp) => + await CreatePosMasterHistoryEmployeeTemp(posMasterTemp.id, req), + ), + ); + } + + const positionNew = await this.employeePositionRepository.findOne({ + where: { + id: profile.positionIdTemp, + posMasterId: profile.posmasterIdTemp, + }, + }); + + if (positionNew != null) { + // Create Keycloak + const checkUser = await getUserByUsername(profile.citizenId); + if (checkUser.length == 0) { + let password = profile.citizenId; + if (profile.birthDate != null) { + // const gregorianYear = profile.birthDate.getFullYear() + 543; + + // const formattedDate = + // profile.birthDate.toISOString().slice(8, 10) + + // profile.birthDate.toISOString().slice(5, 7) + + // gregorianYear; + // password = formattedDate; + const _date = new Date(profile.birthDate.toDateString()) + .getDate() + .toString() + .padStart(2, "0"); + const _month = (new Date(profile.birthDate.toDateString()).getMonth() + 1) + .toString() + .padStart(2, "0"); + const _year = new Date(profile.birthDate.toDateString()).getFullYear() + 543; + password = `${_date}${_month}${_year}`; + } + // กรอง "." ออกจาก firstName ก่อนส่งไป keycloak + const sanitizedFirstName = profile.firstName?.replace(/\./g, "") ?? ""; + const userKeycloakId = await createUser(profile.citizenId, password, { + firstName: sanitizedFirstName, + lastName: profile.lastName, + // email: profile.email, + }); + // if (typeof userKeycloakId !== "string") { + // throw new Error(userKeycloakId.errorMessage); + // } + const list = await getRoles(); + if (!Array.isArray(list)) + throw new Error("Failed. Cannot get role(s) data from the server."); + const result = await addUserRoles( + userKeycloakId, + list + .filter((v) => v.name === "USER") + .map((x) => ({ + id: x.id, + name: x.name, + })), + ); + // if (!result) throw new Error("Failed. Cannot set user's role."); + profile.keycloak = + userKeycloakId && typeof userKeycloakId == "string" ? userKeycloakId : ""; + profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : []; + // End Create Keycloak + } else { + const rolesData = await getRoleMappings(checkUser[0].id); + if (rolesData) { + const _roleKeycloak = await this.roleKeycloakRepo.find({ + where: { name: In(rolesData.map((x: any) => x.name)) }, + }); + profile.roleKeycloaks = + _roleKeycloak && _roleKeycloak.length > 0 ? _roleKeycloak : []; + } + profile.keycloak = checkUser[0].id; + } + positionNew.positionIsSelected = true; + profile.posLevelId = positionNew.posLevelId; + profile.posTypeId = positionNew.posTypeId; + profile.position = positionNew.positionName; + profile.employeeOc = posMaster?.orgRoot?.orgRootName ?? null; + profile.positionEmployeePositionId = positionNew.positionName; + profile.statusTemp = "DONE"; + profile.employeeClass = "PERM"; + const _null: any = null; + profile.employeeWage = item.amount == null ? _null : item.amount.toString(); + profile.dateStart = _command ? _command.commandExcecuteDate : new Date(); + profile.dateAppoint = _command ? _command.commandExcecuteDate : new Date(); + profile.amount = item.amount == null ? _null : item.amount; + profile.amountSpecial = item.amountSpecial == null ? _null : item.amountSpecial; + _reqBody.push({ + profileId: profile.id, + prefix: profile.prefix, + firstName: profile.firstName, + lastName: profile.lastName, + citizenId: profile.citizenId, + root: posMaster.orgRoot.orgRootName, + rootId: posMaster.orgRootId, + rootShortName: posMaster.orgRoot.orgRootShortName, + rootDnaId: posMaster.orgRoot?.ancestorDNA ?? _null, + child1DnaId: posMaster.orgChild1?.ancestorDNA ?? _null, + child2DnaId: posMaster.orgChild2?.ancestorDNA ?? _null, + child3DnaId: posMaster.orgChild3?.ancestorDNA ?? _null, + child4DnaId: posMaster.orgChild4?.ancestorDNA ?? _null, + }); + await this.profileEmployeeRepository.save(profile); + await this.employeePositionRepository.save(positionNew); + await CreatePosMasterHistoryEmployee(posMaster.id, req); + //ลบออกคนออกจากโครงสร้างลูกจ้างชั่วคราว + const posMasterTemp = await this.employeeTempPosMasterRepository.findOne({ + where: { + orgRevisionId: orgRevision?.id, + current_holderId: profile.id, + }, + }); + if (posMasterTemp) { + await this.employeeTempPosMasterRepository.update(posMasterTemp.id, { + current_holderId: _null, + }); + await CreatePosMasterHistoryEmployeeTemp(posMasterTemp.id, req); + } + } + }), + ); + await new CallAPI() + .PostData(req, "/placement/appointment/employee-appoint-21/report/excecute", { + profileEmps: _reqBody, + }) + .catch((error) => { + throw new Error("Failed. Cannot update status. ", error); + }); return new HttpSuccess(); } @@ -4544,14 +8144,153 @@ export class CommandController extends Controller { }[]; }, ) { - /** - * Thin wrapper — เรียก ExecuteOrgCommandService.executeCommand40Officer (C-PM-40) - * ทั้ง endpoint นี้และ consumer ใน rabbitmq ใช้ service ตัวเดียวกัน (Linear Flow) - */ - await new ExecuteOrgCommandService().executeCommand40Officer(body.refIds, { - user: { sub: req.user.sub, name: req.user.name }, - req, + // 1. Bulk update status + await this.posMasterActRepository.update( + { id: In(body.refIds.map((x) => x.refId)) }, + { statusReport: "DONE" }, + ); + + // 2. ดึงข้อมูลครบทุก relation ที่จำเป็น + const posMasters = await this.posMasterActRepository.find({ + where: { id: In(body.refIds.map((x) => x.refId)) }, + relations: [ + "posMasterChild", + "posMasterChild.current_holder", + "posMaster", + "posMaster.current_holder", + "posMaster.positions", + "posMaster.orgRoot", + "posMaster.orgChild1", + "posMaster.orgChild2", + "posMaster.orgChild3", + "posMaster.orgChild4", + ], }); + + // 3. ตรวจสอบว่ามี body.refIds[0] หรือไม่ + const firstRef = body.refIds[0]; + if (!firstRef) { + throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบข้อมูล refIds"); + } + + const profileIdsToClearCache = new Set(); + + await Promise.all( + posMasters.map(async (item) => { + // 4. ตรวจสอบข้อมูลที่จำเป็นทั้งหมด + if (!item.posMasterChild?.current_holderId || !item.posMaster) { + console.warn(`ข้ามรายการ ${item.id}: ข้อมูลไม่ครบ`); + return; + } + + if (item.posMasterChild.current_holderId) { + profileIdsToClearCache.add(item.posMasterChild.current_holderId); + } + + // 5. สร้าง orgShortName แบบปลอดภัย + const orgShortName = + [ + item.posMaster?.orgChild4?.orgChild4ShortName, + item.posMaster?.orgChild3?.orgChild3ShortName, + item.posMaster?.orgChild2?.orgChild2ShortName, + item.posMaster?.orgChild1?.orgChild1ShortName, + item.posMaster?.orgRoot?.orgRootShortName, + ].find(Boolean) ?? ""; + + // 6. หา position ที่ถูกเลือกแบบปลอดภัย + const selectedPosition = item.posMaster?.positions; + const positionName = + selectedPosition + ?.map((pos) => pos.positionName) + .filter(Boolean) + .join(", ") ?? "-"; + + // 7. สร้าง metaAct แบบปลอดภัย + const metaAct = { + profileId: item.posMasterChild.current_holderId, + dateStart: firstRef.commandDateAffect ?? null, + dateEnd: null, + position: positionName, + status: true, + commandId: firstRef.commandId ?? null, + createdUserId: req.user?.sub ?? null, + createdFullName: req.user?.name ?? null, + lastUpdateUserId: req.user?.sub ?? null, + lastUpdateFullName: req.user?.name ?? null, + createdAt: new Date(), + lastUpdatedAt: new Date(), + commandNo: firstRef.commandNo ?? null, + refCommandNo: `${firstRef.commandNo ?? ""}/${firstRef.commandYear ? Extension.ToThaiYear(firstRef.commandYear) : ""}`, + commandYear: firstRef.commandYear ? Extension.ToThaiYear(firstRef.commandYear) : null, + posNo: + orgShortName && item.posMaster?.posMasterNo + ? `${orgShortName} ${item.posMaster.posMasterNo}` + : item.posMaster?.posMasterNo ?? "-", + posNoAbb: orgShortName, + commandDateAffect: firstRef.commandDateAffect ?? null, + commandDateSign: firstRef.commandDateSign ?? null, + commandCode: firstRef.commandCode ?? null, + commandName: firstRef.commandName ?? null, + remark: firstRef.remark ?? null, + }; + + try { + // 8. ปิดสถานะรักษาการ + const existingActPositions = await this.actpositionRepository.find({ + where: { + profileId: item.posMasterChild.current_holderId, + status: true, + isDeleted: false, + }, + }); + + if (existingActPositions.length > 0) { + const updatedActPositions = existingActPositions.map((_data) => ({ + ..._data, + status: false, + dateEnd: new Date(), + })); + + await this.actpositionRepository.save(updatedActPositions); + } + + // 9. บันทึกข้อมูลใหม่ + const dataAct = new ProfileActposition(); + Object.assign(dataAct, metaAct); + + const historyAct = new ProfileActpositionHistory(); + Object.assign(historyAct, { ...dataAct, id: undefined }); + + await this.actpositionRepository.save(dataAct); + historyAct.profileActpositionId = dataAct.id; + await this.actpositionHistoryRepository.save(historyAct); + } catch (error) { + console.error(`Error processing item ${item.id}:`, error); + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + `เกิดข้อผิดพลาดในการประมวลผลรายการ ${item.id}`, + ); + } + }), + ); + + if (profileIdsToClearCache.size > 0) { + await Promise.all( + Array.from(profileIdsToClearCache).map(async (profileId) => { + const redisClient = await this.redis.createClient({ + host: REDIS_HOST, + port: REDIS_PORT, + }); + + const delAsync = promisify(redisClient.del).bind(redisClient); + await delAsync("role_" + profileId); + await delAsync("menu_" + profileId); + + redisClient.quit(); + }), + ); + } + return new HttpSuccess(); } @@ -4702,14 +8441,166 @@ export class CommandController extends Controller { }[]; }, ) { - /** - * Thin wrapper — เรียก ExecuteOrgCommandService.executeCommand38Officer (C-PM-38) - * ทั้ง endpoint นี้และ consumer ใน rabbitmq ใช้ service ตัวเดียวกัน (Linear Flow) - */ - await new ExecuteOrgCommandService().executeCommand38Officer(body.refIds, { - user: { sub: req.user.sub, name: req.user.name }, - req, + let _posNumCodeSit: string = ""; + let _posNumCodeSitAbb: string = ""; + const _command = await this.commandRepository.findOne({ + where: { id: body.refIds.find((x) => x.commandId)?.commandId ?? "" }, }); + if (_command) { + if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { + const orgRootDeputy = await this.orgRootRepository.findOne({ + where: { + isDeputy: true, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: ["orgRevision"], + }); + _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; + _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; + } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { + _posNumCodeSit = "กรุงเทพมหานคร"; + _posNumCodeSitAbb = "กทม."; + } else { + let _profileAdmin = await this.profileRepository.findOne({ + where: { + keycloak: _command?.createdUserId.toString(), + current_holders: { + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + }, + relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], + }); + _posNumCodeSit = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? + ""; + _posNumCodeSitAbb = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot + .orgRootShortName ?? ""; + } + } + await Promise.all( + body.refIds.map(async (item) => { + const posMaster = await this.posMasterRepository.findOne({ + where: { id: item.refId }, + relations: [ + "orgRoot", + "orgChild1", + "orgChild2", + "orgChild3", + "orgChild4", + "current_holder", + "current_holder.posLevel", + "current_holder.posType", + ], + }); + if (!posMaster) { + throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบตำแหน่งดังกล่าว"); + } + if (posMaster.next_holderId != null) { + const orgRootRef = posMaster?.orgRoot ?? null; + const orgChild1Ref = posMaster?.orgChild1 ?? null; + const orgChild2Ref = posMaster?.orgChild2 ?? null; + const orgChild3Ref = posMaster?.orgChild3 ?? null; + const orgChild4Ref = posMaster?.orgChild4 ?? null; + const shortName = + posMaster != null && posMaster.orgChild4 != null + ? `${posMaster.orgChild4.orgChild4ShortName}` + : posMaster != null && posMaster.orgChild3 != null + ? `${posMaster.orgChild3.orgChild3ShortName}` + : posMaster != null && posMaster.orgChild2 != null + ? `${posMaster.orgChild2.orgChild2ShortName}` + : posMaster != null && posMaster.orgChild1 != null + ? `${posMaster.orgChild1.orgChild1ShortName}` + : posMaster != null && posMaster?.orgRoot != null + ? `${posMaster.orgRoot.orgRootShortName}` + : null; + const profile = await this.profileRepository.findOne({ + where: { id: posMaster.next_holderId }, + }); + const position = await this.positionRepository.findOne({ + where: { + posMasterId: posMaster.id, + positionIsSelected: true, + }, + relations: ["posType", "posLevel"], + }); + const dest_item = await this.salaryRepo.findOne({ + where: { profileId: profile?.id }, + order: { order: "DESC" }, + }); + const before = null; + const data = new ProfileSalary(); + data.posNumCodeSit = _posNumCodeSit; + data.posNumCodeSitAbb = _posNumCodeSitAbb; + const meta = { + profileId: profile?.id, + date: new Date(), + amount: item.amount, + commandId: item.commandId, + positionSalaryAmount: item.positionSalaryAmount, + mouthSalaryAmount: item.mouthSalaryAmount, + position: position?.positionName ?? null, + positionType: position?.posType?.posTypeName ?? null, + positionLevel: position?.posLevel?.posLevelName ?? null, + order: dest_item == null ? 1 : dest_item.order + 1, + orgRoot: orgRootRef?.orgRootName ?? null, + orgChild1: orgChild1Ref?.orgChild1Name ?? null, + orgChild2: orgChild2Ref?.orgChild2Name ?? null, + orgChild3: orgChild3Ref?.orgChild3Name ?? null, + orgChild4: orgChild4Ref?.orgChild4Name ?? null, + createdUserId: req.user.sub, + createdFullName: req.user.name, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + commandNo: item.commandNo, + commandYear: item.commandYear, + posNo: posMaster.posMasterNo, + posNoAbb: shortName, + commandDateAffect: item.commandDateAffect, + commandDateSign: item.commandDateSign, + commandCode: item.commandCode, + commandName: item.commandName, + remark: item.remark, + }; + Object.assign(data, meta); + const history = new ProfileSalaryHistory(); + Object.assign(history, { ...data, id: undefined }); + + await this.salaryRepo.save(data, { data: req }); + setLogDataDiff(req, { before, after: data }); + history.profileSalaryId = data.id; + await this.salaryHistoryRepo.save(history, { data: req }); + + // if (profile != null) { + // profile.position = position?.positionName ?? ""; + // profile.posTypeId = position?.posTypeId ?? ""; + // profile.posLevelId = position?.posLevelId ?? ""; + // profile.lastUpdateUserId = req.user.sub; + // profile.lastUpdateFullName = req.user.name; + // profile.lastUpdatedAt = new Date(); + // await this.profileRepository.save(profile); + // } + } + }), + ); + // const posMasters = await this.posMasterRepository.find({ + // where: { id: In(body.refIds.map((x) => x.refId)) }, + // }); + // const data = posMasters.map((_data) => ({ + // ..._data, + // current_holderId: _data.next_holderId, + // next_holderId: null, + // statusReport: "PENDING", + // })); + // await this.posMasterRepository.save(data); return new HttpSuccess(); } diff --git a/src/controllers/EmployeePositionController.ts b/src/controllers/EmployeePositionController.ts index 5809841c..ecf11619 100644 --- a/src/controllers/EmployeePositionController.ts +++ b/src/controllers/EmployeePositionController.ts @@ -39,7 +39,8 @@ import { RequestWithUser } from "../middlewares/user"; import permission from "../interfaces/permission"; import { setLogDataDiff } from "../interfaces/utils"; import { - CreatePosMasterHistoryEmployee + CreatePosMasterHistoryEmployee, + CreatePosMasterHistoryOfficer, } from "../services/PositionService"; import { PosMasterEmployeeHistory } from "../entities/PosMasterEmployeeHistory"; import { KeycloakAttributeService } from "../services/KeycloakAttributeService"; @@ -2376,47 +2377,26 @@ export class EmployeePositionController extends Controller { dataMaster.positions.forEach(async (position) => { if (position.id === requestBody.position) { position.positionIsSelected = true; + const profile = await this.profileRepository.findOne({ + where: { id: requestBody.profileId }, + }); + if (profile != null) { + const _null: any = null; + profile.posLevelId = position?.posLevelId ?? _null; + profile.posTypeId = position?.posTypeId ?? _null; + profile.position = position?.positionName ?? _null; + await this.profileRepository.save(profile); + } } else { position.positionIsSelected = false; } await this.employeePositionRepository.save(position); }); - const _null: any = null; - const before = null; + dataMaster.isSit = requestBody.isSit; + dataMaster.current_holderId = requestBody.profileId; dataMaster.lastUpdatedAt = new Date(); - - //เช็คถ้า revision ปัจจุบันให้ปั๊มที่ profile - const chkRevision = await this.orgRevisionRepository.findOne({ - where: { id: dataMaster.orgRevisionId }, - }); - if (chkRevision?.orgRevisionIsCurrent) { - const _profile = await this.profileRepository.findOne({ - where: { id: requestBody.profileId }, - }); - if (_profile) { - let _position = await this.employeePositionRepository.findOne({ - where: { id: requestBody.position, posMasterId: requestBody.posMaster }, - }); - if (_position) { - - // ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ - if (!dataMaster.isSit) { - _profile.position = _position.positionName; - _profile.posTypeId = _position.posTypeId; - _profile.posLevelId = _position.posLevelId; - } - await this.profileRepository.save(_profile); - setLogDataDiff(request, { before, after: _profile }); - } - } - dataMaster.current_holderId = requestBody.profileId; - dataMaster.next_holderId = _null; - } else { - dataMaster.next_holderId = requestBody.profileId; - dataMaster.current_holderId = _null; - } - + // dataMaster.next_holderId = requestBody.profileId; await this.employeePosMasterRepository.save(dataMaster); await CreatePosMasterHistoryEmployee(dataMaster.id, request); diff --git a/src/controllers/OrgChild1Controller.ts b/src/controllers/OrgChild1Controller.ts index 6817d6fd..6e44af05 100644 --- a/src/controllers/OrgChild1Controller.ts +++ b/src/controllers/OrgChild1Controller.ts @@ -74,11 +74,6 @@ export class OrgChild1Controller { DIVISION_CODE: orgChild1.DIVISION_CODE, SECTION_CODE: orgChild1.SECTION_CODE, JOB_CODE: orgChild1.JOB_CODE, - ROOT_CODE: orgChild1.ROOT_CODE, - CHILD1_CODE: orgChild1.CHILD1_CODE, - CHILD2_CODE: orgChild1.CHILD2_CODE, - CHILD3_CODE: orgChild1.CHILD3_CODE, - CHILD4_CODE: orgChild1.CHILD4_CODE, orgCode: orgChild1.orgRoot.orgRootCode + orgChild1.orgChild1Code, }; return new HttpSuccess(getOrgChild1); @@ -351,11 +346,6 @@ export class OrgChild1Controller { DIVISION_CODE: requestBody.DIVISION_CODE != null ? requestBody.DIVISION_CODE : _null, SECTION_CODE: requestBody.SECTION_CODE != null ? requestBody.SECTION_CODE : _null, JOB_CODE: requestBody.JOB_CODE != null ? requestBody.JOB_CODE : _null, - ROOT_CODE: requestBody.ROOT_CODE != null ? requestBody.ROOT_CODE : _null, - CHILD1_CODE: requestBody.CHILD1_CODE != null ? requestBody.CHILD1_CODE : _null, - CHILD2_CODE: requestBody.CHILD2_CODE != null ? requestBody.CHILD2_CODE : _null, - CHILD3_CODE: requestBody.CHILD3_CODE != null ? requestBody.CHILD3_CODE : _null, - CHILD4_CODE: requestBody.CHILD4_CODE != null ? requestBody.CHILD4_CODE : _null, isOfficer: requestBody.isOfficer, isInformation: requestBody.isInformation, orgChild1PhoneEx: requestBody.orgChild1PhoneEx, diff --git a/src/controllers/OrgChild2Controller.ts b/src/controllers/OrgChild2Controller.ts index 650cc96e..28ce564f 100644 --- a/src/controllers/OrgChild2Controller.ts +++ b/src/controllers/OrgChild2Controller.ts @@ -85,11 +85,6 @@ export class OrgChild2Controller extends Controller { DIVISION_CODE: orgChild2.DIVISION_CODE, SECTION_CODE: orgChild2.SECTION_CODE, JOB_CODE: orgChild2.JOB_CODE, - ROOT_CODE: orgChild2.ROOT_CODE, - CHILD1_CODE: orgChild2.CHILD1_CODE, - CHILD2_CODE: orgChild2.CHILD2_CODE, - CHILD3_CODE: orgChild2.CHILD3_CODE, - CHILD4_CODE: orgChild2.CHILD4_CODE, orgCode: orgChild2.orgRoot.orgRootCode + orgChild2.orgChild2Code, }; return new HttpSuccess(getOrgChild2); @@ -257,11 +252,6 @@ export class OrgChild2Controller extends Controller { DIVISION_CODE: requestBody.DIVISION_CODE != null ? requestBody.DIVISION_CODE : _null, SECTION_CODE: requestBody.SECTION_CODE != null ? requestBody.SECTION_CODE : _null, JOB_CODE: requestBody.JOB_CODE != null ? requestBody.JOB_CODE : _null, - ROOT_CODE: requestBody.ROOT_CODE != null ? requestBody.ROOT_CODE : _null, - CHILD1_CODE: requestBody.CHILD1_CODE != null ? requestBody.CHILD1_CODE : _null, - CHILD2_CODE: requestBody.CHILD2_CODE != null ? requestBody.CHILD2_CODE : _null, - CHILD3_CODE: requestBody.CHILD3_CODE != null ? requestBody.CHILD3_CODE : _null, - CHILD4_CODE: requestBody.CHILD4_CODE != null ? requestBody.CHILD4_CODE : _null, orgChild2PhoneEx: requestBody.orgChild2PhoneEx, orgChild2PhoneIn: requestBody.orgChild2PhoneIn, orgChild2Fax: requestBody.orgChild2Fax, diff --git a/src/controllers/OrgChild3Controller.ts b/src/controllers/OrgChild3Controller.ts index d8cc5939..4ed10804 100644 --- a/src/controllers/OrgChild3Controller.ts +++ b/src/controllers/OrgChild3Controller.ts @@ -69,11 +69,6 @@ export class OrgChild3Controller { DIVISION_CODE: orgChild3.DIVISION_CODE, SECTION_CODE: orgChild3.SECTION_CODE, JOB_CODE: orgChild3.JOB_CODE, - ROOT_CODE: orgChild3.ROOT_CODE, - CHILD1_CODE: orgChild3.CHILD1_CODE, - CHILD2_CODE: orgChild3.CHILD2_CODE, - CHILD3_CODE: orgChild3.CHILD3_CODE, - CHILD4_CODE: orgChild3.CHILD4_CODE, orgCode: orgChild3.orgRoot.orgRootCode + orgChild3.orgChild3Code, }; return new HttpSuccess(getOrgChild3); @@ -212,11 +207,6 @@ export class OrgChild3Controller { DIVISION_CODE: requestBody.DIVISION_CODE != null ? requestBody.DIVISION_CODE : _null, SECTION_CODE: requestBody.SECTION_CODE != null ? requestBody.SECTION_CODE : _null, JOB_CODE: requestBody.JOB_CODE != null ? requestBody.JOB_CODE : _null, - ROOT_CODE: requestBody.ROOT_CODE != null ? requestBody.ROOT_CODE : _null, - CHILD1_CODE: requestBody.CHILD1_CODE != null ? requestBody.CHILD1_CODE : _null, - CHILD2_CODE: requestBody.CHILD2_CODE != null ? requestBody.CHILD2_CODE : _null, - CHILD3_CODE: requestBody.CHILD3_CODE != null ? requestBody.CHILD3_CODE : _null, - CHILD4_CODE: requestBody.CHILD4_CODE != null ? requestBody.CHILD4_CODE : _null, orgChild3PhoneEx: requestBody.orgChild3PhoneEx, orgChild3PhoneIn: requestBody.orgChild3PhoneIn, orgChild3Fax: requestBody.orgChild3Fax, diff --git a/src/controllers/OrgChild4Controller.ts b/src/controllers/OrgChild4Controller.ts index 4dac9e82..e18c15f9 100644 --- a/src/controllers/OrgChild4Controller.ts +++ b/src/controllers/OrgChild4Controller.ts @@ -82,11 +82,6 @@ export class OrgChild4Controller extends Controller { DIVISION_CODE: orgChild4.DIVISION_CODE, SECTION_CODE: orgChild4.SECTION_CODE, JOB_CODE: orgChild4.JOB_CODE, - ROOT_CODE: orgChild4.ROOT_CODE, - CHILD1_CODE: orgChild4.CHILD1_CODE, - CHILD2_CODE: orgChild4.CHILD2_CODE, - CHILD3_CODE: orgChild4.CHILD3_CODE, - CHILD4_CODE: orgChild4.CHILD4_CODE, orgCode: orgChild4.orgRoot.orgRootCode + orgChild4.orgChild4Code, }; return new HttpSuccess(getOrgChild4); @@ -259,11 +254,6 @@ export class OrgChild4Controller extends Controller { DIVISION_CODE: requestBody.DIVISION_CODE != null ? requestBody.DIVISION_CODE : _null, SECTION_CODE: requestBody.SECTION_CODE != null ? requestBody.SECTION_CODE : _null, JOB_CODE: requestBody.JOB_CODE != null ? requestBody.JOB_CODE : _null, - ROOT_CODE: requestBody.ROOT_CODE != null ? requestBody.ROOT_CODE : _null, - CHILD1_CODE: requestBody.CHILD1_CODE != null ? requestBody.CHILD1_CODE : _null, - CHILD2_CODE: requestBody.CHILD2_CODE != null ? requestBody.CHILD2_CODE : _null, - CHILD3_CODE: requestBody.CHILD3_CODE != null ? requestBody.CHILD3_CODE : _null, - CHILD4_CODE: requestBody.CHILD4_CODE != null ? requestBody.CHILD4_CODE : _null, orgChild4PhoneEx: requestBody.orgChild4PhoneEx, orgChild4PhoneIn: requestBody.orgChild4PhoneIn, orgChild4Fax: requestBody.orgChild4Fax, diff --git a/src/controllers/OrgRootController.ts b/src/controllers/OrgRootController.ts index 97f3f6a3..45bf1436 100644 --- a/src/controllers/OrgRootController.ts +++ b/src/controllers/OrgRootController.ts @@ -83,11 +83,6 @@ export class OrgRootController extends Controller { DIVISION_CODE: orgRoot.DIVISION_CODE, SECTION_CODE: orgRoot.SECTION_CODE, JOB_CODE: orgRoot.JOB_CODE, - ROOT_CODE: orgRoot.ROOT_CODE, - CHILD1_CODE: orgRoot.CHILD1_CODE, - CHILD2_CODE: orgRoot.CHILD2_CODE, - CHILD3_CODE: orgRoot.CHILD3_CODE, - CHILD4_CODE: orgRoot.CHILD4_CODE, orgCode: orgRoot.orgRootCode + "00", }; return new HttpSuccess(getOrgRoot); @@ -355,11 +350,6 @@ export class OrgRootController extends Controller { DIVISION_CODE: requestBody.DIVISION_CODE != null ? requestBody.DIVISION_CODE : _null, SECTION_CODE: requestBody.SECTION_CODE != null ? requestBody.SECTION_CODE : _null, JOB_CODE: requestBody.JOB_CODE != null ? requestBody.JOB_CODE : _null, - ROOT_CODE: requestBody.ROOT_CODE != null ? requestBody.ROOT_CODE : _null, - CHILD1_CODE: requestBody.CHILD1_CODE != null ? requestBody.CHILD1_CODE : _null, - CHILD2_CODE: requestBody.CHILD2_CODE != null ? requestBody.CHILD2_CODE : _null, - CHILD3_CODE: requestBody.CHILD3_CODE != null ? requestBody.CHILD3_CODE : _null, - CHILD4_CODE: requestBody.CHILD4_CODE != null ? requestBody.CHILD4_CODE : _null, }); await this.orgRootRepository.save(orgRoot, { data: request }); setLogDataDiff(request, { before, after: orgRoot }); diff --git a/src/controllers/OrganizationController.ts b/src/controllers/OrganizationController.ts index 8358313b..7edc5998 100644 --- a/src/controllers/OrganizationController.ts +++ b/src/controllers/OrganizationController.ts @@ -468,157 +468,157 @@ export class OrganizationController extends Controller { const orgChild1Data = orgRootIds && orgRootIds.length > 0 ? await AppDataSource.getRepository(OrgChild1) - .createQueryBuilder("orgChild1") - .where("orgChild1.orgRootId IN (:...ids)", { ids: orgRootIds }) - // .andWhere( - // _data.child1 != undefined && _data.child1 != null - // ? _data.child1[0] != null - // ? `orgChild1.id IN (:...node)` - // : `orgChild1.id is null` - // : "1=1", - // { - // node: _data.child1, - // }, - // ) - .select([ - "orgChild1.id", - "orgChild1.isOfficer", - "orgChild1.isInformation", - "orgChild1.orgChild1Name", - "orgChild1.orgChild1ShortName", - "orgChild1.orgChild1Code", - "orgChild1.orgChild1Order", - "orgChild1.orgChild1PhoneEx", - "orgChild1.orgChild1PhoneIn", - "orgChild1.orgChild1Fax", - "orgChild1.orgRootId", - "orgChild1.orgChild1Rank", - "orgChild1.orgChild1RankSub", - "orgChild1.DEPARTMENT_CODE", - "orgChild1.DIVISION_CODE", - "orgChild1.SECTION_CODE", - "orgChild1.JOB_CODE", - "orgChild1.responsibility", - ]) - .orderBy("orgChild1.orgChild1Order", "ASC") - .getMany() + .createQueryBuilder("orgChild1") + .where("orgChild1.orgRootId IN (:...ids)", { ids: orgRootIds }) + // .andWhere( + // _data.child1 != undefined && _data.child1 != null + // ? _data.child1[0] != null + // ? `orgChild1.id IN (:...node)` + // : `orgChild1.id is null` + // : "1=1", + // { + // node: _data.child1, + // }, + // ) + .select([ + "orgChild1.id", + "orgChild1.isOfficer", + "orgChild1.isInformation", + "orgChild1.orgChild1Name", + "orgChild1.orgChild1ShortName", + "orgChild1.orgChild1Code", + "orgChild1.orgChild1Order", + "orgChild1.orgChild1PhoneEx", + "orgChild1.orgChild1PhoneIn", + "orgChild1.orgChild1Fax", + "orgChild1.orgRootId", + "orgChild1.orgChild1Rank", + "orgChild1.orgChild1RankSub", + "orgChild1.DEPARTMENT_CODE", + "orgChild1.DIVISION_CODE", + "orgChild1.SECTION_CODE", + "orgChild1.JOB_CODE", + "orgChild1.responsibility", + ]) + .orderBy("orgChild1.orgChild1Order", "ASC") + .getMany() : []; const orgChild1Ids = orgChild1Data.map((orgChild1) => orgChild1.id) || null; const orgChild2Data = orgChild1Ids && orgChild1Ids.length > 0 ? await AppDataSource.getRepository(OrgChild2) - .createQueryBuilder("orgChild2") - .where("orgChild2.orgChild1Id IN (:...ids)", { ids: orgChild1Ids }) - // .andWhere( - // _data.child2 != undefined && _data.child2 != null - // ? _data.child2[0] != null - // ? `orgChild2.id IN (:...node)` - // : `orgChild2.id is null` - // : "1=1", - // { - // node: _data.child2, - // }, - // ) - .select([ - "orgChild2.id", - "orgChild2.orgChild2Name", - "orgChild2.orgChild2ShortName", - "orgChild2.orgChild2Code", - "orgChild2.orgChild2Order", - "orgChild2.orgChild2PhoneEx", - "orgChild2.orgChild2PhoneIn", - "orgChild2.orgChild2Fax", - "orgChild2.orgRootId", - "orgChild2.orgChild2Rank", - "orgChild2.orgChild2RankSub", - "orgChild2.DEPARTMENT_CODE", - "orgChild2.DIVISION_CODE", - "orgChild2.SECTION_CODE", - "orgChild2.JOB_CODE", - "orgChild2.orgChild1Id", - "orgChild2.responsibility", - ]) - .orderBy("orgChild2.orgChild2Order", "ASC") - .getMany() + .createQueryBuilder("orgChild2") + .where("orgChild2.orgChild1Id IN (:...ids)", { ids: orgChild1Ids }) + // .andWhere( + // _data.child2 != undefined && _data.child2 != null + // ? _data.child2[0] != null + // ? `orgChild2.id IN (:...node)` + // : `orgChild2.id is null` + // : "1=1", + // { + // node: _data.child2, + // }, + // ) + .select([ + "orgChild2.id", + "orgChild2.orgChild2Name", + "orgChild2.orgChild2ShortName", + "orgChild2.orgChild2Code", + "orgChild2.orgChild2Order", + "orgChild2.orgChild2PhoneEx", + "orgChild2.orgChild2PhoneIn", + "orgChild2.orgChild2Fax", + "orgChild2.orgRootId", + "orgChild2.orgChild2Rank", + "orgChild2.orgChild2RankSub", + "orgChild2.DEPARTMENT_CODE", + "orgChild2.DIVISION_CODE", + "orgChild2.SECTION_CODE", + "orgChild2.JOB_CODE", + "orgChild2.orgChild1Id", + "orgChild2.responsibility", + ]) + .orderBy("orgChild2.orgChild2Order", "ASC") + .getMany() : []; const orgChild2Ids = orgChild2Data.map((orgChild2) => orgChild2.id) || null; const orgChild3Data = orgChild2Ids && orgChild2Ids.length > 0 ? await AppDataSource.getRepository(OrgChild3) - .createQueryBuilder("orgChild3") - .where("orgChild3.orgChild2Id IN (:...ids)", { ids: orgChild2Ids }) - // .andWhere( - // _data.child3 != undefined && _data.child3 != null - // ? _data.child3[0] != null - // ? `orgChild3.id IN (:...node)` - // : `orgChild3.id is null` - // : "1=1", - // { - // node: _data.child3, - // }, - // ) - .select([ - "orgChild3.id", - "orgChild3.orgChild3Name", - "orgChild3.orgChild3ShortName", - "orgChild3.orgChild3Code", - "orgChild3.orgChild3Order", - "orgChild3.orgChild3PhoneEx", - "orgChild3.orgChild3PhoneIn", - "orgChild3.orgChild3Fax", - "orgChild3.orgRootId", - "orgChild3.orgChild3Rank", - "orgChild3.orgChild3RankSub", - "orgChild3.DEPARTMENT_CODE", - "orgChild3.DIVISION_CODE", - "orgChild3.SECTION_CODE", - "orgChild3.JOB_CODE", - "orgChild3.orgChild2Id", - "orgChild3.responsibility", - ]) - .orderBy("orgChild3.orgChild3Order", "ASC") - .getMany() + .createQueryBuilder("orgChild3") + .where("orgChild3.orgChild2Id IN (:...ids)", { ids: orgChild2Ids }) + // .andWhere( + // _data.child3 != undefined && _data.child3 != null + // ? _data.child3[0] != null + // ? `orgChild3.id IN (:...node)` + // : `orgChild3.id is null` + // : "1=1", + // { + // node: _data.child3, + // }, + // ) + .select([ + "orgChild3.id", + "orgChild3.orgChild3Name", + "orgChild3.orgChild3ShortName", + "orgChild3.orgChild3Code", + "orgChild3.orgChild3Order", + "orgChild3.orgChild3PhoneEx", + "orgChild3.orgChild3PhoneIn", + "orgChild3.orgChild3Fax", + "orgChild3.orgRootId", + "orgChild3.orgChild3Rank", + "orgChild3.orgChild3RankSub", + "orgChild3.DEPARTMENT_CODE", + "orgChild3.DIVISION_CODE", + "orgChild3.SECTION_CODE", + "orgChild3.JOB_CODE", + "orgChild3.orgChild2Id", + "orgChild3.responsibility", + ]) + .orderBy("orgChild3.orgChild3Order", "ASC") + .getMany() : []; const orgChild3Ids = orgChild3Data.map((orgChild3) => orgChild3.id) || null; const orgChild4Data = orgChild3Ids && orgChild3Ids.length > 0 ? await AppDataSource.getRepository(OrgChild4) - .createQueryBuilder("orgChild4") - .where("orgChild4.orgChild3Id IN (:...ids)", { ids: orgChild3Ids }) - // .andWhere( - // _data.child4 != undefined && _data.child4 != null - // ? _data.child4[0] != null - // ? `orgChild4.id IN (:...node)` - // : `orgChild4.id is null` - // : "1=1", - // { - // node: _data.child4, - // }, - // ) - .select([ - "orgChild4.id", - "orgChild4.orgChild4Name", - "orgChild4.orgChild4ShortName", - "orgChild4.orgChild4Code", - "orgChild4.orgChild4Order", - "orgChild4.orgChild4PhoneEx", - "orgChild4.orgChild4PhoneIn", - "orgChild4.orgChild4Fax", - "orgChild4.orgRootId", - "orgChild4.orgChild4Rank", - "orgChild4.orgChild4RankSub", - "orgChild4.DEPARTMENT_CODE", - "orgChild4.DIVISION_CODE", - "orgChild4.SECTION_CODE", - "orgChild4.JOB_CODE", - "orgChild4.orgChild3Id", - "orgChild4.responsibility", - ]) - .orderBy("orgChild4.orgChild4Order", "ASC") - .getMany() + .createQueryBuilder("orgChild4") + .where("orgChild4.orgChild3Id IN (:...ids)", { ids: orgChild3Ids }) + // .andWhere( + // _data.child4 != undefined && _data.child4 != null + // ? _data.child4[0] != null + // ? `orgChild4.id IN (:...node)` + // : `orgChild4.id is null` + // : "1=1", + // { + // node: _data.child4, + // }, + // ) + .select([ + "orgChild4.id", + "orgChild4.orgChild4Name", + "orgChild4.orgChild4ShortName", + "orgChild4.orgChild4Code", + "orgChild4.orgChild4Order", + "orgChild4.orgChild4PhoneEx", + "orgChild4.orgChild4PhoneIn", + "orgChild4.orgChild4Fax", + "orgChild4.orgRootId", + "orgChild4.orgChild4Rank", + "orgChild4.orgChild4RankSub", + "orgChild4.DEPARTMENT_CODE", + "orgChild4.DIVISION_CODE", + "orgChild4.SECTION_CODE", + "orgChild4.JOB_CODE", + "orgChild4.orgChild3Id", + "orgChild4.responsibility", + ]) + .orderBy("orgChild4.orgChild4Order", "ASC") + .getMany() : []; const formattedData = await Promise.all( @@ -1271,13 +1271,13 @@ export class OrganizationController extends Controller { where: orgRevision.orgRevisionIsCurrent && !orgRevision.orgRevisionIsDraft ? { - orgRevisionId: id, - current_holderId: profile.id, - } + orgRevisionId: id, + current_holderId: profile.id, + } : { - orgRevisionId: id, - next_holderId: profile.id, - }, + orgRevisionId: id, + next_holderId: profile.id, + }, }); if (!posMaster) return new HttpSuccess([]); @@ -1441,14 +1441,7 @@ export class OrganizationController extends Controller { orgRoot.orgRootCode + orgChild1.orgChild1Code + " " + - orgChild1.orgChild1ShortName + - "/" + - orgRoot.orgRootName + - " " + - orgRoot.orgRootCode + - "00" + - " " + - orgRoot.orgRootShortName, + orgChild1.orgChild1ShortName, // totalPosition: child1Counts.totalPosition, // totalPositionCurrentUse: child1Counts.totalPositionCurrentUse, // totalPositionCurrentVacant: child1Counts.totalPositionCurrentVacant, @@ -1498,21 +1491,7 @@ export class OrganizationController extends Controller { orgRoot.orgRootCode + orgChild2.orgChild2Code + " " + - orgChild2.orgChild2ShortName + - "/" + - orgChild1.orgChild1Name + - " " + - orgRoot.orgRootCode + - orgChild1.orgChild1Code + - " " + - orgChild1.orgChild1ShortName + - "/" + - orgRoot.orgRootName + - " " + - orgRoot.orgRootCode + - "00" + - " " + - orgRoot.orgRootShortName, + orgChild2.orgChild2ShortName, // totalPosition: child2Counts.totalPosition, // totalPositionCurrentUse: child2Counts.totalPositionCurrentUse, // totalPositionCurrentVacant: child2Counts.totalPositionCurrentVacant, @@ -1563,28 +1542,7 @@ export class OrganizationController extends Controller { orgRoot.orgRootCode + orgChild3.orgChild3Code + " " + - orgChild3.orgChild3ShortName + - "/" + - orgChild2.orgChild2Name + - " " + - orgRoot.orgRootCode + - orgChild2.orgChild2Code + - " " + - orgChild2.orgChild2ShortName + - "/" + - orgChild1.orgChild1Name + - " " + - orgRoot.orgRootCode + - orgChild1.orgChild1Code + - " " + - orgChild1.orgChild1ShortName + - "/" + - orgRoot.orgRootName + - " " + - orgRoot.orgRootCode + - "00" + - " " + - orgRoot.orgRootShortName, + orgChild3.orgChild3ShortName, // totalPosition: child3Counts.totalPosition, // totalPositionCurrentUse: child3Counts.totalPositionCurrentUse, // totalPositionCurrentVacant: child3Counts.totalPositionCurrentVacant, @@ -1637,35 +1595,7 @@ export class OrganizationController extends Controller { orgRoot.orgRootCode + orgChild4.orgChild4Code + " " + - orgChild4.orgChild4ShortName + - "/" + - orgChild3.orgChild3Name + - " " + - orgRoot.orgRootCode + - orgChild3.orgChild3Code + - " " + - orgChild3.orgChild3ShortName + - "/" + - orgChild2.orgChild2Name + - " " + - orgRoot.orgRootCode + - orgChild2.orgChild2Code + - " " + - orgChild2.orgChild2ShortName + - "/" + - orgChild1.orgChild1Name + - " " + - orgRoot.orgRootCode + - orgChild1.orgChild1Code + - " " + - orgChild1.orgChild1ShortName + - "/" + - orgRoot.orgRootName + - " " + - orgRoot.orgRootCode + - "00" + - " " + - orgRoot.orgRootShortName, + orgChild4.orgChild4ShortName, // totalPosition: child4Counts.totalPosition, // totalPositionCurrentUse: child4Counts.totalPositionCurrentUse, // totalPositionCurrentVacant: child4Counts.totalPositionCurrentVacant, @@ -1817,11 +1747,6 @@ export class OrganizationController extends Controller { "orgRoot.DIVISION_CODE", "orgRoot.SECTION_CODE", "orgRoot.JOB_CODE", - "orgRoot.ROOT_CODE", - "orgRoot.CHILD1_CODE", - "orgRoot.CHILD2_CODE", - "orgRoot.CHILD3_CODE", - "orgRoot.CHILD4_CODE", "orgRoot.responsibility", ]) .orderBy("orgRoot.orgRootOrder", "ASC") @@ -1830,181 +1755,161 @@ export class OrganizationController extends Controller { const orgChild1Data = orgRootIds && orgRootIds.length > 0 ? await AppDataSource.getRepository(OrgChild1) - .createQueryBuilder("orgChild1") - .where("orgChild1.orgRootId IN (:...ids)", { ids: orgRootIds }) - .andWhere( - _data.child1 !== undefined && _data.child1 !== null - ? _data.child1[0] !== null - ? `orgChild1.id IN (:...node)` - : `orgChild1.id is null` - : "1=1", - { - node: _data.child1, - }, - ) - .select([ - "orgChild1.id", - "orgChild1.misId", - "orgChild1.isOfficer", - "orgChild1.isInformation", - "orgChild1.orgChild1Name", - "orgChild1.orgChild1ShortName", - "orgChild1.orgChild1Code", - "orgChild1.orgChild1Order", - "orgChild1.orgChild1PhoneEx", - "orgChild1.orgChild1PhoneIn", - "orgChild1.orgChild1Fax", - "orgChild1.orgRootId", - "orgChild1.orgChild1Rank", - "orgChild1.orgChild1RankSub", - "orgChild1.DEPARTMENT_CODE", - "orgChild1.DIVISION_CODE", - "orgChild1.SECTION_CODE", - "orgChild1.JOB_CODE", - "orgChild1.ROOT_CODE", - "orgChild1.CHILD1_CODE", - "orgChild1.CHILD2_CODE", - "orgChild1.CHILD3_CODE", - "orgChild1.CHILD4_CODE", - "orgChild1.responsibility", - ]) - .orderBy("orgChild1.orgChild1Order", "ASC") - .getMany() + .createQueryBuilder("orgChild1") + .where("orgChild1.orgRootId IN (:...ids)", { ids: orgRootIds }) + .andWhere( + _data.child1 !== undefined && _data.child1 !== null + ? _data.child1[0] !== null + ? `orgChild1.id IN (:...node)` + : `orgChild1.id is null` + : "1=1", + { + node: _data.child1, + }, + ) + .select([ + "orgChild1.id", + "orgChild1.misId", + "orgChild1.isOfficer", + "orgChild1.isInformation", + "orgChild1.orgChild1Name", + "orgChild1.orgChild1ShortName", + "orgChild1.orgChild1Code", + "orgChild1.orgChild1Order", + "orgChild1.orgChild1PhoneEx", + "orgChild1.orgChild1PhoneIn", + "orgChild1.orgChild1Fax", + "orgChild1.orgRootId", + "orgChild1.orgChild1Rank", + "orgChild1.orgChild1RankSub", + "orgChild1.DEPARTMENT_CODE", + "orgChild1.DIVISION_CODE", + "orgChild1.SECTION_CODE", + "orgChild1.JOB_CODE", + "orgChild1.responsibility", + ]) + .orderBy("orgChild1.orgChild1Order", "ASC") + .getMany() : []; const orgChild1Ids = orgChild1Data.map((orgChild1) => orgChild1.id) || null; const orgChild2Data = orgChild1Ids && orgChild1Ids.length > 0 ? await AppDataSource.getRepository(OrgChild2) - .createQueryBuilder("orgChild2") - .where("orgChild2.orgChild1Id IN (:...ids)", { ids: orgChild1Ids }) - .andWhere( - _data.child2 !== undefined && _data.child2 !== null - ? _data.child2[0] !== null - ? `orgChild2.id IN (:...node)` - : `orgChild2.id is null` - : "1=1", - { - node: _data.child2, - }, - ) - .select([ - "orgChild2.id", - "orgChild2.misId", - "orgChild2.orgChild2Name", - "orgChild2.orgChild2ShortName", - "orgChild2.orgChild2Code", - "orgChild2.orgChild2Order", - "orgChild2.orgChild2PhoneEx", - "orgChild2.orgChild2PhoneIn", - "orgChild2.orgChild2Fax", - "orgChild2.orgRootId", - "orgChild2.orgChild2Rank", - "orgChild2.orgChild2RankSub", - "orgChild2.DEPARTMENT_CODE", - "orgChild2.DIVISION_CODE", - "orgChild2.SECTION_CODE", - "orgChild2.JOB_CODE", - "orgChild2.ROOT_CODE", - "orgChild2.CHILD1_CODE", - "orgChild2.CHILD2_CODE", - "orgChild2.CHILD3_CODE", - "orgChild2.CHILD4_CODE", - "orgChild2.orgChild1Id", - "orgChild2.responsibility", - ]) - .orderBy("orgChild2.orgChild2Order", "ASC") - .getMany() + .createQueryBuilder("orgChild2") + .where("orgChild2.orgChild1Id IN (:...ids)", { ids: orgChild1Ids }) + .andWhere( + _data.child2 !== undefined && _data.child2 !== null + ? _data.child2[0] !== null + ? `orgChild2.id IN (:...node)` + : `orgChild2.id is null` + : "1=1", + { + node: _data.child2, + }, + ) + .select([ + "orgChild2.id", + "orgChild2.misId", + "orgChild2.orgChild2Name", + "orgChild2.orgChild2ShortName", + "orgChild2.orgChild2Code", + "orgChild2.orgChild2Order", + "orgChild2.orgChild2PhoneEx", + "orgChild2.orgChild2PhoneIn", + "orgChild2.orgChild2Fax", + "orgChild2.orgRootId", + "orgChild2.orgChild2Rank", + "orgChild2.orgChild2RankSub", + "orgChild2.DEPARTMENT_CODE", + "orgChild2.DIVISION_CODE", + "orgChild2.SECTION_CODE", + "orgChild2.JOB_CODE", + "orgChild2.orgChild1Id", + "orgChild2.responsibility", + ]) + .orderBy("orgChild2.orgChild2Order", "ASC") + .getMany() : []; const orgChild2Ids = orgChild2Data.map((orgChild2) => orgChild2.id) || null; const orgChild3Data = orgChild2Ids && orgChild2Ids.length > 0 ? await AppDataSource.getRepository(OrgChild3) - .createQueryBuilder("orgChild3") - .where("orgChild3.orgChild2Id IN (:...ids)", { ids: orgChild2Ids }) - .andWhere( - _data.child3 !== undefined && _data.child3 !== null - ? _data.child3[0] !== null - ? `orgChild3.id IN (:...node)` - : `orgChild3.id is null` - : "1=1", - { - node: _data.child3, - }, - ) - .select([ - "orgChild3.id", - "orgChild3.misId", - "orgChild3.orgChild3Name", - "orgChild3.orgChild3ShortName", - "orgChild3.orgChild3Code", - "orgChild3.orgChild3Order", - "orgChild3.orgChild3PhoneEx", - "orgChild3.orgChild3PhoneIn", - "orgChild3.orgChild3Fax", - "orgChild3.orgRootId", - "orgChild3.orgChild3Rank", - "orgChild3.orgChild3RankSub", - "orgChild3.DEPARTMENT_CODE", - "orgChild3.DIVISION_CODE", - "orgChild3.SECTION_CODE", - "orgChild3.JOB_CODE", - "orgChild3.ROOT_CODE", - "orgChild3.CHILD1_CODE", - "orgChild3.CHILD2_CODE", - "orgChild3.CHILD3_CODE", - "orgChild3.CHILD4_CODE", - "orgChild3.orgChild2Id", - "orgChild3.responsibility", - ]) - .orderBy("orgChild3.orgChild3Order", "ASC") - .getMany() + .createQueryBuilder("orgChild3") + .where("orgChild3.orgChild2Id IN (:...ids)", { ids: orgChild2Ids }) + .andWhere( + _data.child3 !== undefined && _data.child3 !== null + ? _data.child3[0] !== null + ? `orgChild3.id IN (:...node)` + : `orgChild3.id is null` + : "1=1", + { + node: _data.child3, + }, + ) + .select([ + "orgChild3.id", + "orgChild3.misId", + "orgChild3.orgChild3Name", + "orgChild3.orgChild3ShortName", + "orgChild3.orgChild3Code", + "orgChild3.orgChild3Order", + "orgChild3.orgChild3PhoneEx", + "orgChild3.orgChild3PhoneIn", + "orgChild3.orgChild3Fax", + "orgChild3.orgRootId", + "orgChild3.orgChild3Rank", + "orgChild3.orgChild3RankSub", + "orgChild3.DEPARTMENT_CODE", + "orgChild3.DIVISION_CODE", + "orgChild3.SECTION_CODE", + "orgChild3.JOB_CODE", + "orgChild3.orgChild2Id", + "orgChild3.responsibility", + ]) + .orderBy("orgChild3.orgChild3Order", "ASC") + .getMany() : []; const orgChild3Ids = orgChild3Data.map((orgChild3) => orgChild3.id) || null; const orgChild4Data = orgChild3Ids && orgChild3Ids.length > 0 ? await AppDataSource.getRepository(OrgChild4) - .createQueryBuilder("orgChild4") - .where("orgChild4.orgChild3Id IN (:...ids)", { ids: orgChild3Ids }) - .andWhere( - _data.child4 !== undefined && _data.child4 !== null - ? _data.child4[0] !== null - ? `orgChild4.id IN (:...node)` - : `orgChild4.id is null` - : "1=1", - { - node: _data.child4, - }, - ) - .select([ - "orgChild4.id", - "orgChild4.misId", - "orgChild4.orgChild4Name", - "orgChild4.orgChild4ShortName", - "orgChild4.orgChild4Code", - "orgChild4.orgChild4Order", - "orgChild4.orgChild4PhoneEx", - "orgChild4.orgChild4PhoneIn", - "orgChild4.orgChild4Fax", - "orgChild4.orgRootId", - "orgChild4.orgChild4Rank", - "orgChild4.orgChild4RankSub", - "orgChild4.DEPARTMENT_CODE", - "orgChild4.DIVISION_CODE", - "orgChild4.SECTION_CODE", - "orgChild4.JOB_CODE", - "orgChild4.ROOT_CODE", - "orgChild4.CHILD1_CODE", - "orgChild4.CHILD2_CODE", - "orgChild4.CHILD3_CODE", - "orgChild4.CHILD4_CODE", - "orgChild4.orgChild3Id", - "orgChild4.responsibility", - ]) - .orderBy("orgChild4.orgChild4Order", "ASC") - .getMany() + .createQueryBuilder("orgChild4") + .where("orgChild4.orgChild3Id IN (:...ids)", { ids: orgChild3Ids }) + .andWhere( + _data.child4 !== undefined && _data.child4 !== null + ? _data.child4[0] !== null + ? `orgChild4.id IN (:...node)` + : `orgChild4.id is null` + : "1=1", + { + node: _data.child4, + }, + ) + .select([ + "orgChild4.id", + "orgChild4.misId", + "orgChild4.orgChild4Name", + "orgChild4.orgChild4ShortName", + "orgChild4.orgChild4Code", + "orgChild4.orgChild4Order", + "orgChild4.orgChild4PhoneEx", + "orgChild4.orgChild4PhoneIn", + "orgChild4.orgChild4Fax", + "orgChild4.orgRootId", + "orgChild4.orgChild4Rank", + "orgChild4.orgChild4RankSub", + "orgChild4.DEPARTMENT_CODE", + "orgChild4.DIVISION_CODE", + "orgChild4.SECTION_CODE", + "orgChild4.JOB_CODE", + "orgChild4.orgChild3Id", + "orgChild4.responsibility", + ]) + .orderBy("orgChild4.orgChild4Order", "ASC") + .getMany() : []; const formattedData = await Promise.all( @@ -2025,11 +1930,6 @@ export class OrganizationController extends Controller { DIVISION_CODE: orgRoot.DIVISION_CODE, SECTION_CODE: orgRoot.SECTION_CODE, JOB_CODE: orgRoot.JOB_CODE, - ROOT_CODE: orgRoot.ROOT_CODE, - CHILD1_CODE: orgRoot.CHILD1_CODE, - CHILD2_CODE: orgRoot.CHILD2_CODE, - CHILD3_CODE: orgRoot.CHILD3_CODE, - CHILD4_CODE: orgRoot.CHILD4_CODE, orgTreeOrder: orgRoot.orgRootOrder, orgTreePhoneEx: orgRoot.orgRootPhoneEx, orgTreePhoneIn: orgRoot.orgRootPhoneIn, @@ -2061,11 +1961,6 @@ export class OrganizationController extends Controller { DIVISION_CODE: orgChild1.DIVISION_CODE, SECTION_CODE: orgChild1.SECTION_CODE, JOB_CODE: orgChild1.JOB_CODE, - ROOT_CODE: orgChild1.ROOT_CODE, - CHILD1_CODE: orgChild1.CHILD1_CODE, - CHILD2_CODE: orgChild1.CHILD2_CODE, - CHILD3_CODE: orgChild1.CHILD3_CODE, - CHILD4_CODE: orgChild1.CHILD4_CODE, orgTreeOrder: orgChild1.orgChild1Order, orgRootCode: orgRoot.orgRootCode, orgTreePhoneEx: orgChild1.orgChild1PhoneEx, @@ -2110,11 +2005,6 @@ export class OrganizationController extends Controller { DIVISION_CODE: orgChild2.DIVISION_CODE, SECTION_CODE: orgChild2.SECTION_CODE, JOB_CODE: orgChild2.JOB_CODE, - ROOT_CODE: orgChild2.ROOT_CODE, - CHILD1_CODE: orgChild2.CHILD1_CODE, - CHILD2_CODE: orgChild2.CHILD2_CODE, - CHILD3_CODE: orgChild2.CHILD3_CODE, - CHILD4_CODE: orgChild2.CHILD4_CODE, orgTreeOrder: orgChild2.orgChild2Order, orgRootCode: orgRoot.orgRootCode, orgTreePhoneEx: orgChild2.orgChild2PhoneEx, @@ -2164,11 +2054,6 @@ export class OrganizationController extends Controller { DIVISION_CODE: orgChild3.DIVISION_CODE, SECTION_CODE: orgChild3.SECTION_CODE, JOB_CODE: orgChild3.JOB_CODE, - ROOT_CODE: orgChild3.ROOT_CODE, - CHILD1_CODE: orgChild3.CHILD1_CODE, - CHILD2_CODE: orgChild3.CHILD2_CODE, - CHILD3_CODE: orgChild3.CHILD3_CODE, - CHILD4_CODE: orgChild3.CHILD4_CODE, orgTreeOrder: orgChild3.orgChild3Order, orgRootCode: orgRoot.orgRootCode, orgTreePhoneEx: orgChild3.orgChild3PhoneEx, @@ -2225,11 +2110,6 @@ export class OrganizationController extends Controller { DIVISION_CODE: orgChild4.DIVISION_CODE, SECTION_CODE: orgChild4.SECTION_CODE, JOB_CODE: orgChild4.JOB_CODE, - ROOT_CODE: orgChild4.ROOT_CODE, - CHILD1_CODE: orgChild4.CHILD1_CODE, - CHILD2_CODE: orgChild4.CHILD2_CODE, - CHILD3_CODE: orgChild4.CHILD3_CODE, - CHILD4_CODE: orgChild4.CHILD4_CODE, orgTreeOrder: orgChild4.orgChild4Order, orgRootCode: orgRoot.orgRootCode, orgTreePhoneEx: orgChild4.orgChild4PhoneEx, @@ -3599,18 +3479,18 @@ export class OrganizationController extends Controller { const formattedData_ = rootId === "root" ? [ - { - personID: "", - name: "", - avatar: "", - positionName: "", - positionNum: "", - positionNumInt: null, - departmentName: data.orgRevisionName, - organizationId: data.id, - children: formattedData, - }, - ] + { + personID: "", + name: "", + avatar: "", + positionName: "", + positionNum: "", + positionNumInt: null, + departmentName: data.orgRevisionName, + organizationId: data.id, + children: formattedData, + }, + ] : formattedData; return new HttpSuccess(formattedData_); } @@ -5936,163 +5816,163 @@ export class OrganizationController extends Controller { const orgChild1Data = orgRootIds && orgRootIds.length > 0 ? await AppDataSource.getRepository(OrgChild1) - .createQueryBuilder("orgChild1") - .select([ - "orgChild1.id", - "orgChild1.orgRootId", - "orgChild1.orgChild1Name", - "orgChild1.orgChild1ShortName", - "orgChild1.orgChild1Code", - "orgChild1.orgChild1Order", - ]) - .addSelect([ - "posMasters.id", - "posMasters.posMasterNo", - "posMasters.orgChild2Id", - "posMasters.isDirector", - ]) - .addSelect([ - "current_holder.prefix", - "current_holder.firstName", - "current_holder.lastName", - ]) - .where("orgChild1.orgRootId IN (:...ids)", { ids: orgRootIds }) - .andWhere( - _data.child1 != undefined && _data.child1 != null - ? _data.child1[0] != null - ? `orgChild1.id IN (:...node)` - : `orgChild1.id is null` - : "1=1", - { - node: _data.child1, - }, - ) - .leftJoin("orgChild1.posMasters", "posMasters") - .leftJoin("posMasters.current_holder", "current_holder") - .orderBy("orgChild1.orgChild1Order", "ASC") - .addOrderBy("posMasters.posMasterOrder", "ASC") - .getMany() + .createQueryBuilder("orgChild1") + .select([ + "orgChild1.id", + "orgChild1.orgRootId", + "orgChild1.orgChild1Name", + "orgChild1.orgChild1ShortName", + "orgChild1.orgChild1Code", + "orgChild1.orgChild1Order", + ]) + .addSelect([ + "posMasters.id", + "posMasters.posMasterNo", + "posMasters.orgChild2Id", + "posMasters.isDirector", + ]) + .addSelect([ + "current_holder.prefix", + "current_holder.firstName", + "current_holder.lastName", + ]) + .where("orgChild1.orgRootId IN (:...ids)", { ids: orgRootIds }) + .andWhere( + _data.child1 != undefined && _data.child1 != null + ? _data.child1[0] != null + ? `orgChild1.id IN (:...node)` + : `orgChild1.id is null` + : "1=1", + { + node: _data.child1, + }, + ) + .leftJoin("orgChild1.posMasters", "posMasters") + .leftJoin("posMasters.current_holder", "current_holder") + .orderBy("orgChild1.orgChild1Order", "ASC") + .addOrderBy("posMasters.posMasterOrder", "ASC") + .getMany() : []; const orgChild1Ids = orgChild1Data.map((orgChild1) => orgChild1.id) || null; const orgChild2Data = orgChild1Ids && orgChild1Ids.length > 0 ? await AppDataSource.getRepository(OrgChild2) - .createQueryBuilder("orgChild2") - .select([ - "orgChild2.id", - "orgChild2.orgChild1Id", - "orgChild2.orgChild2Name", - "orgChild2.orgChild2ShortName", - "orgChild2.orgChild2Code", - "orgChild2.orgChild2Order", - ]) - .addSelect([ - "posMasters.id", - "posMasters.posMasterNo", - "posMasters.orgChild3Id", - "posMasters.isDirector", - ]) - .addSelect([ - "current_holder.prefix", - "current_holder.firstName", - "current_holder.lastName", - ]) - .where("orgChild2.orgChild1Id IN (:...ids)", { ids: orgChild1Ids }) - .andWhere( - _data.child2 != undefined && _data.child2 != null - ? _data.child2[0] != null - ? `orgChild2.id IN (:...node)` - : `orgChild2.id is null` - : "1=1", - { - node: _data.child2, - }, - ) - .leftJoin("orgChild2.posMasters", "posMasters") - .leftJoin("posMasters.current_holder", "current_holder") - .orderBy("orgChild2.orgChild2Order", "ASC") - .addOrderBy("posMasters.posMasterOrder", "ASC") - .getMany() + .createQueryBuilder("orgChild2") + .select([ + "orgChild2.id", + "orgChild2.orgChild1Id", + "orgChild2.orgChild2Name", + "orgChild2.orgChild2ShortName", + "orgChild2.orgChild2Code", + "orgChild2.orgChild2Order", + ]) + .addSelect([ + "posMasters.id", + "posMasters.posMasterNo", + "posMasters.orgChild3Id", + "posMasters.isDirector", + ]) + .addSelect([ + "current_holder.prefix", + "current_holder.firstName", + "current_holder.lastName", + ]) + .where("orgChild2.orgChild1Id IN (:...ids)", { ids: orgChild1Ids }) + .andWhere( + _data.child2 != undefined && _data.child2 != null + ? _data.child2[0] != null + ? `orgChild2.id IN (:...node)` + : `orgChild2.id is null` + : "1=1", + { + node: _data.child2, + }, + ) + .leftJoin("orgChild2.posMasters", "posMasters") + .leftJoin("posMasters.current_holder", "current_holder") + .orderBy("orgChild2.orgChild2Order", "ASC") + .addOrderBy("posMasters.posMasterOrder", "ASC") + .getMany() : []; const orgChild2Ids = orgChild2Data.map((orgChild2) => orgChild2.id) || null; const orgChild3Data = orgChild2Ids && orgChild2Ids.length > 0 ? await AppDataSource.getRepository(OrgChild3) - .createQueryBuilder("orgChild3") - .select([ - "orgChild3.id", - "orgChild3.orgChild2Id", - "orgChild3.orgChild3Name", - "orgChild3.orgChild3ShortName", - "orgChild3.orgChild3Code", - "orgChild3.orgChild3Order", - ]) - .addSelect([ - "posMasters.id", - "posMasters.posMasterNo", - "posMasters.orgChild4Id", - "posMasters.isDirector", - ]) - .addSelect([ - "current_holder.prefix", - "current_holder.firstName", - "current_holder.lastName", - ]) - .where("orgChild3.orgChild2Id IN (:...ids)", { ids: orgChild2Ids }) - .andWhere( - _data.child3 != undefined && _data.child3 != null - ? _data.child3[0] != null - ? `orgChild3.id IN (:...node)` - : `orgChild3.id is null` - : "1=1", - { - node: _data.child3, - }, - ) - .leftJoin("orgChild3.posMasters", "posMasters") - .leftJoin("posMasters.current_holder", "current_holder") - .orderBy("orgChild3.orgChild3Order", "ASC") - .addOrderBy("posMasters.posMasterOrder", "ASC") - .getMany() + .createQueryBuilder("orgChild3") + .select([ + "orgChild3.id", + "orgChild3.orgChild2Id", + "orgChild3.orgChild3Name", + "orgChild3.orgChild3ShortName", + "orgChild3.orgChild3Code", + "orgChild3.orgChild3Order", + ]) + .addSelect([ + "posMasters.id", + "posMasters.posMasterNo", + "posMasters.orgChild4Id", + "posMasters.isDirector", + ]) + .addSelect([ + "current_holder.prefix", + "current_holder.firstName", + "current_holder.lastName", + ]) + .where("orgChild3.orgChild2Id IN (:...ids)", { ids: orgChild2Ids }) + .andWhere( + _data.child3 != undefined && _data.child3 != null + ? _data.child3[0] != null + ? `orgChild3.id IN (:...node)` + : `orgChild3.id is null` + : "1=1", + { + node: _data.child3, + }, + ) + .leftJoin("orgChild3.posMasters", "posMasters") + .leftJoin("posMasters.current_holder", "current_holder") + .orderBy("orgChild3.orgChild3Order", "ASC") + .addOrderBy("posMasters.posMasterOrder", "ASC") + .getMany() : []; const orgChild3Ids = orgChild3Data.map((orgChild3) => orgChild3.id) || null; const orgChild4Data = orgChild3Ids && orgChild3Ids.length > 0 ? await AppDataSource.getRepository(OrgChild4) - .createQueryBuilder("orgChild4") - .select([ - "orgChild4.id", - "orgChild4.orgChild3Id", - "orgChild4.orgChild4Name", - "orgChild4.orgChild4ShortName", - "orgChild4.orgChild4Code", - "orgChild4.orgChild4Order", - ]) - .addSelect(["posMasters.id", "posMasters.posMasterNo", "posMasters.isDirector"]) - .addSelect([ - "current_holder.prefix", - "current_holder.firstName", - "current_holder.lastName", - ]) - .where("orgChild4.orgChild3Id IN (:...ids)", { ids: orgChild3Ids }) - .andWhere( - _data.child4 != undefined && _data.child4 != null - ? _data.child4[0] != null - ? `orgChild4.id IN (:...node)` - : `orgChild4.id is null` - : "1=1", - { - node: _data.child4, - }, - ) - .leftJoin("orgChild4.posMasters", "posMasters") - .leftJoin("posMasters.current_holder", "current_holder") - .orderBy("orgChild4.orgChild4Order", "ASC") - .addOrderBy("posMasters.posMasterOrder", "ASC") - .getMany() + .createQueryBuilder("orgChild4") + .select([ + "orgChild4.id", + "orgChild4.orgChild3Id", + "orgChild4.orgChild4Name", + "orgChild4.orgChild4ShortName", + "orgChild4.orgChild4Code", + "orgChild4.orgChild4Order", + ]) + .addSelect(["posMasters.id", "posMasters.posMasterNo", "posMasters.isDirector"]) + .addSelect([ + "current_holder.prefix", + "current_holder.firstName", + "current_holder.lastName", + ]) + .where("orgChild4.orgChild3Id IN (:...ids)", { ids: orgChild3Ids }) + .andWhere( + _data.child4 != undefined && _data.child4 != null + ? _data.child4[0] != null + ? `orgChild4.id IN (:...node)` + : `orgChild4.id is null` + : "1=1", + { + node: _data.child4, + }, + ) + .leftJoin("orgChild4.posMasters", "posMasters") + .leftJoin("posMasters.current_holder", "current_holder") + .orderBy("orgChild4.orgChild4Order", "ASC") + .addOrderBy("posMasters.posMasterOrder", "ASC") + .getMany() : []; const cannotViewRootPosMaster = @@ -6144,8 +6024,8 @@ export class OrganizationController extends Controller { posMaster: cannotViewRootPosMaster ? [] : filterPosMasters(orgRoot.posMasters, "orgChild1Id").map((x) => - formatPosMaster(x, orgRoot.orgRootShortName, orgRoot.id, 0), - ), + formatPosMaster(x, orgRoot.orgRootShortName, orgRoot.id, 0), + ), children: orgChild1Data .filter((orgChild1) => orgChild1.orgRootId === orgRoot.id) .map((orgChild1) => ({ @@ -6169,8 +6049,8 @@ export class OrganizationController extends Controller { posMaster: cannotViewChild1PosMaster ? [] : filterPosMasters(orgChild1.posMasters, "orgChild2Id").map((x) => - formatPosMaster(x, orgChild1.orgChild1ShortName, orgChild1.id, 1), - ), + formatPosMaster(x, orgChild1.orgChild1ShortName, orgChild1.id, 1), + ), children: orgChild2Data .filter((orgChild2) => orgChild2.orgChild1Id === orgChild1.id) .map((orgChild2) => ({ @@ -6197,8 +6077,8 @@ export class OrganizationController extends Controller { posMaster: cannotViewChild2PosMaster ? [] : filterPosMasters(orgChild2.posMasters, "orgChild3Id").map((x) => - formatPosMaster(x, orgChild2.orgChild2ShortName, orgChild2.id, 2), - ), + formatPosMaster(x, orgChild2.orgChild2ShortName, orgChild2.id, 2), + ), children: orgChild3Data .filter((orgChild3) => orgChild3.orgChild2Id === orgChild2.id) .map((orgChild3) => ({ @@ -6225,8 +6105,8 @@ export class OrganizationController extends Controller { posMaster: cannotViewChild3PosMaster ? [] : filterPosMasters(orgChild3.posMasters, "orgChild4Id").map((x) => - formatPosMaster(x, orgChild3.orgChild3ShortName, orgChild3.id, 3), - ), + formatPosMaster(x, orgChild3.orgChild3ShortName, orgChild3.id, 3), + ), children: orgChild4Data .filter((orgChild4) => orgChild4.orgChild3Id === orgChild3.id) .map((orgChild4) => ({ @@ -6257,10 +6137,10 @@ export class OrganizationController extends Controller { posMaster: cannotViewChild4PosMaster ? [] : orgChild4.posMasters - .filter((x) => x.isDirector === true) - .map((x) => - formatPosMaster(x, orgChild4.orgChild4ShortName, orgChild4.id, 4), - ), + .filter((x) => x.isDirector === true) + .map((x) => + formatPosMaster(x, orgChild4.orgChild4ShortName, orgChild4.id, 4), + ), })), })), })), @@ -6431,159 +6311,159 @@ export class OrganizationController extends Controller { const orgChild1Data = orgRootIds && orgRootIds.length > 0 ? await AppDataSource.getRepository(OrgChild1) - .createQueryBuilder("orgChild1") - .where("orgChild1.orgRootId IN (:...ids)", { ids: orgRootIds }) - .andWhere( - _data.child1 != undefined && _data.child1 != null - ? _data.child1[0] != null - ? `orgChild1.id IN (:...node)` - : `orgChild1.id is ${_data.privilege == "PARENT" ? "not null" : "null"}` - : "1=1", - { - node: _data.child1, - }, - ) - .select([ - "orgChild1.id", - "orgChild1.ancestorDNA", - "orgChild1.orgChild1Name", - "orgChild1.orgChild1ShortName", - "orgChild1.orgChild1Code", - "orgChild1.orgChild1Order", - "orgChild1.orgChild1PhoneEx", - "orgChild1.orgChild1PhoneIn", - "orgChild1.orgChild1Fax", - "orgChild1.orgRootId", - "orgChild1.orgChild1Rank", - "orgChild1.orgChild1RankSub", - "orgChild1.DEPARTMENT_CODE", - "orgChild1.DIVISION_CODE", - "orgChild1.SECTION_CODE", - "orgChild1.JOB_CODE", - "orgChild1.responsibility", - ]) - .orderBy("orgChild1.orgChild1Order", "ASC") - .getMany() + .createQueryBuilder("orgChild1") + .where("orgChild1.orgRootId IN (:...ids)", { ids: orgRootIds }) + .andWhere( + _data.child1 != undefined && _data.child1 != null + ? _data.child1[0] != null + ? `orgChild1.id IN (:...node)` + : `orgChild1.id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + : "1=1", + { + node: _data.child1, + }, + ) + .select([ + "orgChild1.id", + "orgChild1.ancestorDNA", + "orgChild1.orgChild1Name", + "orgChild1.orgChild1ShortName", + "orgChild1.orgChild1Code", + "orgChild1.orgChild1Order", + "orgChild1.orgChild1PhoneEx", + "orgChild1.orgChild1PhoneIn", + "orgChild1.orgChild1Fax", + "orgChild1.orgRootId", + "orgChild1.orgChild1Rank", + "orgChild1.orgChild1RankSub", + "orgChild1.DEPARTMENT_CODE", + "orgChild1.DIVISION_CODE", + "orgChild1.SECTION_CODE", + "orgChild1.JOB_CODE", + "orgChild1.responsibility", + ]) + .orderBy("orgChild1.orgChild1Order", "ASC") + .getMany() : []; const orgChild1Ids = orgChild1Data.map((orgChild1) => orgChild1.id) || null; const orgChild2Data = orgChild1Ids && orgChild1Ids.length > 0 ? await AppDataSource.getRepository(OrgChild2) - .createQueryBuilder("orgChild2") - .where("orgChild2.orgChild1Id IN (:...ids)", { ids: orgChild1Ids }) - .andWhere( - _data.child2 != undefined && _data.child2 != null - ? _data.child2[0] != null - ? `orgChild2.id IN (:...node)` - : `orgChild2.id is null` - : "1=1", - { - node: _data.child2, - }, - ) - .select([ - "orgChild2.id", - "orgChild2.ancestorDNA", - "orgChild2.orgChild2Name", - "orgChild2.orgChild2ShortName", - "orgChild2.orgChild2Code", - "orgChild2.orgChild2Order", - "orgChild2.orgChild2PhoneEx", - "orgChild2.orgChild2PhoneIn", - "orgChild2.orgChild2Fax", - "orgChild2.orgRootId", - "orgChild2.orgChild2Rank", - "orgChild2.orgChild2RankSub", - "orgChild2.DEPARTMENT_CODE", - "orgChild2.DIVISION_CODE", - "orgChild2.SECTION_CODE", - "orgChild2.JOB_CODE", - "orgChild2.orgChild1Id", - "orgChild2.responsibility", - ]) - .orderBy("orgChild2.orgChild2Order", "ASC") - .getMany() + .createQueryBuilder("orgChild2") + .where("orgChild2.orgChild1Id IN (:...ids)", { ids: orgChild1Ids }) + .andWhere( + _data.child2 != undefined && _data.child2 != null + ? _data.child2[0] != null + ? `orgChild2.id IN (:...node)` + : `orgChild2.id is null` + : "1=1", + { + node: _data.child2, + }, + ) + .select([ + "orgChild2.id", + "orgChild2.ancestorDNA", + "orgChild2.orgChild2Name", + "orgChild2.orgChild2ShortName", + "orgChild2.orgChild2Code", + "orgChild2.orgChild2Order", + "orgChild2.orgChild2PhoneEx", + "orgChild2.orgChild2PhoneIn", + "orgChild2.orgChild2Fax", + "orgChild2.orgRootId", + "orgChild2.orgChild2Rank", + "orgChild2.orgChild2RankSub", + "orgChild2.DEPARTMENT_CODE", + "orgChild2.DIVISION_CODE", + "orgChild2.SECTION_CODE", + "orgChild2.JOB_CODE", + "orgChild2.orgChild1Id", + "orgChild2.responsibility", + ]) + .orderBy("orgChild2.orgChild2Order", "ASC") + .getMany() : []; const orgChild2Ids = orgChild2Data.map((orgChild2) => orgChild2.id) || null; const orgChild3Data = orgChild2Ids && orgChild2Ids.length > 0 ? await AppDataSource.getRepository(OrgChild3) - .createQueryBuilder("orgChild3") - .where("orgChild3.orgChild2Id IN (:...ids)", { ids: orgChild2Ids }) - .andWhere( - _data.child3 != undefined && _data.child3 != null - ? _data.child3[0] != null - ? `orgChild3.id IN (:...node)` - : `orgChild3.id is null` - : "1=1", - { - node: _data.child3, - }, - ) - .select([ - "orgChild3.id", - "orgChild3.ancestorDNA", - "orgChild3.orgChild3Name", - "orgChild3.orgChild3ShortName", - "orgChild3.orgChild3Code", - "orgChild3.orgChild3Order", - "orgChild3.orgChild3PhoneEx", - "orgChild3.orgChild3PhoneIn", - "orgChild3.orgChild3Fax", - "orgChild3.orgRootId", - "orgChild3.orgChild3Rank", - "orgChild3.orgChild3RankSub", - "orgChild3.DEPARTMENT_CODE", - "orgChild3.DIVISION_CODE", - "orgChild3.SECTION_CODE", - "orgChild3.JOB_CODE", - "orgChild3.orgChild2Id", - "orgChild3.responsibility", - ]) - .orderBy("orgChild3.orgChild3Order", "ASC") - .getMany() + .createQueryBuilder("orgChild3") + .where("orgChild3.orgChild2Id IN (:...ids)", { ids: orgChild2Ids }) + .andWhere( + _data.child3 != undefined && _data.child3 != null + ? _data.child3[0] != null + ? `orgChild3.id IN (:...node)` + : `orgChild3.id is null` + : "1=1", + { + node: _data.child3, + }, + ) + .select([ + "orgChild3.id", + "orgChild3.ancestorDNA", + "orgChild3.orgChild3Name", + "orgChild3.orgChild3ShortName", + "orgChild3.orgChild3Code", + "orgChild3.orgChild3Order", + "orgChild3.orgChild3PhoneEx", + "orgChild3.orgChild3PhoneIn", + "orgChild3.orgChild3Fax", + "orgChild3.orgRootId", + "orgChild3.orgChild3Rank", + "orgChild3.orgChild3RankSub", + "orgChild3.DEPARTMENT_CODE", + "orgChild3.DIVISION_CODE", + "orgChild3.SECTION_CODE", + "orgChild3.JOB_CODE", + "orgChild3.orgChild2Id", + "orgChild3.responsibility", + ]) + .orderBy("orgChild3.orgChild3Order", "ASC") + .getMany() : []; const orgChild3Ids = orgChild3Data.map((orgChild3) => orgChild3.id) || null; const orgChild4Data = orgChild3Ids && orgChild3Ids.length > 0 ? await AppDataSource.getRepository(OrgChild4) - .createQueryBuilder("orgChild4") - .where("orgChild4.orgChild3Id IN (:...ids)", { ids: orgChild3Ids }) - .andWhere( - _data.child4 != undefined && _data.child4 != null - ? _data.child4[0] != null - ? `orgChild4.id IN (:...node)` - : `orgChild4.id is null` - : "1=1", - { - node: _data.child4, - }, - ) - .select([ - "orgChild4.id", - "orgChild4.ancestorDNA", - "orgChild4.orgChild4Name", - "orgChild4.orgChild4ShortName", - "orgChild4.orgChild4Code", - "orgChild4.orgChild4Order", - "orgChild4.orgChild4PhoneEx", - "orgChild4.orgChild4PhoneIn", - "orgChild4.orgChild4Fax", - "orgChild4.orgRootId", - "orgChild4.orgChild4Rank", - "orgChild4.orgChild4RankSub", - "orgChild4.DEPARTMENT_CODE", - "orgChild4.DIVISION_CODE", - "orgChild4.SECTION_CODE", - "orgChild4.JOB_CODE", - "orgChild4.orgChild3Id", - "orgChild4.responsibility", - ]) - .orderBy("orgChild4.orgChild4Order", "ASC") - .getMany() + .createQueryBuilder("orgChild4") + .where("orgChild4.orgChild3Id IN (:...ids)", { ids: orgChild3Ids }) + .andWhere( + _data.child4 != undefined && _data.child4 != null + ? _data.child4[0] != null + ? `orgChild4.id IN (:...node)` + : `orgChild4.id is null` + : "1=1", + { + node: _data.child4, + }, + ) + .select([ + "orgChild4.id", + "orgChild4.ancestorDNA", + "orgChild4.orgChild4Name", + "orgChild4.orgChild4ShortName", + "orgChild4.orgChild4Code", + "orgChild4.orgChild4Order", + "orgChild4.orgChild4PhoneEx", + "orgChild4.orgChild4PhoneIn", + "orgChild4.orgChild4Fax", + "orgChild4.orgRootId", + "orgChild4.orgChild4Rank", + "orgChild4.orgChild4RankSub", + "orgChild4.DEPARTMENT_CODE", + "orgChild4.DIVISION_CODE", + "orgChild4.SECTION_CODE", + "orgChild4.JOB_CODE", + "orgChild4.orgChild3Id", + "orgChild4.responsibility", + ]) + .orderBy("orgChild4.orgChild4Order", "ASC") + .getMany() : []; // const formattedData = orgRootData.map((orgRoot) => { @@ -8470,13 +8350,13 @@ export class OrganizationController extends Controller { profileId: null, shortName: pos ? [ - pos.orgChild4?.orgChild4ShortName, - pos.orgChild3?.orgChild3ShortName, - pos.orgChild2?.orgChild2ShortName, - pos.orgChild1?.orgChild1ShortName, - pos.orgRoot?.orgRootShortName, - ].find((s: string | undefined) => typeof s === "string" && s.trim().length > 0) ?? - null + pos.orgChild4?.orgChild4ShortName, + pos.orgChild3?.orgChild3ShortName, + pos.orgChild2?.orgChild2ShortName, + pos.orgChild1?.orgChild1ShortName, + pos.orgRoot?.orgRootShortName, + ].find((s: string | undefined) => typeof s === "string" && s.trim().length > 0) ?? + null : null, posMasterNoPrefix: pos.posMasterNoPrefix ?? null, posMasterNo: pos.posMasterNo != null ? String(pos.posMasterNo) : null, @@ -8545,13 +8425,13 @@ export class OrganizationController extends Controller { profileId: null, shortName: pos ? [ - pos.orgChild4?.orgChild4ShortName, - pos.orgChild3?.orgChild3ShortName, - pos.orgChild2?.orgChild2ShortName, - pos.orgChild1?.orgChild1ShortName, - pos.orgRoot?.orgRootShortName, - ].find((s: string | undefined) => typeof s === "string" && s.trim().length > 0) ?? - null + pos.orgChild4?.orgChild4ShortName, + pos.orgChild3?.orgChild3ShortName, + pos.orgChild2?.orgChild2ShortName, + pos.orgChild1?.orgChild1ShortName, + pos.orgRoot?.orgRootShortName, + ].find((s: string | undefined) => typeof s === "string" && s.trim().length > 0) ?? + null : null, posMasterNoPrefix: pos.posMasterNoPrefix ?? null, posMasterNo: pos.posMasterNo != null ? String(pos.posMasterNo) : null, @@ -8619,13 +8499,13 @@ export class OrganizationController extends Controller { profileId: null, shortName: pos ? [ - pos.orgChild4?.orgChild4ShortName, - pos.orgChild3?.orgChild3ShortName, - pos.orgChild2?.orgChild2ShortName, - pos.orgChild1?.orgChild1ShortName, - pos.orgRoot?.orgRootShortName, - ].find((s: string | undefined) => typeof s === "string" && s.trim().length > 0) ?? - null + pos.orgChild4?.orgChild4ShortName, + pos.orgChild3?.orgChild3ShortName, + pos.orgChild2?.orgChild2ShortName, + pos.orgChild1?.orgChild1ShortName, + pos.orgRoot?.orgRootShortName, + ].find((s: string | undefined) => typeof s === "string" && s.trim().length > 0) ?? + null : null, posMasterNoPrefix: pos.posMasterNoPrefix ?? null, posMasterNo: pos.posMasterNo != null ? String(pos.posMasterNo) : null, @@ -8682,7 +8562,6 @@ export class OrganizationController extends Controller { orgChild2Id, orgChild3Id, orgChild4Id, - authRoleId: draftPos.authRoleId, current_holderId: draftPos.next_holderId, next_holderId: null, isSit: draftPos.isSit, @@ -8772,14 +8651,14 @@ export class OrganizationController extends Controller { profileId: null, shortName: pmWithRelations ? [ - pmWithRelations.orgChild4?.orgChild4ShortName, - pmWithRelations.orgChild3?.orgChild3ShortName, - pmWithRelations.orgChild2?.orgChild2ShortName, - pmWithRelations.orgChild1?.orgChild1ShortName, - pmWithRelations.orgRoot?.orgRootShortName, - ].find( - (s: string | undefined) => typeof s === "string" && s.trim().length > 0, - ) ?? null + pmWithRelations.orgChild4?.orgChild4ShortName, + pmWithRelations.orgChild3?.orgChild3ShortName, + pmWithRelations.orgChild2?.orgChild2ShortName, + pmWithRelations.orgChild1?.orgChild1ShortName, + pmWithRelations.orgRoot?.orgRootShortName, + ].find( + (s: string | undefined) => typeof s === "string" && s.trim().length > 0, + ) ?? null : null, posMasterNoPrefix: draftPos.posMasterNoPrefix ?? null, posMasterNo: draftPos.posMasterNo != null ? String(draftPos.posMasterNo) : null, @@ -8823,45 +8702,6 @@ export class OrganizationController extends Controller { }; await queryRunner.commitTransaction(); - - // Clear Redis cache after successful publish (only menu and role for menu display) - const redis = require("redis"); - const { promisify } = require("util"); - const redisClient = redis.createClient({ - host: process.env.REDIS_HOST || "localhost", - port: parseInt(process.env.REDIS_PORT || "6379"), - }); - const keysAsync = promisify(redisClient.keys).bind(redisClient); - const delAsync = promisify(redisClient.del).bind(redisClient); - - try { - // Clear only menu and role cache (affects menu display) - const menuRolePatterns = ["menu_*", "role_*"]; - let totalCleared = 0; - - for (const pattern of menuRolePatterns) { - const keys = await keysAsync(pattern); - if (keys.length > 0) { - // Delete in chunks of 1000 to avoid argument limit - const chunkSize = 1000; - for (let i = 0; i < keys.length; i += chunkSize) { - const chunk = keys.slice(i, i + chunkSize); - await delAsync(...chunk); - } - totalCleared += keys.length; - console.log(`[moveDraftToCurrent] Cleared ${keys.length} cache keys for pattern: ${pattern}`); - } - } - console.log(`[moveDraftToCurrent] Total cache cleared: ${totalCleared} keys`); - } catch (err) { - console.error("[moveDraftToCurrent] Error clearing cache:", err); - } finally { - redisClient.quit(); - } - - // Invalidate memory cache - orgStructureCache.invalidate(currentRevisionId); - return new HttpSuccess(summary); } catch (error) { console.error("Error moving draft to current:", error); @@ -9292,7 +9132,7 @@ export class OrganizationController extends Controller { org: draftPosMaster ? getOrgFullName(draftPosMaster as PosMaster) ?? _null : _null, }); } - + if (nextHolderId != null && draftPos.positionIsSelected) { // ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ if (!draftPosMaster?.isSit) { @@ -9375,12 +9215,12 @@ export class OrganizationController extends Controller { child4DnaId: posMaster?.orgChild4?.ancestorDNA ?? null, shortName: posMaster ? [ - posMaster.orgChild4?.orgChild4ShortName, - posMaster.orgChild3?.orgChild3ShortName, - posMaster.orgChild2?.orgChild2ShortName, - posMaster.orgChild1?.orgChild1ShortName, - posMaster.orgRoot?.orgRootShortName, - ].find((s) => typeof s === "string" && s.trim().length > 0) ?? null + posMaster.orgChild4?.orgChild4ShortName, + posMaster.orgChild3?.orgChild3ShortName, + posMaster.orgChild2?.orgChild2ShortName, + posMaster.orgChild1?.orgChild1ShortName, + posMaster.orgRoot?.orgRootShortName, + ].find((s) => typeof s === "string" && s.trim().length > 0) ?? null : null, posMasterNoPrefix: posMaster?.posMasterNoPrefix ?? null, posMasterNo: posMaster?.posMasterNo != null ? String(posMaster.posMasterNo) : null, diff --git a/src/controllers/OrganizationDotnetController.ts b/src/controllers/OrganizationDotnetController.ts index 9d4e006b..8e803569 100644 --- a/src/controllers/OrganizationDotnetController.ts +++ b/src/controllers/OrganizationDotnetController.ts @@ -8918,7 +8918,7 @@ export class OrganizationDotnetController extends Controller { dateStart: profile?.dateStart ?? null, dateAppoint: profile?.dateAppoint ?? null, keycloak: profile?.keycloak ?? null, - posNo: profile?.posMasterNo ?? `${item.shortName} ${item.posMasterNo}`, + posNo: `${item.shortName} ${item.posMasterNo}`, position: item.position, positionLevel: item.posLevel, positionType: item.posType, diff --git a/src/controllers/PermissionProfileController.ts b/src/controllers/PermissionProfileController.ts index 0df114af..99e4cb47 100644 --- a/src/controllers/PermissionProfileController.ts +++ b/src/controllers/PermissionProfileController.ts @@ -65,7 +65,8 @@ export class PermissionProfileController extends Controller { if (!request.user.role.includes("SUPER_ADMIN")) { rootId = - orgRevisionActive?.posMasters?.filter((x) => x.current_holderId == profile.id)[0] + orgRevisionActive?.posMasters?.filter((x) => x.next_holderId == profile.id)[0] + // orgRevisionActive?.posMasters?.filter((x) => x.current_holderId == profile.id)[0] ?.orgRootId || null; if (!rootId) return new HttpSuccess([]); } diff --git a/src/controllers/ProfileChangeNameController.ts b/src/controllers/ProfileChangeNameController.ts index 22f147b8..0261666f 100644 --- a/src/controllers/ProfileChangeNameController.ts +++ b/src/controllers/ProfileChangeNameController.ts @@ -111,12 +111,9 @@ export class ProfileChangeNameController extends Controller { setLogDataDiff(req, { before, after: history }); profile.firstName = body.firstName ?? profile.firstName; profile.lastName = body.lastName ?? profile.lastName; - // profile.prefix = body.prefix ?? profile.prefix; //old + profile.prefix = body.prefix ?? profile.prefix; profile.rank = body.rank ?? profile.rank; - // profile.prefixMain = profile.rank ?? profile.prefix; // old - profile.prefixMain = body.prefix ?? profile.prefix; - profile.prefix = body.rank && body.rank.length > 0 ? body.rank : body.prefix ?? profile.prefix; - + profile.prefixMain = profile.rank ?? profile.prefix; await this.profileRepository.save(profile, { data: req }); setLogDataDiff(req, { before, after: profile }); @@ -187,11 +184,9 @@ export class ProfileChangeNameController extends Controller { if (profile && chkLastRecord.id === record.id) { profile.firstName = body.firstName ?? profile.firstName; profile.lastName = body.lastName ?? profile.lastName; - // profile.prefix = body.prefix ?? profile.prefix; //old + profile.prefix = body.prefix ?? profile.prefix; profile.rank = body.rank ?? profile.rank; - // profile.prefixMain = profile.rank ?? profile.prefix; // old - profile.prefixMain = body.prefix ?? profile.prefix; - profile.prefix = body.rank && body.rank.length > 0 ? body.rank : body.prefix ?? profile.prefix; + profile.prefixMain = profile.rank ?? profile.prefix; await this.profileRepository.save(profile, { data: req }); setLogDataDiff(req, { before: before_profile, after: profile }); } diff --git a/src/controllers/ProfileChangeNameEmployeeController.ts b/src/controllers/ProfileChangeNameEmployeeController.ts index a632e718..91dd3d55 100644 --- a/src/controllers/ProfileChangeNameEmployeeController.ts +++ b/src/controllers/ProfileChangeNameEmployeeController.ts @@ -117,11 +117,9 @@ export class ProfileChangeNameEmployeeController extends Controller { profile.firstName = body.firstName ?? profile.firstName; profile.lastName = body.lastName ?? profile.lastName; - // profile.prefix = body.prefix ?? profile.prefix; //old + profile.prefix = body.prefix ?? profile.prefix; profile.rank = body.rank ?? profile.rank; - // profile.prefixMain = profile.rank ?? profile.prefix; // old - profile.prefixMain = body.prefix ?? profile.prefix; - profile.prefix = body.rank && body.rank.length > 0 ? body.rank : body.prefix ?? profile.prefix; + profile.prefixMain = profile.rank ?? profile.prefix; await this.profileEmployeeRepo.save(profile, { data: req }); setLogDataDiff(req, { before, after: profile }); @@ -193,11 +191,9 @@ export class ProfileChangeNameEmployeeController extends Controller { if (profile && chkLastRecord.id === record.id) { profile.firstName = body.firstName ?? profile.firstName; profile.lastName = body.lastName ?? profile.lastName; - // profile.prefix = body.prefix ?? profile.prefix; //old + profile.prefix = body.prefix ?? profile.prefix; profile.rank = body.rank ?? profile.rank; - // profile.prefixMain = profile.rank ?? profile.prefix; // old - profile.prefixMain = body.prefix ?? profile.prefix; - profile.prefix = body.rank && body.rank.length > 0 ? body.rank : body.prefix ?? profile.prefix; + profile.prefixMain = profile.rank ?? profile.prefix; await this.profileEmployeeRepo.save(profile); } diff --git a/src/controllers/ProfileChangeNameEmployeeTempController.ts b/src/controllers/ProfileChangeNameEmployeeTempController.ts index 86c6b352..7f7b39aa 100644 --- a/src/controllers/ProfileChangeNameEmployeeTempController.ts +++ b/src/controllers/ProfileChangeNameEmployeeTempController.ts @@ -108,11 +108,9 @@ export class ProfileChangeNameEmployeeTempController extends Controller { profile.firstName = body.firstName ?? profile.firstName; profile.lastName = body.lastName ?? profile.lastName; - // profile.prefix = body.prefix ?? profile.prefix; //old + profile.prefix = body.prefix ?? profile.prefix; profile.rank = body.rank ?? profile.rank; - // profile.prefixMain = profile.rank ?? profile.prefix; // old - profile.prefixMain = body.prefix ?? profile.prefix; - profile.prefix = body.rank && body.rank.length > 0 ? body.rank : body.prefix ?? profile.prefix; + profile.prefixMain = profile.rank ?? profile.prefix; await this.profileEmployeeRepo.save(profile, { data: req }); setLogDataDiff(req, { before, after: profile }); @@ -181,11 +179,9 @@ export class ProfileChangeNameEmployeeTempController extends Controller { if (profile && chkLastRecord.id === record.id) { profile.firstName = body.firstName ?? profile.firstName; profile.lastName = body.lastName ?? profile.lastName; - // profile.prefix = body.prefix ?? profile.prefix; //old + profile.prefix = body.prefix ?? profile.prefix; profile.rank = body.rank ?? profile.rank; - // profile.prefixMain = profile.rank ?? profile.prefix; // old - profile.prefixMain = body.prefix ?? profile.prefix; - profile.prefix = body.rank && body.rank.length > 0 ? body.rank : body.prefix ?? profile.prefix; + profile.prefixMain = profile.rank ?? profile.prefix; await this.profileEmployeeRepo.save(profile); } diff --git a/src/controllers/ProfileController.ts b/src/controllers/ProfileController.ts index 05ab62f8..42b322d3 100644 --- a/src/controllers/ProfileController.ts +++ b/src/controllers/ProfileController.ts @@ -5801,8 +5801,7 @@ export class ProfileController extends Controller { Object.assign(record, body); record.dateRetireLaw = calculateRetireLaw(record.birthDate); record.prefixMain = record.prefix; - // record.prefix = record.rank && record.rank.length > 0 ? record.rank : record.prefixMain; - record.prefix = record.rank && record.rank.length > 0 ? record.rank : record.prefix; + record.prefix = record.rank && record.rank.length > 0 ? record.rank : record.prefixMain; record.createdUserId = request.user.sub; record.createdFullName = request.user.name; record.createdAt = new Date(); @@ -7162,7 +7161,6 @@ export class ProfileController extends Controller { : `profile.isLeave IS TRUE` : "1=1", ) - .andWhere("profile.isActive IS TRUE AND profile.isDelete IS FALSE") .andWhere(nodeCondition, { nodeId: nodeId }) .andWhere( new Brackets((qb) => { @@ -8726,10 +8724,14 @@ export class ProfileController extends Controller { "current_holders.orgChild2", "current_holders.orgChild3", "current_holders.orgChild4", + // "profileSalary", "profileEducations", "profileActpositions", ], order: { + // profileSalary: { + // order: "DESC", + // }, profileEducations: { level: "ASC", }, @@ -8794,6 +8796,72 @@ export class ProfileController extends Controller { }); const holder = profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id); const numPart = holder ? [holder.posMasterNoPrefix, holder.posMasterNo, holder.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : ''; + const shortName = + holder == null + ? null + : holder.orgChild4 != null + ? `${holder.orgChild4.orgChild4ShortName} ${numPart}` + : holder.orgChild3 != null + ? `${holder.orgChild3.orgChild3ShortName} ${numPart}` + : holder.orgChild2 != null + ? `${holder.orgChild2.orgChild2ShortName} ${numPart}` + : holder.orgChild1 != null + ? `${holder.orgChild1.orgChild1ShortName} ${numPart}` + : holder.orgRoot != null + ? `${holder.orgRoot.orgRootShortName} ${numPart}` + : null; + // const posMasterActs = await this.posMasterActRepository.find({ + // relations: [ + // "posMaster", + // "posMaster.orgRoot", + // "posMaster.orgChild1", + // "posMaster.orgChild2", + // "posMaster.orgChild3", + // "posMaster.orgChild4", + // "posMaster.current_holder", + // "posMaster.current_holder.posLevel", + // "posMaster.current_holder.posType", + // ], + // where: { + // posMaster: { + // orgRevisionId: orgRevisionPublish.id, + // }, + // posMasterChild: { + // current_holderId: profile.id, + // }, + // }, + // }); + // const data = await Promise.all( + // posMasterActs + // .sort((a, b) => a.posMaster.posMasterOrder - b.posMaster.posMasterOrder) + // .map((item) => { + // const shortName = + // item.posMaster != null && item.posMaster.orgChild4 != null + // ? `${item.posMaster.orgChild4.orgChild4ShortName} ${item.posMaster.posMasterNo}` + // : item.posMaster != null && item.posMaster?.orgChild3 != null + // ? `${item.posMaster.orgChild3.orgChild3ShortName} ${item.posMaster.posMasterNo}` + // : item.posMaster != null && item.posMaster?.orgChild2 != null + // ? `${item.posMaster.orgChild2.orgChild2ShortName} ${item.posMaster.posMasterNo}` + // : item.posMaster != null && item.posMaster?.orgChild1 != null + // ? `${item.posMaster.orgChild1.orgChild1ShortName} ${item.posMaster.posMasterNo}` + // : item.posMaster != null && item.posMaster?.orgRoot != null + // ? `${item.posMaster.orgRoot.orgRootShortName} ${item.posMaster.posMasterNo}` + // : null; + // return { + // id: item.id, + // posMasterOrder: item.posMasterOrder, + // profileId: item.posMaster?.current_holder?.id ?? null, + // citizenId: item.posMaster?.current_holder?.citizenId ?? null, + // prefix: item.posMaster?.current_holder?.prefix ?? null, + // firstName: item.posMaster?.current_holder?.firstName ?? null, + // lastName: item.posMaster?.current_holder?.lastName ?? null, + // posLevel: item.posMaster?.current_holder?.posLevel?.posLevelName ?? null, + // posType: item.posMaster?.current_holder?.posType?.posTypeName ?? null, + // position: item.posMaster?.current_holder?.position ?? null, + // posNo: shortName, + // }; + // }), + // ); const data = await Promise.all( profile.profileActpositions .filter((x) => x.status) @@ -8840,96 +8908,6 @@ export class ProfileController extends Controller { }, }); - let _Org = null; - let _PosNo = null; - if (!profile.org && !profile.posMasterNo) { - if (profile.isLeave) { - const profileWithSalary = await this.profileRepo.findOne({ - where: { - id: id, - profileSalary: { - commandCode: In([ - "0", - "9", - "1", - "2", - "3", - "4", - "8", - "10", - "11", - "12", - "13", - "14", - "15", - "16", - "20", - ]), - }, - }, - relations: { profileSalary: true }, - order: { - profileSalary: { - order: "DESC", - createdAt: "DESC", - }, - }, - }); - const profileSalaryList = profileWithSalary?.profileSalary || []; - - if (profileSalaryList.length > 0) { - const _profileSalary = - profile.leaveType == "RETIRE" - ? profileSalaryList.length > 1 - ? profileSalaryList[1] - : profileSalaryList[0] - : profileSalaryList[0]; - - if (_profileSalary) { - const orgLeaveParts = [ - _profileSalary.orgChild4 ?? null, - _profileSalary.orgChild3 ?? null, - _profileSalary.orgChild2 ?? null, - _profileSalary.orgChild1 ?? null, - _profileSalary.orgRoot ?? null, - ]; - _Org = orgLeaveParts - .filter((x: any) => x !== undefined && x !== null) - .join("\n"); - _PosNo = `${_profileSalary.posNoAbb} ${_profileSalary.posNo}`; - } - } - } else { - _Org = [ - child4?.orgChild4Name, - child3?.orgChild3Name, - child2?.orgChild2Name, - child1?.orgChild1Name, - root?.orgRootName, - ] - .filter((x) => x != null && x !== "") - .join("\n"); - - _PosNo = - holder == null - ? null - : holder.orgChild4 != null - ? `${holder.orgChild4.orgChild4ShortName} ${numPart}` - : holder.orgChild3 != null - ? `${holder.orgChild3.orgChild3ShortName} ${numPart}` - : holder.orgChild2 != null - ? `${holder.orgChild2.orgChild2ShortName} ${numPart}` - : holder.orgChild1 != null - ? `${holder.orgChild1.orgChild1ShortName} ${numPart}` - : holder.orgRoot != null - ? `${holder.orgRoot.orgRootShortName} ${numPart}` - : null; - } - } else { - _Org = profile.org; - _PosNo = profile.posMasterNo; - } - const _profile: any = { profileId: profile.id, prefix: profile.prefix, @@ -8981,8 +8959,7 @@ export class ProfileController extends Controller { node: null, nodeId: null, nodeDnaId: null, - posNo: _PosNo, - org: _Org, + posNo: shortName, isPosmasterAct: data.length > 0, posmasterAct: data, salary: profile ? profile.amount : null, diff --git a/src/controllers/ProfileEmployeeController.ts b/src/controllers/ProfileEmployeeController.ts index 21eb9a5d..59f9e91d 100644 --- a/src/controllers/ProfileEmployeeController.ts +++ b/src/controllers/ProfileEmployeeController.ts @@ -2415,7 +2415,7 @@ export class ProfileEmployeeController extends Controller { Object.assign(record, body); record.dateRetireLaw = calculateRetireLaw(record.birthDate); record.prefixMain = record.prefix; - record.prefix = record.rank && record.rank.length > 0 ? record.rank : record.prefix; + record.prefix = record.rank && record.rank.length > 0 ? record.rank : record.prefixMain; record.createdUserId = request.user.sub; record.createdFullName = request.user.name; record.createdAt = new Date(); @@ -3254,8 +3254,8 @@ export class ProfileEmployeeController extends Controller { .leftJoinAndSelect("current_holders.orgChild2", "orgChild2") .leftJoinAndSelect("current_holders.orgChild3", "orgChild3") .leftJoinAndSelect("current_holders.orgChild4", "orgChild4") - .where("current_holders.orgRevisionId = :orgRevisionId", { - orgRevisionId: findRevision.id, + .where(node && nodeId ? "current_holders.orgRevisionId = :orgRevisionId" : "1=1", { + orgRevisionId: node && nodeId ? findRevision.id : undefined, }) .andWhere( _data.root != undefined && _data.root != null @@ -3338,7 +3338,6 @@ export class ProfileEmployeeController extends Controller { : `profileEmployee.isLeave IS TRUE` : "1=1", ) - .andWhere("profileEmployee.isActive IS TRUE AND profileEmployee.isDelete IS FALSE") .andWhere("profileEmployee.employeeClass LIKE :type", { type: "PERM", }) @@ -6462,92 +6461,6 @@ export class ProfileEmployeeController extends Controller { ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild4; const _numPart = posMaster ? [posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : ''; - - // org / posNo — ล้อ fallback ของ officer (ProfileController.getProfileByProfileid) - // employee ไม่มี profile.org / profile.posMasterNo จึงแยกตาม isLeave โดยตรง - let _Org: string | null = null; - let _PosNo: string | null = null; - if (profile.isLeave) { - const profileWithSalary = await this.profileRepo.findOne({ - where: { - id: id, - profileSalary: { - commandCode: In([ - "0", - "9", - "1", - "2", - "3", - "4", - "8", - "10", - "11", - "12", - "13", - "14", - "15", - "16", - "20", - ]), - }, - }, - relations: { profileSalary: true }, - order: { - profileSalary: { - order: "DESC", - createdAt: "DESC", - }, - }, - }); - const profileSalaryList = profileWithSalary?.profileSalary || []; - - if (profileSalaryList.length > 0) { - const _profileSalary = - profile.leaveType == "RETIRE" - ? profileSalaryList.length > 1 - ? profileSalaryList[1] - : profileSalaryList[0] - : profileSalaryList[0]; - - if (_profileSalary) { - _Org = [ - _profileSalary.orgChild4 ?? null, - _profileSalary.orgChild3 ?? null, - _profileSalary.orgChild2 ?? null, - _profileSalary.orgChild1 ?? null, - _profileSalary.orgRoot ?? null, - ] - .filter((x: any) => x !== undefined && x !== null) - .join("\n"); - _PosNo = `${_profileSalary.posNoAbb} ${_profileSalary.posNo}`; - } - } - } else { - _Org = [ - child4?.orgChild4Name, - child3?.orgChild3Name, - child2?.orgChild2Name, - child1?.orgChild1Name, - root?.orgRootName, - ] - .filter((x) => x != null && x !== "") - .join("\n"); - - _PosNo = - posMaster == null - ? null - : child4 != null - ? `${child4.orgChild4ShortName} ${_numPart}` - : child3 != null - ? `${child3.orgChild3ShortName} ${_numPart}` - : child2 != null - ? `${child2.orgChild2ShortName} ${_numPart}` - : child1 != null - ? `${child1.orgChild1ShortName} ${_numPart}` - : root != null - ? `${root.orgRootShortName} ${_numPart}` - : null; - } const _profile: any = { profileId: profile.id, prefix: profile.prefix, @@ -6560,7 +6473,7 @@ export class ProfileEmployeeController extends Controller { position: profile.position, leaveDate: profile.dateLeave, posMasterNo: posMaster == null ? null : posMaster.posMasterNo, - posLevelName: `${profile?.posType?.posTypeShortName ?? ""} ${profile?.posLevel?.posLevelName ?? ""}`.trim(), + posLevelName: `${profile?.posType?.posTypeShortName ?? ""} ${profile?.posLevel?.posLevelName ?? ""}`, posLevelRank: profile.posLevel == null ? null : profile.posLevel.posLevelRank, posLevelId: profile.posLevel == null ? null : profile.posLevel.id, posTypeName: profile.posType == null ? null : profile.posType.posTypeName, @@ -6589,8 +6502,7 @@ export class ProfileEmployeeController extends Controller { child4ShortName: child4 == null ? null : child4.orgChild4ShortName, node: null, nodeId: null, - posNo: _PosNo, - org: _Org, + posNo: null, salary: profile.amount, education: profile && profile.profileEducations.length > 0 @@ -6605,22 +6517,27 @@ export class ProfileEmployeeController extends Controller { _profile.node = 4; _profile.nodeId = _profile.child4Id; _profile.nodeShortName = _profile.child4ShortName; + _profile.posNo = `${_profile.child4ShortName} ${_numPart}`; } else if (_profile.child3Id != null) { _profile.node = 3; _profile.nodeId = _profile.child3Id; _profile.nodeShortName = _profile.child3ShortName; + _profile.posNo = `${_profile.child3ShortName} ${_numPart}`; } else if (_profile.child2Id != null) { _profile.node = 2; _profile.nodeId = _profile.child2Id; _profile.nodeShortName = _profile.child2ShortName; + _profile.posNo = `${_profile.child2ShortName} ${_numPart}`; } else if (_profile.child1Id != null) { _profile.node = 1; _profile.nodeId = _profile.child1Id; _profile.nodeShortName = _profile.child1ShortName; + _profile.posNo = `${_profile.child1ShortName} ${_numPart}`; } else if (_profile.rootId != null) { _profile.node = 0; _profile.nodeId = _profile.rootId; _profile.nodeShortName = _profile.rootShortName; + _profile.posNo = `${_profile.rootShortName} ${_numPart}`; } return new HttpSuccess(_profile); } diff --git a/src/controllers/ProfileSalaryController.ts b/src/controllers/ProfileSalaryController.ts index c294cb25..8abe9aa2 100644 --- a/src/controllers/ProfileSalaryController.ts +++ b/src/controllers/ProfileSalaryController.ts @@ -23,7 +23,6 @@ import { ProfileEmployee } from "../entities/ProfileEmployee"; import { In, IsNull, LessThan, MoreThan, Not } from "typeorm"; import permission from "../interfaces/permission"; import { setLogDataDiff } from "../interfaces/utils"; -import { ExecuteSalaryReportService } from "../services/ExecuteSalaryReportService"; import { normalizeDurationSumSimple } from "../utils/tenure"; import { TenurePositionOfficer, @@ -1381,10 +1380,91 @@ export class ProfileSalaryController extends Controller { @Post("update") public async updateSalary(@Request() req: RequestWithUser, @Body() body: CreateProfileSalary) { - await new ExecuteSalaryReportService().executeOfficerSalaryUpdate([body], { - user: { sub: req.user.sub, name: req.user.name }, - req, + if (!body.profileId) { + throw new HttpError(HttpStatus.BAD_REQUEST, "กรุณากรอก profileId"); + } + + const profile = await this.profileRepo.findOneBy({ id: body.profileId }); + if (!profile) { + throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); + } + await new permission().PermissionOrgUserUpdate(req, "SYS_REGISTRY_OFFICER", profile.id); + + const dest_item = await this.salaryRepo.findOne({ + where: { profileId: body.profileId }, + order: { order: "DESC" }, }); + const before = null; + let _posNumCodeSit: string = ""; + let _posNumCodeSitAbb: string = ""; + const _command = await this.commandRepository.findOne({ + where: { id: body.commandId ?? "" }, + }); + if (_command) { + if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { + const orgRootDeputy = await this.orgRootRepository.findOne({ + where: { + isDeputy: true, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: ["orgRevision"], + }); + _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; + _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; + } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { + _posNumCodeSit = "กรุงเทพมหานคร"; + _posNumCodeSitAbb = "กทม."; + } else { + let _profileAdmin = await this.profileRepo.findOne({ + where: { + keycloak: _command?.createdUserId.toString(), + current_holders: { + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + }, + relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], + }); + _posNumCodeSit = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? + ""; + _posNumCodeSitAbb = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot + .orgRootShortName ?? ""; + } + } + const data = new ProfileSalary(); + data.posNumCodeSit = _posNumCodeSit; + data.posNumCodeSitAbb = _posNumCodeSitAbb; + const meta = { + order: dest_item == null ? 1 : dest_item.order + 1, + createdUserId: req.user.sub, + createdFullName: req.user.name, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; + + Object.assign(data, { ...body, ...meta }); + const history = new ProfileSalaryHistory(); + Object.assign(history, { ...data, id: undefined }); + await this.salaryRepo.save(data, { data: req }); + setLogDataDiff(req, { before, after: data }); + history.profileSalaryId = data.id; + await this.salaryHistoryRepo.save(history, { data: req }); + + let _null: any = null; + profile.amount = body.amount ?? _null; + profile.amountSpecial = body.amountSpecial ?? _null; + profile.positionSalaryAmount = body.positionSalaryAmount ?? _null; + profile.mouthSalaryAmount = body.mouthSalaryAmount ?? _null; + await this.profileRepo.save(profile); return new HttpSuccess(); } diff --git a/src/controllers/ProfileSalaryEmployeeController.ts b/src/controllers/ProfileSalaryEmployeeController.ts index 6f6847f7..5f2ea997 100644 --- a/src/controllers/ProfileSalaryEmployeeController.ts +++ b/src/controllers/ProfileSalaryEmployeeController.ts @@ -27,7 +27,6 @@ import { Profile } from "../entities/Profile"; import { In, LessThan, IsNull, MoreThan } from "typeorm"; import permission from "../interfaces/permission"; import { setLogDataDiff } from "../interfaces/utils"; -import { ExecuteSalaryReportService } from "../services/ExecuteSalaryReportService"; import { normalizeDurationSumSimple } from "../utils/tenure"; import { Command } from "../entities/Command"; import { OrgRoot } from "../entities/OrgRoot"; @@ -508,10 +507,94 @@ export class ProfileSalaryEmployeeController extends Controller { @Request() req: RequestWithUser, @Body() body: CreateProfileSalaryEmployee, ) { - await new ExecuteSalaryReportService().executeEmployeeSalaryUpdate([body], { - user: { sub: req.user.sub, name: req.user.name }, - req, + if (!body.profileEmployeeId) { + throw new HttpError(HttpStatus.BAD_REQUEST, "กรุณากรอก profileEmployeeId"); + } + + const profile = await this.profileRepo.findOneBy({ id: body.profileEmployeeId }); + if (!profile) { + throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); + } + await new permission().PermissionOrgUserUpdate(req, "SYS_REGISTRY_EMP", profile.id); + + const dest_item = await this.salaryRepo.findOne({ + where: { profileEmployeeId: body.profileEmployeeId }, + order: { order: "DESC" }, }); + const before = null; + let _posNumCodeSit: string = ""; + let _posNumCodeSitAbb: string = ""; + const _command = await this.commandRepository.findOne({ + where: { id: body.commandId ?? "" }, + }); + if (_command) { + if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { + const orgRootDeputy = await this.orgRootRepository.findOne({ + where: { + isDeputy: true, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: ["orgRevision"], + }); + _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; + _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; + } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { + _posNumCodeSit = "กรุงเทพมหานคร"; + _posNumCodeSitAbb = "กทม."; + } else { + let _profileAdmin = await this.profileGovementRepo.findOne({ + where: { + keycloak: _command?.createdUserId.toString(), + current_holders: { + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + }, + relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], + }); + _posNumCodeSit = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? + ""; + _posNumCodeSitAbb = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot + .orgRootShortName ?? ""; + } + } + const data = new ProfileSalary(); + data.posNumCodeSit = _posNumCodeSit; + data.posNumCodeSitAbb = _posNumCodeSitAbb; + const meta = { + order: dest_item == null ? 1 : dest_item.order + 1, + createdUserId: req.user.sub, + createdFullName: req.user.name, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; + + Object.assign(data, { ...body, ...meta }); + const history = new ProfileSalaryHistory(); + Object.assign(history, { ...data, id: undefined }); + + await this.salaryRepo.save(data, { data: req }); + setLogDataDiff(req, { before, after: data }); + history.profileSalaryId = data.id; + await this.salaryHistoryRepo.save(history, { data: req }); + + let _null: any = null; + profile.amount = body.amount ?? _null; + profile.amountSpecial = body.amountSpecial ?? _null; + profile.positionSalaryAmount = body.positionSalaryAmount ?? _null; + profile.mouthSalaryAmount = body.mouthSalaryAmount ?? _null; + profile.salaryLevel = body.salaryLevel ?? _null; + profile.group = body.group ?? _null; + await this.profileRepo.save(profile); return new HttpSuccess(); } diff --git a/src/controllers/WorkflowController.ts b/src/controllers/WorkflowController.ts index d8aa0b77..ae5c8859 100644 --- a/src/controllers/WorkflowController.ts +++ b/src/controllers/WorkflowController.ts @@ -865,7 +865,6 @@ export class WorkflowController extends Controller { type?: string | null; sortBy?: string | null; descending?: boolean; - isAllDirector?: boolean; }, ) { const userKeycloak = body.keycloakId ?? request.user.sub; @@ -954,46 +953,38 @@ export class WorkflowController extends Controller { let mainConditions: any[] = []; if (type.trim().toUpperCase() === "OPERATE" || body.type === "employee") { - - if (body.isAllDirector === true) { - mainConditions = [ - { ...baseCondition, orgRootId: In(roodIds) } - ]; - } - else { - mainConditions = [ - { ...baseCondition, orgRootId: In(roodIds), orgChild1Id: IsNull() }, - { - ...baseCondition, - orgRootId: In(roodIds), - orgChild1Id: posMasterUser.orgChild1Id, - orgChild2Id: IsNull(), - }, - { - ...baseCondition, - orgRootId: In(roodIds), - orgChild1Id: posMasterUser.orgChild1Id, - orgChild2Id: posMasterUser.orgChild2Id, - orgChild3Id: IsNull(), - }, - { - ...baseCondition, - orgRootId: In(roodIds), - orgChild1Id: posMasterUser.orgChild1Id, - orgChild2Id: posMasterUser.orgChild2Id, - orgChild3Id: posMasterUser.orgChild3Id, - orgChild4Id: IsNull(), - }, - { - ...baseCondition, - orgRootId: In(roodIds), - orgChild1Id: posMasterUser.orgChild1Id, - orgChild2Id: posMasterUser.orgChild2Id, - orgChild3Id: posMasterUser.orgChild3Id, - orgChild4Id: posMasterUser.orgChild4Id, - }, - ]; - } + mainConditions = [ + { ...baseCondition, orgRootId: In(roodIds), orgChild1Id: IsNull() }, + { + ...baseCondition, + orgRootId: In(roodIds), + orgChild1Id: posMasterUser.orgChild1Id, + orgChild2Id: IsNull(), + }, + { + ...baseCondition, + orgRootId: In(roodIds), + orgChild1Id: posMasterUser.orgChild1Id, + orgChild2Id: posMasterUser.orgChild2Id, + orgChild3Id: IsNull(), + }, + { + ...baseCondition, + orgRootId: In(roodIds), + orgChild1Id: posMasterUser.orgChild1Id, + orgChild2Id: posMasterUser.orgChild2Id, + orgChild3Id: posMasterUser.orgChild3Id, + orgChild4Id: IsNull(), + }, + { + ...baseCondition, + orgRootId: In(roodIds), + orgChild1Id: posMasterUser.orgChild1Id, + orgChild2Id: posMasterUser.orgChild2Id, + orgChild3Id: posMasterUser.orgChild3Id, + orgChild4Id: posMasterUser.orgChild4Id, + }, + ]; } else if (isLowLevel) { mainConditions = [ { diff --git a/src/entities/OrgChild1.ts b/src/entities/OrgChild1.ts index ef2a9be6..e322df8b 100644 --- a/src/entities/OrgChild1.ts +++ b/src/entities/OrgChild1.ts @@ -174,46 +174,6 @@ export class OrgChild1 extends EntityBase { }) JOB_CODE: string; - @Column({ - nullable: true, - length: 3, - comment: "ROOT_CODE", - default: null, - }) - ROOT_CODE: string; - - @Column({ - nullable: true, - length: 3, - comment: "CHILD1_CODE", - default: null, - }) - CHILD1_CODE: string; - - @Column({ - nullable: true, - length: 3, - comment: "CHILD2_CODE", - default: null, - }) - CHILD2_CODE: string; - - @Column({ - nullable: true, - length: 3, - comment: "CHILD3_CODE", - default: null, - }) - CHILD3_CODE: string; - - @Column({ - nullable: true, - length: 3, - comment: "CHILD4_CODE", - default: null, - }) - CHILD4_CODE: string; - @ManyToOne(() => OrgRoot, (orgRoot) => orgRoot.orgChild1s) @JoinColumn({ name: "orgRootId" }) orgRoot: OrgRoot; @@ -268,21 +228,6 @@ export class CreateOrgChild1 { @Column() JOB_CODE?: string | null; - @Column() - ROOT_CODE?: string | null; - - @Column() - CHILD1_CODE?: string | null; - - @Column() - CHILD2_CODE?: string | null; - - @Column() - CHILD3_CODE?: string | null; - - @Column() - CHILD4_CODE?: string | null; - @Column() orgChild1PhoneEx?: string; @@ -338,21 +283,6 @@ export class UpdateOrgChild1 { @Column() JOB_CODE?: string | null; - @Column() - ROOT_CODE?: string | null; - - @Column() - CHILD1_CODE?: string | null; - - @Column() - CHILD2_CODE?: string | null; - - @Column() - CHILD3_CODE?: string | null; - - @Column() - CHILD4_CODE?: string | null; - @Column() orgChild1PhoneEx?: string; diff --git a/src/entities/OrgChild2.ts b/src/entities/OrgChild2.ts index 50f40769..b2ed6a0c 100644 --- a/src/entities/OrgChild2.ts +++ b/src/entities/OrgChild2.ts @@ -146,46 +146,6 @@ export class OrgChild2 extends EntityBase { }) JOB_CODE: string; - @Column({ - nullable: true, - length: 3, - comment: "ROOT_CODE", - default: null, - }) - ROOT_CODE: string; - - @Column({ - nullable: true, - length: 3, - comment: "CHILD1_CODE", - default: null, - }) - CHILD1_CODE: string; - - @Column({ - nullable: true, - length: 3, - comment: "CHILD2_CODE", - default: null, - }) - CHILD2_CODE: string; - - @Column({ - nullable: true, - length: 3, - comment: "CHILD3_CODE", - default: null, - }) - CHILD3_CODE: string; - - @Column({ - nullable: true, - length: 3, - comment: "CHILD4_CODE", - default: null, - }) - CHILD4_CODE: string; - @Column({ nullable: true, length: 40, @@ -262,21 +222,6 @@ export class CreateOrgChild2 { @Column() JOB_CODE?: string; - @Column() - ROOT_CODE?: string; - - @Column() - CHILD1_CODE?: string; - - @Column() - CHILD2_CODE?: string; - - @Column() - CHILD3_CODE?: string; - - @Column() - CHILD4_CODE?: string; - @Column() orgChild2PhoneEx?: string; @@ -324,21 +269,6 @@ export class UpdateOrgChild2 { @Column() JOB_CODE?: string | null; - @Column() - ROOT_CODE?: string | null; - - @Column() - CHILD1_CODE?: string | null; - - @Column() - CHILD2_CODE?: string | null; - - @Column() - CHILD3_CODE?: string | null; - - @Column() - CHILD4_CODE?: string | null; - @Column() orgChild2PhoneEx?: string; diff --git a/src/entities/OrgChild3.ts b/src/entities/OrgChild3.ts index 72a29336..4c376fb6 100644 --- a/src/entities/OrgChild3.ts +++ b/src/entities/OrgChild3.ts @@ -153,46 +153,6 @@ export class OrgChild3 extends EntityBase { }) JOB_CODE: string; - @Column({ - nullable: true, - length: 3, - comment: "ROOT_CODE", - default: null, - }) - ROOT_CODE: string; - - @Column({ - nullable: true, - length: 3, - comment: "CHILD1_CODE", - default: null, - }) - CHILD1_CODE: string; - - @Column({ - nullable: true, - length: 3, - comment: "CHILD2_CODE", - default: null, - }) - CHILD2_CODE: string; - - @Column({ - nullable: true, - length: 3, - comment: "CHILD3_CODE", - default: null, - }) - CHILD3_CODE: string; - - @Column({ - nullable: true, - length: 3, - comment: "CHILD4_CODE", - default: null, - }) - CHILD4_CODE: string; - @Column({ nullable: true, length: 40, @@ -270,21 +230,6 @@ export class CreateOrgChild3 { @Column() JOB_CODE?: string; - @Column() - ROOT_CODE?: string; - - @Column() - CHILD1_CODE?: string; - - @Column() - CHILD2_CODE?: string; - - @Column() - CHILD3_CODE?: string; - - @Column() - CHILD4_CODE?: string; - @Column() orgChild3PhoneEx?: string; @@ -334,21 +279,6 @@ export class UpdateOrgChild3 { @Column() JOB_CODE?: string | null; - @Column() - ROOT_CODE?: string | null; - - @Column() - CHILD1_CODE?: string | null; - - @Column() - CHILD2_CODE?: string | null; - - @Column() - CHILD3_CODE?: string | null; - - @Column() - CHILD4_CODE?: string | null; - @Column() orgChild3PhoneEx?: string; diff --git a/src/entities/OrgChild4.ts b/src/entities/OrgChild4.ts index 4f053b00..314c9393 100644 --- a/src/entities/OrgChild4.ts +++ b/src/entities/OrgChild4.ts @@ -158,46 +158,6 @@ export class OrgChild4 extends EntityBase { }) JOB_CODE: string; - @Column({ - nullable: true, - length: 3, - comment: "ROOT_CODE", - default: null, - }) - ROOT_CODE: string; - - @Column({ - nullable: true, - length: 3, - comment: "CHILD1_CODE", - default: null, - }) - CHILD1_CODE: string; - - @Column({ - nullable: true, - length: 3, - comment: "CHILD2_CODE", - default: null, - }) - CHILD2_CODE: string; - - @Column({ - nullable: true, - length: 3, - comment: "CHILD3_CODE", - default: null, - }) - CHILD3_CODE: string; - - @Column({ - nullable: true, - length: 3, - comment: "CHILD4_CODE", - default: null, - }) - CHILD4_CODE: string; - @Column({ nullable: true, length: 40, @@ -276,21 +236,6 @@ export class CreateOrgChild4 { @Column() JOB_CODE?: string; - @Column() - ROOT_CODE?: string; - - @Column() - CHILD1_CODE?: string; - - @Column() - CHILD2_CODE?: string; - - @Column() - CHILD3_CODE?: string; - - @Column() - CHILD4_CODE?: string; - @Column() orgChild4PhoneEx?: string; @@ -338,21 +283,6 @@ export class UpdateOrgChild4 { @Column() JOB_CODE?: string | null; - @Column() - ROOT_CODE?: string | null; - - @Column() - CHILD1_CODE?: string | null; - - @Column() - CHILD2_CODE?: string | null; - - @Column() - CHILD3_CODE?: string | null; - - @Column() - CHILD4_CODE?: string | null; - @Column() orgChild4PhoneEx?: string; diff --git a/src/entities/OrgRoot.ts b/src/entities/OrgRoot.ts index 02e22bb8..3aefad6e 100644 --- a/src/entities/OrgRoot.ts +++ b/src/entities/OrgRoot.ts @@ -166,46 +166,6 @@ export class OrgRoot extends EntityBase { }) JOB_CODE: string; - @Column({ - nullable: true, - length: 3, - comment: "ROOT_CODE", - default: null, - }) - ROOT_CODE: string; - - @Column({ - nullable: true, - length: 3, - comment: "CHILD1_CODE", - default: null, - }) - CHILD1_CODE: string; - - @Column({ - nullable: true, - length: 3, - comment: "CHILD2_CODE", - default: null, - }) - CHILD2_CODE: string; - - @Column({ - nullable: true, - length: 3, - comment: "CHILD3_CODE", - default: null, - }) - CHILD3_CODE: string; - - @Column({ - nullable: true, - length: 3, - comment: "CHILD4_CODE", - default: null, - }) - CHILD4_CODE: string; - @ManyToOne(() => OrgRevision, (orgRevision) => orgRevision.orgRoots) @JoinColumn({ name: "orgRevisionId" }) orgRevision: OrgRevision; @@ -266,21 +226,6 @@ export class CreateOrgRoot { @Column() JOB_CODE?: string; - @Column() - ROOT_CODE?: string; - - @Column() - CHILD1_CODE?: string; - - @Column() - CHILD2_CODE?: string; - - @Column() - CHILD3_CODE?: string; - - @Column() - CHILD4_CODE?: string; - @Column() orgRootPhoneEx?: string; @@ -336,21 +281,6 @@ export class UpdateOrgRoot { @Column() JOB_CODE?: string | null; - @Column() - ROOT_CODE?: string | null; - - @Column() - CHILD1_CODE?: string | null; - - @Column() - CHILD2_CODE?: string | null; - - @Column() - CHILD3_CODE?: string | null; - - @Column() - CHILD4_CODE?: string | null; - @Column() orgRootPhoneEx?: string; diff --git a/src/entities/ProfileEmployee.ts b/src/entities/ProfileEmployee.ts index 45f5e159..11c2159b 100644 --- a/src/entities/ProfileEmployee.ts +++ b/src/entities/ProfileEmployee.ts @@ -1014,7 +1014,7 @@ export class UpdateInformationProfileEmployee { } export type UpdateProfileEmployee = { - prefix?: string | null; + prefix: string; rank?: string | null; firstName: string; lastName: string; diff --git a/src/interfaces/utils.ts b/src/interfaces/utils.ts index 27b35585..7b55fd0f 100644 --- a/src/interfaces/utils.ts +++ b/src/interfaces/utils.ts @@ -4,7 +4,7 @@ import { PosMaster } from "../entities/PosMaster"; import { Position } from "../entities/Position"; import { EmployeePosMaster } from "../entities/EmployeePosMaster"; import { EmployeePosition } from "../entities/EmployeePosition"; -import { EntityManager, In, IsNull, MoreThan, Not } from "typeorm"; +import { In, IsNull, MoreThan, Not } from "typeorm"; import { RequestWithUser } from "../middlewares/user"; import { Command } from "../entities/Command"; import { ProfileSalary } from "../entities/ProfileSalary"; @@ -254,23 +254,14 @@ export function calculateRetireYear(birthDate: Date) { return yy + 61; } -export async function removeProfileInOrganize( - profileId: string, - type: string, - manager?: EntityManager, -) { - // ถ้าส่ง manager เข้ามา → ทุก query/update อยู่ใน transaction ของ caller (all-or-nothing) - // ถ้าไม่ส่ง → ใช้ global DataSource เหมือนเดิม (backward compatible) - const ds = manager ?? AppDataSource; - const currentRevision = await ds - .getRepository(OrgRevision) +export async function removeProfileInOrganize(profileId: string, type: string) { + const currentRevision = await AppDataSource.getRepository(OrgRevision) .createQueryBuilder("orgRevision") .where("orgRevision.orgRevisionIsDraft = false") .andWhere("orgRevision.orgRevisionIsCurrent = true") .getOne(); - const draftRevision = await ds - .getRepository(OrgRevision) + const draftRevision = await AppDataSource.getRepository(OrgRevision) .createQueryBuilder("orgRevision") .where("orgRevision.orgRevisionIsDraft = true") .andWhere("orgRevision.orgRevisionIsCurrent = false") @@ -280,30 +271,26 @@ export async function removeProfileInOrganize( return; } if (type === "OFFICER") { - const findProfileInposMaster = await ds - .getRepository(PosMaster) + const findProfileInposMaster = await AppDataSource.getRepository(PosMaster) .createQueryBuilder("posMaster") .where("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: currentRevision?.id }) .andWhere("posMaster.current_holderId = :profileId", { profileId }) .getOne(); - await ds - .getRepository(PosMaster) + await AppDataSource.getRepository(PosMaster) .createQueryBuilder() .update(PosMaster) .set({ current_holderId: null, isSit: false }) .where("id = :id", { id: findProfileInposMaster?.id }) .execute(); - const findProfileInposMasterDraft = await ds - .getRepository(PosMaster) + const findProfileInposMasterDraft = await AppDataSource.getRepository(PosMaster) .createQueryBuilder("posMaster") .where("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: draftRevision?.id }) .andWhere("posMaster.next_holderId = :profileId", { profileId }) .getOne(); - await ds - .getRepository(PosMaster) + await AppDataSource.getRepository(PosMaster) .createQueryBuilder() .update(PosMaster) .set({ next_holderId: null, isSit: false }) @@ -313,8 +300,7 @@ export async function removeProfileInOrganize( if (!findProfileInposMaster && !findProfileInposMasterDraft) { return; } - const findPosition = await ds - .getRepository(Position) + const findPosition = await AppDataSource.getRepository(Position) .createQueryBuilder("position") .where("position.posMasterId = :posMasterId", { posMasterId: findProfileInposMaster?.id }) .getMany(); @@ -322,8 +308,7 @@ export async function removeProfileInOrganize( if (!findPosition) { return; } - await ds - .getRepository(Position) + await AppDataSource.getRepository(Position) .createQueryBuilder() .update(Position) .set({ positionIsSelected: false }) @@ -331,16 +316,14 @@ export async function removeProfileInOrganize( .execute(); } if (type === "EMPLOYEE") { - const findProfileInEmpPosMaster = await ds - .getRepository(EmployeePosMaster) + const findProfileInEmpPosMaster = await AppDataSource.getRepository(EmployeePosMaster) .createQueryBuilder("employeePosMaster") .where("employeePosMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: currentRevision?.id, }) .andWhere("employeePosMaster.current_holderId = :profileId", { profileId }) .getOne(); - await ds - .getRepository(EmployeePosMaster) + await AppDataSource.getRepository(EmployeePosMaster) .createQueryBuilder() .update(EmployeePosMaster) .set({ current_holderId: null, isSit: false }) @@ -350,8 +333,7 @@ export async function removeProfileInOrganize( if (!findProfileInEmpPosMaster) { return; } - const findEmpPosition = await ds - .getRepository(EmployeePosition) + const findEmpPosition = await AppDataSource.getRepository(EmployeePosition) .createQueryBuilder("employeePosition") .where("employeePosition.posMasterId = :posMasterId", { posMasterId: findProfileInEmpPosMaster?.id, @@ -362,8 +344,7 @@ export async function removeProfileInOrganize( return; } - await ds - .getRepository(EmployeePosition) + await AppDataSource.getRepository(EmployeePosition) .createQueryBuilder() .update(EmployeePosition) .set({ positionIsSelected: false }) @@ -372,10 +353,8 @@ export async function removeProfileInOrganize( } } -export async function removePostMasterAct(profileId: string, manager?: EntityManager) { - const ds = manager ?? AppDataSource; - const currentRevision = await ds - .getRepository(OrgRevision) +export async function removePostMasterAct(profileId: string) { + const currentRevision = await AppDataSource.getRepository(OrgRevision) .createQueryBuilder("orgRevision") .where("orgRevision.orgRevisionIsDraft = false") .andWhere("orgRevision.orgRevisionIsCurrent = true") @@ -385,8 +364,7 @@ export async function removePostMasterAct(profileId: string, manager?: EntityMan return; } - const findProfileInposMaster = await ds - .getRepository(PosMaster) + const findProfileInposMaster = await AppDataSource.getRepository(PosMaster) .createQueryBuilder("posMaster") .where("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId: currentRevision?.id }) .andWhere("posMaster.current_holderId = :profileId", { profileId }) @@ -396,12 +374,11 @@ export async function removePostMasterAct(profileId: string, manager?: EntityMan return; } - const posMasterAct = await ds - .getRepository(PosMasterAct) + const posMasterAct = await AppDataSource.getRepository(PosMasterAct) .createQueryBuilder("posMasterAct") .where("posMasterAct.posMasterChildId = :posMasterChildId", { posMasterChildId: findProfileInposMaster.id }) .getMany(); - await ds.getRepository(PosMasterAct).remove(posMasterAct); + await AppDataSource.getRepository(PosMasterAct).remove(posMasterAct); } export async function checkReturnCommandType(commandId: string) { diff --git a/src/migration/1781517610929-update_root_c1_to_c4_add_field_code.ts b/src/migration/1781517610929-update_root_c1_to_c4_add_field_code.ts deleted file mode 100644 index 157ab487..00000000 --- a/src/migration/1781517610929-update_root_c1_to_c4_add_field_code.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; - -export class UpdateRootC1ToC4AddFieldCode1781517610929 implements MigrationInterface { - name = 'UpdateRootC1ToC4AddFieldCode1781517610929' - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE \`orgChild4\` ADD \`CHILD4_CODE\` varchar(3) NULL COMMENT 'CHILD4_CODE'`); - await queryRunner.query(`ALTER TABLE \`orgChild3\` ADD \`CHILD3_CODE\` varchar(3) NULL COMMENT 'CHILD3_CODE'`); - await queryRunner.query(`ALTER TABLE \`orgChild2\` ADD \`CHILD2_CODE\` varchar(3) NULL COMMENT 'CHILD2_CODE'`); - await queryRunner.query(`ALTER TABLE \`orgChild1\` ADD \`CHILD1_CODE\` varchar(3) NULL COMMENT 'CHILD1_CODE'`); - await queryRunner.query(`ALTER TABLE \`orgRoot\` ADD \`ROOT_CODE\` varchar(3) NULL COMMENT 'ROOT_CODE'`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE \`orgRoot\` DROP COLUMN \`ROOT_CODE\``); - await queryRunner.query(`ALTER TABLE \`orgChild1\` DROP COLUMN \`CHILD1_CODE\``); - await queryRunner.query(`ALTER TABLE \`orgChild2\` DROP COLUMN \`CHILD2_CODE\``); - await queryRunner.query(`ALTER TABLE \`orgChild3\` DROP COLUMN \`CHILD3_CODE\``); - await queryRunner.query(`ALTER TABLE \`orgChild4\` DROP COLUMN \`CHILD4_CODE\``); - } - -} diff --git a/src/migration/1781577597453-update_add_field_code_all_tables.ts b/src/migration/1781577597453-update_add_field_code_all_tables.ts deleted file mode 100644 index 39ea6466..00000000 --- a/src/migration/1781577597453-update_add_field_code_all_tables.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; - -export class UpdateAddFieldCodeAllTables1781577597453 implements MigrationInterface { - name = 'UpdateAddFieldCodeAllTables1781577597453' - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE \`orgChild4\` ADD \`ROOT_CODE\` varchar(3) NULL COMMENT 'ROOT_CODE'`); - await queryRunner.query(`ALTER TABLE \`orgChild4\` ADD \`CHILD1_CODE\` varchar(3) NULL COMMENT 'CHILD1_CODE'`); - await queryRunner.query(`ALTER TABLE \`orgChild4\` ADD \`CHILD2_CODE\` varchar(3) NULL COMMENT 'CHILD2_CODE'`); - await queryRunner.query(`ALTER TABLE \`orgChild4\` ADD \`CHILD3_CODE\` varchar(3) NULL COMMENT 'CHILD3_CODE'`); - await queryRunner.query(`ALTER TABLE \`orgChild3\` ADD \`ROOT_CODE\` varchar(3) NULL COMMENT 'ROOT_CODE'`); - await queryRunner.query(`ALTER TABLE \`orgChild3\` ADD \`CHILD1_CODE\` varchar(3) NULL COMMENT 'CHILD1_CODE'`); - await queryRunner.query(`ALTER TABLE \`orgChild3\` ADD \`CHILD2_CODE\` varchar(3) NULL COMMENT 'CHILD2_CODE'`); - await queryRunner.query(`ALTER TABLE \`orgChild3\` ADD \`CHILD4_CODE\` varchar(3) NULL COMMENT 'CHILD4_CODE'`); - await queryRunner.query(`ALTER TABLE \`orgChild2\` ADD \`ROOT_CODE\` varchar(3) NULL COMMENT 'ROOT_CODE'`); - await queryRunner.query(`ALTER TABLE \`orgChild2\` ADD \`CHILD1_CODE\` varchar(3) NULL COMMENT 'CHILD1_CODE'`); - await queryRunner.query(`ALTER TABLE \`orgChild2\` ADD \`CHILD3_CODE\` varchar(3) NULL COMMENT 'CHILD3_CODE'`); - await queryRunner.query(`ALTER TABLE \`orgChild2\` ADD \`CHILD4_CODE\` varchar(3) NULL COMMENT 'CHILD4_CODE'`); - await queryRunner.query(`ALTER TABLE \`orgChild1\` ADD \`ROOT_CODE\` varchar(3) NULL COMMENT 'ROOT_CODE'`); - await queryRunner.query(`ALTER TABLE \`orgChild1\` ADD \`CHILD2_CODE\` varchar(3) NULL COMMENT 'CHILD2_CODE'`); - await queryRunner.query(`ALTER TABLE \`orgChild1\` ADD \`CHILD3_CODE\` varchar(3) NULL COMMENT 'CHILD3_CODE'`); - await queryRunner.query(`ALTER TABLE \`orgChild1\` ADD \`CHILD4_CODE\` varchar(3) NULL COMMENT 'CHILD4_CODE'`); - await queryRunner.query(`ALTER TABLE \`orgRoot\` ADD \`CHILD1_CODE\` varchar(3) NULL COMMENT 'CHILD1_CODE'`); - await queryRunner.query(`ALTER TABLE \`orgRoot\` ADD \`CHILD2_CODE\` varchar(3) NULL COMMENT 'CHILD2_CODE'`); - await queryRunner.query(`ALTER TABLE \`orgRoot\` ADD \`CHILD3_CODE\` varchar(3) NULL COMMENT 'CHILD3_CODE'`); - await queryRunner.query(`ALTER TABLE \`orgRoot\` ADD \`CHILD4_CODE\` varchar(3) NULL COMMENT 'CHILD4_CODE'`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE \`orgRoot\` DROP COLUMN \`CHILD4_CODE\``); - await queryRunner.query(`ALTER TABLE \`orgRoot\` DROP COLUMN \`CHILD3_CODE\``); - await queryRunner.query(`ALTER TABLE \`orgRoot\` DROP COLUMN \`CHILD2_CODE\``); - await queryRunner.query(`ALTER TABLE \`orgRoot\` DROP COLUMN \`CHILD1_CODE\``); - await queryRunner.query(`ALTER TABLE \`orgChild1\` DROP COLUMN \`CHILD4_CODE\``); - await queryRunner.query(`ALTER TABLE \`orgChild1\` DROP COLUMN \`CHILD3_CODE\``); - await queryRunner.query(`ALTER TABLE \`orgChild1\` DROP COLUMN \`CHILD2_CODE\``); - await queryRunner.query(`ALTER TABLE \`orgChild1\` DROP COLUMN \`ROOT_CODE\``); - await queryRunner.query(`ALTER TABLE \`orgChild2\` DROP COLUMN \`CHILD4_CODE\``); - await queryRunner.query(`ALTER TABLE \`orgChild2\` DROP COLUMN \`CHILD3_CODE\``); - await queryRunner.query(`ALTER TABLE \`orgChild2\` DROP COLUMN \`CHILD1_CODE\``); - await queryRunner.query(`ALTER TABLE \`orgChild2\` DROP COLUMN \`ROOT_CODE\``); - await queryRunner.query(`ALTER TABLE \`orgChild3\` DROP COLUMN \`CHILD4_CODE\``); - await queryRunner.query(`ALTER TABLE \`orgChild3\` DROP COLUMN \`CHILD2_CODE\``); - await queryRunner.query(`ALTER TABLE \`orgChild3\` DROP COLUMN \`CHILD1_CODE\``); - await queryRunner.query(`ALTER TABLE \`orgChild3\` DROP COLUMN \`ROOT_CODE\``); - await queryRunner.query(`ALTER TABLE \`orgChild4\` DROP COLUMN \`CHILD3_CODE\``); - await queryRunner.query(`ALTER TABLE \`orgChild4\` DROP COLUMN \`CHILD2_CODE\``); - await queryRunner.query(`ALTER TABLE \`orgChild4\` DROP COLUMN \`CHILD1_CODE\``); - await queryRunner.query(`ALTER TABLE \`orgChild4\` DROP COLUMN \`ROOT_CODE\``); - } -} diff --git a/src/services/CommandService.ts b/src/services/CommandService.ts index 0c0d7fe9..b002462c 100644 --- a/src/services/CommandService.ts +++ b/src/services/CommandService.ts @@ -1,12 +1,8 @@ import { AppDataSource } from "../database/data-source"; import { CommandRecive } from "../entities/CommandRecive"; import { Command } from "../entities/Command"; -import { CommandOperator } from "../entities/CommandOperator"; import { OrgRoot } from "../entities/OrgRoot"; -import { Position } from "../entities/Position"; import { Profile } from "../entities/Profile"; -import { RequestWithUser } from "../middlewares/user"; -import { EntityManager } from "typeorm"; export interface PosNumCodeSitResult { posNumCodeSit: string; @@ -21,42 +17,43 @@ export interface PosNumCodeSitResult { * เรียงลำดับผู้ได้รับคำสั่งใหม่หลังจากลบรายการ และอัพเดทสถานะคำสั่งถ้าไม่มีผู้ได้รับคำสั่งเหลือ * @param reciveId commandRecive.Id ของผู้ได้รับคำสั่ง * @param code ประเภทคำสั่ง - * @param manager ถ้าส่งเข้ามา → ทุก operation อยู่ใน transaction ของ caller (all-or-nothing) * @returns Promise */ export async function reOrderCommandRecivesAndDelete( - reciveId: string, - manager?: EntityManager, + reciveId: string ): Promise { - const ds = manager ?? AppDataSource; - const commandReciveRepo = ds.getRepository(CommandRecive); - const commandRepo = ds.getRepository(Command); + const commandReciveRepo = AppDataSource.getRepository(CommandRecive); + const commandRepo = AppDataSource.getRepository(Command); // ค้นหาข้อมูลผู้ได้รับคำสั่งตาม reciveId const commandRecive = await commandReciveRepo.findOne({ - where: { id: reciveId }, + where: { id: reciveId } }); - if (commandRecive == null) return; + if (commandRecive == null) + return; const commandId = commandRecive.commandId; // ลบตาม refId await commandReciveRepo.delete(commandRecive.id); - + const commandReciveList = await commandReciveRepo.find({ where: { commandId: commandId }, order: { order: "ASC" }, }); // ลำดับผู้ได้รับคำสั่งใหม่ if (commandReciveList.length > 0) { - for (let i = 0; i < commandReciveList.length; i++) { - commandReciveList[i].order = i + 1; - await commandReciveRepo.save(commandReciveList[i]); - } + await Promise.all( + commandReciveList.map(async (p, i) => { + p.order = i + 1; + await commandReciveRepo.save(p); + }) + ); } else { // ถ้าไม่มีผู้ได้รับคำสั่งเหลือเลย ให้ยกเลิกคำสั่ง await commandRepo.update({ id: commandId }, { status: "CANCEL" }); } + } /** @@ -147,101 +144,3 @@ export async function getPosNumCodeSit( commandExcecuteDate, }; } - -/** - * สร้าง/insert CommandOperator "เจ้าหน้าที่ดำเนินการ" สำหรับ command - * ใช้ userProfile ที่ query ไปแล้วถ้ามี ถ้าไม่มีค่อย query ใหม่ - * @param userProfile profile ที่ query ไปแล้ว (หรือ null ถ้ายังไม่ได้ query) - * @param commandId command id ที่จะผูกกับ operator - * @param request request context (สำหรับ user.sub / user.name) - * @param now timestamp สำหรับ audit fields - * @param manager ถ้าส่งเข้ามา → ทุก operation อยู่ใน transaction ของ caller (all-or-nothing) - * @returns Promise - */ -export async function ensureCommandOperator( - userProfile: Profile | null, - commandId: string, - request: RequestWithUser, - now: Date, - manager?: EntityManager, -): Promise { - const ds = manager ?? AppDataSource; - const profileRepo = ds.getRepository(Profile); - const positionRepo = ds.getRepository(Position); - const commandOperatorRepo = ds.getRepository(CommandOperator); - - if (!request.user.sub) return; - // ใช้ userProfile ที่ query ไปแล้วถ้ามี ถ้าไม่มีค่อย query ใหม่ - let profile = userProfile; - if (!profile) { - profile = await profileRepo.findOne({ - where: { keycloak: request.user.sub }, - relations: { - posLevel: true, - posType: true, - current_holders: { - orgRevision: true, - orgRoot: true, - orgChild1: true, - orgChild2: true, - orgChild3: true, - orgChild4: true, - }, - }, - }); - } - if (!profile) return; - - const currentHolder = profile.current_holders?.find( - (x) => - x.orgRevision?.orgRevisionIsDraft === false && - x.orgRevision?.orgRevisionIsCurrent === true, - ); - - const posNo = - currentHolder != null && currentHolder.orgChild4 != null - ? `${currentHolder.orgChild4.orgChild4ShortName} ${currentHolder.posMasterNo}` - : currentHolder != null && currentHolder.orgChild3 != null - ? `${currentHolder.orgChild3.orgChild3ShortName} ${currentHolder.posMasterNo}` - : currentHolder != null && currentHolder.orgChild2 != null - ? `${currentHolder.orgChild2.orgChild2ShortName} ${currentHolder.posMasterNo}` - : currentHolder != null && currentHolder.orgChild1 != null - ? `${currentHolder.orgChild1.orgChild1ShortName} ${currentHolder.posMasterNo}` - : currentHolder != null && currentHolder?.orgRoot != null - ? `${currentHolder.orgRoot.orgRootShortName} ${currentHolder.posMasterNo}` - : null; - - const position = await positionRepo.findOne({ - where: { - positionIsSelected: true, - posMaster: { - orgRevisionId: currentHolder?.orgRevisionId, - current_holderId: profile.id, - }, - }, - order: { createdAt: "DESC" }, - relations: { posExecutive: true }, - }); - - const operator = Object.assign(new CommandOperator(), { - profileId: profile?.id, - prefix: profile?.prefix, - firstName: profile?.firstName, - lastName: profile?.lastName, - posNo: posNo, - posType: profile?.posType?.posTypeName ?? null, - posLevel: profile?.posLevel?.posLevelName ?? null, - position: position?.positionName ?? null, - positionExecutive: position?.posExecutive?.posExecutiveName ?? null, - roleName: "เจ้าหน้าที่ดำเนินการ", - orderNo: 1, - commandId: commandId, - createdUserId: request.user.sub, - createdFullName: request.user.name, - createdAt: now, - lastUpdateUserId: request.user.sub, - lastUpdateFullName: request.user.name, - lastUpdatedAt: now, - }); - await commandOperatorRepo.save(operator); -} diff --git a/src/services/ExecuteOfficerProfileService.ts b/src/services/ExecuteOfficerProfileService.ts deleted file mode 100644 index f06159dc..00000000 --- a/src/services/ExecuteOfficerProfileService.ts +++ /dev/null @@ -1,1423 +0,0 @@ -import { EntityManager, In, Like } from "typeorm"; -import { AppDataSource } from "../database/data-source"; -import HttpError from "../interfaces/http-error"; -import HttpStatusCode from "../interfaces/http-status"; -import { CreateProfileAllFields, Profile } from "../entities/Profile"; -import { ProfileEmployee } from "../entities/ProfileEmployee"; -import { CreateProfileSalary, ProfileSalary } from "../entities/ProfileSalary"; -import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory"; -import { CreateProfileEducation, ProfileEducation } from "../entities/ProfileEducation"; -import { ProfileEducationHistory } from "../entities/ProfileEducationHistory"; -import { - CreateProfileCertificate, - ProfileCertificate, -} from "../entities/ProfileCertificate"; -import { ProfileCertificateHistory } from "../entities/ProfileCertificateHistory"; -import { ProfileFamilyCouple } from "../entities/ProfileFamilyCouple"; -import { ProfileFamilyCoupleHistory } from "../entities/ProfileFamilyCoupleHistory"; -import { ProfileFamilyFather } from "../entities/ProfileFamilyFather"; -import { ProfileFamilyFatherHistory } from "../entities/ProfileFamilyFatherHistory"; -import { ProfileFamilyMother } from "../entities/ProfileFamilyMother"; -import { ProfileFamilyMotherHistory } from "../entities/ProfileFamilyMotherHistory"; -import { CreateProfileInsignia, ProfileInsignia } from "../entities/ProfileInsignia"; -import { ProfileInsigniaHistory } from "../entities/ProfileInsigniaHistory"; -import { ProfileAvatar } from "../entities/ProfileAvatar"; -import { PosLevel } from "../entities/PosLevel"; -import { PosType } from "../entities/PosType"; -import { Province } from "../entities/Province"; -import { District } from "../entities/District"; -import { SubDistrict } from "../entities/SubDistrict"; -import { OrgRoot } from "../entities/OrgRoot"; -import { RoleKeycloak } from "../entities/RoleKeycloak"; -import { PosMaster } from "../entities/PosMaster"; -import { Position } from "../entities/Position"; -import { Command } from "../entities/Command"; -import { - calculateRetireDate, - calculateRetireLaw, - removeProfileInOrganize, - setLogDataDiff, -} from "../interfaces/utils"; -import { - addUserRoles, - createUser, - getRoleMappings, - getRoles, - getUserByUsername, - removeUserRoles, - updateUserAttributes, -} from "../keycloak"; -import { CreatePosMasterHistoryOfficer } from "./PositionService"; -import { getOrgFullName, getPosMasterNo } from "../utils/org-formatting"; -import CallAPI from "../interfaces/call-api"; - -/** - * Input: ตำแหน่งที่จะกำหนดให้กับ profile ใหม่ (ส่งมาจากฝั่งบรรจุ) - */ -export interface OfficerPositionInput { - posmasterId: string; - positionId: string; - positionName: string; - posTypeId: string; - posLevelId: string; - posExecutiveId: string | null; - positionField: string | null; - positionExecutiveField: string | null; - positionArea: string | null; -} - -/** - * Input: ข้อมูลคู่สมรส - */ -export interface OfficerMarryInput { - marry?: boolean | null; - marryPrefix?: string | null; - marryFirstName?: string | null; - marryLastName?: string | null; - marryOccupation?: string | null; - marryNationality?: string | null; -} - -/** - * Input: ข้อมูลบิดา - */ -export interface OfficerFatherInput { - fatherPrefix?: string | null; - fatherFirstName?: string | null; - fatherLastName?: string | null; - fatherOccupation?: string | null; - fatherNationality?: string | null; -} - -/** - * Input: ข้อมูลมารดา - */ -export interface OfficerMotherInput { - motherPrefix?: string | null; - motherFirstName?: string | null; - motherLastName?: string | null; - motherOccupation?: string | null; - motherNationality?: string | null; -} - -/** - * Input: ข้อมูล 1 คนที่จะบรรจุ/แต่งตั้ง (ตรงกับ body.data[i] ของ endpoint เดิม) - */ -export interface OfficerProfileItem { - bodyProfile: CreateProfileAllFields; - bodyEducations?: CreateProfileEducation[]; - bodyCertificates?: CreateProfileCertificate[]; - bodySalarys?: CreateProfileSalary | null; - bodyPosition?: OfficerPositionInput | null; - bodyMarry?: OfficerMarryInput | null; - bodyFather?: OfficerFatherInput | null; - bodyMother?: OfficerMotherInput | null; -} - -/** - * Context สำหรับ audit/log — แยกขาดจาก HTTP request เพื่อให้ service - * สามารถถูกเรียกได้ทั้งจาก endpoint (ผ่าน Express req) และจาก message consumer - * (ผ่าน pseudo-req ที่สร้างขึ้น) โดยไม่ผูกติดกับ HTTP layer - */ -export interface ExecutionContext { - /** user ที่ทริกเกอร์งานนี้ (sub = keycloak id, name = ชื่อเต็ม) */ - user: { sub: string; name: string }; - /** Express request (สำหรับ subscriber pattern: setLogDataDiff, save({data: req})) */ - req?: any; -} - -/** - * Service สำหรับสร้าง/อัปเดตทะเบียนประวัติข้าราชการ (Profile) หลังออกคำสั่งบรรจุ - * - * ใช้กับ commandType: C-PM-01, 02, 14 - * - * - endpoint /org/command/excexute/create-officer-profile เรียกผ่าน service นี้ (thin wrapper) - * - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow) - * - * Behavior ทั้งหมด preserve จาก CommandController.CreateOfficeProfileExcecute ต้นฉบับ - * - * Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential) - * ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด) - * ถ้าทุกคนสำเร็จจะ return result รายงาน success count - * - * ⚠️ หมายเหตุ Keycloak: operations (createUser/addUserRoles/removeUserRoles/updateUserAttributes) - * ทำภายใน transaction เพื่อ preserve behavior เดิม — Keycloak ไม่สามารถ rollback ได้ - * ถ้า DB rollback หลังจาก Keycloak operation สำเร็จ → Keycloak จะถูกเปลี่ยนไปแล้ว - * - * Design note: แก้ปัญหา "Circular Dependency" ระหว่าง API Org กับ API บรรจุ โดยให้ฝั่งบรรจุ - * ส่ง resultData กลับมา แล้วฝั่ง Org ประมวลผลสร้าง profile เองที่ต้นทาง แทนการเรียกซ้อนกัน - */ -export class ExecuteOfficerProfileService { - private commandRepository = AppDataSource.getRepository(Command); - private profileRepository = AppDataSource.getRepository(Profile); - private profileEmployeeRepository = AppDataSource.getRepository(ProfileEmployee); - private salaryRepo = AppDataSource.getRepository(ProfileSalary); - private salaryHistoryRepo = AppDataSource.getRepository(ProfileSalaryHistory); - private posLevelRepo = AppDataSource.getRepository(PosLevel); - private posTypeRepo = AppDataSource.getRepository(PosType); - private provinceRepo = AppDataSource.getRepository(Province); - private districtRepo = AppDataSource.getRepository(District); - private subDistrictRepo = AppDataSource.getRepository(SubDistrict); - private orgRootRepository = AppDataSource.getRepository(OrgRoot); - private roleKeycloakRepo = AppDataSource.getRepository(RoleKeycloak); - private posMasterRepository = AppDataSource.getRepository(PosMaster); - private positionRepository = AppDataSource.getRepository(Position); - private profileEducationRepo = AppDataSource.getRepository(ProfileEducation); - private profileEducationHistoryRepo = AppDataSource.getRepository(ProfileEducationHistory); - private certificateRepo = AppDataSource.getRepository(ProfileCertificate); - private certificateHistoryRepo = AppDataSource.getRepository(ProfileCertificateHistory); - private profileFamilyCoupleRepo = AppDataSource.getRepository(ProfileFamilyCouple); - private profileFamilyCoupleHistoryRepo = AppDataSource.getRepository(ProfileFamilyCoupleHistory); - private profileFamilyFatherRepo = AppDataSource.getRepository(ProfileFamilyFather); - private profileFamilyFatherHistoryRepo = AppDataSource.getRepository(ProfileFamilyFatherHistory); - private profileFamilyMotherRepo = AppDataSource.getRepository(ProfileFamilyMother); - private profileFamilyMotherHistoryRepo = AppDataSource.getRepository(ProfileFamilyMotherHistory); - private insigniaRepo = AppDataSource.getRepository(ProfileInsignia); - private insigniaHistoryRepo = AppDataSource.getRepository(ProfileInsigniaHistory); - private avatarRepository = AppDataSource.getRepository(ProfileAvatar); - - /** - * ประมวลผลสร้าง/อัปเดท profile ข้าราชการ ตามข้อมูลที่ฝั่งบรรจุส่งมา - * - * @param data รายการข้อมูลที่จะประมวลผล (1 คำสั่งอาจมีหลายคน) - * @param ctx context สำหรับ audit/log - */ - async executeCreateOfficerProfile( - data: OfficerProfileItem[], - ctx: ExecutionContext, - ): Promise { - const commandId = - 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 - // ผ่าน HTTP endpoint → tsoa แปลง ISO string → Date ให้อัตโนมัติ - // แต่ผ่าน RabbitMQ handler (axios) → จะได้ string → ต้องแปลงเอง - // ไม่งั้น calculateRetireDate/getFullYear ฯลฯ จะพัง - // ───────────────────────────────────────────────────────────── - const toDate = (v: any): Date | null => { - if (v == null || v === "") return null; - if (v instanceof Date) return isNaN(v.getTime()) ? null : v; - const d = new Date(v); - return isNaN(d.getTime()) ? null : d; - }; - for (const item of data ?? []) { - const bp = item.bodyProfile as any; - if (bp) { - bp.birthDate = toDate(bp.birthDate); - bp.dateStart = toDate(bp.dateStart); - bp.dateAppoint = toDate(bp.dateAppoint); - bp.dateRetire = toDate(bp.dateRetire); - } - const bs = item.bodySalarys as any; - if (bs) { - bs.commandDateAffect = toDate(bs.commandDateAffect); - bs.commandDateSign = toDate(bs.commandDateSign); - } - if (item.bodyEducations) { - for (const edu of item.bodyEducations as any[]) { - edu.startDate = toDate(edu.startDate); - edu.endDate = toDate(edu.endDate); - edu.finishDate = toDate(edu.finishDate); - } - } - if (item.bodyCertificates) { - for (const cer of item.bodyCertificates as any[]) { - cer.expireDate = toDate(cer.expireDate); - cer.issueDate = toDate(cer.issueDate); - } - } - } - - const req = ctx.req; - const roleKeycloak = await this.roleKeycloakRepo.findOne({ - where: { name: Like("USER") }, - }); - console.log("[ExecuteOfficerProfileService] roleKeycloak found:", !!roleKeycloak); - const list = await getRoles(); - console.log( - "[ExecuteOfficerProfileService] Roles list retrieved, length:", - Array.isArray(list) ? list.length : "not array", - ); - if (!Array.isArray(list)) { - console.error( - "[ExecuteOfficerProfileService] Failed - Cannot get role(s) data from the server", - ); - throw new Error("Failed. Cannot get role(s) data from the server."); - } - let _posNumCodeSit: string = ""; - let _posNumCodeSitAbb: string = ""; - console.log("[ExecuteOfficerProfileService] Getting command data"); - const _command = await this.commandRepository.findOne({ - where: { - id: data.find((x) => x.bodySalarys?.commandId)?.bodySalarys?.commandId ?? "", - }, - }); - console.log( - "[ExecuteOfficerProfileService] Command found:", - !!_command, - "isBangkok:", - _command?.isBangkok, - ); - if (_command) { - if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { - console.log("[ExecuteOfficerProfileService] Setting position codes for OFFICE"); - const orgRootDeputy = await this.orgRootRepository.findOne({ - where: { - isDeputy: true, - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - relations: ["orgRevision"], - }); - _posNumCodeSit = orgRootDeputy - ? orgRootDeputy?.orgRootName - : "สำนักปลัดกรุงเทพมหานคร"; - _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; - console.log( - "[ExecuteOfficerProfileService] OFFICE position codes set:", - _posNumCodeSit, - _posNumCodeSitAbb, - ); - } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { - console.log("[ExecuteOfficerProfileService] Setting position codes for BANGKOK"); - _posNumCodeSit = "กรุงเทพมหานคร"; - _posNumCodeSitAbb = "กทม."; - console.log( - "[ExecuteOfficerProfileService] BANGKOK position codes set:", - _posNumCodeSit, - _posNumCodeSitAbb, - ); - } else { - console.log("[ExecuteOfficerProfileService] Setting position codes from admin profile"); - let _profileAdmin = await this.profileRepository.findOne({ - where: { - keycloak: _command?.createdUserId.toString(), - current_holders: { - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - }, - relations: [ - "current_holders", - "current_holders.orgRevision", - "current_holders.orgRoot", - ], - }); - _posNumCodeSit = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot - .orgRootName ?? ""; - _posNumCodeSitAbb = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot - .orgRootShortName ?? ""; - console.log( - "[ExecuteOfficerProfileService] Admin profile position codes set:", - _posNumCodeSit, - _posNumCodeSitAbb, - ); - } - } - const before = null; - const meta = { - createdUserId: ctx.user.sub, - createdFullName: ctx.user.name, - lastUpdateUserId: ctx.user.sub, - lastUpdateFullName: ctx.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - }; - console.log( - "[ExecuteOfficerProfileService] Starting to process", - data.length, - "profile(s)", - ); - - // ───────────────────────────────────────────────────────────── - // 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 { - 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( - "[ExecuteOfficerProfileService] Processing citizenId:", - item.bodyProfile.citizenId, - ); - const _null: any = null; - if (item.bodyProfile.posLevelId === "") item.bodyProfile.posLevelId = null; - if (item.bodyProfile.posTypeId === "") item.bodyProfile.posTypeId = null; - if ( - item.bodyProfile.posLevelId && - !(await posLevelRepo.findOneBy({ id: item.bodyProfile.posLevelId })) - ) { - console.error( - "[ExecuteOfficerProfileService] ไม่พบข้อมูลระดับตำแหน่งนี้ posLevelId:", - item.bodyProfile.posLevelId, - ); - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลระดับตำแหน่งนี้"); - } - if ( - item.bodyProfile.posTypeId && - !(await posTypeRepo.findOneBy({ id: item.bodyProfile.posTypeId })) - ) { - console.error( - "[ExecuteOfficerProfileService] ไม่พบข้อมูลประเภทตำแหน่งนี้ posTypeId:", - item.bodyProfile.posTypeId, - ); - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลประเภทตำแหน่งนี้"); - } - - console.log( - "[ExecuteOfficerProfileService] Processing citizenId:", - item.bodyProfile.citizenId, - ); - let registrationProvinceId = await provinceRepo.findOneBy({ - id: item.bodyProfile.registrationProvinceId ?? "", - }); - let registrationDistrictId = await districtRepo.findOneBy({ - id: item.bodyProfile.registrationDistrictId ?? "", - }); - let registrationSubDistrictId = await subDistrictRepo.findOneBy({ - id: item.bodyProfile.registrationSubDistrictId ?? "", - }); - let currentProvinceId = await provinceRepo.findOneBy({ - id: item.bodyProfile.currentProvinceId ?? "", - }); - let currentDistrictId = await districtRepo.findOneBy({ - id: item.bodyProfile.currentDistrictId ?? "", - }); - let currentSubDistrictId = await subDistrictRepo.findOneBy({ - id: item.bodyProfile.currentSubDistrictId ?? "", - }); - console.log("[ExecuteOfficerProfileService] Address validation completed"); - - let _dateRetire = - item.bodyProfile.birthDate == null - ? _null - : calculateRetireDate(item.bodyProfile.birthDate); - let _dateRetireLaw = - item.bodyProfile.birthDate == null - ? _null - : calculateRetireLaw(item.bodyProfile.birthDate); - - let userKeycloakId: any; - let result: any; - console.log( - "[ExecuteOfficerProfileService] Checking Keycloak user for citizenId:", - item.bodyProfile.citizenId, - ); - const checkUser = await getUserByUsername(item.bodyProfile.citizenId); - console.log( - "[ExecuteOfficerProfileService] Keycloak user exists:", - checkUser.length > 0, - ); - if (checkUser.length == 0) { - console.log("[ExecuteOfficerProfileService] Creating new Keycloak user"); - let password = item.bodyProfile.citizenId; - if (item.bodyProfile.birthDate != null) { - const _date = new Date(item.bodyProfile.birthDate.toDateString()) - .getDate() - .toString() - .padStart(2, "0"); - const _month = ( - new Date(item.bodyProfile.birthDate.toDateString()).getMonth() + 1 - ) - .toString() - .padStart(2, "0"); - const _year = new Date(item.bodyProfile.birthDate.toDateString()).getFullYear() + 543; - password = `${_date}${_month}${_year}`; - } - console.log( - "[ExecuteOfficerProfileService] Calling createUser for:", - item.bodyProfile.citizenId, - ); - console.log( - "[ExecuteOfficerProfileService] createUser data - firstName:", - item.bodyProfile.firstName, - "lastName:", - item.bodyProfile.lastName, - ); - // กรอง "." ออกจาก firstName ก่อนส่งไป keycloak (ป้องกัน . หรืออักขระอื่นๆ) - const sanitizedFirstName = item.bodyProfile.firstName?.replace(/\./g, "") ?? ""; - userKeycloakId = await createUser(item.bodyProfile.citizenId, password, { - firstName: sanitizedFirstName, - lastName: item.bodyProfile.lastName, - }); - if ( - userKeycloakId && - typeof userKeycloakId === "object" && - userKeycloakId.errorMessage - ) { - console.error( - "[ExecuteOfficerProfileService] createUser FAILED - field:", - userKeycloakId.field, - "errorMessage:", - userKeycloakId.errorMessage, - "params:", - userKeycloakId.params, - ); - throw new HttpError( - HttpStatusCode.BAD_REQUEST, - `Keycloak validation failed: ${userKeycloakId.field} - ${userKeycloakId.errorMessage}`, - ); - } - console.log( - "[ExecuteOfficerProfileService] User created in Keycloak, userKeycloakId:", - userKeycloakId, - ); - result = await addUserRoles( - userKeycloakId, - list - .filter((v) => v.name === "USER") - .map((x) => ({ - id: x.id, - name: x.name, - })), - ); - console.log( - "[ExecuteOfficerProfileService] USER role assigned to new user, result:", - result, - ); - } else { - console.log("[ExecuteOfficerProfileService] Updating existing Keycloak user"); - userKeycloakId = checkUser[0].id; - console.log( - "[ExecuteOfficerProfileService] Existing userKeycloakId:", - userKeycloakId, - ); - const rolesData = await getRoleMappings(userKeycloakId); - if (rolesData) { - const _delRole = rolesData.map((x: any) => ({ - id: x.id, - name: x.name, - })); - console.log( - "[ExecuteOfficerProfileService] Removing old roles:", - _delRole.length, - ); - await removeUserRoles(userKeycloakId, _delRole); - } - result = await addUserRoles( - userKeycloakId, - list - .filter((v) => v.name === "USER") - .map((x) => ({ - id: x.id, - name: x.name, - })), - ); - console.log( - "[ExecuteOfficerProfileService] USER role assigned to existing user", - ); - } - - let profile: any = await profileRepository.findOne({ - where: { citizenId: item.bodyProfile.citizenId /*, isActive: true */ }, - relations: ["roleKeycloaks", "profileInsignias", "profileAvatars"], - }); - console.log( - "[ExecuteOfficerProfileService] Profile found:", - !!profile, - "for citizenId:", - item.bodyProfile.citizenId, - ); - let _oldInsigniaIds: string[] = []; - let _oldSalaries: any[] = []; - //ลูกจ้างประจำ หรือ บุคคลภายนอก - if (!profile) { - console.log( - "[ExecuteOfficerProfileService] No existing profile found, creating new profile", - ); - //กรณีลูกจ้างประจำมาสอบเป็นข้าราชการ ต้อง update สถานะโปรไฟล์เดิม - let profileEmployee: any = await profileEmployeeRepository.findOne({ - where: { citizenId: item.bodyProfile.citizenId }, - relations: ["profileInsignias", "roleKeycloaks"], - }); - console.log( - "[ExecuteOfficerProfileService] Employee profile found:", - !!profileEmployee, - ); - if (profileEmployee) { - console.log( - "[ExecuteOfficerProfileService] Converting employee profile to officer profile", - ); - const _order = await salaryRepo.findOne({ - where: { profileEmployeeId: profileEmployee.id }, - order: { order: "DESC" }, - }); - const profileEmpSalary = new ProfileSalary(); - profileEmpSalary.posNumCodeSit = _posNumCodeSit; - profileEmpSalary.posNumCodeSitAbb = _posNumCodeSitAbb; - profileEmpSalary.order = _order == null ? 1 : _order.order + 1; - Object.assign(profileEmpSalary, { - ...item.bodySalarys, - ...meta, - profileEmployeeId: profileEmployee.id, - profileId: undefined, - }); - const history = new ProfileSalaryHistory(); - Object.assign(history, { ...profileEmpSalary, id: undefined }); - profileEmpSalary.dateGovernment = item.bodySalarys?.commandDateAffect ?? meta.createdAt; - (profileEmpSalary.profileId = _null), - await salaryRepo.save(profileEmpSalary, { data: req }); - setLogDataDiff(req, { before, after: profileEmpSalary }); - history.profileSalaryId = profileEmpSalary.id; - await salaryHistoryRepo.save(history, { data: req }); - - if (profileEmployee.profileInsignias.length > 0) { - _oldInsigniaIds = profileEmployee.profileInsignias?.map((x: any) => x.id) ?? []; - } - await removeProfileInOrganize(profileEmployee.id, "EMPLOYEE", manager); - if (profileEmployee.keycloak != null) { - // const delUserKeycloak = await deleteUser(profileEmployee.keycloak); - // if (delUserKeycloak) { - // Task #228 - // profileEmployee.keycloak = _null; - profileEmployee.roleKeycloaks = []; - profileEmployee.isActive = false; - // } - } - profileEmployee.isLeave = true; - profileEmployee.leaveReason = "บรรจุข้าราชการ"; - profileEmployee.lastUpdateUserId = ctx.user.sub; - profileEmployee.lastUpdateFullName = ctx.user.name; - profileEmployee.lastUpdatedAt = new Date(); - await profileEmployeeRepository.save(profileEmployee); - setLogDataDiff(req, { before, after: profileEmployee }); - } - profile = Object.assign({ ...item.bodyProfile, ...meta }); - profile.dateRetire = _dateRetire; - profile.dateRetireLaw = _dateRetireLaw; - profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : []; - profile.keycloak = - userKeycloakId && typeof userKeycloakId === "string" ? userKeycloakId : ""; - profile.registrationAddress = item.bodyProfile.registrationAddress; - profile.registrationProvinceId = registrationProvinceId - ? registrationProvinceId.id - : _null; - profile.registrationDistrictId = registrationDistrictId - ? registrationDistrictId.id - : _null; - profile.registrationSubDistrictId = registrationSubDistrictId - ? registrationSubDistrictId.id - : _null; - profile.registrationZipCode = item.bodyProfile.registrationZipCode; - profile.currentAddress = item.bodyProfile.currentAddress; - profile.currentProvinceId = currentProvinceId ? currentProvinceId.id : _null; - profile.currentDistrictId = currentDistrictId ? currentDistrictId.id : _null; - profile.currentSubDistrictId = currentSubDistrictId ? currentSubDistrictId.id : _null; - profile.currentZipCode = item.bodyProfile.currentZipCode; - profile.email = item.bodyProfile.email; - profile.dateStart = item.bodyProfile.dateStart; - profile.amount = item.bodyProfile.amount ?? null; - profile.amountSpecial = item.bodyProfile.amountSpecial ?? null; - profile.isProbation = item.bodyProfile.isProbation; - //เพิ่มใหม่จากรับโอน - profile.rank = item?.bodyProfile?.rank || null; - profile.prefix = item?.bodyProfile?.rank || item?.bodyProfile?.prefix || null; - profile.prefixMain = item?.bodyProfile?.prefix ?? null; - profile.firstName = item.bodyProfile.firstName ?? null; - profile.lastName = item.bodyProfile.lastName ?? null; - profile.birthDate = item.bodyProfile.birthDate ?? null; - profile.gender = item.bodyProfile.gender ?? null; - profile.relationship = item.bodyProfile.relationship ?? null; - profile.religion = item.bodyProfile.religion ?? null; - profile.ethnicity = item.bodyProfile.ethnicity; - profile.nationality = item.bodyProfile.nationality ?? null; - profile.bloodGroup = item.bodyProfile.bloodGroup ?? null; - profile.phone = item.bodyProfile.phone ?? null; - - console.log("[ExecuteOfficerProfileService] Saving new profile"); - await profileRepository.save(profile); - console.log( - "[ExecuteOfficerProfileService] New profile saved, profileId:", - profile.id, - ); - // update user attribute in keycloak - await updateUserAttributes(profile.keycloak ?? "", { - profileId: [profile.id], - prefix: [profile.prefix || ""], - }); - console.log("[ExecuteOfficerProfileService] Keycloak attributes updated"); - setLogDataDiff(req, { before, after: profile }); - } - //ขรก.ในระบบ หรือ ขรก.ในระบบที่สถานะพ้นจากราชการ - else { - console.log( - "[ExecuteOfficerProfileService] Existing profile found, isLeave:", - profile.isLeave, - "leaveType:", - profile.leaveType, - ); - //สร้างโปรไฟล์ใหม่ ถ้าสถานะพ้นราชการ คำสั่งโอนออกหรือคำสั่งขอลาออก - if ( - profile.isLeave && - ["PLACEMENT_TRANSFER", "RETIRE_RESIGN"].includes(profile.leaveType) - ) { - console.log( - "[ExecuteOfficerProfileService] Profile is leaving with eligible leave type, creating new profile record", - ); - //ดึง profileSalary เดิม - _oldSalaries = await salaryRepo.find({ - where: { profileId: profile.id }, - order: { order: "ASC" }, - }); - if (profile.profileInsignias.length > 0) { - _oldInsigniaIds = profile.profileInsignias?.map((x: any) => x.id) ?? []; - } - profile = Object.assign({ ...item.bodyProfile, ...meta }); - profile.dateRetire = _dateRetire; - profile.dateRetireLaw = _dateRetireLaw; - profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : []; - profile.keycloak = - userKeycloakId && typeof userKeycloakId === "string" ? userKeycloakId : ""; - profile.registrationAddress = item.bodyProfile.registrationAddress; - profile.registrationProvinceId = registrationProvinceId - ? registrationProvinceId.id - : _null; - profile.registrationDistrictId = registrationDistrictId - ? registrationDistrictId.id - : _null; - profile.registrationSubDistrictId = registrationSubDistrictId - ? registrationSubDistrictId.id - : _null; - profile.registrationZipCode = item.bodyProfile.registrationZipCode; - profile.currentAddress = item.bodyProfile.currentAddress; - profile.currentProvinceId = currentProvinceId ? currentProvinceId.id : _null; - profile.currentDistrictId = currentDistrictId ? currentDistrictId.id : _null; - profile.currentSubDistrictId = currentSubDistrictId ? currentSubDistrictId.id : _null; - profile.currentZipCode = item.bodyProfile.currentZipCode; - profile.email = item.bodyProfile.email; - profile.dateStart = item.bodyProfile.dateStart; - profile.amount = item.bodyProfile.amount ?? null; - profile.amountSpecial = item.bodyProfile.amountSpecial ?? null; - profile.isProbation = item.bodyProfile.isProbation; - profile.rank = item?.bodyProfile?.rank || null; - profile.prefix = item?.bodyProfile?.rank || item?.bodyProfile?.prefix || null; - profile.prefixMain = item?.bodyProfile?.prefix ?? null; - profile.firstName = item.bodyProfile.firstName ?? null; - profile.lastName = item.bodyProfile.lastName ?? null; - profile.birthDate = item.bodyProfile.birthDate ?? null; - profile.gender = item.bodyProfile.gender ?? null; - profile.relationship = item.bodyProfile.relationship ?? null; - profile.religion = item.bodyProfile.religion ?? null; - profile.ethnicity = item.bodyProfile.ethnicity; - profile.nationality = item.bodyProfile.nationality ?? null; - profile.bloodGroup = item.bodyProfile.bloodGroup ?? null; - profile.phone = item.bodyProfile.phone ?? null; - await profileRepository.save(profile); - console.log( - "[ExecuteOfficerProfileService] New profile record saved for leaving officer, profileId:", - profile.id, - ); - setLogDataDiff(req, { before, after: profile }); - } else { - console.log( - "[ExecuteOfficerProfileService] Updating existing active profile", - ); - profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : []; - profile.keycloak = - userKeycloakId && typeof userKeycloakId === "string" ? userKeycloakId : ""; - profile.isProbation = item.bodyProfile.isProbation; - profile.isLeave = item.bodyProfile.isLeave; - profile.isRetirement = false; - profile.isActive = true; - profile.isDelete = false; - profile.dateLeave = _null; - profile.dateRetire = _dateRetire; - profile.dateRetireLaw = _dateRetireLaw; - profile.registrationAddress = item.bodyProfile.registrationAddress; - profile.registrationProvinceId = registrationProvinceId - ? registrationProvinceId.id - : _null; - profile.registrationDistrictId = registrationDistrictId - ? registrationDistrictId.id - : _null; - profile.registrationSubDistrictId = registrationSubDistrictId - ? registrationSubDistrictId.id - : _null; - profile.registrationZipCode = item.bodyProfile.registrationZipCode; - profile.currentAddress = item.bodyProfile.currentAddress; - profile.currentProvinceId = currentProvinceId ? currentProvinceId.id : _null; - profile.currentDistrictId = currentDistrictId ? currentDistrictId.id : _null; - profile.currentSubDistrictId = currentSubDistrictId ? currentSubDistrictId.id : _null; - profile.currentZipCode = item.bodyProfile.currentZipCode; - profile.email = item.bodyProfile.email; - profile.telephoneNumber = item.bodyProfile.telephoneNumber; - profile.phone = item.bodyProfile.phone; - profile.dateStart = item.bodyProfile.dateStart; - profile.amount = item.bodyProfile.amount ?? null; - profile.amountSpecial = item.bodyProfile.amountSpecial ?? null; - profile.leaveCommandId = _null; - profile.leaveCommandNo = _null; - profile.leaveRemark = _null; - profile.leaveDate = _null; - profile.leaveType = _null; - profile.leaveReason = _null; - profile.lastUpdateUserId = ctx.user.sub; - profile.lastUpdateFullName = ctx.user.name; - profile.lastUpdatedAt = new Date(); - //เพิ่มใหม่จากรับโอน - profile.rank = item?.bodyProfile?.rank || null; - profile.prefix = item?.bodyProfile?.rank || item?.bodyProfile?.prefix || null; - profile.prefixMain = item?.bodyProfile?.prefix ?? null; - profile.firstName = - item.bodyProfile.firstName && item.bodyProfile.firstName != "" - ? item.bodyProfile.firstName - : profile.firstName; - profile.lastName = - item.bodyProfile.lastName && item.bodyProfile.lastName != "" - ? item.bodyProfile.lastName - : profile.lastName; - profile.birthDate = item.bodyProfile.birthDate - ? item.bodyProfile.birthDate - : profile.birthDate; - profile.gender = - item.bodyProfile.gender && item.bodyProfile.gender != "" - ? item.bodyProfile.gender - : profile.gender; - profile.relationship = - item.bodyProfile.relationship && item.bodyProfile.relationship != "" - ? item.bodyProfile.relationship - : profile.relationship; - profile.religion = - item.bodyProfile.religion && item.bodyProfile.religion != "" - ? item.bodyProfile.religion - : profile.religion; - profile.ethnicity = - item.bodyProfile.ethnicity && item.bodyProfile.ethnicity != "" - ? item.bodyProfile.ethnicity - : profile.ethnicity; - profile.nationality = - item.bodyProfile.nationality && item.bodyProfile.nationality != "" - ? item.bodyProfile.nationality - : profile.nationality; - profile.bloodGroup = - item.bodyProfile.bloodGroup && item.bodyProfile.bloodGroup != "" - ? item.bodyProfile.bloodGroup - : profile.bloodGroup; - profile.phone = - item.bodyProfile.phone && item.bodyProfile.phone != "" - ? item.bodyProfile.phone - : profile.phone; - await profileRepository.save(profile); - console.log( - "[ExecuteOfficerProfileService] Existing active profile updated, profileId:", - profile.id, - ); - setLogDataDiff(req, { before, after: profile }); - } - } - - if (profile && profile.id) { - console.log( - "[ExecuteOfficerProfileService] Processing additional data for profileId:", - profile.id, - ); - //Educations - if (item.bodyEducations && item.bodyEducations.length > 0) { - console.log( - "[ExecuteOfficerProfileService] Processing educations, count:", - item.bodyEducations.length, - ); - for (const education of item.bodyEducations) { - const profileEdu = new ProfileEducation(); - Object.assign(profileEdu, { ...education, ...meta }); - const eduHistory = new ProfileEducationHistory(); - Object.assign(eduHistory, { ...profileEdu, id: undefined }); - profileEdu.profileId = profile.id; - const educationLevel = await profileEducationRepo.findOne({ - select: ["id", "level", "profileId"], - where: { profileId: profile.id, isDeleted: false }, - order: { level: "DESC" }, - }); - profileEdu.level = educationLevel == null ? 1 : educationLevel.level + 1; - await profileEducationRepo.save(profileEdu, { data: req }); - setLogDataDiff(req, { before, after: profileEdu }); - eduHistory.profileEducationId = profileEdu.id; - await profileEducationHistoryRepo.save(eduHistory, { data: req }); - } - } - //Certificates - if (item.bodyCertificates && item.bodyCertificates.length > 0) { - console.log( - "[ExecuteOfficerProfileService] Processing certificates, count:", - item.bodyCertificates.length, - ); - for (const cer of item.bodyCertificates) { - const profileCer = new ProfileCertificate(); - Object.assign(profileCer, { ...cer, ...meta }); - const cerHistory = new ProfileCertificateHistory(); - Object.assign(cerHistory, { ...profileCer, id: undefined }); - profileCer.profileId = profile.id; - await certificateRepo.save(profileCer, { data: req }); - setLogDataDiff(req, { before, after: profileCer }); - cerHistory.profileCertificateId = profileCer.id; - await certificateHistoryRepo.save(cerHistory, { data: req }); - } - } - //FamilyCouple - if (item.bodyMarry != null) { - console.log("[ExecuteOfficerProfileService] Processing couple/marry data"); - const profileCouple = new ProfileFamilyCouple(); - const data = { - profileId: profile.id, - couple: item.bodyMarry.marry, - couplePrefix: item.bodyMarry.marryPrefix, - coupleFirstName: item.bodyMarry.marryFirstName, - coupleLastName: item.bodyMarry.marryLastName, - coupleCareer: item.bodyMarry.marryOccupation, - coupleLive: true, - }; - Object.assign(profileCouple, { ...data, ...meta }); - const coupleHistory = new ProfileFamilyCoupleHistory(); - Object.assign(coupleHistory, { ...profileCouple, id: undefined }); - profileCouple.profileId = profile.id; - await profileFamilyCoupleRepo.save(profileCouple, { data: req }); - setLogDataDiff(req, { before, after: profileCouple }); - coupleHistory.profileFamilyCoupleId = profileCouple.id; - await profileFamilyCoupleHistoryRepo.save(coupleHistory, { data: req }); - } - //FamilyFather - if (item.bodyFather != null) { - console.log("[ExecuteOfficerProfileService] Processing father data"); - const profileFather = new ProfileFamilyFather(); - const data = { - profileId: profile.id, - fatherPrefix: item.bodyFather.fatherPrefix, - fatherFirstName: item.bodyFather.fatherFirstName, - fatherLastName: item.bodyFather.fatherLastName, - fatherCareer: item.bodyFather.fatherOccupation, - fatherLive: true, - }; - Object.assign(profileFather, { ...data, ...meta }); - const fatherHistory = new ProfileFamilyFatherHistory(); - Object.assign(fatherHistory, { ...profileFather, id: undefined }); - profileFather.profileId = profile.id; - await profileFamilyFatherRepo.save(profileFather, { data: req }); - setLogDataDiff(req, { before, after: profileFather }); - fatherHistory.profileFamilyFatherId = profileFather.id; - await profileFamilyFatherHistoryRepo.save(fatherHistory, { data: req }); - } - //FamilyMother - if (item.bodyMother != null) { - console.log("[ExecuteOfficerProfileService] Processing mother data"); - const profileMother = new ProfileFamilyMother(); - const data = { - profileId: profile.id, - motherPrefix: item.bodyMother.motherPrefix, - motherFirstName: item.bodyMother.motherFirstName, - motherLastName: item.bodyMother.motherLastName, - motherCareer: item.bodyMother.motherOccupation, - motherLive: true, - }; - Object.assign(profileMother, { ...data, ...meta }); - const motherHistory = new ProfileFamilyMotherHistory(); - Object.assign(motherHistory, { ...profileMother, id: undefined }); - profileMother.profileId = profile.id; - await profileFamilyMotherRepo.save(profileMother, { data: req }); - setLogDataDiff(req, { before, after: profileMother }); - motherHistory.profileFamilyMotherId = profileMother.id; - await profileFamilyMotherHistoryRepo.save(motherHistory, { data: req }); - } - //Salary - //insert profileSalary อันเก่า กรณีพ้นราชการแล้วกลับมาบรรจุ - if (_oldSalaries.length > 0) { - console.log( - "[ExecuteOfficerProfileService] Restoring old salaries, count:", - _oldSalaries.length, - ); - for (const oldSal of _oldSalaries) { - const profileSal: any = new ProfileSalary(); - Object.assign(profileSal, { ...oldSal, ...meta }); - const salaryHistory = new ProfileSalaryHistory(); - Object.assign(salaryHistory, { ...profileSal, id: undefined }); - profileSal.profileId = profile.id; - await salaryRepo.save(profileSal, { data: req }); - setLogDataDiff(req, { before, after: profileSal }); - salaryHistory.profileSalaryId = profileSal.id; - await salaryHistoryRepo.save(salaryHistory, { data: req }); - } - } - //insert item.bodySalarys ต่อจากที่ insert เดิมไปแล้ว - if (item.bodySalarys && item.bodySalarys != null) { - console.log("[ExecuteOfficerProfileService] Processing new salary data"); - const dest_item = await salaryRepo.findOne({ - where: { profileId: profile.id }, - order: { order: "DESC" }, - }); - const profileSal: any = new ProfileSalary(); - profileSal.posNumCodeSit = _posNumCodeSit; - profileSal.posNumCodeSitAbb = _posNumCodeSitAbb; - Object.assign(profileSal, { ...item.bodySalarys, ...meta }); - const salaryHistory = new ProfileSalaryHistory(); - Object.assign(salaryHistory, { ...profileSal, id: undefined }); - profileSal.order = dest_item == null ? 1 : dest_item.order + 1; - profileSal.profileId = profile.id; - profileSal.dateGovernment = item.bodySalarys.commandDateAffect ?? meta.createdAt; - profileSal.amount = item.bodySalarys.amount ?? null; - profileSal.amountSpecial = item.bodySalarys.amountSpecial ?? null; - profileSal.positionSalaryAmount = item.bodySalarys.positionSalaryAmount ?? null; - profileSal.mouthSalaryAmount = item.bodySalarys.mouthSalaryAmount ?? null; - await salaryRepo.save(profileSal, { data: req }); - setLogDataDiff(req, { before, after: profileSal }); - salaryHistory.profileSalaryId = profileSal.id; - await salaryHistoryRepo.save(salaryHistory, { data: req }); - } - //Position - if (item.bodyPosition && item.bodyPosition != null) { - console.log("[ExecuteOfficerProfileService] Processing position assignment"); - // STEP 1: หา posMaster ที่จะใช้งานตาม id ที่ส่งมา (อาจเป็นตำแหน่งเก่าหรือใหม่ก็ได้) - console.log( - "[ExecuteOfficerProfileService] STEP 1: Finding posMaster, posmasterId:", - item.bodyPosition.posmasterId, - ); - let posMaster = await posMasterRepository.findOne({ - where: { - id: item.bodyPosition.posmasterId, - }, - relations: { - orgRevision: true, - orgRoot: true, - orgChild1: true, - orgChild2: true, - orgChild3: true, - orgChild4: true, - }, - }); - console.log("[ExecuteOfficerProfileService] posMaster found:", !!posMaster); - - // เช็คว่า posMaster ที่หามาอยู่ในโครงสร้างปัจจุบันหรือไม่ - const isCurrent = - posMaster?.orgRevision?.orgRevisionIsCurrent === true && - posMaster?.orgRevision?.orgRevisionIsDraft === false; - console.log("[ExecuteOfficerProfileService] posMaster isCurrent:", isCurrent); - - // ถ้าไม่อยู่ในโครงสร้างปัจจุบัน ให้หาตัวใหม่จาก ancestorDNA - if (!isCurrent && posMaster?.ancestorDNA) { - console.log( - "[ExecuteOfficerProfileService] Finding current posMaster from ancestorDNA", - ); - posMaster = await posMasterRepository.findOne({ - where: { - ancestorDNA: posMaster.ancestorDNA, - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - relations: { - orgRevision: true, - orgRoot: true, - orgChild1: true, - orgChild2: true, - orgChild3: true, - orgChild4: true, - }, - }); - console.log( - "[ExecuteOfficerProfileService] Current posMaster from ancestorDNA found:", - !!posMaster, - ); - } - - if (posMaster == null) { - console.error( - `[ExecuteOfficerProfileService] not found posMasterId: ${item.bodyPosition.posmasterId}`, - ); - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้"); - } - - // STEP 2: เคลียร์ข้อมูลตำแหน่งเก่าที่ครองอยู่ ในโครงสร้างปัจจุบัน - console.log("[ExecuteOfficerProfileService] STEP 2: Clearing old position data"); - const posMasterOld = await posMasterRepository.findOne({ - where: { - current_holderId: profile.id, - orgRevisionId: posMaster.orgRevisionId, - }, - }); - if (posMasterOld != null) { - // เคลียร์คนครองเก่าออกจากตำแหน่งเดิม - posMasterOld.current_holderId = null; - posMasterOld.lastUpdatedAt = new Date(); - } - - // หา position เก่าที่เลือกไว้ แล้วเคลียร์การเลือก - const positionOld = await positionRepository.findOne({ - where: { - posMasterId: posMasterOld?.id, - positionIsSelected: true, - }, - }); - if (positionOld != null) { - positionOld.positionIsSelected = false; - await positionRepository.save(positionOld); - } - - // STEP 3: เคลียร์ position ที่เลือกไว้อื่นๆ ใน posMaster ตัวใหม่ - console.log( - "[ExecuteOfficerProfileService] STEP 3: Clearing other selected positions in new posMaster", - ); - const checkPosition = await positionRepository.find({ - where: { - posMasterId: posMaster.id, - positionIsSelected: true, - }, - }); - if (checkPosition.length > 0) { - const clearPosition = checkPosition.map((positions) => ({ - ...positions, - positionIsSelected: false, - })); - await positionRepository.save(clearPosition); - } - - // STEP 4: กำหนดคนครองใหม่ให้กับ posMaster - console.log( - "[ExecuteOfficerProfileService] STEP 4: Assigning new holder to posMaster", - ); - posMaster.current_holderId = profile.id; - posMaster.lastUpdatedAt = new Date(); - // posMaster.conditionReason = _null; - // posMaster.isCondition = false; - if (posMasterOld != null) { - await posMasterRepository.save(posMasterOld); - console.log( - `[ExecuteOfficerProfileService] Creating PosMasterHistory — posMasterId: ${posMasterOld.id}, citizenId: ${item.bodyProfile?.citizenId} (old)`, - ); - await CreatePosMasterHistoryOfficer(posMasterOld.id, req, null, null, manager); - } - await posMasterRepository.save(posMaster); - console.log("[ExecuteOfficerProfileService] posMaster saved with new holder"); - - // STEP 5: กำหนด position ใหม่ - console.log( - "[ExecuteOfficerProfileService] STEP 5: Determining position to assign", - ); - // Match position ตามลำดับ priority: - // Condition 1: match จาก positionId - // Condition 2: match 7 ฟิลด์ (positionName, posTypeId, posLevelId, positionField, positionArea, positionExecutiveField, posExecutiveId) - // Condition 3: match 3 ฟิลด์ (positionName, posTypeId, posLevelId) - // Fallback: เลือก position แรกใน posMaster - - let positionNew: Position | null = null; - - // ═══════════════════════════════════════════════════════════ - // CONDITION 1: เช็คจาก positionId ตรง - // ═══════════════════════════════════════════════════════════ - console.log( - "[ExecuteOfficerProfileService] CONDITION 1: Checking by positionId:", - item.bodyPosition?.positionId, - ); - if (item.bodyPosition?.positionId) { - const positionById = await positionRepository.findOne({ - where: { - id: item.bodyPosition.positionId, - posMasterId: posMaster.id, // ต้องอยู่ใน posMaster ที่ถูกต้อง - }, - relations: ["posExecutive"], - }); - - if (positionById) { - positionNew = positionById; - console.log( - "[ExecuteOfficerProfileService] CONDITION 1 matched, positionId:", - positionById.id, - ); - } - } - - // ═══════════════════════════════════════════════════════════ - // CONDITION 2: Match 7 ฟิลด์ (ถ้า Condition 1 ไม่ match) - // ═══════════════════════════════════════════════════════════ - if (!positionNew && item.bodyPosition) { - console.log( - "[ExecuteOfficerProfileService] CONDITION 1 not matched, trying CONDITION 2: Match 7 fields", - ); - // สร้าง where clause แบบ dynamic - ใส่เฉพาะฟิลด์ที่ไม่ใช่ null - const whereCondition: any = { - posMasterId: posMaster.id, - positionName: item.bodyPosition.positionName, - posTypeId: item.bodyPosition.posTypeId, - posLevelId: item.bodyPosition.posLevelId, - }; - - if (item.bodyPosition.positionField) { - whereCondition.positionField = item.bodyPosition.positionField; - } - if (item.bodyPosition.posExecutiveId) { - whereCondition.posExecutiveId = item.bodyPosition.posExecutiveId; - } - if (item.bodyPosition.positionExecutiveField) { - whereCondition.positionExecutiveField = item.bodyPosition.positionExecutiveField; - } - if (item.bodyPosition.positionArea) { - whereCondition.positionArea = item.bodyPosition.positionArea; - } - - const positionBy7Fields = await positionRepository.findOne({ - where: whereCondition, - relations: ["posExecutive"], - order: { orderNo: "ASC" }, - }); - - if (positionBy7Fields) { - positionNew = positionBy7Fields; - console.log( - "[ExecuteOfficerProfileService] CONDITION 2 matched with 7 fields, positionId:", - positionBy7Fields.id, - ); - } - } - - // ═══════════════════════════════════════════════════════════ - // CONDITION 3: Match 3 ฟิลด์ (ถ้า Condition 2 ไม่ match) - // ═══════════════════════════════════════════════════════════ - if (!positionNew && item.bodyPosition) { - console.log( - "[ExecuteOfficerProfileService] CONDITION 2 not matched, trying CONDITION 3: Match 3 fields", - ); - const positionBy3Fields = await positionRepository.findOne({ - where: { - posMasterId: posMaster.id, - positionName: item.bodyPosition.positionName, - posTypeId: item.bodyPosition.posTypeId, - posLevelId: item.bodyPosition.posLevelId, - }, - relations: ["posExecutive"], - order: { orderNo: "ASC" }, - }); - - if (positionBy3Fields) { - positionNew = positionBy3Fields; - console.log( - "[ExecuteOfficerProfileService] CONDITION 3 matched with 3 fields, positionId:", - positionBy3Fields.id, - ); - } else { - console.log( - "[ExecuteOfficerProfileService] No position matched for profileId:", - profile.id, - ); - } - } - - // อัพเดท org และ posMasterNo ตลอดไม่ต้องดัก isSit - profile.posMasterNo = getPosMasterNo(posMaster); - profile.org = getOrgFullName(posMaster); - // ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ - if (positionNew != null) { - console.log( - "[ExecuteOfficerProfileService] Final position assignment, isSit:", - posMaster.isSit, - "positionId:", - positionNew.id, - ); - positionNew.positionIsSelected = true; - if (!posMaster.isSit) { - profile.posLevelId = positionNew.posLevelId; - profile.posTypeId = positionNew.posTypeId; - profile.position = positionNew.positionName; - profile.positionField = positionNew.positionField ?? null; - profile.posExecutive = positionNew.posExecutive?.posExecutiveName ?? null; - profile.positionArea = positionNew.positionArea ?? null; - profile.positionExecutiveField = positionNew.positionExecutiveField ?? null; - // profile.dateStart = new Date(); - } - await positionRepository.save(positionNew, { data: req }); - } else if (!posMaster.isSit) { - // fallback: ตำแหน่งในโครงสร้างถูกแก้ไข ใช้ข้อมูลตำแหน่งที่สมัครสอบมา - console.log( - "[ExecuteOfficerProfileService] positionNew is null, using bodyPosition data as fallback", - ); - profile.position = item.bodyPosition.positionName ?? null; - profile.posTypeId = item.bodyPosition.posTypeId ?? null; - profile.posLevelId = item.bodyPosition.posLevelId ?? null; - profile.positionField = item.bodyPosition.positionField ?? null; - profile.positionArea = item.bodyPosition.positionArea ?? null; - profile.positionExecutiveField = item.bodyPosition.positionExecutiveField ?? null; - } - await profileRepository.save(profile, { data: req }); - setLogDataDiff(req, { before, after: profile }); - // await CreatePosMasterHistoryOfficer(posMaster.id, req); - console.log( - `[ExecuteOfficerProfileService] Creating PosMasterHistory — posMasterId: ${posMaster.id}, citizenId: ${item.bodyProfile?.citizenId}`, - ); - await CreatePosMasterHistoryOfficer(posMaster.id, req, null, { - positionId: positionNew?.id, - }, manager); - } - // Insignia - if (_oldInsigniaIds.length > 0) { - console.log( - "[ExecuteOfficerProfileService] Processing old insignias, count:", - _oldInsigniaIds.length, - ); - const _insignias = await insigniaRepo.find({ - where: { id: In(_oldInsigniaIds), isDeleted: false }, - order: { createdAt: "ASC" }, - }); - for (const oldInsignia of _insignias) { - const newInsigniaData: CreateProfileInsignia = { - profileId: profile.id, - year: oldInsignia.year, - no: oldInsignia.no, - volume: oldInsignia.volume, - section: oldInsignia.section, - page: oldInsignia.page, - receiveDate: oldInsignia.receiveDate, - insigniaId: oldInsignia.insigniaId, - dateAnnounce: oldInsignia.dateAnnounce, - issue: oldInsignia.issue, - volumeNo: oldInsignia.volumeNo, - refCommandDate: oldInsignia.refCommandDate, - refCommandNo: oldInsignia.refCommandNo, - note: oldInsignia.note, - isUpload: oldInsignia.isUpload, - }; - const insignia = new ProfileInsignia(); - Object.assign(insignia, { ...newInsigniaData, ...meta }); - const history = new ProfileInsigniaHistory(); - Object.assign(history, { ...insignia, id: undefined }); - await insigniaRepo.save(insignia, { data: req }); - setLogDataDiff(req, { before, after: insignia }); - history.profileInsigniaId = insignia.id; - await insigniaHistoryRepo.save(history, { data: req }); - } - } - // เพิ่มรูปภาพโปรไฟล์ - if (item.bodyProfile.objectRefId) { - console.log( - "[ExecuteOfficerProfileService] Processing profile avatar image, objectRefId:", - item.bodyProfile.objectRefId, - ); - const _profileAvatar = new ProfileAvatar(); - Object.assign(_profileAvatar, { - ...meta, - profileId: profile.id, - profileEmployeeId: undefined, - }); - if (profile.profileAvatars && profile.profileAvatars.length > 0) { - for (const avatarItem of profile.profileAvatars) { - avatarItem.isActive = false; - await avatarRepository.save(avatarItem); - } - } - await avatarRepository.save(_profileAvatar); - let avatar = `ทะเบียนประวัติ/โปรไฟล์/${profile.id}`; - let fileName = `profile-${_profileAvatar.id}`; - _profileAvatar.isActive = true; - _profileAvatar.avatar = avatar; - _profileAvatar.avatarName = fileName; - await avatarRepository.save(_profileAvatar, { data: req }); - profile.avatar = avatar; - profile.avatarName = fileName; - await profileRepository.save(profile, { data: req }); - const checkAvatar = await avatarRepository.findOne({ - where: { avatar: avatar, avatarName: fileName }, - }); - if (checkAvatar && checkAvatar.profileId == null) { - checkAvatar.profileId = profile.id; - await avatarRepository.save(checkAvatar); - } - //duplicate รูปภาพโปรไฟล์โดยอิงจากรูปภาพเดิม - await new CallAPI() - .PostData(req, `/salary/file/avatar/${item.bodyProfile.objectRefId}`, { - prefix: avatar, - fileName: fileName, - }) - .then(() => {}) - .catch(() => {}); - } - } - - console.log( - `[ExecuteOfficerProfileService] Completed processOne — citizenId: ${item.bodyProfile?.citizenId}`, - ); - } -} diff --git a/src/services/ExecuteOrgCommandService.ts b/src/services/ExecuteOrgCommandService.ts deleted file mode 100644 index 62ed231f..00000000 --- a/src/services/ExecuteOrgCommandService.ts +++ /dev/null @@ -1,860 +0,0 @@ -import { Double, EntityManager, In, Like } from "typeorm"; -import { AppDataSource } from "../database/data-source"; -import HttpError from "../interfaces/http-error"; -import HttpStatusCode from "../interfaces/http-status"; -import Extension from "../interfaces/extension"; -import CallAPI from "../interfaces/call-api"; -import { setLogDataDiff } from "../interfaces/utils"; -import { - CreatePosMasterHistoryEmployee, - CreatePosMasterHistoryEmployeeTemp, -} from "./PositionService"; -import { - addUserRoles, - createUser, - getRoles, - getUserByUsername, - getRoleMappings, -} from "../keycloak"; -import { Command } from "../entities/Command"; -import { Profile } from "../entities/Profile"; -import { ProfileEmployee } from "../entities/ProfileEmployee"; -import { OrgRoot } from "../entities/OrgRoot"; -import { OrgRevision } from "../entities/OrgRevision"; -import { RoleKeycloak } from "../entities/RoleKeycloak"; -import { EmployeePosMaster } from "../entities/EmployeePosMaster"; -import { EmployeeTempPosMaster } from "../entities/EmployeeTempPosMaster"; -import { EmployeePosition } from "../entities/EmployeePosition"; -import { PosMaster } from "../entities/PosMaster"; -import { Position } from "../entities/Position"; -import { ProfileSalary } from "../entities/ProfileSalary"; -import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory"; -import { PosMasterAct } from "../entities/PosMasterAct"; -import { ProfileActposition } from "../entities/ProfileActposition"; -import { ProfileActpositionHistory } from "../entities/ProfileActpositionHistory"; -import { promisify } from "util"; - -const redis = require("redis"); -const REDIS_HOST = process.env.REDIS_HOST; -const REDIS_PORT = process.env.REDIS_PORT; - -/** - * Input: refIds ที่ consumer ใน rabbitmq build ขึ้น (เดิมคือ body.refIds ของ endpoint /excecute) - * ใช้กับ C-PM-21, C-PM-38, C-PM-40 - */ -export interface CommandRefItem { - refId: string; - commandId?: string | null; - amount: Double | null; - amountSpecial?: Double | null; - positionSalaryAmount: Double | null; - mouthSalaryAmount: Double | null; - commandNo: string | null; - commandYear: number; - commandDateAffect?: Date | string | null; - commandDateSign?: Date | string | null; - commandCode?: string | null; - commandName?: string | null; - remark: string | null; -} - -/** - * Context สำหรับ audit/log (เหมือน ExecuteSalaryService) - */ -export interface OrgCommandExecutionContext { - user: { sub: string; name: string }; - req?: any; -} - -/** - * Service สำหรับคำสั่งที่เดิม "ยิงเข้าตัว" (HTTP loopback เข้า org เอง) - * - * ใช้กับ commandType: - * - C-PM-21 : command21/employee/report/excecute (ลูกจ้าง → พนักงานประจำ) - * - C-PM-38 : command38/officer/report/excecute (เงินเดือน next_holder ข้าราชการ) - * - C-PM-40 : command40/officer/report/excecute (รักษาการ) - * - * - endpoint commandXX/.../excecute ทั้ง 3 เรียกผ่าน service นี้ (thin wrapper) - * - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow / ทำต่อ) - * แทนการ PostData(path + "/excecute") ที่เป็น HTTP loopback เข้า org ตัวเอง - * - * Behavior ทั้งหมด preserve จาก CommandController ต้นฉบับ - * - * Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential) - * ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด) - * - * ⚠️ หมายเหตุ side-effect ที่อยู่นอก DB transaction: - * - Keycloak operations (createUser/addUserRoles/getRoleMappings) ใน C-PM-21 ทำภายใน transaction - * เพื่อ preserve behavior เดิม — Keycloak ไม่สามารถ rollback ได้ ถ้า DB rollback หลังจากนี้ - * Keycloak จะถูกเปลี่ยนไปแล้ว - * - .NET call (C-PM-21) ทำหลัง transaction commit แล้ว เพราะ .NET ไม่สามารถ rollback ได้ - * - Redis cache clear (C-PM-40) ทำหลัง transaction commit (เป็นการ del cache key — idempotent) - * - CreatePosMasterHistoryEmployeeTemp สร้าง nested transaction ของตัวเอง (ไม่รับ manager) - */ -export class ExecuteOrgCommandService { - private commandRepository = AppDataSource.getRepository(Command); - private profileRepository = AppDataSource.getRepository(Profile); - private profileEmployeeRepository = AppDataSource.getRepository(ProfileEmployee); - private orgRootRepository = AppDataSource.getRepository(OrgRoot); - private orgRevisionRepository = AppDataSource.getRepository(OrgRevision); - private roleKeycloakRepo = AppDataSource.getRepository(RoleKeycloak); - private posMasterRepository = AppDataSource.getRepository(PosMaster); - private posMasterActRepository = AppDataSource.getRepository(PosMasterAct); - - // ───────────────────────────────────────────────────────────── - // แก้ปัญหา _posNumCodeSit/_command resolution ที่ซ้ำกันในทุก endpoint - // (เดิมอยู่ใน controller — ย้ายมานี่ ทำครั้งเดียวก่อนเข้า transaction) - // ───────────────────────────────────────────────────────────── - private async resolvePosNumCodeSit( - commandId: string | null | undefined, - ): Promise<{ command: Command | null; posNumCodeSit: string; posNumCodeSitAbb: string }> { - let posNumCodeSit = ""; - let posNumCodeSitAbb = ""; - const command = commandId - ? await this.commandRepository.findOne({ where: { id: commandId } }) - : null; - if (command) { - if (command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { - const orgRootDeputy = await this.orgRootRepository.findOne({ - where: { - isDeputy: true, - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - relations: ["orgRevision"], - }); - posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; - posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; - } else if (command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { - posNumCodeSit = "กรุงเทพมหานคร"; - posNumCodeSitAbb = "กทม."; - } else { - let profileAdmin = await this.profileRepository.findOne({ - where: { - keycloak: command?.createdUserId.toString(), - current_holders: { - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - }, - relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], - }); - posNumCodeSit = - profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? - ""; - posNumCodeSitAbb = - profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot - .orgRootShortName ?? ""; - } - } - return { command, posNumCodeSit, posNumCodeSitAbb }; - } - - // ═══════════════════════════════════════════════════════════════ - // C-PM-21 : command21/employee/report/excecute - // ลูกจ้างชั่วคราว → พนักงานประจำ (บรรจุ) - // ═══════════════════════════════════════════════════════════════ - /** - * @returns profileEmps ที่จะส่งต่อให้ .NET (สำหรับ consumer rabbitmq เรียก .NET เอง) - * ถ้าเรียกจาก thin-wrapper endpoint จะเรียก .NET ภายใน method นี้เอง - */ - async executeCommand21Employee( - data: CommandRefItem[], - ctx: OrgCommandExecutionContext, - options?: { callDotNet?: boolean }, - ): Promise<{ profileEmps: any[] }> { - const req = ctx.req; - const callDotNet = options?.callDotNet ?? true; - const commandId = data?.find((x) => x.commandId)?.commandId ?? ""; - console.log( - `[ExecuteOrgCommandService] executeCommand21Employee — commandId: ${commandId}, count: ${data?.length ?? 0}`, - ); - - const roleKeycloak = await this.roleKeycloakRepo.findOne({ - where: { name: Like("USER") }, - }); - const { command: _command, posNumCodeSit: _posNumCodeSit, posNumCodeSitAbb: _posNumCodeSitAbb } = - await this.resolvePosNumCodeSit(commandId); - - const profileEmps: any[] = []; - - await AppDataSource.transaction(async (manager) => { - for (const item of data ?? []) { - try { - await this.processOneCommand21( - item, - ctx, - manager, - roleKeycloak, - _command, - _posNumCodeSit, - _posNumCodeSitAbb, - profileEmps, - ); - } catch (err) { - const reason = - err instanceof HttpError - ? err.message - : err instanceof Error - ? err.message - : "unexpected error"; - console.error( - `[ExecuteOrgCommandService] Failed C-PM-21, commandId=${commandId}, refId=${item.refId}: ${reason}`, - err, - ); - throw err; // → rollback ทั้ง transaction + propagate เป็น batch failure - } - } - }); - - // .NET call ทำหลัง commit (เหมือน endpoint เดิมที่เรียกหลัง Promise.all) — .NET ไม่ rollback ได้ - if (callDotNet && profileEmps.length > 0) { - await new CallAPI() - .PostData(req, "/placement/appointment/employee-appoint-21/report/excecute", { - profileEmps, - }) - .catch((error) => { - throw new Error(`Failed. Cannot update status. ${error?.message ?? ""}`); - }); - } - - console.log( - `[ExecuteOrgCommandService] Completed C-PM-21 — ${profileEmps.length} profiles sent to .NET`, - ); - return { profileEmps }; - } - - private async processOneCommand21( - item: CommandRefItem, - ctx: OrgCommandExecutionContext, - manager: EntityManager, - roleKeycloak: RoleKeycloak | null, - _command: Command | null, - _posNumCodeSit: string, - _posNumCodeSitAbb: string, - profileEmps: any[], - ): Promise { - const req = ctx.req; - - const profileEmployeeRepository = manager.getRepository(ProfileEmployee); - const employeePosMasterRepository = manager.getRepository(EmployeePosMaster); - const employeeTempPosMasterRepository = manager.getRepository(EmployeeTempPosMaster); - const employeePositionRepository = manager.getRepository(EmployeePosition); - const salaryRepo = manager.getRepository(ProfileSalary); - const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory); - - const profile = await profileEmployeeRepository.findOne({ - where: { id: item.refId }, - relations: ["roleKeycloaks"], - }); - if (!profile) { - throw new HttpError(HttpStatusCode.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); - } - const orgRevision = await this.orgRevisionRepository.findOne({ - where: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }); - const _posMaster = await employeePosMasterRepository.findOne({ - where: { - orgRevisionId: orgRevision?.id, - id: profile.posmasterIdTemp, - }, - relations: { - orgRoot: true, - orgChild1: true, - orgChild2: true, - orgChild3: true, - orgChild4: true, - }, - }); - const orgRootRef = _posMaster?.orgRoot ?? null; - const orgChild1Ref = _posMaster?.orgChild1 ?? null; - const orgChild2Ref = _posMaster?.orgChild2 ?? null; - const orgChild3Ref = _posMaster?.orgChild3 ?? null; - const orgChild4Ref = _posMaster?.orgChild4 ?? null; - let orgShortName = ""; - if (_posMaster != null) { - if (_posMaster.orgChild1Id === null) { - orgShortName = _posMaster.orgRoot?.orgRootShortName; - } else if (_posMaster.orgChild2Id === null) { - orgShortName = _posMaster.orgChild1?.orgChild1ShortName; - } else if (_posMaster.orgChild3Id === null) { - orgShortName = _posMaster.orgChild2?.orgChild2ShortName; - } else if (_posMaster.orgChild4Id === null) { - orgShortName = _posMaster.orgChild3?.orgChild3ShortName; - } else { - orgShortName = _posMaster.orgChild4?.orgChild4ShortName; - } - } - const dest_item = await salaryRepo.findOne({ - where: { profileEmployeeId: item.refId }, - order: { order: "DESC" }, - }); - const before = null; - const dataSalary = new ProfileSalary(); - dataSalary.posNumCodeSit = _posNumCodeSit; - dataSalary.posNumCodeSitAbb = _posNumCodeSitAbb; - const meta = { - profileEmployeeId: profile.id, - amount: item.amount, - amountSpecial: item.amountSpecial, - positionSalaryAmount: item.positionSalaryAmount, - mouthSalaryAmount: item.mouthSalaryAmount, - position: profile.positionTemp, - positionName: profile.positionTemp, - positionType: profile.posTypeNameTemp, - positionLevel: profile.posLevelNameTemp, - order: dest_item == null ? 1 : dest_item.order + 1, - orgRoot: orgRootRef?.orgRootName ?? null, - orgChild1: orgChild1Ref?.orgChild1Name ?? null, - orgChild2: orgChild2Ref?.orgChild2Name ?? null, - orgChild3: orgChild3Ref?.orgChild3Name ?? null, - orgChild4: orgChild4Ref?.orgChild4Name ?? null, - createdUserId: ctx.user.sub, - createdFullName: ctx.user.name, - lastUpdateUserId: ctx.user.sub, - lastUpdateFullName: ctx.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - commandNo: item.commandNo, - commandYear: item.commandYear, - posNo: profile.posMasterNoTemp ?? "", - posNoAbb: orgShortName, - commandDateAffect: item.commandDateAffect, - commandDateSign: item.commandDateSign, - commandCode: item.commandCode, - commandName: item.commandName, - remark: item.remark, - }; - - Object.assign(dataSalary, meta); - const history = new ProfileSalaryHistory(); - Object.assign(history, { ...dataSalary, id: undefined }); - - await salaryRepo.save(dataSalary, { data: req }); - setLogDataDiff(req, { before, after: dataSalary }); - history.profileSalaryId = dataSalary.id; - await salaryHistoryRepo.save(history, { data: req }); - - const posMaster = await employeePosMasterRepository.findOne({ - where: { id: profile.posmasterIdTemp }, - relations: ["orgRoot", "orgChild1", "orgChild2", "orgChild3", "orgChild4"], - }); - if (posMaster == null) - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้"); - - const posMasterOld = await employeePosMasterRepository.findOne({ - where: { - current_holderId: profile.id, - orgRevisionId: posMaster.orgRevisionId, - }, - }); - if (posMasterOld != null) { - posMasterOld.current_holderId = null; - posMasterOld.lastUpdatedAt = new Date(); - } - - const positionOld = await employeePositionRepository.findOne({ - where: { - posMasterId: posMasterOld?.id, - positionIsSelected: true, - }, - }); - if (positionOld != null) { - positionOld.positionIsSelected = false; - await employeePositionRepository.save(positionOld); - } - - const checkPosition = await employeePositionRepository.find({ - where: { - posMasterId: profile.posmasterIdTemp, - positionIsSelected: true, - }, - }); - if (checkPosition.length > 0) { - const clearPosition = checkPosition.map((positions) => ({ - ...positions, - positionIsSelected: false, - })); - await employeePositionRepository.save(clearPosition); - } - - posMaster.current_holderId = profile.id; - posMaster.lastUpdatedAt = new Date(); - posMaster.next_holderId = null; - if (posMasterOld != null) { - await employeePosMasterRepository.save(posMasterOld); - await CreatePosMasterHistoryEmployee(posMasterOld.id, req, undefined, manager); - } - await employeePosMasterRepository.save(posMaster); - await CreatePosMasterHistoryEmployee(posMaster.id, req, undefined, manager); - - const clsTempPosmaster = await employeeTempPosMasterRepository.find({ - where: { - current_holderId: profile.id, - orgRevisionId: posMaster.orgRevisionId, - }, - }); - - if (clsTempPosmaster.length > 0) { - const clearTempPosmaster = clsTempPosmaster.map((posMasterTemp) => ({ - ...posMasterTemp, - current_holderId: null, - next_holderId: null, - })); - await employeeTempPosMasterRepository.save(clearTempPosmaster); - - const checkTempPosition = await employeePositionRepository.find({ - where: { - posMasterTempId: In(clearTempPosmaster.map((x) => x.id)), - positionIsSelected: true, - }, - }); - if (checkTempPosition.length > 0) { - const clearTempPosition = checkTempPosition.map((positions) => ({ - ...positions, - positionIsSelected: false, - })); - await employeePositionRepository.save(clearTempPosition); - } - await Promise.all( - clsTempPosmaster.map( - async (posMasterTemp) => - await CreatePosMasterHistoryEmployeeTemp(posMasterTemp.id, req), - ), - ); - } - - const positionNew = await employeePositionRepository.findOne({ - where: { - id: profile.positionIdTemp, - posMasterId: profile.posmasterIdTemp, - }, - }); - - if (positionNew != null) { - // Create Keycloak - const checkUser = await getUserByUsername(profile.citizenId); - if (checkUser.length == 0) { - let password = profile.citizenId; - if (profile.birthDate != null) { - const _date = new Date(profile.birthDate.toDateString()) - .getDate() - .toString() - .padStart(2, "0"); - const _month = (new Date(profile.birthDate.toDateString()).getMonth() + 1) - .toString() - .padStart(2, "0"); - const _year = new Date(profile.birthDate.toDateString()).getFullYear() + 543; - password = `${_date}${_month}${_year}`; - } - // กรอง "." ออกจาก firstName ก่อนส่งไป keycloak - const sanitizedFirstName = profile.firstName?.replace(/\./g, "") ?? ""; - const userKeycloakId = await createUser(profile.citizenId, password, { - firstName: sanitizedFirstName, - lastName: profile.lastName, - }); - const list = await getRoles(); - if (!Array.isArray(list)) - throw new Error("Failed. Cannot get role(s) data from the server."); - const result = await addUserRoles( - userKeycloakId, - list - .filter((v) => v.name === "USER") - .map((x) => ({ - id: x.id, - name: x.name, - })), - ); - profile.keycloak = - userKeycloakId && typeof userKeycloakId == "string" ? userKeycloakId : ""; - profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : []; - // End Create Keycloak - } else { - const rolesData = await getRoleMappings(checkUser[0].id); - if (rolesData) { - const _roleKeycloak = await this.roleKeycloakRepo.find({ - where: { name: In(rolesData.map((x: any) => x.name)) }, - }); - profile.roleKeycloaks = - _roleKeycloak && _roleKeycloak.length > 0 ? _roleKeycloak : []; - } - profile.keycloak = checkUser[0].id; - } - positionNew.positionIsSelected = true; - profile.posLevelId = positionNew.posLevelId; - profile.posTypeId = positionNew.posTypeId; - profile.position = positionNew.positionName; - profile.employeeOc = posMaster?.orgRoot?.orgRootName ?? null; - profile.positionEmployeePositionId = positionNew.positionName; - profile.statusTemp = "DONE"; - profile.employeeClass = "PERM"; - const _null: any = null; - profile.employeeWage = item.amount == null ? _null : item.amount.toString(); - profile.dateStart = _command ? _command.commandExcecuteDate : new Date(); - profile.dateAppoint = _command ? _command.commandExcecuteDate : new Date(); - profile.amount = item.amount == null ? _null : item.amount; - profile.amountSpecial = item.amountSpecial == null ? _null : item.amountSpecial; - profileEmps.push({ - profileId: profile.id, - prefix: profile.prefix, - firstName: profile.firstName, - lastName: profile.lastName, - citizenId: profile.citizenId, - root: posMaster.orgRoot.orgRootName, - rootId: posMaster.orgRootId, - rootShortName: posMaster.orgRoot.orgRootShortName, - rootDnaId: posMaster.orgRoot?.ancestorDNA ?? _null, - child1DnaId: posMaster.orgChild1?.ancestorDNA ?? _null, - child2DnaId: posMaster.orgChild2?.ancestorDNA ?? _null, - child3DnaId: posMaster.orgChild3?.ancestorDNA ?? _null, - child4DnaId: posMaster.orgChild4?.ancestorDNA ?? _null, - }); - await profileEmployeeRepository.save(profile); - await employeePositionRepository.save(positionNew); - await CreatePosMasterHistoryEmployee(posMaster.id, req, undefined, manager); - //ลบออกคนออกจากโครงสร้างลูกจ้างชั่วคราว - const posMasterTemp = await employeeTempPosMasterRepository.findOne({ - where: { - orgRevisionId: orgRevision?.id, - current_holderId: profile.id, - }, - }); - if (posMasterTemp) { - await employeeTempPosMasterRepository.update(posMasterTemp.id, { - current_holderId: _null, - }); - await CreatePosMasterHistoryEmployeeTemp(posMasterTemp.id, req); - } - } - } - - // ═══════════════════════════════════════════════════════════════ - // C-PM-38 : command38/officer/report/excecute - // เงินเดือน next_holder ของข้าราชการ - // ═══════════════════════════════════════════════════════════════ - async executeCommand38Officer( - data: CommandRefItem[], - ctx: OrgCommandExecutionContext, - ): Promise { - const req = ctx.req; - const commandId = data?.find((x) => x.commandId)?.commandId ?? ""; - console.log( - `[ExecuteOrgCommandService] executeCommand38Officer — commandId: ${commandId}, count: ${data?.length ?? 0}`, - ); - - const { posNumCodeSit: _posNumCodeSit, posNumCodeSitAbb: _posNumCodeSitAbb } = - await this.resolvePosNumCodeSit(commandId); - - await AppDataSource.transaction(async (manager) => { - for (const item of data ?? []) { - try { - await this.processOneCommand38( - item, - ctx, - manager, - _posNumCodeSit, - _posNumCodeSitAbb, - ); - } catch (err) { - const reason = - err instanceof HttpError - ? err.message - : err instanceof Error - ? err.message - : "unexpected error"; - console.error( - `[ExecuteOrgCommandService] Failed C-PM-38, commandId=${commandId}, refId=${item.refId}: ${reason}`, - err, - ); - throw err; // → rollback ทั้ง transaction + propagate เป็น batch failure - } - } - }); - - console.log(`[ExecuteOrgCommandService] Completed C-PM-38 — ${data?.length ?? 0} items`); - } - - private async processOneCommand38( - item: CommandRefItem, - ctx: OrgCommandExecutionContext, - manager: EntityManager, - _posNumCodeSit: string, - _posNumCodeSitAbb: string, - ): Promise { - const req = ctx.req; - - const posMasterRepository = manager.getRepository(PosMaster); - const profileRepository = manager.getRepository(Profile); - const positionRepository = manager.getRepository(Position); - const salaryRepo = manager.getRepository(ProfileSalary); - const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory); - - const posMaster = await posMasterRepository.findOne({ - where: { id: item.refId }, - relations: [ - "orgRoot", - "orgChild1", - "orgChild2", - "orgChild3", - "orgChild4", - "current_holder", - "current_holder.posLevel", - "current_holder.posType", - ], - }); - if (!posMaster) { - throw new HttpError(HttpStatusCode.BAD_REQUEST, "ไม่พบตำแหน่งดังกล่าว"); - } - if (posMaster.next_holderId != null) { - const orgRootRef = posMaster?.orgRoot ?? null; - const orgChild1Ref = posMaster?.orgChild1 ?? null; - const orgChild2Ref = posMaster?.orgChild2 ?? null; - const orgChild3Ref = posMaster?.orgChild3 ?? null; - const orgChild4Ref = posMaster?.orgChild4 ?? null; - const shortName = - posMaster != null && posMaster.orgChild4 != null - ? `${posMaster.orgChild4.orgChild4ShortName}` - : posMaster != null && posMaster.orgChild3 != null - ? `${posMaster.orgChild3.orgChild3ShortName}` - : posMaster != null && posMaster.orgChild2 != null - ? `${posMaster.orgChild2.orgChild2ShortName}` - : posMaster != null && posMaster.orgChild1 != null - ? `${posMaster.orgChild1.orgChild1ShortName}` - : posMaster != null && posMaster?.orgRoot != null - ? `${posMaster.orgRoot.orgRootShortName}` - : null; - const profile = await profileRepository.findOne({ - where: { id: posMaster.next_holderId }, - }); - const position = await positionRepository.findOne({ - where: { - posMasterId: posMaster.id, - positionIsSelected: true, - }, - relations: ["posType", "posLevel"], - }); - const dest_item = await salaryRepo.findOne({ - where: { profileId: profile?.id }, - order: { order: "DESC" }, - }); - const before = null; - const dataSalary = new ProfileSalary(); - dataSalary.posNumCodeSit = _posNumCodeSit; - dataSalary.posNumCodeSitAbb = _posNumCodeSitAbb; - const meta = { - profileId: profile?.id, - date: new Date(), - amount: item.amount, - commandId: item.commandId, - positionSalaryAmount: item.positionSalaryAmount, - mouthSalaryAmount: item.mouthSalaryAmount, - position: position?.positionName ?? null, - positionType: position?.posType?.posTypeName ?? null, - positionLevel: position?.posLevel?.posLevelName ?? null, - order: dest_item == null ? 1 : dest_item.order + 1, - orgRoot: orgRootRef?.orgRootName ?? null, - orgChild1: orgChild1Ref?.orgChild1Name ?? null, - orgChild2: orgChild2Ref?.orgChild2Name ?? null, - orgChild3: orgChild3Ref?.orgChild3Name ?? null, - orgChild4: orgChild4Ref?.orgChild4Name ?? null, - createdUserId: ctx.user.sub, - createdFullName: ctx.user.name, - lastUpdateUserId: ctx.user.sub, - lastUpdateFullName: ctx.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - commandNo: item.commandNo, - commandYear: item.commandYear, - posNo: posMaster.posMasterNo, - posNoAbb: shortName, - commandDateAffect: item.commandDateAffect, - commandDateSign: item.commandDateSign, - commandCode: item.commandCode, - commandName: item.commandName, - remark: item.remark, - }; - Object.assign(dataSalary, meta); - const history = new ProfileSalaryHistory(); - Object.assign(history, { ...dataSalary, id: undefined }); - - await salaryRepo.save(dataSalary, { data: req }); - setLogDataDiff(req, { before, after: dataSalary }); - history.profileSalaryId = dataSalary.id; - await salaryHistoryRepo.save(history, { data: req }); - } - } - - // ═══════════════════════════════════════════════════════════════ - // C-PM-40 : command40/officer/report/excecute - // รักษาการ (ProfileActposition) - // ═══════════════════════════════════════════════════════════════ - async executeCommand40Officer( - data: CommandRefItem[], - ctx: OrgCommandExecutionContext, - ): Promise { - const commandId = data?.find((x) => x.commandId)?.commandId ?? ""; - console.log( - `[ExecuteOrgCommandService] executeCommand40Officer — commandId: ${commandId}, count: ${data?.length ?? 0}`, - ); - - // 3. ตรวจสอบว่ามี data[0] หรือไม่ - const firstRef = data[0]; - if (!firstRef) { - throw new HttpError(HttpStatusCode.BAD_REQUEST, "ไม่พบข้อมูล refIds"); - } - - const profileIdsToClearCache = new Set(); - - await AppDataSource.transaction(async (manager) => { - // 1. Bulk update status - await manager.getRepository(PosMasterAct).update( - { id: In(data.map((x) => x.refId)) }, - { statusReport: "DONE" }, - ); - - // 2. ดึงข้อมูลครบทุก relation ที่จำเป็น - const posMasters = await manager.getRepository(PosMasterAct).find({ - where: { id: In(data.map((x) => x.refId)) }, - relations: [ - "posMasterChild", - "posMasterChild.current_holder", - "posMaster", - "posMaster.current_holder", - "posMaster.positions", - "posMaster.orgRoot", - "posMaster.orgChild1", - "posMaster.orgChild2", - "posMaster.orgChild3", - "posMaster.orgChild4", - ], - }); - - for (const item of posMasters) { - try { - // 4. ตรวจสอบข้อมูลที่จำเป็นทั้งหมด - if (!item.posMasterChild?.current_holderId || !item.posMaster) { - console.warn(`ข้ามรายการ ${item.id}: ข้อมูลไม่ครบ`); - continue; - } - - if (item.posMasterChild.current_holderId) { - profileIdsToClearCache.add(item.posMasterChild.current_holderId); - } - - // 5. สร้าง orgShortName แบบปลอดภัย - const orgShortName = - [ - item.posMaster?.orgChild4?.orgChild4ShortName, - item.posMaster?.orgChild3?.orgChild3ShortName, - item.posMaster?.orgChild2?.orgChild2ShortName, - item.posMaster?.orgChild1?.orgChild1ShortName, - item.posMaster?.orgRoot?.orgRootShortName, - ].find(Boolean) ?? ""; - - // 6. หา position ที่ถูกเลือกแบบปลอดภัย - const selectedPosition = item.posMaster?.positions; - const positionName = - selectedPosition - ?.map((pos) => pos.positionName) - .filter(Boolean) - .join(", ") ?? "-"; - - // 7. สร้าง metaAct แบบปลอดภัย - const metaAct = { - profileId: item.posMasterChild.current_holderId, - dateStart: firstRef.commandDateAffect ?? null, - dateEnd: null, - position: positionName, - status: true, - commandId: firstRef.commandId ?? null, - createdUserId: ctx.user.sub, - createdFullName: ctx.user.name, - lastUpdateUserId: ctx.user.sub, - lastUpdateFullName: ctx.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - commandNo: firstRef.commandNo ?? null, - refCommandNo: `${firstRef.commandNo ?? ""}/${firstRef.commandYear ? Extension.ToThaiYear(firstRef.commandYear) : ""}`, - commandYear: firstRef.commandYear ? Extension.ToThaiYear(firstRef.commandYear) : null, - posNo: - orgShortName && item.posMaster?.posMasterNo - ? `${orgShortName} ${item.posMaster.posMasterNo}` - : item.posMaster?.posMasterNo ?? "-", - posNoAbb: orgShortName, - commandDateAffect: firstRef.commandDateAffect ?? null, - commandDateSign: firstRef.commandDateSign ?? null, - commandCode: firstRef.commandCode ?? null, - commandName: firstRef.commandName ?? null, - remark: firstRef.remark ?? null, - }; - - // 8. ปิดสถานะรักษาการ - const actpositionRepository = manager.getRepository(ProfileActposition); - const actpositionHistoryRepository = manager.getRepository(ProfileActpositionHistory); - - const existingActPositions = await actpositionRepository.find({ - where: { - profileId: item.posMasterChild.current_holderId, - status: true, - isDeleted: false, - }, - }); - - if (existingActPositions.length > 0) { - const updatedActPositions = existingActPositions.map((_data) => ({ - ..._data, - status: false, - dateEnd: new Date(), - })); - - await actpositionRepository.save(updatedActPositions); - } - - // 9. บันทึกข้อมูลใหม่ - const dataAct = new ProfileActposition(); - Object.assign(dataAct, metaAct); - - const historyAct = new ProfileActpositionHistory(); - Object.assign(historyAct, { ...dataAct, id: undefined }); - - await actpositionRepository.save(dataAct); - historyAct.profileActpositionId = dataAct.id; - await actpositionHistoryRepository.save(historyAct); - } catch (error) { - console.error(`Error processing item ${item.id}:`, error); - throw new HttpError( - HttpStatusCode.INTERNAL_SERVER_ERROR, - `เกิดข้อผิดพลาดในการประมวลผลรายการ ${item.id}`, - ); - } - } - }); - - // Redis cache clear ทำหลัง commit (del cache key — idempotent) - if (profileIdsToClearCache.size > 0) { - await Promise.all( - Array.from(profileIdsToClearCache).map(async (profileId) => { - const redisClient = await redis.createClient({ - host: REDIS_HOST, - port: REDIS_PORT, - }); - - const delAsync = promisify(redisClient.del).bind(redisClient); - await delAsync("role_" + profileId); - await delAsync("menu_" + profileId); - - redisClient.quit(); - }), - ); - } - - console.log(`[ExecuteOrgCommandService] Completed C-PM-40 — ${data?.length ?? 0} items`); - } -} diff --git a/src/services/ExecuteSalaryCurrentService.ts b/src/services/ExecuteSalaryCurrentService.ts deleted file mode 100644 index d9039b30..00000000 --- a/src/services/ExecuteSalaryCurrentService.ts +++ /dev/null @@ -1,499 +0,0 @@ -import { Double, EntityManager } from "typeorm"; -import { AppDataSource } from "../database/data-source"; -import HttpError from "../interfaces/http-error"; -import HttpStatusCode from "../interfaces/http-status"; -import { Profile } from "../entities/Profile"; -import { ProfileSalary } from "../entities/ProfileSalary"; -import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory"; -import { OrgRoot } from "../entities/OrgRoot"; -import { PosMaster } from "../entities/PosMaster"; -import { Position } from "../entities/Position"; -import { Command } from "../entities/Command"; -import { getOrgFullName, getPosMasterNo } from "../utils/org-formatting"; -import { logPositionIsSelectedChange, setLogDataDiff } from "../interfaces/utils"; -import { CreatePosMasterHistoryOfficer } from "./PositionService"; - -/** - * Input: ข้อมูล 1 คนสำหรับ endpoint excexute/salary-current - * (C-PM-03, 04, 05, 06, 07, 39, 47 — เปลี่ยนตำแหน่งปัจจุบันของข้าราชการ + salary ใหม่) - */ -export interface SalaryCurrentItem { - profileId: string; - amount?: Double | null; - amountSpecial?: Double | null; - positionSalaryAmount?: Double | null; - mouthSalaryAmount?: Double | null; - positionExecutive: string | null; - positionExecutiveField?: string | null; - positionArea?: string | null; - positionType: string | null; - positionLevel: string | null; - positionTypeId?: string | null; - positionLevelId?: string | null; - posmasterId: string; - positionId: string; - posExecutiveId?: string | null; - positionField?: string | null; - commandId?: string | null; - orgRoot?: string | null; - orgChild1?: string | null; - orgChild2?: string | null; - orgChild3?: string | null; - orgChild4?: string | null; - commandNo: string | null; - commandYear: number | null; - posNo: string | null; - posNoAbb: string | null; - commandDateAffect?: Date | string | null; - commandDateSign?: Date | string | null; - positionName: string | null; - commandCode?: string | null; - commandName?: string | null; - remark: string | null; -} - -/** - * Context สำหรับ audit/log - */ -export interface SalaryCurrentExecutionContext { - user: { sub: string; name: string }; - req?: any; -} - -/** - * Service สำหรับสร้าง ProfileSalary ของข้าราชการ + อัปเดตตำแหน่งปัจจุบัน (เปลี่ยนตำแหน่ง) - * - * ใช้กับ commandType: C-PM-03, 04, 05, 06, 07, 39, 47 - * - * - endpoint /org/command/excexute/salary-current เรียกผ่าน service นี้ (thin wrapper) - * - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow) - * - * Behavior ทั้งหมด preserve จาก CommandController.newSalaryAndUpdateCurrent ต้นฉบับ - * - * Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential) - * ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด) - * ถ้าทุกคนสำเร็จจะ return result รายงาน success count - */ -export class ExecuteSalaryCurrentService { - private commandRepository = AppDataSource.getRepository(Command); - private profileRepository = AppDataSource.getRepository(Profile); - private orgRootRepository = AppDataSource.getRepository(OrgRoot); - - /** - * ประมวลผลสร้าง ProfileSalary + อัปเดตตำแหน่งปัจจุบันของข้าราชการทั้ง batch - * - * @returns สรุปผล success/failure ต่อคน - */ - async executeSalaryCurrent( - data: SalaryCurrentItem[], - ctx: SalaryCurrentExecutionContext, - ): Promise { - const commandId = data?.find((x) => x.commandId)?.commandId ?? "unknown"; - const commandCode = data?.find((x) => x.commandCode)?.commandCode ?? "unknown"; - console.log( - `[ExecuteSalaryCurrentService] Starting executeSalaryCurrent — commandCode: ${commandCode}, commandId: ${commandId}`, - ); - console.log(`[ExecuteSalaryCurrentService] Request body count: ${data?.length ?? 0}`); - - // ───────────────────────────────────────────────────────────── - // Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date) - // ───────────────────────────────────────────────────────────── - const toDate = (v: any): Date | null => { - if (v == null || v === "") return null; - if (v instanceof Date) return isNaN(v.getTime()) ? null : v; - const d = new Date(v); - return isNaN(d.getTime()) ? null : d; - }; - for (const item of data ?? []) { - const it = item as any; - it.commandDateAffect = toDate(it.commandDateAffect); - it.commandDateSign = toDate(it.commandDateSign); - } - - let _posNumCodeSit: string = ""; - let _posNumCodeSitAbb: string = ""; - const _command = await this.commandRepository.findOne({ - where: { id: data.find((x) => x.commandId)?.commandId ?? "" }, - }); - if (_command) { - if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { - const orgRootDeputy = await this.orgRootRepository.findOne({ - where: { - isDeputy: true, - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - relations: ["orgRevision"], - }); - _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; - _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; - } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { - _posNumCodeSit = "กรุงเทพมหานคร"; - _posNumCodeSitAbb = "กทม."; - } else { - let _profileAdmin = await this.profileRepository.findOne({ - where: { - keycloak: _command?.createdUserId.toString(), - current_holders: { - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - }, - relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], - }); - _posNumCodeSit = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? - ""; - _posNumCodeSitAbb = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot - .orgRootShortName ?? ""; - } - } - - // ───────────────────────────────────────────────────────────── - // 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( - `[ExecuteSalaryCurrentService] Failed — commandCode: ${commandCode}, commandId: ${commandId}, profileId: ${item.profileId}, reason: ${reason}`, - err, - ); - throw err; // → rollback ทั้ง transaction + propagate เป็น batch failure - } - } - }); - } - - /** - * ประมวลผล 1 คน ภายใน transaction เดียว (manager) - * ทุก save ใช้ manager.getRepository(...) เพื่อให้อยู่ใน transaction เดียวกัน - * ถ้า throw ระหว่างทาง → rollback ทั้งหมดของคนนี้ (กัน partial commit) - */ - private async processOne( - item: SalaryCurrentItem, - ctx: SalaryCurrentExecutionContext, - manager: EntityManager, - _posNumCodeSit: string, - _posNumCodeSitAbb: string, - ): Promise { - 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 positionRepository = manager.getRepository(Position); - - const profile: any = await profileRepository.findOneBy({ id: item.profileId }); - if (!profile) { - throw new HttpError(HttpStatusCode.NOT_FOUND, `ไม่พบข้อมูลทะเบียนประวัตินี้ profileId: ${item.profileId}`); - } - let _null: any = null; - const dest_item = await salaryRepo.findOne({ - where: { profileId: item.profileId }, - order: { order: "DESC" }, - }); - const before = null; - const dataSalary = new ProfileSalary(); - - const meta = { - order: dest_item == null ? 1 : dest_item.order + 1, - createdUserId: ctx.user.sub, - createdFullName: ctx.user.name, - lastUpdateUserId: ctx.user.sub, - lastUpdateFullName: ctx.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - }; - dataSalary.posNumCodeSit = _posNumCodeSit; - dataSalary.posNumCodeSitAbb = _posNumCodeSitAbb; - Object.assign(dataSalary, { ...item, ...meta }); - const history = new ProfileSalaryHistory(); - Object.assign(history, { ...dataSalary, id: undefined }); - await salaryRepo.save(dataSalary, { data: req }); - setLogDataDiff(req, { before, after: dataSalary }); - history.commandId = item.commandId ?? _null; - history.profileSalaryId = dataSalary.id; - await salaryHistoryRepo.save(history, { data: req }); - - // STEP 1: หา posMaster ที่จะใช้งานตาม id ที่ส่งมา - console.log( - `[ExecuteSalaryCurrentService] STEP 1: Finding posMaster — posmasterId: ${item.posmasterId}, profileId: ${item.profileId}`, - ); - let posMaster = await posMasterRepository.findOne({ - where: { id: item.posmasterId }, - relations: { - orgRevision: true, - orgRoot: true, - orgChild1: true, - orgChild2: true, - orgChild3: true, - orgChild4: true, - }, - }); - console.log( - `[ExecuteSalaryCurrentService] STEP 1: posMaster found: ${!!posMaster}, ancestorDNA: ${posMaster?.ancestorDNA ?? "null"}, orgRevisionId: ${posMaster?.orgRevisionId ?? "null"}`, - ); - - // เช็คว่า posMaster ที่หามาอยู่ในโครงสร้างปัจจุบันหรือไม่ - const isCurrent = - posMaster?.orgRevision?.orgRevisionIsCurrent === true && - posMaster?.orgRevision?.orgRevisionIsDraft === false; - console.log(`[ExecuteSalaryCurrentService] STEP 1: isCurrent: ${isCurrent}`); - - // ถ้าไม่อยู่ในโครงสร้างปัจจุบัน ให้หาตัวใหม่จาก ancestorDNA - if (!isCurrent && posMaster?.ancestorDNA) { - console.log( - `[ExecuteSalaryCurrentService] STEP 1: Not current — re-resolving via ancestorDNA: ${posMaster.ancestorDNA}`, - ); - posMaster = await posMasterRepository.findOne({ - where: { - ancestorDNA: posMaster.ancestorDNA, - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - relations: { - orgRevision: true, - orgRoot: true, - orgChild1: true, - orgChild2: true, - orgChild3: true, - orgChild4: true, - }, - }); - console.log( - `[ExecuteSalaryCurrentService] STEP 1: ancestorDNA re-resolve — found: ${!!posMaster}`, - ); - } - - if (posMaster == null) { - console.error( - `[ExecuteSalaryCurrentService] STEP 1: PosMaster not found — posmasterId: ${item.posmasterId}`, - ); - throw new HttpError(HttpStatusCode.NOT_FOUND, `ไม่พบข้อมูลตำแหน่งนี้ posMasterId: ${item.posmasterId}`); - } - - const posMasterOld = await posMasterRepository.findOne({ - where: { - current_holderId: item.profileId, - orgRevisionId: posMaster.orgRevisionId, - }, - }); - if (posMasterOld != null) { - posMasterOld.current_holderId = null; - posMasterOld.lastUpdatedAt = new Date(); - } - - const positionOld = await positionRepository.findOne({ - where: { - posMasterId: posMasterOld?.id, - positionIsSelected: true, - }, - }); - if (positionOld != null) { - logPositionIsSelectedChange(positionOld.id, positionOld.positionIsSelected, false, { - posMasterId: posMasterOld?.id, - userId: ctx.user.sub, - endpoint: "updateMaster", - action: "command_change_reset_old_position", - }); - - positionOld.positionIsSelected = false; - await positionRepository.save(positionOld); - } - - const checkPosition = await positionRepository.find({ - where: { - posMasterId: posMaster!.id, // ใช้ posMaster ตัวใหม่ (ที่อาจจะเปลี่ยนจาก ancestorDNA) - positionIsSelected: true, - }, - }); - if (checkPosition.length > 0) { - console.log( - `[positionIsSelected-DEBUG] Command change: clearing ${checkPosition.length} positions (posMasterId: ${posMaster!.id}, userId: ${ctx.user.sub}, endpoint: updateMaster)`, - ); - - const clearPosition = checkPosition.map((positions) => { - logPositionIsSelectedChange(positions.id, positions.positionIsSelected, false, { - posMasterId: posMaster!.id, - userId: ctx.user.sub, - endpoint: "updateMaster", - action: "command_change_clear_positions", - }); - - return { - ...positions, - positionIsSelected: false, - }; - }); - await positionRepository.save(clearPosition); - } - - posMaster.current_holderId = item.profileId; - posMaster.lastUpdatedAt = new Date(); - // posMaster.conditionReason = _null; - // posMaster.isCondition = false; - if (posMasterOld != null) { - await posMasterRepository.save(posMasterOld); - // ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน - console.log( - `[ExecuteSalaryCurrentService] Creating PosMasterHistory — posMasterId: ${posMasterOld.id}, profileId: ${item.profileId} (old)`, - ); - await CreatePosMasterHistoryOfficer(posMasterOld.id, req, null, null, manager); - } - await posMasterRepository.save(posMaster); - - // STEP 2: กำหนด position ใหม่ - // Match position ตามลำดับ priority: - // Condition 1: match จาก positionId - // Condition 2: match 7 ฟิลด์ (positionName, posTypeId, posLevelId, positionField, positionArea, positionExecutiveField, posExecutiveId) - // Condition 3: match 3 ฟิลด์ (positionName, posTypeId, posLevelId) - // Fallback: เลือก position แรกใน posMaster - - let positionNew: Position | null = null; - - // Resolve ID: ใช้ positionTypeId/positionLevelId ก่อน ถ้าไม่มี fallback เป็น positionType/positionLevel - const posTypeId = item.positionTypeId || item.positionType; - const posLevelId = item.positionLevelId || item.positionLevel; - - console.log( - `[ExecuteSalaryCurrentService] STEP 2: Resolving position — posMasterId: ${posMaster.id}, positionId: ${item.positionId ?? "null"}, positionName: ${item.positionName ?? "null"}, posTypeId: ${posTypeId ?? "null"}, posLevelId: ${posLevelId ?? "null"}`, - ); - - // ═══════════════════════════════════════════════════════════ - // CONDITION 1: เช็คจาก positionId ตรง - // ═══════════════════════════════════════════════════════════ - if (item.positionId) { - const positionById = await positionRepository.findOne({ - where: { - id: item.positionId, - posMasterId: posMaster.id, // ต้องอยู่ใน posMaster ที่ถูกต้อง - }, - relations: ["posExecutive"], - }); - console.log( - `[ExecuteSalaryCurrentService] STEP 2 / Condition 1: match: ${!!positionById}`, - ); - - if (positionById) { - positionNew = positionById; - } - } - - // ═══════════════════════════════════════════════════════════ - // CONDITION 2: Match 7 ฟิลด์ (ถ้า Condition 1 ไม่ match) - // ═══════════════════════════════════════════════════════════ - if (!positionNew && item.positionName && posTypeId && posLevelId) { - // สร้าง where clause แบบ dynamic - ใส่เฉพาะฟิลด์ที่มีค่า - const whereCondition: any = { - posMasterId: posMaster.id, - positionName: item.positionName, - posTypeId: posTypeId, - posLevelId: posLevelId, - }; - - // เพิ่มเฉพาะฟิลด์ที่มีค่า (ไม่ใช่ null, undefined, หรือ string ว่าง) - if (item.positionField) { - whereCondition.positionField = item.positionField; - } - if (item.posExecutiveId) { - whereCondition.posExecutiveId = item.posExecutiveId; - } - if (item.positionExecutiveField) { - whereCondition.positionExecutiveField = item.positionExecutiveField; - } - if (item.positionArea) { - whereCondition.positionArea = item.positionArea; - } - - const positionBy7Fields = await positionRepository.findOne({ - where: whereCondition, - relations: ["posExecutive"], - order: { orderNo: "ASC" }, - }); - console.log( - `[ExecuteSalaryCurrentService] STEP 2 / Condition 2: match: ${!!positionBy7Fields}`, - whereCondition, - ); - - if (positionBy7Fields) { - positionNew = positionBy7Fields; - } - } - - // ═══════════════════════════════════════════════════════════ - // CONDITION 3: Match 3 ฟิลด์ (ถ้า Condition 2 ไม่ match) - // ═══════════════════════════════════════════════════════════ - if (!positionNew && item.positionName && posTypeId && posLevelId) { - const positionBy3Fields = await positionRepository.findOne({ - where: { - posMasterId: posMaster.id, - positionName: item.positionName, - posTypeId: posTypeId, - posLevelId: posLevelId, - }, - relations: ["posExecutive"], - order: { orderNo: "ASC" }, - }); - console.log( - `[ExecuteSalaryCurrentService] STEP 2 / Condition 3: match: ${!!positionBy3Fields}`, - ); - - if (positionBy3Fields) { - positionNew = positionBy3Fields; - } - } - - console.log( - `[ExecuteSalaryCurrentService] STEP 2: Resolved positionNew: ${positionNew ? positionNew.id : "null (no match — profile position not updated)"}`, - ); - - // ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ - if (positionNew != null) { - positionNew.positionIsSelected = true; - // อัพเดท org และ posMasterNo ตลอดไม่ต้องดัก isSit - profile.posMasterNo = getPosMasterNo(posMaster); - profile.org = getOrgFullName(posMaster); - if (!posMaster.isSit) { - profile.posLevelId = positionNew.posLevelId; - profile.posTypeId = positionNew.posTypeId; - profile.position = positionNew.positionName; - profile.positionField = positionNew.positionField ?? null; - profile.posExecutive = positionNew.posExecutive?.posExecutiveName ?? null; - profile.positionArea = positionNew.positionArea ?? null; - profile.positionExecutiveField = positionNew.positionExecutiveField ?? null; - } - profile.amount = item.amount ?? null; - profile.amountSpecial = item.amountSpecial ?? null; - await profileRepository.save(profile); - await positionRepository.save(positionNew); - } - // ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน - console.log( - `[ExecuteSalaryCurrentService] Creating PosMasterHistory — posMasterId: ${posMaster.id}, profileId: ${item.profileId}`, - ); - await CreatePosMasterHistoryOfficer(posMaster.id, req, null, null, manager); - - console.log( - `[ExecuteSalaryCurrentService] Completed processOne — profileId: ${item.profileId}, posMasterId: ${posMaster.id}`, - ); - } -} diff --git a/src/services/ExecuteSalaryEmployeeCurrentService.ts b/src/services/ExecuteSalaryEmployeeCurrentService.ts deleted file mode 100644 index 93b6eaed..00000000 --- a/src/services/ExecuteSalaryEmployeeCurrentService.ts +++ /dev/null @@ -1,316 +0,0 @@ -import { Double, EntityManager } from "typeorm"; -import { AppDataSource } from "../database/data-source"; -import HttpError from "../interfaces/http-error"; -import HttpStatusCode from "../interfaces/http-status"; -import HttpStatus from "../interfaces/http-status"; -import { Profile } from "../entities/Profile"; -import { ProfileEmployee } from "../entities/ProfileEmployee"; -import { ProfileSalary } from "../entities/ProfileSalary"; -import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory"; -import { OrgRoot } from "../entities/OrgRoot"; -import { EmployeePosMaster } from "../entities/EmployeePosMaster"; -import { EmployeePosition } from "../entities/EmployeePosition"; -import { Command } from "../entities/Command"; -import { setLogDataDiff } from "../interfaces/utils"; -import { CreatePosMasterHistoryEmployee } from "./PositionService"; - -/** - * Input: ข้อมูล 1 คนสำหรับ endpoint excexute/salary-employee-current - * (C-PM-22, 24 — เปลี่ยนตำแหน่งปัจจุบันของลูกจ้าง + salary ใหม่) - */ -export interface SalaryEmployeeCurrentItem { - profileId: string; - amount?: Double | null; - amountSpecial?: Double | null; - positionSalaryAmount?: Double | null; - mouthSalaryAmount?: Double | null; - positionType: string | null; - positionLevel: string | null; - posmasterId: string; - positionId: string; - commandId?: string | null; - orgRoot?: string | null; - orgChild1?: string | null; - orgChild2?: string | null; - orgChild3?: string | null; - orgChild4?: string | null; - commandNo: string | null; - commandYear: number | null; - posNo: string | null; - posNoAbb: string | null; - commandDateAffect?: Date | string | null; - commandDateSign?: Date | string | null; - positionName: string | null; - commandCode?: string | null; - commandName?: string | null; - remark: string | null; -} - -/** - * Context สำหรับ audit/log - */ -export interface SalaryEmployeeCurrentExecutionContext { - user: { sub: string; name: string }; - req?: any; -} - -/** - * Service สำหรับสร้าง ProfileSalary ของลูกจ้าง + อัปเดตตำแหน่งปัจจุบัน (เปลี่ยนตำแหน่ง) - * - * ใช้กับ commandType: C-PM-22, 24 - * - * - endpoint /org/command/excexute/salary-employee-current เรียกผ่าน service นี้ (thin wrapper) - * - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow) - * - * Behavior ทั้งหมด preserve จาก CommandController.newSalaryEmployeeAndUpdateCurrent ต้นฉบับ - * - * Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential) - * ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด) - * ถ้าทุกคนสำเร็จจะ return result รายงาน success count - */ -export class ExecuteSalaryEmployeeCurrentService { - private commandRepository = AppDataSource.getRepository(Command); - private profileRepository = AppDataSource.getRepository(Profile); - private orgRootRepository = AppDataSource.getRepository(OrgRoot); - - /** - * ประมวลผลสร้าง ProfileSalary + อัปเดตตำแหน่งปัจจุบันของลูกจ้างทั้ง batch - */ - async executeSalaryEmployeeCurrent( - data: SalaryEmployeeCurrentItem[], - ctx: SalaryEmployeeCurrentExecutionContext, - ): Promise { - const commandId = data?.find((x) => x.commandId)?.commandId ?? "unknown"; - const commandCode = data?.find((x) => x.commandCode)?.commandCode ?? "unknown"; - console.log( - `[ExecuteSalaryEmployeeCurrentService] Starting executeSalaryEmployeeCurrent — commandCode: ${commandCode}, commandId: ${commandId}`, - ); - console.log(`[ExecuteSalaryEmployeeCurrentService] Request body count: ${data?.length ?? 0}`); - - // ───────────────────────────────────────────────────────────── - // Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date) - // ───────────────────────────────────────────────────────────── - const toDate = (v: any): Date | null => { - if (v == null || v === "") return null; - if (v instanceof Date) return isNaN(v.getTime()) ? null : v; - const d = new Date(v); - return isNaN(d.getTime()) ? null : d; - }; - for (const item of data ?? []) { - const it = item as any; - it.commandDateAffect = toDate(it.commandDateAffect); - it.commandDateSign = toDate(it.commandDateSign); - } - - let _posNumCodeSit: string = ""; - let _posNumCodeSitAbb: string = ""; - const _command = await this.commandRepository.findOne({ - where: { id: data.find((x) => x.commandId)?.commandId ?? "" }, - }); - if (_command) { - if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { - const orgRootDeputy = await this.orgRootRepository.findOne({ - where: { - isDeputy: true, - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - relations: ["orgRevision"], - }); - _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; - _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; - } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { - _posNumCodeSit = "กรุงเทพมหานคร"; - _posNumCodeSitAbb = "กทม."; - } else { - let _profileAdmin = await this.profileRepository.findOne({ - where: { - keycloak: _command?.createdUserId.toString(), - current_holders: { - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - }, - relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], - }); - _posNumCodeSit = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? - ""; - _posNumCodeSitAbb = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot - .orgRootShortName ?? ""; - } - } - - // ───────────────────────────────────────────────────────────── - // 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 { - 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) { - throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); - } - - const dest_item = await salaryRepo.findOne({ - where: { profileEmployeeId: item.profileId }, - order: { order: "DESC" }, - }); - const before = null; - const dataSalary = new ProfileSalary(); - dataSalary.posNumCodeSit = _posNumCodeSit; - dataSalary.posNumCodeSitAbb = _posNumCodeSitAbb; - const meta = { - order: dest_item == null ? 1 : dest_item.order + 1, - createdUserId: ctx.user.sub, - createdFullName: ctx.user.name, - lastUpdateUserId: ctx.user.sub, - lastUpdateFullName: ctx.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - }; - - Object.assign(dataSalary, { - ...item, - ...meta, - profileEmployeeId: item.profileId, - profileId: undefined, - }); - const history = new ProfileSalaryHistory(); - Object.assign(history, { ...dataSalary, id: undefined }); - - await salaryRepo.save(dataSalary, { data: req }); - setLogDataDiff(req, { before, after: dataSalary }); - history.profileSalaryId = dataSalary.id; - await salaryHistoryRepo.save(history, { data: req }); - - const posMaster = await employeePosMasterRepository.findOne({ - where: { id: item.posmasterId }, - relations: ["orgRoot"], - }); - if (posMaster == null) - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้"); - - const posMasterOld = await employeePosMasterRepository.findOne({ - where: { - current_holderId: item.profileId, - orgRevisionId: posMaster.orgRevisionId, - }, - }); - if (posMasterOld != null) { - posMasterOld.current_holderId = null; - posMasterOld.lastUpdatedAt = new Date(); - } - // if (posMasterOld != null) posMasterOld.next_holderId = null; - - const positionOld = await employeePositionRepository.findOne({ - where: { - posMasterId: posMasterOld?.id, - positionIsSelected: true, - }, - }); - if (positionOld != null) { - positionOld.positionIsSelected = false; - await employeePositionRepository.save(positionOld); - } - - const checkPosition = await employeePositionRepository.find({ - where: { - posMasterId: item.posmasterId, - positionIsSelected: true, - }, - }); - if (checkPosition.length > 0) { - const clearPosition = checkPosition.map((positions) => ({ - ...positions, - positionIsSelected: false, - })); - await employeePositionRepository.save(clearPosition); - } - - posMaster.current_holderId = item.profileId; - posMaster.lastUpdatedAt = new Date(); - posMaster.next_holderId = null; - if (posMasterOld != null) { - await employeePosMasterRepository.save(posMasterOld); - // ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน - console.log( - `[ExecuteSalaryEmployeeCurrentService] Creating PosMasterHistory — posMasterId: ${posMasterOld.id}, profileId: ${item.profileId} (old)`, - ); - await CreatePosMasterHistoryEmployee(posMasterOld.id, req, null, manager); - } - await employeePosMasterRepository.save(posMaster); - const positionNew = await employeePositionRepository.findOne({ - where: { - id: item.positionId, - posMasterId: item.posmasterId, - }, - }); - if (positionNew != null) { - positionNew.positionIsSelected = true; - profile.posLevelId = positionNew.posLevelId; - profile.posTypeId = positionNew.posTypeId; - profile.position = positionNew.positionName; - profile.employeeOc = posMaster?.orgRoot?.orgRootName ?? null; - profile.positionEmployeePositionId = positionNew.positionName; - profile.amount = item.amount ?? null; - profile.amountSpecial = item.amountSpecial ?? null; - await profileEmployeeRepository.save(profile); - await employeePositionRepository.save(positionNew); - } - // ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน - console.log( - `[ExecuteSalaryEmployeeCurrentService] Creating PosMasterHistory — posMasterId: ${posMaster.id}, profileId: ${item.profileId}`, - ); - await CreatePosMasterHistoryEmployee(posMaster.id, req, null, manager); - - console.log( - `[ExecuteSalaryEmployeeCurrentService] Completed processOne — profileId: ${item.profileId}, posMasterId: ${posMaster.id}`, - ); - } -} diff --git a/src/services/ExecuteSalaryEmployeeLeaveService.ts b/src/services/ExecuteSalaryEmployeeLeaveService.ts deleted file mode 100644 index 95bfa948..00000000 --- a/src/services/ExecuteSalaryEmployeeLeaveService.ts +++ /dev/null @@ -1,382 +0,0 @@ -import { Double, EntityManager } from "typeorm"; -import { AppDataSource } from "../database/data-source"; -import HttpError from "../interfaces/http-error"; -import HttpStatus from "../interfaces/http-status"; -import { Profile } from "../entities/Profile"; -import { ProfileEmployee } from "../entities/ProfileEmployee"; -import { ProfileSalary } from "../entities/ProfileSalary"; -import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory"; -import { OrgRoot } from "../entities/OrgRoot"; -import { OrgRevision } from "../entities/OrgRevision"; -import { EmployeePosMaster } from "../entities/EmployeePosMaster"; -import { CommandRecive } from "../entities/CommandRecive"; -import { Command } from "../entities/Command"; -import { checkCommandType, removeProfileInOrganize, setLogDataDiff } from "../interfaces/utils"; -import { reOrderCommandRecivesAndDelete } from "./CommandService"; -import { CreatePosMasterHistoryEmployee } from "./PositionService"; -import { deleteUser } from "../keycloak"; - -/** - * Input: ข้อมูล 1 คนสำหรับ endpoint excexute/salary-employee-leave - * (C-PM-23, 42, 43 — ลาออก/ยกเลิกลาออก/กลับเข้าราชการ ของลูกจ้าง) - */ -export interface SalaryEmployeeLeaveItem { - profileId: string; - amount?: Double | null; - amountSpecial?: Double | null; - positionSalaryAmount?: Double | null; - mouthSalaryAmount?: Double | null; - positionType: string | null; - positionLevel: string | null; - isLeave: boolean; - leaveReason?: string | null; - dateLeave?: Date | string | null; - isGovernment?: boolean | null; - commandId?: string | null; - orgRoot?: string | null; - orgChild1?: string | null; - orgChild2?: string | null; - orgChild3?: string | null; - orgChild4?: string | null; - positionExecutive?: string | null; - positionExecutiveField?: string | null; - positionArea?: string | null; - commandNo: string | null; - commandYear: number | null; - posNo: string | null; - posNoAbb: string | null; - commandDateAffect?: Date | string | null; - commandDateSign?: Date | string | null; - positionName: string | null; - commandCode?: string | null; - commandName?: string | null; - remark: string | null; - resignId: string | null; -} - -/** - * Context สำหรับ audit/log - */ -export interface SalaryEmployeeLeaveExecutionContext { - user: { sub: string; name: string }; - req?: any; -} - -/** - * Service สำหรับสร้าง ProfileSalary ลูกจ้าง + handle leave/กลับเข้าราชการ - * - * ใช้กับ commandType: C-PM-23, 42, 43 - * - * - endpoint /org/command/excexute/salary-employee-leave เรียกผ่าน service นี้ (thin wrapper) - * - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow) - * - * Behavior ทั้งหมด preserve จาก CommandController.newSalaryEmployeeAndUpdateLeave ต้นฉบับ - * - * Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential) - * ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด) - * ถ้าทุกคนสำเร็จจะ return result รายงาน success count - * - * ⚠️ หมายเหตุ Keycloak: operation (deleteUser) ทำภายใน transaction เพื่อ preserve behavior - * เดิม — Keycloak ไม่สามารถ rollback ได้ ถ้า DB rollback หลังจาก Keycloak operation สำเร็จ - * → Keycloak จะถูกเปลี่ยนไปแล้ว - */ -export class ExecuteSalaryEmployeeLeaveService { - private commandRepository = AppDataSource.getRepository(Command); - private commandReciveRepository = AppDataSource.getRepository(CommandRecive); - private profileRepository = AppDataSource.getRepository(Profile); - private orgRootRepository = AppDataSource.getRepository(OrgRoot); - - /** - * ประมวลผลสร้าง ProfileSalary + handle leave ของลูกจ้างทั้ง batch - */ - async executeSalaryEmployeeLeave( - data: SalaryEmployeeLeaveItem[], - ctx: SalaryEmployeeLeaveExecutionContext, - ): Promise { - const commandId = data?.find((x) => x.commandId)?.commandId ?? "unknown"; - const commandCode = data?.find((x) => x.commandCode)?.commandCode ?? "unknown"; - console.log( - `[ExecuteSalaryEmployeeLeaveService] Starting executeSalaryEmployeeLeave — commandCode: ${commandCode}, commandId: ${commandId}`, - ); - console.log(`[ExecuteSalaryEmployeeLeaveService] Request body count: ${data?.length ?? 0}`); - - // ───────────────────────────────────────────────────────────── - // Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date) - // ───────────────────────────────────────────────────────────── - const toDate = (v: any): Date | null => { - if (v == null || v === "") return null; - if (v instanceof Date) return isNaN(v.getTime()) ? null : v; - const d = new Date(v); - return isNaN(d.getTime()) ? null : d; - }; - for (const item of data ?? []) { - const it = item as any; - it.dateLeave = toDate(it.dateLeave); - it.commandDateAffect = toDate(it.commandDateAffect); - it.commandDateSign = toDate(it.commandDateSign); - } - - let _posNumCodeSit: string = ""; - let _posNumCodeSitAbb: string = ""; - const _command = await this.commandRepository.findOne({ - where: { id: data.find((x) => x.commandId)?.commandId ?? "" }, - relations: { commandType: true }, - }); - if (_command) { - if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { - const orgRootDeputy = await this.orgRootRepository.findOne({ - where: { - isDeputy: true, - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - relations: ["orgRevision"], - }); - _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; - _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; - } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { - _posNumCodeSit = "กรุงเทพมหานคร"; - _posNumCodeSitAbb = "กทม."; - } else { - let _profileAdmin = await this.profileRepository.findOne({ - where: { - keycloak: _command?.createdUserId.toString(), - current_holders: { - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - }, - relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], - }); - _posNumCodeSit = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? - ""; - _posNumCodeSitAbb = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot - .orgRootShortName ?? ""; - } - } - const today = new Date().setHours(0, 0, 0, 0); - - // ───────────────────────────────────────────────────────────── - // 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 { - const req = ctx.req; - - const commandReciveRepository = manager.getRepository(CommandRecive); - const profileEmployeeRepository = manager.getRepository(ProfileEmployee); - const salaryRepo = manager.getRepository(ProfileSalary); - const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory); - const employeePosMasterRepository = manager.getRepository(EmployeePosMaster); - const orgRevisionRepo = manager.getRepository(OrgRevision); - - const profile = await profileEmployeeRepository.findOne({ - where: { id: item.profileId }, - relations: { - roleKeycloaks: true, - posType: true, - posLevel: true, - }, - }); - if (!profile) { - throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); - } - const code = _command?.commandType?.code; - //ออกคำสั่งยกเลิกลาออก ลบเฉพาะคนที่ขอยกเลิกลาออก - if (item.resignId && code && ["C-PM-42"].includes(code)) { - const commandResign = await commandReciveRepository.findOne({ - where: { refId: item.resignId }, - relations: { command: true }, - }); - const executeDate = commandResign - ? new Date(commandResign.command.commandExcecuteDate).setHours(0, 0, 0, 0) - : today; - if ( - commandResign && - _command.status !== "REPORTED" && - (_command.status !== "WAITING" || today < executeDate) - ) { - // ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน - await reOrderCommandRecivesAndDelete(commandResign!.id, manager); - } - } - let _commandYear = item.commandYear; - if (item.commandYear) { - _commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543; - } - const dest_item = await salaryRepo.findOne({ - where: { profileEmployeeId: item.profileId }, - order: { order: "DESC" }, - }); - const before = null; - const dataSalary = new ProfileSalary(); - dataSalary.posNumCodeSit = _posNumCodeSit; - dataSalary.posNumCodeSitAbb = _posNumCodeSitAbb; - const meta = { - order: dest_item == null ? 1 : dest_item.order + 1, - createdUserId: ctx.user.sub, - createdFullName: ctx.user.name, - lastUpdateUserId: ctx.user.sub, - lastUpdateFullName: ctx.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - }; - - Object.assign(dataSalary, { - ...item, - ...meta, - profileEmployeeId: item.profileId, - profileId: undefined, - }); - const history = new ProfileSalaryHistory(); - Object.assign(history, { ...dataSalary, id: undefined }); - dataSalary.dateGovernment = (item.commandDateAffect as Date) ?? meta.createdAt; - await salaryRepo.save(dataSalary, { data: req }); - setLogDataDiff(req, { before, after: dataSalary }); - history.profileSalaryId = dataSalary.id; - await salaryHistoryRepo.save(history, { data: req }); - - const _null: any = null; - profile.isLeave = item.isLeave; - profile.leaveReason = item.leaveReason ?? _null; - profile.dateLeave = item.dateLeave ?? _null; - profile.lastUpdateUserId = ctx.user.sub; - profile.lastUpdateFullName = ctx.user.name; - profile.lastUpdatedAt = new Date(); - // บันทึกประวัติก่อนลบตำแหน่ง - const clearProfile = await checkCommandType(String(item.commandId)); - const curRevision = await orgRevisionRepo.findOne({ - where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, - }); - let orgRootRef = null; - let orgChild1Ref = null; - let orgChild2Ref = null; - let orgChild3Ref = null; - let orgChild4Ref = null; - if (curRevision) { - const curPosMaster = await employeePosMasterRepository.findOne({ - where: { - current_holderId: profile.id, - orgRevisionId: curRevision.id, - }, - relations: { - orgRoot: true, - orgChild1: true, - orgChild2: true, - orgChild3: true, - orgChild4: true, - }, - }); - orgRootRef = curPosMaster?.orgRoot ?? null; - orgChild1Ref = curPosMaster?.orgChild1 ?? null; - orgChild2Ref = curPosMaster?.orgChild2 ?? null; - orgChild3Ref = curPosMaster?.orgChild3 ?? null; - orgChild4Ref = curPosMaster?.orgChild4 ?? null; - if (curPosMaster) { - // ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน - console.log( - `[ExecuteSalaryEmployeeLeaveService] Creating PosMasterHistory — posMasterId: ${curPosMaster.id}, profileId: ${item.profileId}, type: DELETE`, - ); - await CreatePosMasterHistoryEmployee(curPosMaster.id, req, "DELETE", manager); - } - } - - // ลบตำแหน่ง - if (item.isLeave == true) { - // ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน - await removeProfileInOrganize(profile.id, "EMPLOYEE", manager); - } - - if (clearProfile.status) { - if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) { - // Keycloak deleteUser ทำภายใน transaction — ถ้า DB rollback หลังจากนี้ Keycloak จะถูกลบไปแล้ว - // (Keycloak ไม่สามารถ rollback ได้) - const delUserKeycloak = await deleteUser(profile.keycloak); - if (delUserKeycloak) { - // Task #228 - // profile.keycloak = _null; - profile.roleKeycloaks = []; - profile.isActive = false; - profile.isDelete = true; - } - } - profile.leaveCommandId = item.commandId ?? _null; - profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`; - profile.leaveRemark = clearProfile.leaveRemark ?? _null; - profile.leaveDate = item.commandDateAffect ?? _null; - profile.leaveType = clearProfile.LeaveType ?? _null; - //ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516) - // profile.position = _null; - // profile.posTypeId = _null; - // profile.posLevelId = _null; - } - await profileEmployeeRepository.save(profile); - - // if (profile.id) { - // await this.keycloakAttributeService.clearOrgDnaAttributes( - // [profile.id], - // "PROFILE_EMPLOYEE", - // ); - // } - - // Task #2190 - if (code && ["C-PM-23", "C-PM-43"].includes(code)) { - let organizeName = ""; - if (orgRootRef) { - const names = [ - orgChild4Ref?.orgChild4Name, - orgChild3Ref?.orgChild3Name, - orgChild2Ref?.orgChild2Name, - orgChild1Ref?.orgChild1Name, - orgRootRef?.orgRootName, - ].filter(Boolean); - organizeName = names.join(" "); - } - } - - console.log( - `[ExecuteSalaryEmployeeLeaveService] Completed processOne — profileId: ${item.profileId}`, - ); - } -} diff --git a/src/services/ExecuteSalaryLeaveDisciplineService.ts b/src/services/ExecuteSalaryLeaveDisciplineService.ts deleted file mode 100644 index 99a1dffc..00000000 --- a/src/services/ExecuteSalaryLeaveDisciplineService.ts +++ /dev/null @@ -1,615 +0,0 @@ -import { Double, EntityManager } from "typeorm"; -import { AppDataSource } from "../database/data-source"; -import HttpError from "../interfaces/http-error"; -import HttpStatusCode from "../interfaces/http-status"; -import { Profile } from "../entities/Profile"; -import { ProfileEmployee } from "../entities/ProfileEmployee"; -import { ProfileSalary } from "../entities/ProfileSalary"; -import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory"; -import { ProfileDiscipline } from "../entities/ProfileDiscipline"; -import { ProfileDisciplineHistory } from "../entities/ProfileDisciplineHistory"; -import { OrgRoot } from "../entities/OrgRoot"; -import { OrgRevision } from "../entities/OrgRevision"; -import { EmployeePosMaster } from "../entities/EmployeePosMaster"; -import { Command } from "../entities/Command"; -import { - checkCommandType, - removePostMasterAct, - removeProfileInOrganize, - setLogDataDiff, -} from "../interfaces/utils"; -import { - CreatePosMasterHistoryEmployee, - CreatePosMasterHistoryOfficer, -} from "./PositionService"; -import { deleteUser } from "../keycloak"; - -/** - * Input: ข้อมูล 1 คนสำหรับ endpoint excexute/salary-leave-discipline - * (C-PM-19, 20, 25, 26, 27, 28, 29, 30, 31, 32 — คำสั่งวินัย ข้าราชการ/ลูกจ้าง) - * - * profileType "OFFICER" → ข้าราชการ, ค่าอื่น/null → ลูกจ้าง - */ -export interface SalaryLeaveDisciplineItem { - profileId: string; - profileType?: string | null; - isLeave: boolean | null; - leaveReason?: string | null; - dateLeave?: Date | string | null; - detail?: string | null; - level?: string | null; - unStigma?: string | null; - commandId?: string | null; - amount?: Double | null; - amountSpecial?: Double | null; - positionSalaryAmount?: Double | null; - mouthSalaryAmount?: Double | null; - isGovernment?: boolean | null; - commandNo: string | null; - commandYear: number | null; - commandDateAffect?: Date | string | null; - commandDateSign?: Date | string | null; - commandCode?: string | null; - commandName?: string | null; - remark: string | null; - orgRoot?: string | null; - orgChild1?: string | null; - orgChild2?: string | null; - orgChild3?: string | null; - orgChild4?: string | null; - posNo?: string | null; - posNoAbb?: string | null; -} - -/** - * Context สำหรับ audit/log - */ -export interface SalaryLeaveDisciplineExecutionContext { - user: { sub: string; name: string }; - req?: any; -} - -/** - * Service สำหรับสร้าง ProfileSalary + ProfileDiscipline + handle leave ของคำสั่งวินัย - * - * ใช้กับ commandType: C-PM-19, 20, 25, 26, 27, 28, 29, 30, 31, 32 - * - * - endpoint /org/command/excexute/salary-leave-discipline เรียกผ่าน service นี้ (thin wrapper) - * - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow) - * - * Behavior ทั้งหมด preserve จาก CommandController.newSalaryAndUpdateLeaveDiscipline ต้นฉบับ - * รวมถึงกรณี OFFICER ที่การ save ProfileSalary + ProfileDiscipline ถูก comment out ไว้ - * (เก็บไว้เพื่อ preserve behavior เดิม — มีเพียง EMPLOYEE เท่านั้นที่ save จริง) - * - * Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential) - * ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด) - * ถ้าทุกคนสำเร็จจะ return result รายงาน success count - * - * ⚠️ หมายเหตุ Keycloak: operation (deleteUser) ทำภายใน transaction เพื่อ preserve behavior - * เดิม — Keycloak ไม่สามารถ rollback ได้ ถ้า DB rollback หลังจาก Keycloak operation สำเร็จ - * → Keycloak จะถูกเปลี่ยนไปแล้ว - */ -export class ExecuteSalaryLeaveDisciplineService { - private commandRepository = AppDataSource.getRepository(Command); - private profileRepository = AppDataSource.getRepository(Profile); - private orgRootRepository = AppDataSource.getRepository(OrgRoot); - - /** - * ประมวลผลคำสั่งวินัยทั้ง batch - * - * @returns สรุปผล success/failure ต่อคน - */ - async executeSalaryLeaveDiscipline( - data: SalaryLeaveDisciplineItem[], - ctx: SalaryLeaveDisciplineExecutionContext, - ): Promise { - const commandId = data?.find((x) => x.commandId)?.commandId ?? "unknown"; - const commandCode = data?.find((x) => x.commandCode)?.commandCode ?? "unknown"; - console.log( - `[ExecuteSalaryLeaveDisciplineService] Starting executeSalaryLeaveDiscipline — commandCode: ${commandCode}, commandId: ${commandId}`, - ); - console.log(`[ExecuteSalaryLeaveDisciplineService] Request body count: ${data?.length ?? 0}`); - - // ───────────────────────────────────────────────────────────── - // Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date) - // ───────────────────────────────────────────────────────────── - const toDate = (v: any): Date | null => { - if (v == null || v === "") return null; - if (v instanceof Date) return isNaN(v.getTime()) ? null : v; - const d = new Date(v); - return isNaN(d.getTime()) ? null : d; - }; - for (const item of data ?? []) { - const it = item as any; - it.dateLeave = toDate(it.dateLeave); - it.commandDateAffect = toDate(it.commandDateAffect); - it.commandDateSign = toDate(it.commandDateSign); - } - - let _posNumCodeSit: string = ""; - let _posNumCodeSitAbb: string = ""; - const _command = await this.commandRepository.findOne({ - relations: ["commandType"], - where: { id: data.find((x) => x.commandId)?.commandId ?? "" }, - }); - if (_command) { - if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { - const orgRootDeputy = await this.orgRootRepository.findOne({ - where: { - isDeputy: true, - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - relations: ["orgRevision"], - }); - _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; - _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; - } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { - _posNumCodeSit = "กรุงเทพมหานคร"; - _posNumCodeSitAbb = "กทม."; - } else { - let _profileAdmin = await this.profileRepository.findOne({ - where: { - keycloak: _command?.createdUserId.toString(), - current_holders: { - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - }, - relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], - }); - _posNumCodeSit = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? - ""; - _posNumCodeSitAbb = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot - .orgRootShortName ?? ""; - } - } - - // ───────────────────────────────────────────────────────────── - // 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); - } catch (err) { - const reason = - err instanceof HttpError - ? err.message - : err instanceof Error - ? err.message - : "unexpected error"; - console.error( - `[ExecuteSalaryLeaveDisciplineService] 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: SalaryLeaveDisciplineItem, - ctx: SalaryLeaveDisciplineExecutionContext, - manager: EntityManager, - _command: Command | null, - _posNumCodeSit: string, - _posNumCodeSitAbb: string, - ): Promise { - const req = ctx.req; - - const profileRepository = manager.getRepository(Profile); - const profileEmployeeRepository = manager.getRepository(ProfileEmployee); - const salaryRepo = manager.getRepository(ProfileSalary); - const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory); - const disciplineRepository = manager.getRepository(ProfileDiscipline); - const disciplineHistoryRepository = manager.getRepository(ProfileDisciplineHistory); - const orgRevisionRepo = manager.getRepository(OrgRevision); - const employeePosMasterRepository = manager.getRepository(EmployeePosMaster); - - let _commandYear = item.commandYear; - if (item.commandYear) { - _commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543; - } - - const orgRevision = await orgRevisionRepo.findOne({ - where: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }); - - let orgRootRef: any = null; - let orgChild1Ref: any = null; - let orgChild2Ref: any = null; - let orgChild3Ref: any = null; - let orgChild4Ref: any = null; - - const code = _command?.commandType?.code; - - // ═══════════════════════════════════════════════════════════ - // OFFICER (ข้าราชการ) - // ═══════════════════════════════════════════════════════════ - if (item.profileType && item.profileType.trim().toUpperCase() == "OFFICER") { - const profile: any = await profileRepository.findOne({ - relations: [ - "posLevel", - "posType", - "current_holders", - "current_holders.orgRoot", - "current_holders.orgChild1", - "current_holders.orgChild2", - "current_holders.orgChild3", - "current_holders.orgChild4", - "current_holders.positions", - "current_holders.positions.posExecutive", - "roleKeycloaks", - ], - where: { id: item.profileId }, - }); - if (!profile) { - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); - } - const lastSalary = await salaryRepo.findOne({ - where: { profileId: item.profileId }, - select: ["order"], - order: { order: "DESC" }, - }); - const nextOrder = lastSalary ? lastSalary.order + 1 : 1; - - //ลบตำแหน่งที่รักษาการแทน (await + ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน) - if (code && ["C-PM-19", "C-PM-20"].includes(code)) { - await removePostMasterAct(profile.id, manager); - } - - const orgRevisionRef = - profile?.current_holders?.find((x: any) => x.orgRevisionId == orgRevision?.id) ?? null; - orgRootRef = orgRevisionRef?.orgRoot ?? null; - orgChild1Ref = orgRevisionRef?.orgChild1 ?? null; - orgChild2Ref = orgRevisionRef?.orgChild2 ?? null; - orgChild3Ref = orgRevisionRef?.orgChild3 ?? null; - orgChild4Ref = orgRevisionRef?.orgChild4 ?? null; - - const position = - profile.current_holders - .filter((x: any) => x.orgRevisionId == orgRevision?.id)[0] - ?.positions?.filter((pos: any) => pos.positionIsSelected === true)[0] ?? null; - - // ประวัติตำแหน่ง - const data = new ProfileSalary(); - data.posNumCodeSit = _posNumCodeSit; - data.posNumCodeSitAbb = _posNumCodeSitAbb; - const meta = { - profileId: profile.id, - commandId: item.commandId, - position: profile.position, - positionName: profile.position, - positionType: profile?.posType?.posTypeName ?? null, - positionLevel: profile?.posLevel?.posLevelName ?? null, - positionExecutive: position?.posExecutive?.posExecutiveName ?? null, - amount: item.amount ? item.amount : null, - positionSalaryAmount: item.positionSalaryAmount ? item.positionSalaryAmount : null, - amountSpecial: item.amountSpecial ? item.amountSpecial : null, - mouthSalaryAmount: item.mouthSalaryAmount ? item.mouthSalaryAmount : null, - order: nextOrder, - orgRoot: item.orgRoot, - orgChild1: item.orgChild1, - orgChild2: item.orgChild2, - orgChild3: item.orgChild3, - orgChild4: item.orgChild4, - createdUserId: ctx.user.sub, - createdFullName: ctx.user.name, - lastUpdateUserId: ctx.user.sub, - lastUpdateFullName: ctx.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - dateGovernment: item.commandDateAffect ?? new Date(), - isGovernment: item.isGovernment, - commandNo: item.commandNo, - commandYear: item.commandYear, - posNo: item.posNo, - posNoAbb: item.posNoAbb, - commandDateAffect: item.commandDateAffect, - commandDateSign: item.commandDateSign, - commandCode: item.commandCode, - commandName: item.commandName, - remark: item.remark, - }; - Object.assign(data, meta); - const history = new ProfileSalaryHistory(); - Object.assign(history, { ...data, id: undefined }); - // ── preserve: OFFICER branch ไม่ save ProfileSalary (comment ตามต้นฉบับ) ── - // await salaryRepo.save(data, { data: req }); - // history.profileSalaryId = data.id; - // await salaryHistoryRepo.save(history, { data: req }); - - // ประวัติวินัย - const dataDis = new ProfileDiscipline(); - const metaDis = { - date: item.commandDateAffect, - refCommandDate: item.commandDateSign, - refCommandNo: `${item.commandNo}/${item.commandYear}`, - refCommandId: item.commandId, - createdUserId: ctx.user.sub, - createdFullName: ctx.user.name, - lastUpdateUserId: ctx.user.sub, - lastUpdateFullName: ctx.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - }; - Object.assign(dataDis, { ...item, ...metaDis }); - const historyDis = new ProfileDisciplineHistory(); - Object.assign(historyDis, { ...dataDis, id: undefined }); - // ── preserve: OFFICER branch ไม่ save ProfileDiscipline (comment ตามต้นฉบับ) ── - // await disciplineRepository.save(dataDis, { data: req }); - // historyDis.profileDisciplineId = dataDis.id; - // await disciplineHistoryRepository.save(historyDis, { data: req }); - - // ทะเบียนประวัติ - if (item.isLeave != null) { - const _profile: any = await profileRepository.findOne({ - where: { id: item.profileId }, - relations: ["roleKeycloaks"], - }); - if (!_profile) { - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); - } - const _null: any = null; - _profile.isLeave = item.isLeave; - _profile.leaveReason = item.leaveReason ?? _null; - _profile.dateLeave = item.dateLeave ?? _null; - _profile.lastUpdateUserId = ctx.user.sub; - _profile.lastUpdateFullName = ctx.user.name; - _profile.lastUpdatedAt = new Date(); - if (item.isLeave == true) { - if (orgRevisionRef) { - // ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน - await CreatePosMasterHistoryOfficer(orgRevisionRef.id, req, "DELETE", null, manager); - } - // ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน - await removeProfileInOrganize(_profile.id, "OFFICER", manager); - } - const clearProfile = await checkCommandType(String(item.commandId)); - if (clearProfile.status) { - if ( - _profile.keycloak != null && - _profile.keycloak != "" && - _profile.isDelete === false - ) { - // Keycloak ทำภายใน transaction — ไม่สามารถ rollback ได้ (ดู docstring ของ class) - const delUserKeycloak = await deleteUser(_profile.keycloak); - if (delUserKeycloak) { - // Task #228 - // _profile.keycloak = _null; - _profile.roleKeycloaks = []; - _profile.isActive = false; - _profile.isDelete = true; - } - } - _profile.leaveCommandId = item.commandId ?? _null; - _profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`; - _profile.leaveRemark = clearProfile.leaveRemark ?? _null; - _profile.leaveDate = item.commandDateAffect ?? _null; - _profile.leaveType = clearProfile.LeaveType ?? _null; - //ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516) - // _profile.position = _null; - // _profile.posTypeId = _null; - // _profile.posLevelId = _null; - } - await profileRepository.save(_profile, { data: req }); - setLogDataDiff(req, { before: null, after: _profile }); - } - } - // ═══════════════════════════════════════════════════════════ - // EMPLOYEE (ลูกจ้าง) - // ═══════════════════════════════════════════════════════════ - else { - const profile: any = await profileEmployeeRepository.findOne({ - relations: [ - "posLevel", - "posType", - "current_holders", - "current_holders.orgRoot", - "current_holders.orgChild1", - "current_holders.orgChild2", - "current_holders.orgChild3", - "current_holders.orgChild4", - "roleKeycloaks", - ], - where: { id: item.profileId }, - }); - if (!profile) { - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); - } - const lastSalary = await salaryRepo.findOne({ - where: { profileEmployeeId: item.profileId }, - select: ["order"], - order: { order: "DESC" }, - }); - const nextOrder = lastSalary ? lastSalary.order + 1 : 1; - const orgRevisionRef = - profile?.current_holders?.find((x: any) => x.orgRevisionId == orgRevision?.id) ?? null; - orgRootRef = orgRevisionRef?.orgRoot ?? null; - orgChild1Ref = orgRevisionRef?.orgChild1 ?? null; - orgChild2Ref = orgRevisionRef?.orgChild2 ?? null; - orgChild3Ref = orgRevisionRef?.orgChild3 ?? null; - orgChild4Ref = orgRevisionRef?.orgChild4 ?? null; - - // ประวัติตำแหน่ง - const data = new ProfileSalary(); - data.posNumCodeSit = _posNumCodeSit; - data.posNumCodeSitAbb = _posNumCodeSitAbb; - const meta = { - profileEmployeeId: profile.id, - commandId: item.commandId, - position: profile.position, - positionName: profile.position, - positionType: profile?.posType?.posTypeName ?? null, - positionLevel: - profile?.posType && profile?.posLevel - ? `${profile?.posType?.posTypeShortName} ${profile?.posLevel?.posLevelName}` - : null, - amount: item.amount ? item.amount : null, - positionSalaryAmount: item.positionSalaryAmount ? item.positionSalaryAmount : null, - mouthSalaryAmount: item.mouthSalaryAmount ? item.mouthSalaryAmount : null, - order: nextOrder, - orgRoot: item.orgRoot, - orgChild1: item.orgChild1, - orgChild2: item.orgChild2, - orgChild3: item.orgChild3, - orgChild4: item.orgChild4, - createdUserId: ctx.user.sub, - createdFullName: ctx.user.name, - lastUpdateUserId: ctx.user.sub, - lastUpdateFullName: ctx.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - dateGovernment: item.commandDateAffect ?? new Date(), - isGovernment: item.isGovernment, - commandNo: item.commandNo, - commandYear: item.commandYear, - posNo: item.posNo, - posNoAbb: item.posNoAbb, - commandDateAffect: item.commandDateAffect, - commandDateSign: item.commandDateSign, - commandCode: item.commandCode, - commandName: item.commandName, - remark: item.remark, - }; - Object.assign(data, meta); - const history = new ProfileSalaryHistory(); - Object.assign(history, { ...data, id: undefined }); - await salaryRepo.save(data, { data: req }); - setLogDataDiff(req, { before: null, after: data }); - history.profileSalaryId = data.id; - await salaryHistoryRepo.save(history, { data: req }); - - // ประวัติวินัย - const dataDis = new ProfileDiscipline(); - const metaDis = { - createdUserId: ctx.user.sub, - createdFullName: ctx.user.name, - lastUpdateUserId: ctx.user.sub, - lastUpdateFullName: ctx.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - }; - Object.assign(dataDis, { - ...item, - ...metaDis, - date: item.commandDateAffect, - refCommandDate: item.commandDateSign, - refCommandNo: item.commandNo, - profileEmployeeId: item.profileId, - profileId: undefined, - }); - const historyDis = new ProfileDisciplineHistory(); - Object.assign(historyDis, { ...dataDis, id: undefined }); - await disciplineRepository.save(dataDis, { data: req }); - setLogDataDiff(req, { before: null, after: dataDis }); - historyDis.profileDisciplineId = dataDis.id; - await disciplineHistoryRepository.save(historyDis, { data: req }); - - // ทะเบียนประวัติ - if (item.isLeave != null) { - const _profile: any = await profileEmployeeRepository.findOne({ - where: { id: item.profileId }, - relations: ["roleKeycloaks"], - }); - if (!_profile) { - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); - } - const _null: any = null; - _profile.isLeave = item.isLeave; - _profile.leaveReason = item.leaveReason ?? _null; - _profile.dateLeave = item.dateLeave ?? _null; - _profile.lastUpdateUserId = ctx.user.sub; - _profile.lastUpdateFullName = ctx.user.name; - _profile.lastUpdatedAt = new Date(); - if (item.isLeave == true) { - // บันทึกประวัติก่อนลบตำแหน่ง - const curRevision = await orgRevisionRepo.findOne({ - where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, - }); - if (curRevision) { - const curPosMaster = await employeePosMasterRepository.findOne({ - where: { - current_holderId: _profile.id, - orgRevisionId: curRevision.id, - }, - }); - if (curPosMaster) { - // ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน - await CreatePosMasterHistoryEmployee(curPosMaster.id, req, "DELETE", manager); - } - } - // ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน - await removeProfileInOrganize(_profile.id, "EMPLOYEE", manager); - } - const clearProfile = await checkCommandType(String(item.commandId)); - if (clearProfile.status) { - if ( - _profile.keycloak != null && - _profile.keycloak != "" && - _profile.isDelete === false - ) { - // Keycloak deleteUser ทำภายใน transaction — ถ้า DB rollback หลังจากนี้ Keycloak จะถูกลบไปแล้ว - // (Keycloak ไม่สามารถ rollback ได้) - const delUserKeycloak = await deleteUser(_profile.keycloak); - if (delUserKeycloak) { - // Task #228 - // _profile.keycloak = _null; - _profile.roleKeycloaks = []; - _profile.isActive = false; - _profile.isDelete = true; - } - } - _profile.leaveCommandId = item.commandId ?? _null; - _profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`; - _profile.leaveRemark = clearProfile.leaveRemark ?? _null; - _profile.leaveDate = item.commandDateAffect ?? _null; - _profile.leaveType = clearProfile.LeaveType ?? _null; - //ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516) - // _profile.position = _null; - // _profile.posTypeId = _null; - // _profile.posLevelId = _null; - } - await profileEmployeeRepository.save(_profile, { data: req }); - setLogDataDiff(req, { before: null, after: _profile }); - } - } - - // Task #2190 (preserve: organizeName computed แต่ยังไม่ได้ใช้ในต้นฉบับ — เก็บไว้ตาม behavior เดิม) - if (_command && ["C-PM-19", "C-PM-20"].includes(_command.commandType.code)) { - let organizeName = ""; - if (orgRootRef) { - const names = [ - orgChild4Ref?.orgChild4Name, - orgChild3Ref?.orgChild3Name, - orgChild2Ref?.orgChild2Name, - orgChild1Ref?.orgChild1Name, - orgRootRef?.orgRootName, - ].filter(Boolean); - organizeName = names.join(" "); - } - } - - console.log( - `[ExecuteSalaryLeaveDisciplineService] Completed processOne — profileId: ${item.profileId}`, - ); - } -} diff --git a/src/services/ExecuteSalaryLeaveService.ts b/src/services/ExecuteSalaryLeaveService.ts deleted file mode 100644 index 98a1da41..00000000 --- a/src/services/ExecuteSalaryLeaveService.ts +++ /dev/null @@ -1,708 +0,0 @@ -import { Double, EntityManager, In, Like } from "typeorm"; -import { AppDataSource } from "../database/data-source"; -import HttpError from "../interfaces/http-error"; -import HttpStatusCode from "../interfaces/http-status"; -import { Profile } from "../entities/Profile"; -import { ProfileSalary } from "../entities/ProfileSalary"; -import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory"; -import { OrgRoot } from "../entities/OrgRoot"; -import { OrgRevision } from "../entities/OrgRevision"; -import { PosMaster } from "../entities/PosMaster"; -import { Position } from "../entities/Position"; -import { RoleKeycloak } from "../entities/RoleKeycloak"; -import { CommandRecive } from "../entities/CommandRecive"; -import { Command } from "../entities/Command"; -import { - checkCommandType, - checkReturnCommandType, - removePostMasterAct, - removeProfileInOrganize, - setLogDataDiff, -} from "../interfaces/utils"; -import { reOrderCommandRecivesAndDelete } from "./CommandService"; -import { CreatePosMasterHistoryOfficer } from "./PositionService"; -import { getOrgFullName, getPosMasterNo } from "../utils/org-formatting"; -import { - addUserRoles, - createUser, - deleteUser, - getRoleMappings, - getRoles, - getUserByUsername, - updateUserAttributes, -} from "../keycloak"; - -/** - * Input: ข้อมูล 1 คนสำหรับ endpoint excexute/salary-leave - * (C-PM-08, 09, 17, 18, 41, 48 — ลาออก/พักราชการ/กลับเข้าราชการ ของข้าราชการ) - */ -export interface SalaryLeaveItem { - profileId: string; - amount?: Double | null; - amountSpecial?: Double | null; - positionSalaryAmount?: Double | null; - mouthSalaryAmount?: Double | null; - positionExecutive: string | null; - positionExecutiveField?: string | null; - positionArea?: string | null; - positionType: string | null; - positionLevel: string | null; - isLeave: boolean; - leaveReason?: string | null; - dateLeave?: Date | string | null; - posExecutiveId?: string | null; - positionField?: string | null; - commandId?: string | null; - isGovernment?: boolean | null; - orgRoot?: string | null; - orgChild1?: string | null; - orgChild2?: string | null; - orgChild3?: string | null; - orgChild4?: string | null; - commandNo: string | null; - commandYear: number | null; - posNo: string | null; - posNoAbb: string | null; - commandDateAffect?: Date | string | null; - commandDateSign?: Date | string | null; - positionName: string | null; - commandCode?: string | null; - commandName?: string | null; - remark: string | null; - positionId?: string | null; - positionTypeNew?: string | null; - positionLevelNew?: string | null; - positionNameNew?: string | null; - posmasterId?: string | null; - posTypeNameNew?: string | null; - posLevelNameNew?: string | null; - posNoNew?: string | null; - posNoAbbNew?: string | null; - orgRootNew?: string | null; - orgChild1New?: string | null; - orgChild2New?: string | null; - orgChild3New?: string | null; - orgChild4New?: string | null; - resignId?: string | null; -} - -/** - * Context สำหรับ audit/log - */ -export interface SalaryLeaveExecutionContext { - user: { sub: string; name: string }; - req?: any; -} - -/** - * Service สำหรับสร้าง ProfileSalary ข้าราชการ + handle leave/กลับเข้าราชการ - * - * ใช้กับ commandType: C-PM-08, 09, 17, 18, 41, 48 - * - * - endpoint /org/command/excexute/salary-leave เรียกผ่าน service นี้ (thin wrapper) - * - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow) - * - * Behavior ทั้งหมด preserve จาก CommandController.newSalaryAndUpdateLeave ต้นฉบับ - * - * Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential) - * ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด) - * ถ้าทุกคนสำเร็จจะ return result รายงาน success count - * - * ⚠️ หมายเหตุ Keycloak: operations (deleteUser/createUser/addUserRoles/updateUserAttributes) - * ทำภายใน transaction เพื่อ preserve behavior เดิม — Keycloak ไม่สามารถ rollback ได้ - * ถ้า DB rollback หลังจาก Keycloak operation สำเร็จ → Keycloak จะถูกเปลี่ยนไปแล้ว - */ -export class ExecuteSalaryLeaveService { - private commandRepository = AppDataSource.getRepository(Command); - private commandReciveRepository = AppDataSource.getRepository(CommandRecive); - private profileRepository = AppDataSource.getRepository(Profile); - private orgRootRepository = AppDataSource.getRepository(OrgRoot); - private roleKeycloakRepo = AppDataSource.getRepository(RoleKeycloak); - - /** - * ประมวลผลสร้าง ProfileSalary + handle leave/กลับเข้าราชการ ของข้าราชการทั้ง batch - * - * @returns สรุปผล success/failure ต่อคน - */ - async executeSalaryLeave(data: SalaryLeaveItem[], ctx: SalaryLeaveExecutionContext): Promise { - const commandId = data?.find((x) => x.commandId)?.commandId ?? "unknown"; - const commandCode = data?.find((x) => x.commandCode)?.commandCode ?? "unknown"; - console.log( - `[ExecuteSalaryLeaveService] Starting executeSalaryLeave — commandCode: ${commandCode}, commandId: ${commandId}`, - ); - console.log(`[ExecuteSalaryLeaveService] Request body count: ${data?.length ?? 0}`); - - // ───────────────────────────────────────────────────────────── - // Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date) - // ───────────────────────────────────────────────────────────── - const toDate = (v: any): Date | null => { - if (v == null || v === "") return null; - if (v instanceof Date) return isNaN(v.getTime()) ? null : v; - const d = new Date(v); - return isNaN(d.getTime()) ? null : d; - }; - for (const item of data ?? []) { - const it = item as any; - it.dateLeave = toDate(it.dateLeave); - it.commandDateAffect = toDate(it.commandDateAffect); - it.commandDateSign = toDate(it.commandDateSign); - } - - const roleKeycloak = await this.roleKeycloakRepo.findOne({ - where: { name: Like("USER") }, - }); - let _posNumCodeSit: string = ""; - let _posNumCodeSitAbb: string = ""; - const _command = await this.commandRepository.findOne({ - relations: ["commandType"], - where: { id: data.find((x) => x.commandId)?.commandId ?? "" }, - }); - if (_command) { - if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { - const orgRootDeputy = await this.orgRootRepository.findOne({ - where: { - isDeputy: true, - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - relations: ["orgRevision"], - }); - _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; - _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; - } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { - _posNumCodeSit = "กรุงเทพมหานคร"; - _posNumCodeSitAbb = "กทม."; - } else { - let _profileAdmin = await this.profileRepository.findOne({ - where: { - keycloak: _command?.createdUserId.toString(), - current_holders: { - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - }, - relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], - }); - _posNumCodeSit = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? - ""; - _posNumCodeSitAbb = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot - .orgRootShortName ?? ""; - } - } - const today = new Date().setHours(0, 0, 0, 0); - - // ───────────────────────────────────────────────────────────── - // 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 { - const req = ctx.req; - - const commandReciveRepository = manager.getRepository(CommandRecive); - const profileRepository = manager.getRepository(Profile); - const salaryRepo = manager.getRepository(ProfileSalary); - const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory); - const posMasterRepository = manager.getRepository(PosMaster); - const positionRepository = manager.getRepository(Position); - const orgRevisionRepo = manager.getRepository(OrgRevision); - const roleKeycloakRepo = manager.getRepository(RoleKeycloak); - - const profile = await profileRepository.findOne({ - where: { id: item.profileId }, - relations: { - roleKeycloaks: true, - }, - }); - if (!profile) { - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); - } - //ลบตำแหน่งที่รักษาการแทน - const code = _command?.commandType?.code; - if (code && ["C-PM-08", "C-PM-17", "C-PM-18", "C-PM-48"].includes(code)) { - // await (เดิมไม่ await = fire-and-forget bug) + ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction - await removePostMasterAct(profile.id, manager); - } - //ออกคำสั่งยกเลิกลาออก ลบเฉพาะคนที่ขอยกเลิกลาออก - else if (item.resignId && code && ["C-PM-41"].includes(code)) { - const commandResign = await commandReciveRepository.findOne({ - where: { refId: item.resignId }, - relations: { command: true }, - }); - const executeDate = commandResign - ? new Date(commandResign.command.commandExcecuteDate).setHours(0, 0, 0, 0) - : today; - if ( - commandResign && - _command.status !== "REPORTED" && - (_command.status !== "WAITING" || today < executeDate) - ) { - // ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน - await reOrderCommandRecivesAndDelete(commandResign!.id, manager); - } - } - let _commandYear = item.commandYear; - if (item.commandYear) { - _commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543; - } - const returnWork = await checkReturnCommandType(String(item.commandId)); - const dest_item = await salaryRepo.findOne({ - where: { profileId: item.profileId }, - order: { order: "DESC" }, - }); - const before = null; - const dataSalary = new ProfileSalary(); - dataSalary.posNumCodeSit = _posNumCodeSit; - dataSalary.posNumCodeSitAbb = _posNumCodeSitAbb; - dataSalary.dateGovernment = (item.commandDateAffect as Date) ?? new Date(); - dataSalary.order = dest_item == null ? 1 : dest_item.order + 1; - const meta = { - createdUserId: ctx.user.sub, - createdFullName: ctx.user.name, - lastUpdateUserId: ctx.user.sub, - lastUpdateFullName: ctx.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - }; - if (!returnWork) { - Object.assign(dataSalary, { ...item, ...meta }); - const history = new ProfileSalaryHistory(); - Object.assign(history, { ...dataSalary, id: undefined }); - await salaryRepo.save(dataSalary, { data: req }); - setLogDataDiff(req, { before, after: dataSalary }); - history.profileSalaryId = dataSalary.id; - await salaryHistoryRepo.save(history, { data: req }); - } - const _null: any = null; - profile.isLeave = item.isLeave; - profile.leaveReason = item.leaveReason ?? _null; - profile.dateLeave = item.dateLeave ?? _null; - profile.lastUpdateUserId = ctx.user.sub; - profile.lastUpdateFullName = ctx.user.name; - profile.lastUpdatedAt = new Date(); - const clearProfile = await checkCommandType(String(item.commandId)); - - //ปั๊มประวัติก่อนลบตำแหน่ง - const curRevision = await orgRevisionRepo.findOne({ - where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, - }); - let orgRootRef = null; - let orgChild1Ref = null; - let orgChild2Ref = null; - let orgChild3Ref = null; - let orgChild4Ref = null; - if (curRevision) { - const curPosMaster = await posMasterRepository.findOne({ - where: { - current_holderId: profile.id, - orgRevisionId: curRevision.id, - }, - relations: { - orgRoot: true, - orgChild1: true, - orgChild2: true, - orgChild3: true, - orgChild4: true, - }, - }); - orgRootRef = curPosMaster?.orgRoot ?? null; - orgChild1Ref = curPosMaster?.orgChild1 ?? null; - orgChild2Ref = curPosMaster?.orgChild2 ?? null; - orgChild3Ref = curPosMaster?.orgChild3 ?? null; - orgChild4Ref = curPosMaster?.orgChild4 ?? null; - if (curPosMaster && clearProfile.LeaveType != "RETIRE_OUT_EMP") { - // ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน - console.log( - `[ExecuteSalaryLeaveService] Creating PosMasterHistory — posMasterId: ${curPosMaster.id}, profileId: ${item.profileId}, type: DELETE`, - ); - await CreatePosMasterHistoryOfficer(curPosMaster.id, req, "DELETE", null, manager); - } - } - - //ลบตำแหน่ง - if (item.isLeave == true) { - // ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน - await removeProfileInOrganize(profile.id, "OFFICER", manager); - } - if (clearProfile.status) { - if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) { - // Keycloak ทำภายใน transaction — ไม่สามารถ rollback ได้ (ดู docstring ของ class) - const delUserKeycloak = await deleteUser(profile.keycloak); - if (delUserKeycloak) { - // Task #228 - // profile.keycloak = _null; - profile.roleKeycloaks = []; - profile.isActive = false; - profile.isDelete = true; - } - } - profile.leaveCommandId = item.commandId ?? _null; - profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`; - profile.leaveRemark = clearProfile.leaveRemark ?? _null; - profile.leaveDate = item.commandDateAffect ?? _null; - profile.leaveType = clearProfile.LeaveType ?? _null; - //ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516) - // profile.position = _null; - // profile.posTypeId = _null; - // profile.posLevelId = _null; - } - - if (item.isGovernment == true) { - if (returnWork) { - //ปลดตำแหน่งเดิมที่ไม่ถูกปลดออกจากกิ่งครั้งเมื่อออกคำสั่งพักราชการหรือออกราชการไว้ - await removeProfileInOrganize(profile.id, "OFFICER", manager); - //ปั๊มตำแหน่งใหม่ - // หา posMaster และเช็ค orgRevisionIsCurrent - let posMaster = await posMasterRepository.findOne({ - where: { id: item.posmasterId?.toString() }, - relations: { - orgRevision: true, - orgRoot: true, - orgChild1: true, - orgChild2: true, - orgChild3: true, - orgChild4: true, - }, - }); - - // เช็คว่า posMaster ที่หามาอยู่ในโครงสร้างปัจจุบันหรือไม่ - const isCurrent = - posMaster?.orgRevision?.orgRevisionIsCurrent === true && - posMaster?.orgRevision?.orgRevisionIsDraft === false; - - // ถ้าไม่อยู่ในโครงสร้างปัจจุบัน ให้หาตัวใหม่จาก ancestorDNA - if (!isCurrent && posMaster?.ancestorDNA) { - posMaster = await posMasterRepository.findOne({ - where: { - ancestorDNA: posMaster.ancestorDNA, - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - relations: { - orgRevision: true, - orgRoot: true, - orgChild1: true, - orgChild2: true, - orgChild3: true, - orgChild4: true, - }, - }); - } - - if (posMaster) { - const checkPosition = await positionRepository.find({ - where: { - posMasterId: posMaster.id, - positionIsSelected: true, - }, - }); - if (checkPosition.length > 0) { - const clearPosition = checkPosition.map((positions) => ({ - ...positions, - positionIsSelected: false, - })); - await positionRepository.save(clearPosition); - } - posMaster.current_holderId = profile.id; - posMaster.lastUpdatedAt = new Date(); - // posMaster.conditionReason = _null; - // posMaster.isCondition = false; - await posMasterRepository.save(posMaster); - - // Match position ตามลำดับ priority: - // Condition 1: match จาก positionId - // Condition 2: match 7 ฟิลด์ (positionName, posTypeId, posLevelId, positionField, positionArea, positionExecutiveField, posExecutiveId) - // Condition 3: match 3 ฟิลด์ (positionName, posTypeId, posLevelId) - // Fallback: เลือก position แรกใน posMaster - - let positionNew: Position | null = null; - - // ═══════════════════════════════════════════════════════════ - // CONDITION 1: เช็คจาก positionId ตรง - // ═══════════════════════════════════════════════════════════ - if (item.positionId) { - const positionById = await positionRepository.findOne({ - where: { - id: item.positionId, - posMasterId: posMaster.id, // ต้องอยู่ใน posMaster ที่ถูกต้อง - }, - relations: ["posExecutive"], - }); - - if (positionById) { - positionNew = positionById; - } - } - - // ═══════════════════════════════════════════════════════════ - // CONDITION 2: Match 7 ฟิลด์ (ถ้า Condition 1 ไม่ match) - // ═══════════════════════════════════════════════════════════ - if (!positionNew && item.positionNameNew && item.positionTypeNew && item.positionLevelNew) { - // สร้าง where clause แบบ dynamic - ใส่เฉพาะฟิลด์ที่มีค่า - const whereCondition: any = { - posMasterId: posMaster.id, - positionName: item.positionNameNew, - posTypeId: item.positionTypeNew, - posLevelId: item.positionLevelNew, - }; - - if (item.positionField) { - whereCondition.positionField = item.positionField; - } - if (item.posExecutiveId) { - whereCondition.posExecutiveId = item.posExecutiveId; - } - if (item.positionExecutiveField) { - whereCondition.positionExecutiveField = item.positionExecutiveField; - } - if (item.positionArea) { - whereCondition.positionArea = item.positionArea; - } - - const positionBy7Fields = await positionRepository.findOne({ - where: whereCondition, - relations: ["posExecutive"], - order: { orderNo: "ASC" }, - }); - - if (positionBy7Fields) { - positionNew = positionBy7Fields; - } - } - - // ═══════════════════════════════════════════════════════════ - // CONDITION 3: Match 3 ฟิลด์ (ถ้า Condition 2 ไม่ match) - // ═══════════════════════════════════════════════════════════ - if (!positionNew && item.positionNameNew && item.positionTypeNew && item.positionLevelNew) { - const positionBy3Fields = await positionRepository.findOne({ - where: { - posMasterId: posMaster.id, - positionName: item.positionNameNew, - posTypeId: item.positionTypeNew, - posLevelId: item.positionLevelNew, - }, - relations: ["posExecutive"], - order: { orderNo: "ASC" }, - }); - - if (positionBy3Fields) { - positionNew = positionBy3Fields; - } - } - - // // FALLBACK: เลือก position แรก (ถ้าไม่เจอทั้ง 2 condition) - // if (!positionNew) { - // const fallbackPositions = await positionRepository.find({ - // where: { - // posMasterId: posMaster.id, - // }, - // relations: ["posExecutive"], - // order: { - // orderNo: "ASC", - // }, - // take: 1, - // }); - - // if (fallbackPositions.length > 0) { - // positionNew = fallbackPositions[0]; - // } - // } - - if (positionNew) { - positionNew.positionIsSelected = true; - await positionRepository.save(positionNew, { data: req }); - } - // ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction เดียวกัน - console.log( - `[ExecuteSalaryLeaveService] Creating PosMasterHistory — posMasterId: ${posMaster.id}, profileId: ${item.profileId}`, - ); - await CreatePosMasterHistoryOfficer(posMaster.id, req, null, null, manager); - profile.posMasterNo = getPosMasterNo(posMaster); - profile.org = getOrgFullName(posMaster); - } - const newMapProfileSalary = { - profileId: profile.id, - commandId: item.commandId, - positionName: item.positionNameNew ?? null, - positionType: item.posTypeNameNew ?? null, - positionLevel: item.posLevelNameNew ?? null, - amount: item.amount ? item.amount : null, - positionSalaryAmount: item.positionSalaryAmount ? item.positionSalaryAmount : null, - amountSpecial: item.amountSpecial ? item.amountSpecial : null, - mouthSalaryAmount: item.mouthSalaryAmount ? item.mouthSalaryAmount : null, - posNo: item.posNoNew, - posNoAbb: item.posNoAbbNew, - orgRoot: item.orgRootNew, - orgChild1: item.orgChild1New, - orgChild2: item.orgChild2New, - orgChild3: item.orgChild3New, - orgChild4: item.orgChild4New, - isGovernment: item.isGovernment, - commandNo: item.commandNo, - commandYear: item.commandYear, - commandDateAffect: item.commandDateAffect, - commandDateSign: item.commandDateSign, - commandCode: item.commandCode, - commandName: item.commandName, - remark: item.remark, - }; - Object.assign(dataSalary, { ...newMapProfileSalary, ...meta }); - const history = new ProfileSalaryHistory(); - Object.assign(history, { ...dataSalary, id: undefined }); - await salaryRepo.save(dataSalary); - history.profileSalaryId = dataSalary.id; - await salaryHistoryRepo.save(history); - profile.leaveReason = _null; - profile.leaveCommandId = _null; - profile.leaveCommandNo = _null; - profile.leaveRemark = _null; - profile.leaveDate = _null; - profile.leaveType = _null; - profile.position = item.positionNameNew ?? _null; - profile.posTypeId = item.positionTypeNew ?? _null; - profile.posLevelId = item.positionLevelNew ?? _null; - } - let userKeycloakId; - const checkUser = await getUserByUsername(profile.citizenId); - //ถ้ายังไม่มี user keycloak ให้สร้างใหม่ - if (checkUser.length == 0) { - let password = profile.citizenId; - if (profile.birthDate != null) { - const _date = new Date(profile.birthDate.toDateString()) - .getDate() - .toString() - .padStart(2, "0"); - const _month = (new Date(profile.birthDate.toDateString()).getMonth() + 1) - .toString() - .padStart(2, "0"); - const _year = new Date(profile.birthDate.toDateString()).getFullYear() + 543; - password = `${_date}${_month}${_year}`; - } - // กรอง "." ออกจาก firstName ก่อนส่งไป keycloak - const sanitizedFirstName = profile.firstName?.replace(/\./g, "") ?? ""; - // Keycloak ทำภายใน transaction — ไม่สามารถ rollback ได้ (ดู docstring ของ class) - userKeycloakId = await createUser(profile.citizenId, password, { - firstName: sanitizedFirstName, - lastName: profile.lastName, - }); - const list = await getRoles(); - let result = false; - if (Array.isArray(list) && userKeycloakId) { - result = await addUserRoles( - userKeycloakId, - list - .filter((v) => v.name === "USER") - .map((x) => ({ - id: x.id, - name: x.name, - })), - ); - } - profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : []; - profile.keycloak = - userKeycloakId && typeof userKeycloakId === "string" ? userKeycloakId : ""; - } - //ถ้ามีอยู่แล้วให้ใช้อันเดิม - else { - const rolesData = await getRoleMappings(checkUser[0].id); - if (rolesData) { - const _roleKeycloak = await roleKeycloakRepo.find({ - where: { name: In(rolesData.map((x: any) => x.name)) }, - }); - profile.roleKeycloaks = - _roleKeycloak && _roleKeycloak.length > 0 ? _roleKeycloak : []; - } - profile.keycloak = checkUser[0].id; - } - profile.amount = item.amount ?? _null; - profile.amountSpecial = item.amountSpecial ?? _null; - profile.isActive = true; - profile.isDelete = false; - } - await profileRepository.save(profile); - - // if (profile.id) { - // await this.keycloakAttributeService.clearOrgDnaAttributes( - // [profile.id], - // "PROFILE", - // ); - // } - - // update user attribute in keycloak - await updateUserAttributes(profile.keycloak ?? "", { - profileId: [profile.id], - prefix: [profile.prefix || ""], - }); - - // Task #2190 - if (code && ["C-PM-17", "C-PM-18", "C-PM-48"].includes(code)) { - let organizeName = ""; - if (orgRootRef) { - const names = [ - orgChild4Ref?.orgChild4Name, - orgChild3Ref?.orgChild3Name, - orgChild2Ref?.orgChild2Name, - orgChild1Ref?.orgChild1Name, - orgRootRef?.orgRootName, - ].filter(Boolean); - organizeName = names.join(" "); - } - } - - console.log( - `[ExecuteSalaryLeaveService] Completed processOne — profileId: ${item.profileId}`, - ); - } -} diff --git a/src/services/ExecuteSalaryProbationService.ts b/src/services/ExecuteSalaryProbationService.ts deleted file mode 100644 index 41dfb7b6..00000000 --- a/src/services/ExecuteSalaryProbationService.ts +++ /dev/null @@ -1,539 +0,0 @@ -import { Double, EntityManager, In, Repository } from "typeorm"; -import { AppDataSource } from "../database/data-source"; -import HttpError from "../interfaces/http-error"; -import HttpStatusCode from "../interfaces/http-status"; -import { Profile } from "../entities/Profile"; -import { ProfileSalary } from "../entities/ProfileSalary"; -import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory"; -import { Command } from "../entities/Command"; -import { OrgRoot } from "../entities/OrgRoot"; -import { OrgRevision } from "../entities/OrgRevision"; -import { checkCommandType, removeProfileInOrganize } from "../interfaces/utils"; -import { CreatePosMasterHistoryOfficer } from "./PositionService"; -import { deleteUser } from "../keycloak"; - -/** - * Input: ข้อมูล 1 คนที่ probation return กลับมา (หลัง Linear Flow refactor) - * - C-PM-11 : excexute/salary-probation (ผ่านทดลองงาน) - * - C-PM-12 : excexute/salary-probation-leave (ออกเพราะผลทดลองฯ ต่ำกว่ามาตรฐาน) - * - * shape เดียวกับ body.data ของ endpoint /org/command/excexute/salary-probation(-leave) เดิม - */ -export interface ProbationSalaryItem { - profileId: string; - commandId?: string | null; - amount?: Double | null; - amountSpecial?: Double | null; - positionSalaryAmount?: Double | null; - mouthSalaryAmount?: Double | null; - commandNo: string | null; - commandYear: number | null; - commandDateAffect?: Date | string | null; - commandDateSign?: Date | string | null; - positionName?: string | null; - commandCode?: string | null; - commandName?: string | null; - remark: string | null; - isGovernment?: boolean | null; // C-PM-12 เท่านั้น -} - -/** - * Context สำหรับ audit/log (เหมือน ExecuteSalaryService) - */ -export interface ProbationExecutionContext { - user: { sub: string; name: string }; - req?: any; -} - -/** - * Service สำหรับคำสั่งทดลองปฏิบัติหน้าที่ราชการ (probation) — C-PM-11, C-PM-12 - * - * เดิมเป็น circular callback: org AMQ → probation → PostData("/org/command/excexute/salary-probation(-leave)") - * หลัง Linear Flow: org AMQ → probation (return salary data) → เรียก service นี้โดยตรง (no callback) - * - * - C-PM-11 : executeProbationPass — สร้าง ProfileSalary + history, isProbation=false - * - C-PM-12 : executeProbationLeave — leave logic + deleteUser(Keycloak) + สร้าง ProfileSalary + history - * - * - endpoint /org/command/excexute/salary-probation(-leave) เรียกผ่าน service นี้ (thin wrapper) - * - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow) - * - * Behavior ทั้งหมด preserve จาก CommandController - * (newSalaryAndUpdateLeaveDisciplinefgh / ExecuteCommand12Async ต้นฉบับ) - * - * Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential) - * ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด) - * - * ⚠️ Keycloak: deleteUser (C-PM-12) ทำภายใน transaction เพื่อ preserve behavior เดิม - * (consistent กับ ExecuteSalaryService C-PM-13/15/16) — Keycloak ไม่สามารถ rollback ได้ - * ถ้า DB rollback หลังจาก deleteUser สำเร็จ → user จะถูกลบใน Keycloak ไปแล้ว - */ -export class ExecuteSalaryProbationService { - private commandRepository = AppDataSource.getRepository(Command); - private profileRepository = AppDataSource.getRepository(Profile); - private orgRootRepository = AppDataSource.getRepository(OrgRoot); - - // ───────────────────────────────────────────────────────────── - // แก้ปัญหา _posNumCodeSit resolution ที่ซ้ำกันในทุก endpoint - // (เดิมอยู่ใน controller — ย้ายมานี่ ทำครั้งเดียวก่อนเข้า transaction) - // ───────────────────────────────────────────────────────────── - private async resolvePosNumCodeSit( - commandId: string | null | undefined, - ): Promise<{ posNumCodeSit: string; posNumCodeSitAbb: string }> { - let posNumCodeSit = ""; - let posNumCodeSitAbb = ""; - const command = commandId - ? await this.commandRepository.findOne({ where: { id: commandId } }) - : null; - if (command) { - if (command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { - const orgRootDeputy = await this.orgRootRepository.findOne({ - where: { - isDeputy: true, - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - relations: ["orgRevision"], - }); - posNumCodeSit = orgRootDeputy ? orgRootDeputy.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; - posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy.orgRootShortName : "สนป."; - } else if (command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { - posNumCodeSit = "กรุงเทพมหานคร"; - posNumCodeSitAbb = "กทม."; - } else { - const profileAdmin = await this.profileRepository.findOne({ - where: { - keycloak: command?.createdUserId.toString(), - current_holders: { - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - }, - relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], - }); - posNumCodeSit = - profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? - ""; - posNumCodeSitAbb = - profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot - .orgRootShortName ?? ""; - } - } - return { posNumCodeSit, posNumCodeSitAbb }; - } - - // normalize date (AMQ path ส่ง string มา → แปลงเป็น Date / null ถ้า invalid) - private toDate(v: any): Date | null { - if (v == null || v === "") return null; - if (v instanceof Date) return isNaN(v.getTime()) ? null : v; - const d = new Date(v); - return isNaN(d.getTime()) ? null : d; - } - - // ═══════════════════════════════════════════════════════════════ - // C-PM-11 : ผ่านทดลองปฏิบัติหน้าที่ราชการ - // สร้าง ProfileSalary + history แล้ว set isProbation=false - // ═══════════════════════════════════════════════════════════════ - async executeProbationPass( - data: ProbationSalaryItem[], - ctx: ProbationExecutionContext, - ): Promise { - const commandId = data?.find((x) => x.commandId)?.commandId ?? ""; - console.log( - `[ExecuteSalaryProbationService] executeProbationPass (C-PM-11) — commandId: ${commandId}, count: ${data?.length ?? 0}`, - ); - - // ───────────────────────────────────────────────────────────── - // Normalize date fields (ผ่าน AMQ handler จะได้ string → ต้องแปลงเป็น Date) - // ───────────────────────────────────────────────────────────── - for (const item of data ?? []) { - const it = item as any; - it.commandDateAffect = this.toDate(it.commandDateAffect); - it.commandDateSign = this.toDate(it.commandDateSign); - } - - const { posNumCodeSit: _posNumCodeSit, posNumCodeSitAbb: _posNumCodeSitAbb } = - await this.resolvePosNumCodeSit(commandId); - - const profileIds = (data ?? []).map((x) => x.profileId).filter(Boolean); - - await AppDataSource.transaction(async (manager) => { - const profileRepository = manager.getRepository(Profile); - const salaryRepo = manager.getRepository(ProfileSalary); - const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory); - const orgRevisionRepo = manager.getRepository(OrgRevision); - - for (const item of data ?? []) { - try { - await this.processOneProbationPass( - item, - ctx, - _posNumCodeSit, - _posNumCodeSitAbb, - salaryRepo, - salaryHistoryRepo, - profileRepository, - orgRevisionRepo, - ); - } catch (err) { - const reason = - err instanceof HttpError - ? err.message - : err instanceof Error - ? err.message - : "unexpected error"; - console.error( - `[ExecuteSalaryProbationService] Failed C-PM-11, commandId=${commandId}, profileId=${item.profileId}: ${reason}`, - err, - ); - throw err; // → rollback ทั้ง transaction + propagate เป็น batch failure - } - } - - // C-PM-11: ผ่านทดลองงาน → isProbation = false (bulk update ใน transaction เดียวกัน) - if (profileIds.length > 0) { - await profileRepository.update({ id: In(profileIds) }, { isProbation: false }); - } - }); - - console.log(`[ExecuteSalaryProbationService] Completed C-PM-11 — ${data?.length ?? 0} items`); - } - - private async processOneProbationPass( - item: ProbationSalaryItem, - ctx: ProbationExecutionContext, - _posNumCodeSit: string, - _posNumCodeSitAbb: string, - salaryRepo: Repository, - salaryHistoryRepo: Repository, - profileRepository: Repository, - orgRevisionRepo: Repository, - ): Promise { - // current orgRevision (อ่านครั้งเดียวต่อคน — preserve query pattern ของ endpoint เดิม) - const orgRevision = await orgRevisionRepo.findOne({ - where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, - }); - - const profile: any = await profileRepository.findOne({ - relations: [ - "posType", - "posLevel", - "current_holders", - "current_holders.orgRoot", - "current_holders.orgChild1", - "current_holders.orgChild2", - "current_holders.orgChild3", - "current_holders.orgChild4", - ], - where: { id: item.profileId }, - }); - if (!profile) { - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); - } - - const lastSalary = await salaryRepo.findOne({ - where: { profileId: item.profileId }, - select: ["order"], - order: { order: "DESC" }, - }); - const nextOrder = lastSalary ? lastSalary.order + 1 : 1; - - const orgRevisionRef = - profile?.current_holders?.find((x: any) => x.orgRevisionId == orgRevision?.id) ?? null; - const shortName = - orgRevisionRef?.orgChild4?.orgChild4ShortName ?? - orgRevisionRef?.orgChild3?.orgChild3ShortName ?? - orgRevisionRef?.orgChild2?.orgChild2ShortName ?? - orgRevisionRef?.orgChild1?.orgChild1ShortName ?? - orgRevisionRef?.orgRoot?.orgRootShortName ?? - null; - const posNo = orgRevisionRef?.posMasterNo?.toString() ?? null; - // NOTE: endpoint เดิมไม่ได้ load relation "current_holders.positions" → position เป็น null (preserve) - const position = - profile.current_holders - ?.filter((x: any) => x.orgRevisionId == orgRevision?.id)[0] - ?.positions?.filter((pos: any) => pos.positionIsSelected === true)[0] ?? null; - - const dataSalary = new ProfileSalary(); - dataSalary.posNumCodeSit = _posNumCodeSit; - dataSalary.posNumCodeSitAbb = _posNumCodeSitAbb; - const meta = { - profileId: item.profileId, - commandId: item.commandId, - positionName: profile.position, - positionType: profile?.posType?.posTypeName ?? null, - positionLevel: profile?.posLevel?.posLevelName ?? null, - positionExecutive: position?.posExecutive?.posExecutiveName ?? null, - amount: item.amount ? item.amount : null, - amountSpecial: item.amountSpecial ? item.amountSpecial : null, - positionSalaryAmount: item.positionSalaryAmount ? item.positionSalaryAmount : null, - mouthSalaryAmount: item.mouthSalaryAmount ? item.mouthSalaryAmount : null, - order: nextOrder, - orgRoot: orgRevisionRef?.orgRoot?.orgRootName ?? null, - orgChild1: orgRevisionRef?.orgChild1?.orgChild1Name ?? null, - orgChild2: orgRevisionRef?.orgChild2?.orgChild2Name ?? null, - orgChild3: orgRevisionRef?.orgChild3?.orgChild3Name ?? null, - orgChild4: orgRevisionRef?.orgChild4?.orgChild4Name ?? null, - createdUserId: ctx.user.sub, - createdFullName: ctx.user.name, - lastUpdateUserId: ctx.user.sub, - lastUpdateFullName: ctx.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - commandNo: item.commandNo, - commandYear: item.commandYear, - posNo: posNo, - posNoAbb: shortName, - commandDateAffect: item.commandDateAffect, - commandDateSign: item.commandDateSign, - commandCode: item.commandCode, - commandName: item.commandName, - remark: item.remark, - }; - Object.assign(dataSalary, meta); - const history = new ProfileSalaryHistory(); - Object.assign(history, { ...dataSalary, id: undefined }); - - await salaryRepo.save(dataSalary); - history.profileSalaryId = dataSalary.id; - await salaryHistoryRepo.save(history); - } - - // ═══════════════════════════════════════════════════════════════ - // C-PM-12 : ออกจากราชการเพราะผลการทดลองปฏิบัติหน้าที่ราชการต่ำกว่ามาตรฐาน - // leave logic (removeProfileInOrganize + deleteUser Keycloak) + สร้าง ProfileSalary + history - // ═══════════════════════════════════════════════════════════════ - async executeProbationLeave( - data: ProbationSalaryItem[], - ctx: ProbationExecutionContext, - ): Promise { - const commandId = data?.find((x) => x.commandId)?.commandId ?? ""; - console.log( - `[ExecuteSalaryProbationService] executeProbationLeave (C-PM-12) — commandId: ${commandId}, count: ${data?.length ?? 0}`, - ); - - for (const item of data ?? []) { - const it = item as any; - it.commandDateAffect = this.toDate(it.commandDateAffect); - it.commandDateSign = this.toDate(it.commandDateSign); - } - - const { posNumCodeSit: _posNumCodeSit, posNumCodeSitAbb: _posNumCodeSitAbb } = - await this.resolvePosNumCodeSit(commandId); - - await AppDataSource.transaction(async (manager) => { - const profileRepository = manager.getRepository(Profile); - const salaryRepo = manager.getRepository(ProfileSalary); - const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory); - const orgRevisionRepo = manager.getRepository(OrgRevision); - - for (const item of data ?? []) { - try { - await this.processOneProbationLeave( - item, - ctx, - manager, - _posNumCodeSit, - _posNumCodeSitAbb, - salaryRepo, - salaryHistoryRepo, - profileRepository, - orgRevisionRepo, - ); - } catch (err) { - const reason = - err instanceof HttpError - ? err.message - : err instanceof Error - ? err.message - : "unexpected error"; - console.error( - `[ExecuteSalaryProbationService] Failed C-PM-12, commandId=${commandId}, profileId=${item.profileId}: ${reason}`, - err, - ); - throw err; // → rollback ทั้ง transaction + propagate เป็น batch failure - } - } - }); - - console.log(`[ExecuteSalaryProbationService] Completed C-PM-12 — ${data?.length ?? 0} items`); - } - - private async processOneProbationLeave( - item: ProbationSalaryItem, - ctx: ProbationExecutionContext, - manager: EntityManager, - _posNumCodeSit: string, - _posNumCodeSitAbb: string, - salaryRepo: Repository, - salaryHistoryRepo: Repository, - profileRepository: Repository, - orgRevisionRepo: Repository, - ): Promise { - const req = ctx.req; - - const profile: any = await profileRepository.findOne({ - relations: [ - "posType", - "posLevel", - "current_holders", - "current_holders.orgRoot", - "current_holders.orgChild1", - "current_holders.orgChild2", - "current_holders.orgChild3", - "current_holders.orgChild4", - "current_holders.positions", - "current_holders.positions.posExecutive", - ], - where: { id: item.profileId }, - }); - if (!profile) { - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลโปรไฟล์"); - } - - const lastSalary = await salaryRepo.findOne({ - where: { profileId: item.profileId }, - select: ["order"], - order: { order: "DESC" }, - }); - const nextOrder = lastSalary ? lastSalary.order + 1 : 1; - let _commandYear = item.commandYear; - if (item.commandYear) { - _commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543; - } - - // _profile (load แยกสำหรับ mutation เกี่ยวกับ Keycloak/leave — preserve pattern เดิม) - const _profile: any = await profileRepository.findOne({ - where: { id: item.profileId }, - relations: ["roleKeycloaks"], - }); - if (!_profile) { - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลโปรไฟล์"); - } - - let dateLeave_: any = item.commandDateAffect; - _profile.isLeave = true; - _profile.leaveReason = - "คำสั่งให้ข้าราชการออกจากราชการเพราะผลการทดลองปฏิบัติหน้าที่ราชการต่ำกว่ามาตรฐานที่กำหนด"; - _profile.dateLeave = dateLeave_; - _profile.lastUpdateUserId = ctx.user.sub; - _profile.lastUpdateFullName = ctx.user.name; - _profile.lastUpdatedAt = new Date(); - - const orgRevision = await orgRevisionRepo.findOne({ - where: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }); - const orgRevisionRef = - profile?.current_holders?.find((x: any) => x.orgRevisionId == orgRevision?.id) ?? null; - const orgRootRef = orgRevisionRef?.orgRoot ?? null; - const orgChild1Ref = orgRevisionRef?.orgChild1 ?? null; - const orgChild2Ref = orgRevisionRef?.orgChild2 ?? null; - const orgChild3Ref = orgRevisionRef?.orgChild3 ?? null; - const orgChild4Ref = orgRevisionRef?.orgChild4 ?? null; - - const matchHolder = profile.current_holders?.find((x: any) => x.orgRevisionId == orgRevision?.id); - const shortName = - !profile.current_holders || profile.current_holders.length == 0 - ? null - : matchHolder != null && matchHolder?.orgChild4 != null - ? `${matchHolder.orgChild4.orgChild4ShortName}` - : matchHolder != null && matchHolder?.orgChild3 != null - ? `${matchHolder.orgChild3.orgChild3ShortName}` - : matchHolder != null && matchHolder?.orgChild2 != null - ? `${matchHolder.orgChild2.orgChild2ShortName}` - : matchHolder != null && matchHolder?.orgChild1 != null - ? `${matchHolder.orgChild1.orgChild1ShortName}` - : matchHolder != null && matchHolder?.orgRoot != null - ? `${matchHolder.orgRoot.orgRootShortName}` - : null; - const posNo = `${matchHolder?.posMasterNo}`; - const position = - matchHolder?.positions?.filter((pos: any) => pos.positionIsSelected === true)[0] ?? null; - - const profileSalary: ProfileSalary = Object.assign(new ProfileSalary(), { - profileId: item.profileId, - commandId: item.commandId, - positionName: profile.position, - positionType: profile?.posType?.posTypeName ?? null, - positionLevel: profile?.posLevel?.posLevelName ?? null, - positionExecutive: position?.posExecutive?.posExecutiveName ?? null, - amount: item.amount ? item.amount : null, - amountSpecial: item.amountSpecial ? item.amountSpecial : null, - positionSalaryAmount: item.positionSalaryAmount ? item.positionSalaryAmount : null, - mouthSalaryAmount: item.mouthSalaryAmount ? item.mouthSalaryAmount : null, - order: nextOrder, - orgRoot: orgRootRef?.orgRootName ?? null, - orgChild1: orgChild1Ref?.orgChild1Name ?? null, - orgChild2: orgChild2Ref?.orgChild2Name ?? null, - orgChild3: orgChild3Ref?.orgChild3Name ?? null, - orgChild4: orgChild4Ref?.orgChild4Name ?? null, - createdUserId: ctx.user.sub, - createdFullName: ctx.user.name, - lastUpdateUserId: ctx.user.sub, - lastUpdateFullName: ctx.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - dateGovernment: item.commandDateAffect ?? new Date(), - isGovernment: item.isGovernment, - commandNo: item.commandNo, - commandYear: item.commandYear, - posNo: posNo, - posNoAbb: shortName, - commandDateAffect: item.commandDateAffect, - commandDateSign: item.commandDateSign, - commandCode: item.commandCode, - commandName: item.commandName, - remark: item.remark, - posNumCodeSit: _posNumCodeSit, - posNumCodeSitAbb: _posNumCodeSitAbb, - }); - - if (orgRevisionRef) { - await CreatePosMasterHistoryOfficer(orgRevisionRef.id, req, "DELETE", null, manager); - } - await removeProfileInOrganize(profile.id, "OFFICER", manager); - - const clearProfile = await checkCommandType(String(item.commandId)); - const _null: any = null; - if (clearProfile.status) { - // Keycloak deleteUser ทำภายใน transaction (preserve behavior เดิม — Keycloak ไม่ rollback ได้) - if (_profile.keycloak != null && _profile.keycloak != "" && _profile.isDelete === false) { - const delUserKeycloak = await deleteUser(_profile.keycloak); - if (delUserKeycloak) { - // Task #228 - _profile.roleKeycloaks = []; - _profile.isActive = false; - _profile.isDelete = true; - } - } - _profile.leaveCommandId = item.commandId ?? _null; - _profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`; - _profile.leaveRemark = clearProfile.leaveRemark ?? _null; - _profile.leaveDate = item.commandDateAffect ?? _null; - _profile.leaveType = clearProfile.LeaveType ?? _null; - //ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516) - // _profile.position = _null; - // _profile.posTypeId = _null; - // _profile.posLevelId = _null; - } - await Promise.all([ - profileRepository.save(_profile), - salaryRepo.save(profileSalary), - ]); - - const history = new ProfileSalaryHistory(); - Object.assign(history, { ...profileSalary, id: undefined }); - history.profileSalaryId = profileSalary.id; - await salaryHistoryRepo.save(history); - - console.log( - `[ExecuteSalaryProbationService] processOneProbationLeave done — profileId: ${item.profileId}`, - ); - } -} diff --git a/src/services/ExecuteSalaryReportService.ts b/src/services/ExecuteSalaryReportService.ts deleted file mode 100644 index 7c72c367..00000000 --- a/src/services/ExecuteSalaryReportService.ts +++ /dev/null @@ -1,318 +0,0 @@ -import { EntityManager, Repository } from "typeorm"; -import { AppDataSource } from "../database/data-source"; -import HttpError from "../interfaces/http-error"; -import HttpStatusCode from "../interfaces/http-status"; -import permission from "../interfaces/permission"; -import { setLogDataDiff } from "../interfaces/utils"; -import { Profile } from "../entities/Profile"; -import { ProfileEmployee } from "../entities/ProfileEmployee"; -import { - CreateProfileSalary, - CreateProfileSalaryEmployee, - ProfileSalary, -} from "../entities/ProfileSalary"; -import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory"; -import { Command } from "../entities/Command"; -import { OrgRoot } from "../entities/OrgRoot"; - -/** - * Context สำหรับ audit/log (เหมือน ExecuteSalaryService) - */ -export interface SalaryReportExecutionContext { - user: { sub: string; name: string }; - req?: any; -} - -/** - * Service สำหรับคำสั่งเงินเดือนที่ยิงมาจาก salary service - * - * - C-PM-33, C-PM-34, C-PM-35, C-PM-45 : officer → /org/profile/salary/update - * - C-PM-36, C-PM-37, C-PM-46 : employee → /org/profile-employee/salary/update - * - * เดิมเป็น circular callback: org AMQ → salary service → PostData("/org/profile/(employee/)salary/update") - * หลัง Linear Flow: org AMQ → salary service (return salary data) → เรียก service นี้โดยตรง (no callback) - * - * - executeOfficerSalaryUpdate : สร้าง ProfileSalary + history + อัปเดต Profile (amount*) - * - executeEmployeeSalaryUpdate : สร้าง ProfileSalary + history + อัปเดต ProfileEmployee (amount* + salaryLevel/group) - * - * Behavior ทั้งหมด preserve จาก - * ProfileSalaryController.updateSalary / ProfileSalaryEmployeeController.updateSalary ต้นฉบับ - * (รวม permission check + setLogDataDiff + save({data: req})) - * - * Batch semantics: all-or-nothing — transaction เดียวครอบทั้ง batch - * ถ้าคนใด throw (validation/permission) จะ rollback ทั้ง batch และ propagate error - * - * ⚠️ หมายเหตุ permission check: ทำ per-item ภายใน transaction (preserve behavior เดิมที่เช็คทุกคน) - * ทำ HTTP loopback ไป /org/permission/user/... ด้วย token ใน ctx.req — หาก batch ใหญ่อาจช้า - * (เหมือนเดิม เพราะ salary เดิมก็ยิงเข้า endpoint ทีละคนพร้อม permission check) - */ -export class ExecuteSalaryReportService { - private commandRepository = AppDataSource.getRepository(Command); - private profileRepository = AppDataSource.getRepository(Profile); - private profileEmployeeRepository = AppDataSource.getRepository(ProfileEmployee); - private orgRootRepository = AppDataSource.getRepository(OrgRoot); - - // ───────────────────────────────────────────────────────────── - // resolve _posNumCodeSit/Abb จาก command (ทำครั้งเดียวก่อนเข้า transaction) - // admin-lookup ใช้ Profile (officer: profileRepo / employee: profileGovementRepo — ทั้งคู่คือ Profile) - // ───────────────────────────────────────────────────────────── - private async resolvePosNumCodeSit( - commandId: string | null | undefined, - ): Promise<{ posNumCodeSit: string; posNumCodeSitAbb: string }> { - let posNumCodeSit = ""; - let posNumCodeSitAbb = ""; - const command = commandId - ? await this.commandRepository.findOne({ where: { id: commandId } }) - : null; - if (command) { - if (command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { - const orgRootDeputy = await this.orgRootRepository.findOne({ - where: { - isDeputy: true, - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - relations: ["orgRevision"], - }); - posNumCodeSit = orgRootDeputy ? orgRootDeputy.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; - posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy.orgRootShortName : "สนป."; - } else if (command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { - posNumCodeSit = "กรุงเทพมหานคร"; - posNumCodeSitAbb = "กทม."; - } else { - const profileAdmin = await this.profileRepository.findOne({ - where: { - keycloak: command?.createdUserId.toString(), - current_holders: { - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - }, - relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], - }); - posNumCodeSit = - profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? - ""; - posNumCodeSitAbb = - profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot - .orgRootShortName ?? ""; - } - } - return { posNumCodeSit, posNumCodeSitAbb }; - } - - // ═══════════════════════════════════════════════════════════════ - // C-PM-33/34/35/45 : officer salary update - // ═══════════════════════════════════════════════════════════════ - async executeOfficerSalaryUpdate( - data: CreateProfileSalary[], - ctx: SalaryReportExecutionContext, - ): Promise { - const commandId = data?.find((x) => x.commandId)?.commandId ?? ""; - console.log( - `[ExecuteSalaryReportService] executeOfficerSalaryUpdate — commandId: ${commandId}, count: ${data?.length ?? 0}`, - ); - - const { posNumCodeSit: _posNumCodeSit, posNumCodeSitAbb: _posNumCodeSitAbb } = - await this.resolvePosNumCodeSit(commandId); - - await AppDataSource.transaction(async (manager) => { - const profileRepository = manager.getRepository(Profile); - const salaryRepo = manager.getRepository(ProfileSalary); - const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory); - - for (const item of data ?? []) { - try { - await this.processOneOfficer( - item, - ctx, - _posNumCodeSit, - _posNumCodeSitAbb, - profileRepository, - salaryRepo, - salaryHistoryRepo, - ); - } catch (err) { - const reason = - err instanceof HttpError - ? err.message - : err instanceof Error - ? err.message - : "unexpected error"; - console.error( - `[ExecuteSalaryReportService] Failed officer, commandId=${commandId}, profileId=${item.profileId}: ${reason}`, - err, - ); - throw err; // → rollback ทั้ง transaction + propagate เป็น batch failure - } - } - }); - - console.log(`[ExecuteSalaryReportService] Completed officer — ${data?.length ?? 0} items`); - } - - private async processOneOfficer( - item: CreateProfileSalary, - ctx: SalaryReportExecutionContext, - _posNumCodeSit: string, - _posNumCodeSitAbb: string, - profileRepository: Repository, - salaryRepo: Repository, - salaryHistoryRepo: Repository, - ): Promise { - const req = ctx.req; - - if (!item.profileId) { - throw new HttpError(HttpStatusCode.BAD_REQUEST, "กรุณากรอก profileId"); - } - const profile = await profileRepository.findOneBy({ id: item.profileId }); - if (!profile) { - throw new HttpError(HttpStatusCode.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); - } - await new permission().PermissionOrgUserUpdate(req, "SYS_REGISTRY_OFFICER", profile.id); - - const dest_item = await salaryRepo.findOne({ - where: { profileId: item.profileId }, - order: { order: "DESC" }, - }); - const before = null; - const data = new ProfileSalary(); - data.posNumCodeSit = _posNumCodeSit; - data.posNumCodeSitAbb = _posNumCodeSitAbb; - const meta = { - order: dest_item == null ? 1 : dest_item.order + 1, - createdUserId: ctx.user.sub, - createdFullName: ctx.user.name, - lastUpdateUserId: ctx.user.sub, - lastUpdateFullName: ctx.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - }; - - Object.assign(data, { ...item, ...meta }); - const history = new ProfileSalaryHistory(); - Object.assign(history, { ...data, id: undefined }); - await salaryRepo.save(data, { data: req }); - setLogDataDiff(req, { before, after: data }); - history.profileSalaryId = data.id; - await salaryHistoryRepo.save(history, { data: req }); - - const _null: any = null; - profile.amount = item.amount ?? _null; - profile.amountSpecial = item.amountSpecial ?? _null; - profile.positionSalaryAmount = item.positionSalaryAmount ?? _null; - profile.mouthSalaryAmount = item.mouthSalaryAmount ?? _null; - await profileRepository.save(profile); - } - - // ═══════════════════════════════════════════════════════════════ - // C-PM-36/37/46 : employee salary update - // ═══════════════════════════════════════════════════════════════ - async executeEmployeeSalaryUpdate( - data: CreateProfileSalaryEmployee[], - ctx: SalaryReportExecutionContext, - ): Promise { - const commandId = data?.find((x) => x.commandId)?.commandId ?? ""; - console.log( - `[ExecuteSalaryReportService] executeEmployeeSalaryUpdate — commandId: ${commandId}, count: ${data?.length ?? 0}`, - ); - - const { posNumCodeSit: _posNumCodeSit, posNumCodeSitAbb: _posNumCodeSitAbb } = - await this.resolvePosNumCodeSit(commandId); - - await AppDataSource.transaction(async (manager) => { - const profileEmployeeRepository = manager.getRepository(ProfileEmployee); - const salaryRepo = manager.getRepository(ProfileSalary); - const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory); - - for (const item of data ?? []) { - try { - await this.processOneEmployee( - item, - ctx, - _posNumCodeSit, - _posNumCodeSitAbb, - profileEmployeeRepository, - salaryRepo, - salaryHistoryRepo, - ); - } catch (err) { - const reason = - err instanceof HttpError - ? err.message - : err instanceof Error - ? err.message - : "unexpected error"; - console.error( - `[ExecuteSalaryReportService] Failed employee, commandId=${commandId}, profileEmployeeId=${item.profileEmployeeId}: ${reason}`, - err, - ); - throw err; // → rollback ทั้ง transaction + propagate เป็น batch failure - } - } - }); - - console.log(`[ExecuteSalaryReportService] Completed employee — ${data?.length ?? 0} items`); - } - - private async processOneEmployee( - item: CreateProfileSalaryEmployee, - ctx: SalaryReportExecutionContext, - _posNumCodeSit: string, - _posNumCodeSitAbb: string, - profileEmployeeRepository: Repository, - salaryRepo: Repository, - salaryHistoryRepo: Repository, - ): Promise { - const req = ctx.req; - - if (!item.profileEmployeeId) { - throw new HttpError(HttpStatusCode.BAD_REQUEST, "กรุณากรอก profileEmployeeId"); - } - const profile = await profileEmployeeRepository.findOneBy({ id: item.profileEmployeeId }); - if (!profile) { - throw new HttpError(HttpStatusCode.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); - } - await new permission().PermissionOrgUserUpdate(req, "SYS_REGISTRY_EMP", profile.id); - - const dest_item = await salaryRepo.findOne({ - where: { profileEmployeeId: item.profileEmployeeId }, - order: { order: "DESC" }, - }); - const before = null; - const data = new ProfileSalary(); - data.posNumCodeSit = _posNumCodeSit; - data.posNumCodeSitAbb = _posNumCodeSitAbb; - const meta = { - order: dest_item == null ? 1 : dest_item.order + 1, - createdUserId: ctx.user.sub, - createdFullName: ctx.user.name, - lastUpdateUserId: ctx.user.sub, - lastUpdateFullName: ctx.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - }; - - Object.assign(data, { ...item, ...meta }); - const history = new ProfileSalaryHistory(); - Object.assign(history, { ...data, id: undefined }); - - await salaryRepo.save(data, { data: req }); - setLogDataDiff(req, { before, after: data }); - history.profileSalaryId = data.id; - await salaryHistoryRepo.save(history, { data: req }); - - const _null: any = null; - profile.amount = item.amount ?? _null; - profile.amountSpecial = item.amountSpecial ?? _null; - profile.positionSalaryAmount = item.positionSalaryAmount ?? _null; - profile.mouthSalaryAmount = item.mouthSalaryAmount ?? _null; - profile.salaryLevel = item.salaryLevel ?? _null; - profile.group = item.group ?? _null; - await profileEmployeeRepository.save(profile); - } -} diff --git a/src/services/ExecuteSalaryService.ts b/src/services/ExecuteSalaryService.ts deleted file mode 100644 index 7bc07823..00000000 --- a/src/services/ExecuteSalaryService.ts +++ /dev/null @@ -1,391 +0,0 @@ -import { Double, EntityManager } from "typeorm"; -import { AppDataSource } from "../database/data-source"; -import HttpError from "../interfaces/http-error"; -import HttpStatusCode from "../interfaces/http-status"; -import { Profile } from "../entities/Profile"; -import { ProfileSalary } from "../entities/ProfileSalary"; -import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory"; -import { ProfileAssistance } from "../entities/ProfileAssistance"; -import { ProfileAssistanceHistory } from "../entities/ProfileAssistanceHistory"; -import { OrgRoot } from "../entities/OrgRoot"; -import { PosMaster } from "../entities/PosMaster"; -import { Command } from "../entities/Command"; -import { - checkCommandType, - removePostMasterAct, - removeProfileInOrganize, - setLogDataDiff, -} from "../interfaces/utils"; -import { CreatePosMasterHistoryOfficer } from "./PositionService"; -import { deleteUser } from "../keycloak"; - -/** - * Input: ข้อมูล 1 คนสำหรับ endpoint excexute/salary - * (C-PM-13 โอน, C-PM-15 ช่วยราชการ, C-PM-16 เกษียณ/ปลดเกษียณ) - */ -export interface SalaryItem { - profileId: string; - amount?: Double | null; - amountSpecial?: Double | null; - positionSalaryAmount?: Double | null; - mouthSalaryAmount?: Double | null; - positionExecutive: string | null; - positionExecutiveField?: string | null; - positionArea?: string | null; - positionType: string | null; - positionLevel: string | null; - commandId?: string | null; - leaveReason?: string | null; - dateLeave?: Date | string | null; - isLeave?: boolean; - orgRoot?: string | null; - orgChild1?: string | null; - orgChild2?: string | null; - orgChild3?: string | null; - orgChild4?: string | null; - officerOrg?: string | null; - dateStart?: Date | string | null; - dateEnd?: Date | string | null; - commandNo: string | null; - commandYear: number | null; - posNo: string | null; - posNoAbb: string | null; - commandDateAffect?: Date | string | null; - commandDateSign?: Date | string | null; - positionName: string | null; - commandCode?: string | null; - commandName?: string | null; - remark: string | null; - refId?: string | null; -} - -/** - * Context สำหรับ audit/log (เหมือน ExecuteOfficerProfileService) - */ -export interface SalaryExecutionContext { - user: { sub: string; name: string }; - req?: any; -} - -/** - * Service สำหรับสร้าง ProfileSalary ของข้าราชการ + handle leave/ออกจากราชการ/ช่วยราชการ - * - * ใช้กับ commandType: C-PM-13, 15, 16 - * - * - endpoint /org/command/excexute/salary เรียกผ่าน service นี้ (thin wrapper) - * - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow) - * - * Behavior ทั้งหมด preserve จาก CommandController.newSalaryAndUpdate ต้นฉบับ - * - * Batch semantics: all-or-nothing — ประมวลผลทุกคนภายใต้ transaction เดียว (sequential) - * ถ้าคนใด throw จะ rollback ทั้ง batch และ propagate error ออกไป (ล้มเหลวทั้งหมด) - * ถ้าทุกคนสำเร็จจะ return result รายงาน success count - * - * ⚠️ หมายเหตุ Keycloak: operation (deleteUser) ทำภายใน transaction เพื่อ preserve behavior - * เดิม — Keycloak ไม่สามารถ rollback ได้ ถ้า DB rollback หลังจาก Keycloak operation สำเร็จ - * → Keycloak จะถูกเปลี่ยนไปแล้ว - */ -export class ExecuteSalaryService { - private commandRepository = AppDataSource.getRepository(Command); - private profileRepository = AppDataSource.getRepository(Profile); - private orgRootRepository = AppDataSource.getRepository(OrgRoot); - - /** - * ประมวลผลสร้าง ProfileSalary + handle leave/assistance ทั้ง batch - * - * @returns สรุปผล success/failure ต่อคน - */ - async executeSalary(data: SalaryItem[], ctx: SalaryExecutionContext): Promise { - const commandId = data?.find((x) => x.commandId)?.commandId ?? "unknown"; - const commandCode = data?.find((x) => x.commandCode)?.commandCode ?? "unknown"; - console.log( - `[ExecuteSalaryService] Starting executeSalary — commandCode: ${commandCode}, commandId: ${commandId}`, - ); - console.log(`[ExecuteSalaryService] Request body count: ${data?.length ?? 0}`); - - // ───────────────────────────────────────────────────────────── - // Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date) - // ───────────────────────────────────────────────────────────── - const toDate = (v: any): Date | null => { - if (v == null || v === "") return null; - if (v instanceof Date) return isNaN(v.getTime()) ? null : v; - const d = new Date(v); - return isNaN(d.getTime()) ? null : d; - }; - for (const item of data ?? []) { - const it = item as any; - it.dateLeave = toDate(it.dateLeave); - it.dateStart = toDate(it.dateStart); - it.dateEnd = toDate(it.dateEnd); - it.commandDateAffect = toDate(it.commandDateAffect); - it.commandDateSign = toDate(it.commandDateSign); - } - - let _posNumCodeSit: string = ""; - let _posNumCodeSitAbb: string = ""; - const _command = await this.commandRepository.findOne({ - relations: ["commandType"], - where: { id: data.find((x) => x.commandId)?.commandId ?? "" }, - }); - if (_command) { - if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { - const orgRootDeputy = await this.orgRootRepository.findOne({ - where: { - isDeputy: true, - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - relations: ["orgRevision"], - }); - _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; - _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; - } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { - _posNumCodeSit = "กรุงเทพมหานคร"; - _posNumCodeSitAbb = "กทม."; - } else { - let _profileAdmin = await this.profileRepository.findOne({ - where: { - keycloak: _command?.createdUserId.toString(), - current_holders: { - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - }, - relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], - }); - _posNumCodeSit = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? - ""; - _posNumCodeSitAbb = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot - .orgRootShortName ?? ""; - } - } - - // ───────────────────────────────────────────────────────────── - // 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 { - const req = ctx.req; - - const profileRepository = manager.getRepository(Profile); - const salaryRepo = manager.getRepository(ProfileSalary); - const salaryHistoryRepo = manager.getRepository(ProfileSalaryHistory); - const posMasterRepository = manager.getRepository(PosMaster); - const assistanceRepository = manager.getRepository(ProfileAssistance); - const assistanceHistoryRepository = manager.getRepository(ProfileAssistanceHistory); - - const profile: any = await profileRepository.findOne({ - where: { id: item.profileId }, - relations: { - roleKeycloaks: true, - posType: true, - posLevel: true, - }, - }); - if (!profile) { - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); - } - const posMaster: any = await posMasterRepository.findOne({ - where: { - current_holderId: item.profileId, - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - relations: { - orgRevision: true, - orgRoot: true, - orgChild1: true, - orgChild2: true, - orgChild3: true, - orgChild4: true, - }, - }); - - const orgRevisionRef = posMaster ? posMaster.id : null; - const orgRootRef = orgRevisionRef?.orgRoot ?? null; - const orgChild1Ref = orgRevisionRef?.orgChild1 ?? null; - const orgChild2Ref = orgRevisionRef?.orgChild2 ?? null; - const orgChild3Ref = orgRevisionRef?.orgChild3 ?? null; - const orgChild4Ref = orgRevisionRef?.orgChild4 ?? null; - - //ลบตำแหน่งที่รักษาการแทน - const code = _command?.commandType?.code; - if (code && ["C-PM-13"].includes(code)) { - // await (เดิมไม่ await = fire-and-forget bug) + ส่ง manager เข้าไปเพื่อให้อยู่ใน transaction - await removePostMasterAct(profile.id, manager); - } - - let _commandYear = item.commandYear; - if (item.commandYear) { - _commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543; - } - const dest_item = await salaryRepo.findOne({ - where: { profileId: item.profileId }, - order: { order: "DESC" }, - }); - const before = null; - const dataSalary = new ProfileSalary(); - dataSalary.posNumCodeSit = _posNumCodeSit; - dataSalary.posNumCodeSitAbb = _posNumCodeSitAbb; - const meta = { - order: dest_item == null ? 1 : dest_item.order + 1, - createdUserId: ctx.user.sub, - createdFullName: ctx.user.name, - lastUpdateUserId: ctx.user.sub, - lastUpdateFullName: ctx.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - }; - if (item.isLeave != undefined && item.isLeave == true) { - console.log( - `[ExecuteSalaryService] Creating PosMasterHistory — posMasterId: ${orgRevisionRef}, profileId: ${item.profileId}, type: DELETE`, - ); - await CreatePosMasterHistoryOfficer(orgRevisionRef, req, "DELETE", null, manager); - await removeProfileInOrganize(profile.id, "OFFICER", manager); - } - const clearProfile = await checkCommandType(String(item.commandId)); - const _null: any = null; - if (clearProfile.status) { - // Keycloak deleteUser ทำก่อนเข้า transaction-bound save ด้านล่าง - // (ทำภายใน transaction เดียวกัน เพราะถ้า fail ต้อง rollback DB ด้วย) - // หมายเหตุ: Keycloak ไม่สามารถ rollback ได้ → ถ้า DB rollback หลังจากนี้ Keycloak จะถูกลบไปแล้ว - if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) { - const delUserKeycloak = await deleteUser(profile.keycloak); - if (delUserKeycloak) { - // Task #228 - // profile.keycloak = _null; - profile.roleKeycloaks = []; - profile.isActive = false; - profile.isDelete = true; - } - } - profile.isLeave = item.isLeave; - profile.leaveCommandId = item.commandId ?? _null; - profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`; - profile.leaveRemark = clearProfile.leaveRemark ?? _null; - profile.leaveDate = item.commandDateAffect ?? _null; - profile.leaveType = clearProfile.LeaveType ?? _null; - //ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516) - // profile.position = _null; - // profile.posTypeId = _null; - // profile.posLevelId = _null; - profile.leaveReason = item.leaveReason ?? _null; - profile.dateLeave = item.dateLeave ?? _null; - profile.amount = item.amount ?? _null; - profile.amountSpecial = item.amountSpecial ?? _null; - await profileRepository.save(profile, { data: req }); - - // if (profile.id) { - // await this.keycloakAttributeService.clearOrgDnaAttributes( - // [profile.id], - // "PROFILE", - // ); - // } - } - Object.assign(dataSalary, { ...item, ...meta }); - const history = new ProfileSalaryHistory(); - Object.assign(history, { ...dataSalary, id: undefined }); - - await salaryRepo.save(dataSalary, { data: req }); - setLogDataDiff(req, { before, after: dataSalary }); - history.profileSalaryId = dataSalary.id; - await salaryHistoryRepo.save(history, { data: req }); - - if (_command) { - if (["C-PM-15", "C-PM-16"].includes(_command.commandType.code)) { - // ประวัติคำสั่งให้ช่วยราชการ - const dataAssis = new ProfileAssistance(); - - const metaAssis = { - profileId: item.profileId, - agency: item.officerOrg, - dateStart: item.dateStart, - dateEnd: item.dateEnd, - commandNo: `${item.commandNo}/${_commandYear}`, - commandName: item.commandName, - refId: item.refId, - refCommandDate: new Date(), - commandId: item.commandId, - createdUserId: ctx.user.sub, - createdFullName: ctx.user.name, - lastUpdateUserId: ctx.user.sub, - lastUpdateFullName: ctx.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - status: _command.commandType.code == "C-PM-15" ? "PENDING" : "DONE", - }; - - Object.assign(dataAssis, metaAssis); - const historyAssis = new ProfileAssistanceHistory(); - Object.assign(historyAssis, { ...dataAssis, id: undefined }); - - await assistanceRepository.save(dataAssis); - historyAssis.profileAssistanceId = dataAssis.id; - await assistanceHistoryRepository.save(historyAssis); - } - // Task #2190 - else if (_command.commandType.code == "C-PM-13") { - let organizeName = ""; - if (orgRootRef) { - const names = [ - orgChild4Ref?.orgChild4Name, - orgChild3Ref?.orgChild3Name, - orgChild2Ref?.orgChild2Name, - orgChild1Ref?.orgChild1Name, - orgRootRef?.orgRootName, - ].filter(Boolean); - organizeName = names.join(" "); - } - } - } - - console.log( - `[ExecuteSalaryService] Completed processOne — profileId: ${item.profileId}`, - ); - } -} diff --git a/src/services/PositionService.ts b/src/services/PositionService.ts index 940991d1..45b3c1b0 100644 --- a/src/services/PositionService.ts +++ b/src/services/PositionService.ts @@ -12,7 +12,6 @@ import { Position } from "../entities/Position"; import { ProfileEducation } from "../entities/ProfileEducation"; import { RequestWithUser } from "../middlewares/user"; import { Profile } from "../entities/Profile"; -import { ProfileEmployee } from "../entities/ProfileEmployee"; /** * function สำหรับดึงตำแหน่งที่รักษาการแทน @@ -217,105 +216,74 @@ export async function CreatePosMasterHistoryEmployee( posMasterId: string, request: RequestWithUser | null, type?: string | null, - manager?: EntityManager, ): Promise { - const execute = async (transactionManager: EntityManager) => { - const repoPosmaster = transactionManager.getRepository(EmployeePosMaster); - const repoHistory = transactionManager.getRepository(PosMasterEmployeeHistory); - const repoProfileEmployee = transactionManager.getRepository(ProfileEmployee); - - const pm = await repoPosmaster.findOne({ - where: { id: posMasterId }, - relations: [ - "positions", - "positions.posLevel", - "positions.posType", - // "positions.posExecutive", - "orgRoot", - "orgChild1", - "orgChild2", - "orgChild3", - "orgChild4", - "current_holder", - ], - }); - if (!pm) return; - if (!pm.ancestorDNA) return; - const _null: any = null; - const h = new PosMasterEmployeeHistory(); - const selectedPosition = - pm.positions.length > 0 - ? pm.positions.find((p) => p.positionIsSelected === true) ?? null - : null; - - let position = selectedPosition?.positionName ?? _null; - let posTypeName = selectedPosition?.posType?.posTypeName ?? _null; - let posLevelName = selectedPosition?.posType && selectedPosition?.posLevel - ? `${selectedPosition?.posType?.posTypeShortName ?? ""} ${selectedPosition?.posLevel?.posLevelName ?? ""}`.trim() - : _null; - if (pm.isSit && pm.current_holderId) { - const profile = await repoProfileEmployee.findOne({ - where: { id: pm.current_holderId }, - relations: ["posType", "posLevel"] - }); - position = profile?.position ?? _null; - posTypeName = profile?.posType?.posTypeName ?? _null; - posLevelName = profile?.posType && profile?.posLevel - ? `${profile?.posType?.posTypeShortName ?? ""} ${profile?.posLevel?.posLevelName ?? ""}`.trim() - : _null; - } - h.ancestorDNA = pm.ancestorDNA; - if (!type || type != "DELETE") { - h.profileEmployeeId = pm.current_holder?.id || _null; - h.prefix = pm.current_holder?.prefix || _null; - h.firstName = pm.current_holder?.firstName || _null; - h.lastName = pm.current_holder?.lastName || _null; - h.position = position; - h.posType = posTypeName; - h.posLevel = posLevelName; - } - h.rootDnaId = pm.orgRoot?.ancestorDNA || _null; - h.child1DnaId = pm.orgChild1?.ancestorDNA || _null; - h.child2DnaId = pm.orgChild2?.ancestorDNA || _null; - h.child3DnaId = pm.orgChild3?.ancestorDNA || _null; - h.child4DnaId = pm.orgChild4?.ancestorDNA || _null; - h.posMasterNoPrefix = pm.posMasterNoPrefix ?? _null; - h.posMasterNo = pm.posMasterNo ?? _null; - h.posMasterNoSuffix = pm.posMasterNoSuffix ?? _null; - h.shortName = - [ - pm.orgChild4?.orgChild4ShortName, - pm.orgChild3?.orgChild3ShortName, - pm.orgChild2?.orgChild2ShortName, - pm.orgChild1?.orgChild1ShortName, - pm.orgRoot?.orgRootShortName, - ].find((s) => typeof s === "string" && s.trim().length > 0) ?? _null; - const userId = request?.user?.sub ?? ""; - const userName = request?.user?.name ?? "system"; - h.createdUserId = userId; - h.createdFullName = userName; - h.lastUpdateUserId = userId; - h.lastUpdateFullName = userName; - h.createdAt = new Date(); - h.lastUpdatedAt = new Date(); - await repoHistory.save(h); - }; - try { - if (manager) { - await execute(manager); - return true; - } + await AppDataSource.transaction(async (manager) => { + const repoPosmaster = manager.getRepository(EmployeePosMaster); + const repoHistory = manager.getRepository(PosMasterEmployeeHistory); - await AppDataSource.transaction(async (transactionManager) => { - await execute(transactionManager); + const pm = await repoPosmaster.findOne({ + where: { id: posMasterId }, + relations: [ + "positions", + "positions.posLevel", + "positions.posType", + // "positions.posExecutive", + "orgRoot", + "orgChild1", + "orgChild2", + "orgChild3", + "orgChild4", + "current_holder", + ], + }); + if (!pm) return false; + if (!pm.ancestorDNA) return false; + const _null: any = null; + const h = new PosMasterEmployeeHistory(); + const selectedPosition = + pm.positions.length > 0 + ? pm.positions.find((p) => p.positionIsSelected === true) ?? null + : null; + h.ancestorDNA = pm.ancestorDNA; + if (!type || type != "DELETE") { + h.profileEmployeeId = pm.current_holder?.id || _null; + h.prefix = pm.current_holder?.prefix || _null; + h.firstName = pm.current_holder?.firstName || _null; + h.lastName = pm.current_holder?.lastName || _null; + h.position = selectedPosition?.positionName ?? _null; + h.posType = selectedPosition?.posType?.posTypeName ?? _null; + h.posLevel = selectedPosition?.posLevel?.posLevelName ?? _null; + } + h.rootDnaId = pm.orgRoot?.ancestorDNA || _null; + h.child1DnaId = pm.orgChild1?.ancestorDNA || _null; + h.child2DnaId = pm.orgChild2?.ancestorDNA || _null; + h.child3DnaId = pm.orgChild3?.ancestorDNA || _null; + h.child4DnaId = pm.orgChild4?.ancestorDNA || _null; + h.posMasterNoPrefix = pm.posMasterNoPrefix ?? _null; + h.posMasterNo = pm.posMasterNo ?? _null; + h.posMasterNoSuffix = pm.posMasterNoSuffix ?? _null; + h.shortName = + [ + pm.orgChild4?.orgChild4ShortName, + pm.orgChild3?.orgChild3ShortName, + pm.orgChild2?.orgChild2ShortName, + pm.orgChild1?.orgChild1ShortName, + pm.orgRoot?.orgRootShortName, + ].find((s) => typeof s === "string" && s.trim().length > 0) ?? _null; + const userId = request?.user?.sub ?? ""; + const userName = request?.user?.name ?? "system"; + h.createdUserId = userId; + h.createdFullName = userName; + h.lastUpdateUserId = userId; + h.lastUpdateFullName = userName; + h.createdAt = new Date(); + h.lastUpdatedAt = new Date(); + await repoHistory.save(h); }); + return true; } catch (err) { - if (manager) { - console.error("CreatePosMasterHistoryEmployee error (external transaction):", err); - throw err; - } console.error("CreatePosMasterHistoryEmployee transaction error:", err); return false; } @@ -509,10 +477,10 @@ export async function BatchSavePosMasterHistoryOfficer( const profileChanged = existing && existing.profileId !== op.profileId; const positionChanged = existing && - (existing.position !== op.pm?.position || - existing.posType !== op.pm?.posType || - existing.posLevel !== op.pm?.posLevel || - existing.posExecutive !== op.pm?.posExecutive); + existing.position !== op.pm?.position && + existing.posType !== op.pm?.posType && + existing.posLevel !== op.pm?.posLevel && + existing.posExecutive !== op.pm?.posExecutive; // ถ้าไม่มี record เดิม หรือ profile เปลี่ยน หรือ position เปลี่ยน ให้สร้าง record ใหม่ if (shouldInsert || profileChanged || positionChanged) { diff --git a/src/services/rabbitmq.ts b/src/services/rabbitmq.ts index 227c431c..93c7ef7e 100644 --- a/src/services/rabbitmq.ts +++ b/src/services/rabbitmq.ts @@ -29,16 +29,6 @@ import { sendWebSocket } from "./webSocket"; import { PayloadSendNoti } from "../interfaces/utils"; import { PermissionProfile } from "../entities/PermissionProfile"; import { PosMasterHistory } from "../entities/PosMasterHistory"; -import { ExecuteOfficerProfileService } from "./ExecuteOfficerProfileService"; -import { ExecuteSalaryService } from "./ExecuteSalaryService"; -import { ExecuteSalaryCurrentService } from "./ExecuteSalaryCurrentService"; -import { ExecuteSalaryEmployeeCurrentService } from "./ExecuteSalaryEmployeeCurrentService"; -import { ExecuteSalaryLeaveService } from "./ExecuteSalaryLeaveService"; -import { ExecuteSalaryEmployeeLeaveService } from "./ExecuteSalaryEmployeeLeaveService"; -import { ExecuteSalaryLeaveDisciplineService } from "./ExecuteSalaryLeaveDisciplineService"; -import { ExecuteOrgCommandService } from "./ExecuteOrgCommandService"; -import { ExecuteSalaryProbationService } from "./ExecuteSalaryProbationService"; -import { ExecuteSalaryReportService } from "./ExecuteSalaryReportService"; const redis = require("redis"); const REDIS_HOST = process.env.REDIS_HOST; @@ -330,249 +320,13 @@ async function handler(msg: amqp.ConsumeMessage): Promise { 20, ); - // ───────────────────────────────────────────────────────────── - // Linear Flow - // รับ resultData จาก .NET แล้วเรียก Service ตรงๆ ตาม commandType (ไม่ผ่าน HTTP loopback) - // - ExecuteOfficerProfileService : C-PM-01, 02, 14 (บรรจุ/รับโอน) - // - ExecuteSalaryCurrentService : C-PM-03, 04, 05, 06, 07, 39, 47 (แต่งตั้ง-เลื่อน-ย้าย) - // - ExecuteSalaryEmployeeCurrentService : C-PM-22, 24 (ลูกจ้าง ปรับระดับชั้นงาน-ย้าย) - // - ExecuteSalaryService : C-PM-13, 15, 16 (ให้โอน/ให้ช่วยราชการ/ให้กลับเข้าราชการ) - // - ExecuteSalaryLeaveService : C-PM-08, 09, 17, 18, 41, 48 (ข้าราชการ leave/กลับเข้าราชการ) - // - ExecuteSalaryEmployeeLeaveService : C-PM-23, 42, 43 (ลูกจ้าง leave) - // - ExecuteSalaryLeaveDisciplineService : C-PM-19, 20, 25, 26, 27, 28, 29, 30, 31, 32 (คำสั่งวินัย) - // - ExecuteOrgCommandService : C-PM-21, 38, 40 (org-self — path ชี้กลับ org เอง - // เรียก Service ตรงๆ ไม่ผ่าน HTTP loopback เพราะ PostData(path+"/excecute") = ยิงเข้าตัว) - // - คำสั่งอื่น ยังใช้ Circular Flow เดิม - // ───────────────────────────────────────────────────────────── - const code = command.commandType?.code; - const isOfficerProfile = ["C-PM-01", "C-PM-02", "C-PM-14"].includes(code); - const isSalaryCurrent = ["C-PM-03", "C-PM-04", "C-PM-05", "C-PM-06", "C-PM-07", "C-PM-39", "C-PM-47"].includes(code); - const isSalaryEmployeeCurrent = ["C-PM-22", "C-PM-24"].includes(code); - const isSalary = ["C-PM-13", "C-PM-15", "C-PM-16"].includes(code); - const isSalaryLeave = ["C-PM-08", "C-PM-09", "C-PM-17", "C-PM-18", "C-PM-41", "C-PM-48"].includes(code); - const isSalaryEmployeeLeave = ["C-PM-23", "C-PM-42", "C-PM-43"].includes(code); - const isSalaryLeaveDiscipline = ["C-PM-19", "C-PM-20", "C-PM-25", "C-PM-26", "C-PM-27", "C-PM-28", - "C-PM-29", "C-PM-30", "C-PM-31", "C-PM-32", - ].includes(code); - // C-PM-21/38/40: path ชี้กลับ org เอง (ไม่ใช่ .NET) → ต้องเรียก Service ตรงๆ ไม่ผ่าน loopback - const isCommand21 = code === "C-PM-21"; - const isCommand38 = code === "C-PM-38"; - const isCommand40 = code === "C-PM-40"; - const isOrgSelfLinear = isCommand21 || isCommand38 || isCommand40; - // C-PM-10/11/12: ยิงไป probation service — เป็น branch แยก (ไม่ใช่ .NET linear flow) - // - C-PM-10: fire-only (probation update ในตัวเอง ไม่มี org-side action) - // - C-PM-11/12: probation return salary data → เรียก ExecuteSalaryProbationService ตรงๆ - const isProbation = ["C-PM-10", "C-PM-11", "C-PM-12"].includes(code); - // C-PM-33/34/35/45 (officer) + C-PM-36/37/46 (employee): ยิงไป salary service - // เป็น branch แยก — salary return salary data → เรียก ExecuteSalaryReportService ตรงๆ - const isSalaryServiceOfficer = ["C-PM-33", "C-PM-34", "C-PM-35", "C-PM-45"].includes(code); - const isSalaryServiceEmployee = ["C-PM-36", "C-PM-37", "C-PM-46"].includes(code); - const isSalaryService = isSalaryServiceOfficer || isSalaryServiceEmployee; - const isLinearFlow = - isOfficerProfile || - isSalaryCurrent || - isSalaryEmployeeCurrent || - isSalary || - isSalaryLeave || - isSalaryEmployeeLeave || - isSalaryLeaveDiscipline; - - // Org-self (C-PM-21/38/40): เรียก Service ตรงๆ (Linear Flow / ทำต่อ) ไม่ผ่าน HTTP loopback - // เพราะ path ของ command เหล่านี้ชี้กลับ org เอง → PostData(path + "/excecute") = ยิงเข้าตัว - if (isOrgSelfLinear) { - console.log(`[AMQ] Linear Flow org-self (${code}) — เรียก Service ตรงๆ (no loopback)`); - const pseudoReq = { headers: { authorization: token }, user }; - const ctx = { - user: { sub: user?.sub ?? "system", name: user?.name ?? "System" }, - req: pseudoReq, - }; - const flatRefIds = chunks.flat(); - if (isCommand21) { - await new ExecuteOrgCommandService().executeCommand21Employee(flatRefIds, ctx); - } else if (isCommand38) { - await new ExecuteOrgCommandService().executeCommand38Officer(flatRefIds, ctx); - } else if (isCommand40) { - await new ExecuteOrgCommandService().executeCommand40Officer(flatRefIds, ctx); - } - console.log(`[AMQ] Processed ${flatRefIds.length} items via ExecuteOrgCommandService (${code})`); - } else if (isProbation) { - // Probation Linear Flow (C-PM-10/11/12) - // - C-PM-10: fire-only — probation อัปเดต appoint ในตัวเอง ไม่มี org-side action - // - C-PM-11/12: fire → probation return salary data → route ไป ExecuteSalaryProbationService - // แทนการ callback เข้า org (Circular Flow เดิม) - console.log(`[AMQ] Probation Linear Flow (${code})`); - if (code === "C-PM-10") { - for (const chunk of chunks) { - await new CallAPI().PostData( - { headers: { authorization: token } }, - path + "/excecute", - { refIds: chunk }, - false, - ); - } - console.log(`[AMQ] C-PM-10 fire-only — no org-side action`); - } else { - let resultData: any[] = []; - for (const chunk of chunks) { - const res = await new CallAPI().PostData( - { headers: { authorization: token } }, - path + "/excecute", - { refIds: chunk }, - false, - ); - // รองรับทั้ง array และ { data: [...] } (contract ของ probation หลัง Linear Flow) - if (res && Array.isArray(res.data)) { - resultData.push(...res.data); - } else if (Array.isArray(res)) { - resultData.push(...res); - } - } - - if (resultData.length > 0) { - const pseudoReq = { headers: { authorization: token }, user }; - const ctx = { - user: { sub: user?.sub ?? "system", name: user?.name ?? "System" }, - req: pseudoReq, - }; - - if (code === "C-PM-11") { - await new ExecuteSalaryProbationService().executeProbationPass(resultData, ctx); - console.log( - `[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryProbationService (C-PM-11)`, - ); - } else if (code === "C-PM-12") { - await new ExecuteSalaryProbationService().executeProbationLeave(resultData, ctx); - console.log( - `[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryProbationService (C-PM-12)`, - ); - } - } - } - } else if (isSalaryService) { - // Salary Service Linear Flow (C-PM-33/34/35/45 officer, C-PM-36/37/46 employee) - // fire → salary service return salary data → route ไป ExecuteSalaryReportService - // แทนการ callback เข้า /org/profile(/-employee)/salary/update (Circular Flow เดิม) - console.log(`[AMQ] Salary Service Linear Flow (${code})`); - let resultData: any[] = []; - for (const chunk of chunks) { - const res = await new CallAPI().PostData( - { headers: { authorization: token } }, - path + "/excecute", - { refIds: chunk }, - false, - ); - // รองรับทั้ง array และ { data: [...] } (contract ของ salary service หลัง Linear Flow) - if (res && Array.isArray(res.data)) { - resultData.push(...res.data); - } else if (Array.isArray(res)) { - resultData.push(...res); - } - } - - if (resultData.length > 0) { - const pseudoReq = { headers: { authorization: token }, user }; - const ctx = { - user: { sub: user?.sub ?? "system", name: user?.name ?? "System" }, - req: pseudoReq, - }; - - if (isSalaryServiceOfficer) { - await new ExecuteSalaryReportService().executeOfficerSalaryUpdate(resultData, ctx); - console.log( - `[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryReportService (officer)`, - ); - } else { - await new ExecuteSalaryReportService().executeEmployeeSalaryUpdate(resultData, ctx); - console.log( - `[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryReportService (employee)`, - ); - } - } - } else if (isLinearFlow) { - console.log(`[AMQ] Linear Flow (${code})`); - const isCpm32 = code === "C-PM-32"; - let resultData: any[] = []; - let resultData1: any[] = []; //เฉพาะ C-PM-32 (ฝั่ง "การพิจารณาลงโทษ") - - for (const chunk of chunks) { - const res = await new CallAPI().PostData( - { headers: { authorization: token } }, - path + "/excecute", - { refIds: chunk }, - false, - ); - if (isCpm32 && res && !Array.isArray(res)) { - // C-PM-32: response เป็น object { data, data1 } → แยก 2 track - console.log( - `[AMQ] C-PM-32 split response — data: ${res.data?.length ?? 0}, data1: ${res.data1?.length ?? 0}`, - ); - if (Array.isArray(res.data)) resultData.push(...res.data); - if (Array.isArray(res.data1)) resultData1.push(...res.data1); - } else if (Array.isArray(res)) { - console.log(`[AMQ] Push result data (${res.length})`); - resultData.push(...res); - } - } - - console.log(`[AMQ] Received ${resultData.length} profiles from .NET (${code})`); - - // Route ไป service ที่ถูกต้องตาม commandType - if (resultData.length > 0 || resultData1.length > 0) { - // สร้าง pseudo-req สำหรับ setLogDataDiff/save({data: req}) - const pseudoReq = { - headers: { authorization: token }, - user, - }; - const ctx = { - user: { sub: user?.sub ?? "system", name: user?.name ?? "System" }, - req: pseudoReq, - }; - - if (isOfficerProfile) { - await new ExecuteOfficerProfileService().executeCreateOfficerProfile(resultData, ctx); - console.log(`[AMQ] Processed ${resultData.length} profiles via ExecuteOfficerProfileService`); - } else if (isSalaryCurrent) { - await new ExecuteSalaryCurrentService().executeSalaryCurrent(resultData, ctx); - console.log(`[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryCurrentService`); - } else if (isSalaryEmployeeCurrent) { - await new ExecuteSalaryEmployeeCurrentService().executeSalaryEmployeeCurrent(resultData, ctx); - console.log(`[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryEmployeeCurrentService`); - } else if (isSalary) { - await new ExecuteSalaryService().executeSalary(resultData, ctx); - console.log(`[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryService`); - } else if (isSalaryLeave) { - await new ExecuteSalaryLeaveService().executeSalaryLeave(resultData, ctx); - console.log(`[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryLeaveService`); - } else if (isSalaryEmployeeLeave) { - await new ExecuteSalaryEmployeeLeaveService().executeSalaryEmployeeLeave(resultData, ctx); - console.log(`[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryEmployeeLeaveService`); - } else if (isSalaryLeaveDiscipline) { - // C-PM-32 (คำสั่งยุติเรื่อง): response เป็น object { data, data1 } - // profileId เดียวกันอาจอยู่ในทั้ง 2 track → ต้องส่งให้ org แยก 2 ครั้ง ห้าม merge - if (resultData.length > 0) { - await new ExecuteSalaryLeaveDisciplineService().executeSalaryLeaveDiscipline(resultData, ctx); - console.log( - `[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryLeaveDisciplineService`, - ); - } - if (isCpm32 && resultData1.length > 0) { - await new ExecuteSalaryLeaveDisciplineService().executeSalaryLeaveDiscipline(resultData1, ctx); - console.log( - `[AMQ] Processed resultData1: ${resultData1.length} profiles via ExecuteSalaryLeaveDisciplineService`, - ); - } - } - } - } else { - console.log(`[AMQ] Circular Flow (${code})`); - for (const chunk of chunks) { - await new CallAPI().PostData( - { headers: { authorization: token } }, - path + "/excecute", - { refIds: chunk }, - false, - ); - } + for (const chunk of chunks) { + await new CallAPI().PostData( + { headers: { authorization: token } }, + path + "/excecute", + { refIds: chunk }, + false, + ); } Object.assign(command, { status, lastUpdateUserId, lastUpdateFullName, lastUpdatedAt }); @@ -941,19 +695,7 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { where: { orgRevisionId: orgRevisionPublish.id, }, - select: [ - "id", - "current_holderId", - "ancestorDNA", - "posMasterNo", - "posMasterNoPrefix", - "posMasterNoSuffix", - "orgRootId", - "orgChild1Id", - "orgChild2Id", - "orgChild3Id", - "orgChild4Id", - ], + select: ["id", "current_holderId", "ancestorDNA"], }); // Task #2160 ดึง posMasterAssign ของ revision เดิม @@ -1119,21 +861,7 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { const newHolderId = item?.next_holderId; const isHolderChanged = oldHolderId !== newHolderId; - // เช็คว่า holder เดิม แต่ตำแหน่งเปลี่ยน - const isSameHolder = oldHolderId === newHolderId && oldHolderId != null && newHolderId != null; - const isPositionChanged = - isSameHolder && - oldPm && - (oldPm.posMasterNo !== item.posMasterNo || - oldPm.posMasterNoPrefix !== item.posMasterNoPrefix || - oldPm.posMasterNoSuffix !== item.posMasterNoSuffix || - oldPm.orgRootId !== item.orgRoot?.id || - oldPm.orgChild1Id !== item.orgChild1?.id || - oldPm.orgChild2Id !== item.orgChild2?.id || - oldPm.orgChild3Id !== item.orgChild3?.id || - oldPm.orgChild4Id !== item.orgChild4?.id); - - if (isHolderChanged || isPositionChanged) { + if (isHolderChanged) { const nextHolderProfile = item.next_holderId != null && item.next_holderId !== "" ? profilesMap.get(item.next_holderId) @@ -1983,7 +1711,6 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { } async function clearMenuAndRoleCache(): Promise { - console.log("[AMQ] clearMenuAndRoleCache: Starting..."); const redisClient = redis.createClient({ host: REDIS_HOST, port: REDIS_PORT, @@ -1993,28 +1720,35 @@ async function clearMenuAndRoleCache(): Promise { const delAsync = promisify(redisClient.del).bind(redisClient); try { - // Clear menu and role cache (patterns that affect menu display) - const menuRolePatterns = ["menu_*", "role_*"]; - - for (const pattern of menuRolePatterns) { - console.log(`[AMQ] Checking pattern: ${pattern}`); - const keys = await keysAsync(pattern); - console.log(`[AMQ] Found ${keys.length} keys for pattern: ${pattern}`); - if (keys.length > 0) { - // Delete in chunks of 1000 to avoid argument limit - const chunkSize = 1000; - for (let i = 0; i < keys.length; i += chunkSize) { - const chunk = keys.slice(i, i + chunkSize); - await delAsync(...chunk); - } - console.log(`[AMQ] Cleared ${keys.length} cache keys for pattern: ${pattern}`); - } else { - console.log(`[AMQ] No keys found for pattern: ${pattern}`); - } + const menuKeys = await keysAsync("menu_*"); + if (menuKeys.length > 0) { + await delAsync(...menuKeys); + console.log(`[AMQ] Cleared ${menuKeys.length} menu cache keys`); } - console.log("[AMQ] clearMenuAndRoleCache: Completed successfully"); - } catch (error) { - console.error("[AMQ] clearMenuAndRoleCache ERROR:", error); + + const roleKeys = await keysAsync("role_*"); + if (roleKeys.length > 0) { + await delAsync(...roleKeys); + console.log(`[AMQ] Cleared ${roleKeys.length} role cache keys`); + } + + // const posMasterKeys = await keysAsync("posMaster_*"); + // if (posMasterKeys.length > 0) { + // await delAsync(...posMasterKeys); + // console.log(`[AMQ] Cleared ${posMasterKeys.length} posMaster cache keys`); + // } + + // const userKeys = await keysAsync("user_*"); + // if (userKeys.length > 0) { + // await delAsync(...userKeys); + // console.log(`[AMQ] Cleared ${userKeys.length} user cache keys`); + // } + + // const orgKeys = await keysAsync("org_*"); + // if (orgKeys.length > 0) { + // await delAsync(...orgKeys); + // console.log(`[AMQ] Cleared ${orgKeys.length} org cache keys`); + // } } finally { redisClient.quit(); }