fix: script org move draf to current save posMasterHistory
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m57s

This commit is contained in:
Warunee Tamkoo 2026-02-12 10:38:16 +07:00
parent 7694a83d5a
commit 3c9e3a1bb6
3 changed files with 173 additions and 8 deletions

View file

@ -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<any> = [];
const profileUpdates: Map<string, any> = 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),
),
);
}

View file

@ -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;
}

View file

@ -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<str
.filter(Boolean)
.join("\n");
}
export async function SavePosMasterHistoryOfficer(
queryRunner: any,
posMasterDnaId: string,
profileId: string | null,
pm: SavePosMasterHistory | null,
): Promise<boolean> {
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;
}
}