From 3c9e3a1bb6702178dec7bd5d1cae8d8d9740d0c2 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Thu, 12 Feb 2026 10:38:16 +0700 Subject: [PATCH] fix: script org move draf to current save posMasterHistory --- src/controllers/OrganizationController.ts | 95 +++++++++++++++++++++-- src/interfaces/OrgMapping.ts | 20 +++++ src/services/PositionService.ts | 66 +++++++++++++++- 3 files changed, 173 insertions(+), 8 deletions(-) diff --git a/src/controllers/OrganizationController.ts b/src/controllers/OrganizationController.ts index a4ef7ac6..813e268c 100644 --- a/src/controllers/OrganizationController.ts +++ b/src/controllers/OrganizationController.ts @@ -55,9 +55,10 @@ import { import { CreatePosMasterHistoryEmployee, CreatePosMasterHistoryOfficer, + SavePosMasterHistoryOfficer, } from "../services/PositionService"; import { orgStructureCache } from "../utils/OrgStructureCache"; -import { OrgIdMapping, AllOrgMappings } from "../interfaces/OrgMapping"; +import { OrgIdMapping, AllOrgMappings, SavePosMasterHistory } from "../interfaces/OrgMapping"; @Route("api/v1/org") @Tags("Organization") @@ -8110,6 +8111,12 @@ export class OrganizationController extends Controller { // Then delete posMaster records await queryRunner.manager.delete(PosMaster, toDeleteIds); + + await Promise.all( + toDelete.map(async (pos) => { + await SavePosMasterHistoryOfficer(queryRunner, pos.ancestorDNA, null, null); + }), + ); } // 2.4 Process draft positions (UPDATE or INSERT) @@ -8176,6 +8183,7 @@ export class OrganizationController extends Controller { current_holderId: draftPos.next_holderId, statusReport: "DONE", }); + toInsert.push(newPosMaster); } } @@ -8231,6 +8239,11 @@ export class OrganizationController extends Controller { console.error("Error moving draft to current:", error); await queryRunner.rollbackTransaction(); throw new HttpError(HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาดในการย้ายโครงสร้าง"); + } finally { + if (queryRunner.isTransactionActive) { + await queryRunner.rollbackTransaction(); + } + await queryRunner.release(); } } @@ -8612,18 +8625,27 @@ export class OrganizationController extends Controller { ): Promise<{ deleted: number; updated: number; inserted: number }> { // Extract draft and current posMaster IDs const draftPosMasterIds = Array.from(posMasterMapping.keys()); - const currentPosMasterIds = Array.from(posMasterMapping.values()).map(([currentId]) => currentId); + const currentPosMasterIds = Array.from(posMasterMapping.values()).map( + ([currentId]) => currentId, + ); if (draftPosMasterIds.length === 0) { return { deleted: 0, updated: 0, inserted: 0 }; } + // Fetch draft PosMasters with relations for history tracking + const draftPosMasters = await queryRunner.manager.find(PosMaster, { + where: { id: In(draftPosMasterIds) }, + relations: ["orgRoot", "orgChild1", "orgChild2", "orgChild3", "orgChild4", "next_holder"], + }); + // Fetch ALL positions for ALL posMasters in just 2 queries const [allDraftPositions, allCurrentPositions] = await Promise.all([ queryRunner.manager.find(Position, { where: { posMasterId: In(draftPosMasterIds), }, + relations: ["posType", "posLevel", "posExecutive"], order: { orderNo: "ASC" }, }), queryRunner.manager.find(Position, { @@ -8656,6 +8678,16 @@ export class OrganizationController extends Controller { const allToInsert: Array = []; const profileUpdates: Map = new Map(); + // Create a map for quick lookup of draft PosMasters with relations + const draftPosMasterMap = new Map(draftPosMasters.map((pm: PosMaster) => [pm.id, pm])); + + // Collect PosMasterHistory calls for selected positions + const historyCalls: Array<{ + ancestorDNA: string; + profileId: string | null; + historyData: SavePosMasterHistory; + }> = []; + // Process each posMaster mapping for (const [draftPosMasterId, [currentPosMasterId, nextHolderId]] of posMasterMapping) { const draftPositions = draftPositionsByMaster.get(draftPosMasterId) || []; @@ -8710,6 +8742,10 @@ export class OrganizationController extends Controller { }); } + if (nextHolderId === null) { + await SavePosMasterHistoryOfficer(queryRunner, draftPos.ancestorDNA, null, null); + } + // Collect profile update for the selected position if (nextHolderId != null && draftPos.positionIsSelected) { profileUpdates.set(nextHolderId, { @@ -8718,6 +8754,46 @@ export class OrganizationController extends Controller { posLevelId: draftPos.posLevelId, }); } + + // Collect history data for the selected position + if (nextHolderId != null && draftPos.positionIsSelected) { + const draftPosMaster = draftPosMasterMap.get(draftPosMasterId) as any; + if (draftPosMaster && draftPosMaster.ancestorDNA) { + // Find the selected position from draft positions + const selectedPos = + draftPositions.find((p) => p.positionIsSelected === true) || draftPos; + historyCalls.push({ + ancestorDNA: draftPosMaster.ancestorDNA, + profileId: nextHolderId, + historyData: { + prefix: draftPosMaster.next_holder?.prefix ?? null, + firstName: draftPosMaster.next_holder?.firstName ?? null, + lastName: draftPosMaster.next_holder?.lastName ?? null, + position: selectedPos.positionName ?? null, + posType: (selectedPos as any).posType?.posTypeName ?? null, + posLevel: (selectedPos as any).posLevel?.posLevelName ?? null, + posExecutive: (selectedPos as any).posExecutive?.posExecutiveName ?? null, + profileId: nextHolderId, + rootDnaId: draftPosMaster.orgRoot?.ancestorDNA ?? null, + child1DnaId: draftPosMaster.orgChild1?.ancestorDNA ?? null, + child2DnaId: draftPosMaster.orgChild2?.ancestorDNA ?? null, + child3DnaId: draftPosMaster.orgChild3?.ancestorDNA ?? null, + child4DnaId: draftPosMaster.orgChild4?.ancestorDNA ?? null, + shortName: + [ + draftPosMaster.orgChild4?.orgChild4ShortName, + draftPosMaster.orgChild3?.orgChild3ShortName, + draftPosMaster.orgChild2?.orgChild2ShortName, + draftPosMaster.orgChild1?.orgChild1ShortName, + draftPosMaster.orgRoot?.orgRootShortName, + ].find((s) => typeof s === "string" && s.trim().length > 0) ?? null, + posMasterNoPrefix: draftPosMaster.posMasterNoPrefix ?? null, + posMasterNo: draftPosMaster.posMasterNo ?? null, + posMasterNoSuffix: draftPosMaster.posMasterNoSuffix ?? null, + }, + }); + } + } } } @@ -8738,7 +8814,7 @@ export class OrganizationController extends Controller { for (let i = 0; i < allToUpdate.length; i += batchSize) { const batch = allToUpdate.slice(i, i + batchSize); await Promise.all( - batch.map(({ id, data }) => queryRunner.manager.update(Position, id, data)) + batch.map(({ id, data }) => queryRunner.manager.update(Position, id, data)), ); } updatedCount = allToUpdate.length; @@ -8759,8 +8835,17 @@ export class OrganizationController extends Controller { const profileUpdateEntries = Array.from(profileUpdates.entries()); await Promise.all( profileUpdateEntries.map(([profileId, data]) => - queryRunner.manager.update(Profile, profileId, data) - ) + queryRunner.manager.update(Profile, profileId, data), + ), + ); + } + + // Save PosMasterHistory for updated positions + if (historyCalls.length > 0) { + await Promise.all( + historyCalls.map(({ ancestorDNA, profileId, historyData }) => + SavePosMasterHistoryOfficer(queryRunner, ancestorDNA, profileId, historyData), + ), ); } diff --git a/src/interfaces/OrgMapping.ts b/src/interfaces/OrgMapping.ts index 1cdea98f..b0eb21bf 100644 --- a/src/interfaces/OrgMapping.ts +++ b/src/interfaces/OrgMapping.ts @@ -22,3 +22,23 @@ export interface AllOrgMappings { orgChild3: OrgIdMapping; orgChild4: OrgIdMapping; } + +export interface SavePosMasterHistory { + prefix: string | null; + firstName: string | null; + lastName: string | null; + position: string | null; + posType: string | null; + posLevel: string | null; + posExecutive: string | null; + profileId: string | null; + rootDnaId: string | null; + child1DnaId: string | null; + child2DnaId: string | null; + child3DnaId: string | null; + child4DnaId: string | null; + shortName: string | null; + posMasterNoPrefix: string | null; + posMasterNo: string | null; + posMasterNoSuffix: string | null; +} diff --git a/src/services/PositionService.ts b/src/services/PositionService.ts index 67cbecef..34631666 100644 --- a/src/services/PositionService.ts +++ b/src/services/PositionService.ts @@ -1,3 +1,4 @@ +import { SavePosMasterHistory } from "./../interfaces/OrgMapping"; import { AppDataSource } from "../database/data-source"; import { EmployeePosMaster } from "../entities/EmployeePosMaster"; import { EmployeeTempPosMaster } from "../entities/EmployeeTempPosMaster"; @@ -44,9 +45,9 @@ export async function CreatePosMasterHistoryOfficer( where: { id: pm.orgRevisionId, orgRevisionIsCurrent: true, - orgRevisionIsDraft: false - } - }) + orgRevisionIsDraft: false, + }, + }); const _null: any = null; const h = new PosMasterHistory(); const selectedPosition = @@ -260,3 +261,62 @@ export async function getTopDegrees(educations: ProfileEducation[]): Promise { + try { + // Type workaround: entity columns are nullable but types don't reflect it + const _null: any = null; + const repoPosMasterHistory = queryRunner.manager.getRepository(PosMasterHistory); + const pmh = await repoPosMasterHistory.findOne({ + where: { + ancestorDNA: posMasterDnaId, + }, + order: { createdAt: "DESC" }, + }); + + // Check if we need to insert a new history record + const shouldInsert = !pmh && profileId && pm; + const profileChanged = pmh && pmh.profileId !== profileId; + + if (shouldInsert || profileChanged) { + // insert new record + const newPmh = new PosMasterHistory(); + newPmh.ancestorDNA = posMasterDnaId; + newPmh.prefix = pm?.prefix ?? _null; + newPmh.firstName = pm?.firstName ?? _null; + newPmh.lastName = pm?.lastName ?? _null; + newPmh.position = pm?.position ?? _null; + newPmh.posType = pm?.posType ?? _null; + newPmh.posLevel = pm?.posLevel ?? _null; + newPmh.posExecutive = pm?.posExecutive ?? _null; + newPmh.profileId = profileId ?? _null; + newPmh.rootDnaId = pm?.rootDnaId ?? _null; + newPmh.child1DnaId = pm?.child1DnaId ?? _null; + newPmh.child2DnaId = pm?.child2DnaId ?? _null; + newPmh.child3DnaId = pm?.child3DnaId ?? _null; + newPmh.child4DnaId = pm?.child4DnaId ?? _null; + newPmh.shortName = pm?.shortName ?? _null; + newPmh.posMasterNoPrefix = pm?.posMasterNoPrefix ?? _null; + newPmh.posMasterNo = pm?.posMasterNo ?? _null; + newPmh.posMasterNoSuffix = pm?.posMasterNoSuffix ?? _null; + // Add audit fields for data integrity + newPmh.createdUserId = "system"; + newPmh.createdFullName = "system"; + newPmh.lastUpdateUserId = "system"; + newPmh.lastUpdateFullName = "system"; + newPmh.createdAt = new Date(); + newPmh.lastUpdatedAt = new Date(); + await queryRunner.manager.save(PosMasterHistory, newPmh); + return true; + } + return true; + } catch (err) { + console.error("SavePosMasterHistoryOfficer error:", err); + return false; + } +}