This commit is contained in:
parent
d916334537
commit
22fd9152bf
1 changed files with 306 additions and 308 deletions
|
|
@ -7932,7 +7932,7 @@ export class OrganizationController extends Controller {
|
||||||
const currentRevisionId = currentRevision.id;
|
const currentRevisionId = currentRevision.id;
|
||||||
|
|
||||||
// ตรวจสอบว่ามี rootDnaId ในโครงสร้างร่าง และในโครงสร้างปัจจุบันหรือไม่
|
// ตรวจสอบว่ามี rootDnaId ในโครงสร้างร่าง และในโครงสร้างปัจจุบันหรือไม่
|
||||||
const [orgRootDraft, orgRootCurrent] = await Promise.all([
|
let [orgRootDraft, orgRootCurrent] = await Promise.all([
|
||||||
this.orgRootRepository.findOne({
|
this.orgRootRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
ancestorDNA: rootDnaId,
|
ancestorDNA: rootDnaId,
|
||||||
|
|
@ -7950,333 +7950,331 @@ export class OrganizationController extends Controller {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!orgRootDraft) return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลโครงสร้างร่าง");
|
if (!orgRootDraft) return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลโครงสร้างร่าง");
|
||||||
|
|
||||||
|
// if current record not found, create new one
|
||||||
|
if (!orgRootCurrent) {
|
||||||
|
// Create new current record using draft's ID
|
||||||
|
const newCurrentRoot = queryRunner.manager.create(OrgRoot, {
|
||||||
|
...orgRootDraft,
|
||||||
|
id: undefined, // Let database generate new ID
|
||||||
|
orgRevisionId: currentRevisionId, // Change to current revision
|
||||||
|
});
|
||||||
|
|
||||||
|
const savedRoot = await queryRunner.manager.save(OrgRoot, newCurrentRoot);
|
||||||
|
orgRootCurrent = savedRoot; // Use saved record for sync
|
||||||
|
}
|
||||||
|
|
||||||
// Part 1: Differential sync of organization structure (bottom-up)
|
// Part 1: Differential sync of organization structure (bottom-up)
|
||||||
// Build mapping incrementally as we process each level
|
// Build mapping incrementally as we process each level
|
||||||
|
|
||||||
if (orgRootCurrent) {
|
const allMappings: AllOrgMappings = {
|
||||||
const allMappings: AllOrgMappings = {
|
orgRoot: { byAncestorDNA: new Map(), byDraftId: new Map() },
|
||||||
orgRoot: { byAncestorDNA: new Map(), byDraftId: new Map() },
|
orgChild1: { byAncestorDNA: new Map(), byDraftId: new Map() },
|
||||||
orgChild1: { byAncestorDNA: new Map(), byDraftId: new Map() },
|
orgChild2: { byAncestorDNA: new Map(), byDraftId: new Map() },
|
||||||
orgChild2: { byAncestorDNA: new Map(), byDraftId: new Map() },
|
orgChild3: { byAncestorDNA: new Map(), byDraftId: new Map() },
|
||||||
orgChild3: { byAncestorDNA: new Map(), byDraftId: new Map() },
|
orgChild4: { byAncestorDNA: new Map(), byDraftId: new Map() },
|
||||||
orgChild4: { byAncestorDNA: new Map(), byDraftId: new Map() },
|
};
|
||||||
};
|
|
||||||
|
|
||||||
// Track sync statistics for organization nodes
|
// Track sync statistics for organization nodes
|
||||||
const orgSyncStats: Record<string, { deleted: number; updated: number; inserted: number }> =
|
const orgSyncStats: Record<string, { deleted: number; updated: number; inserted: number }> =
|
||||||
{};
|
{};
|
||||||
|
|
||||||
// Process from top (Root) to bottom (Child4) to handle foreign key constraints
|
// Process from top (Root) to bottom (Child4) to handle foreign key constraints
|
||||||
// OrgRoot (sync first - no parent dependencies)
|
// OrgRoot (sync first - no parent dependencies)
|
||||||
const orgRootResult = await this.syncOrgLevel(
|
const orgRootResult = await this.syncOrgLevel(
|
||||||
queryRunner,
|
queryRunner,
|
||||||
OrgRoot,
|
OrgRoot,
|
||||||
this.orgRootRepository,
|
this.orgRootRepository,
|
||||||
drafRevisionId,
|
drafRevisionId,
|
||||||
currentRevisionId,
|
currentRevisionId,
|
||||||
allMappings,
|
allMappings,
|
||||||
orgRootDraft?.id,
|
orgRootDraft?.id,
|
||||||
orgRootCurrent?.id,
|
orgRootCurrent?.id,
|
||||||
);
|
);
|
||||||
allMappings.orgRoot = orgRootResult.mapping;
|
allMappings.orgRoot = orgRootResult.mapping;
|
||||||
orgSyncStats.orgRoot = orgRootResult.counts;
|
orgSyncStats.orgRoot = orgRootResult.counts;
|
||||||
|
|
||||||
// Child1 (parent OrgRoot already synced)
|
// Child1 (parent OrgRoot already synced)
|
||||||
const child1Result = await this.syncOrgLevel(
|
const child1Result = await this.syncOrgLevel(
|
||||||
queryRunner,
|
queryRunner,
|
||||||
OrgChild1,
|
OrgChild1,
|
||||||
this.child1Repository,
|
this.child1Repository,
|
||||||
drafRevisionId,
|
drafRevisionId,
|
||||||
currentRevisionId,
|
currentRevisionId,
|
||||||
allMappings,
|
allMappings,
|
||||||
orgRootDraft?.id,
|
orgRootDraft?.id,
|
||||||
orgRootCurrent?.id,
|
orgRootCurrent?.id,
|
||||||
);
|
);
|
||||||
allMappings.orgChild1 = child1Result.mapping;
|
allMappings.orgChild1 = child1Result.mapping;
|
||||||
orgSyncStats.orgChild1 = child1Result.counts;
|
orgSyncStats.orgChild1 = child1Result.counts;
|
||||||
|
|
||||||
// Child2 (parents OrgRoot and Child1 already synced)
|
// Child2 (parents OrgRoot and Child1 already synced)
|
||||||
const child2Result = await this.syncOrgLevel(
|
const child2Result = await this.syncOrgLevel(
|
||||||
queryRunner,
|
queryRunner,
|
||||||
OrgChild2,
|
OrgChild2,
|
||||||
this.child2Repository,
|
this.child2Repository,
|
||||||
drafRevisionId,
|
drafRevisionId,
|
||||||
currentRevisionId,
|
currentRevisionId,
|
||||||
allMappings,
|
allMappings,
|
||||||
orgRootDraft?.id,
|
orgRootDraft?.id,
|
||||||
orgRootCurrent?.id,
|
orgRootCurrent?.id,
|
||||||
);
|
);
|
||||||
allMappings.orgChild2 = child2Result.mapping;
|
allMappings.orgChild2 = child2Result.mapping;
|
||||||
orgSyncStats.orgChild2 = child2Result.counts;
|
orgSyncStats.orgChild2 = child2Result.counts;
|
||||||
|
|
||||||
// Child3 (parents OrgRoot, Child1, Child2 already synced)
|
// Child3 (parents OrgRoot, Child1, Child2 already synced)
|
||||||
const child3Result = await this.syncOrgLevel(
|
const child3Result = await this.syncOrgLevel(
|
||||||
queryRunner,
|
queryRunner,
|
||||||
OrgChild3,
|
OrgChild3,
|
||||||
this.child3Repository,
|
this.child3Repository,
|
||||||
drafRevisionId,
|
drafRevisionId,
|
||||||
currentRevisionId,
|
currentRevisionId,
|
||||||
allMappings,
|
allMappings,
|
||||||
orgRootDraft?.id,
|
orgRootDraft?.id,
|
||||||
orgRootCurrent?.id,
|
orgRootCurrent?.id,
|
||||||
);
|
);
|
||||||
allMappings.orgChild3 = child3Result.mapping;
|
allMappings.orgChild3 = child3Result.mapping;
|
||||||
orgSyncStats.orgChild3 = child3Result.counts;
|
orgSyncStats.orgChild3 = child3Result.counts;
|
||||||
|
|
||||||
// Child4 (parents OrgRoot, Child1, Child2, Child3 already synced)
|
// Child4 (parents OrgRoot, Child1, Child2, Child3 already synced)
|
||||||
const child4Result = await this.syncOrgLevel(
|
const child4Result = await this.syncOrgLevel(
|
||||||
queryRunner,
|
queryRunner,
|
||||||
OrgChild4,
|
OrgChild4,
|
||||||
this.child4Repository,
|
this.child4Repository,
|
||||||
drafRevisionId,
|
drafRevisionId,
|
||||||
currentRevisionId,
|
currentRevisionId,
|
||||||
allMappings,
|
allMappings,
|
||||||
orgRootDraft?.id,
|
orgRootDraft?.id,
|
||||||
orgRootCurrent?.id,
|
orgRootCurrent?.id,
|
||||||
);
|
);
|
||||||
allMappings.orgChild4 = child4Result.mapping;
|
allMappings.orgChild4 = child4Result.mapping;
|
||||||
orgSyncStats.orgChild4 = child4Result.counts;
|
orgSyncStats.orgChild4 = child4Result.counts;
|
||||||
|
|
||||||
// Part 2: Sync position data using new org IDs from Part 1
|
// Part 2: Sync position data using new org IDs from Part 1
|
||||||
// 2.1 Clear current_holderId for affected positions (keep existing logic)
|
// 2.1 Clear current_holderId for affected positions (keep existing logic)
|
||||||
// Get draft organization IDs under the given rootDnaId to find positions to clear
|
// Get draft organization IDs under the given rootDnaId to find positions to clear
|
||||||
const draftOrgIds = {
|
const draftOrgIds = {
|
||||||
orgRoot: [...allMappings.orgRoot.byDraftId.keys()],
|
orgRoot: [...allMappings.orgRoot.byDraftId.keys()],
|
||||||
orgChild1: [...allMappings.orgChild1.byDraftId.keys()],
|
orgChild1: [...allMappings.orgChild1.byDraftId.keys()],
|
||||||
orgChild2: [...allMappings.orgChild2.byDraftId.keys()],
|
orgChild2: [...allMappings.orgChild2.byDraftId.keys()],
|
||||||
orgChild3: [...allMappings.orgChild3.byDraftId.keys()],
|
orgChild3: [...allMappings.orgChild3.byDraftId.keys()],
|
||||||
orgChild4: [...allMappings.orgChild4.byDraftId.keys()],
|
orgChild4: [...allMappings.orgChild4.byDraftId.keys()],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get draft positions that belong to any org under the rootDnaId
|
// Get draft positions that belong to any org under the rootDnaId
|
||||||
const posMasterDraft = await this.posMasterRepository.find({
|
const posMasterDraft = await this.posMasterRepository.find({
|
||||||
where: [
|
where: [
|
||||||
{ orgRevisionId: drafRevisionId, orgRootId: In(draftOrgIds.orgRoot) },
|
{ orgRevisionId: drafRevisionId, orgRootId: In(draftOrgIds.orgRoot) },
|
||||||
{ orgRevisionId: drafRevisionId, orgChild1Id: In(draftOrgIds.orgChild1) },
|
{ orgRevisionId: drafRevisionId, orgChild1Id: In(draftOrgIds.orgChild1) },
|
||||||
{ orgRevisionId: drafRevisionId, orgChild2Id: In(draftOrgIds.orgChild2) },
|
{ orgRevisionId: drafRevisionId, orgChild2Id: In(draftOrgIds.orgChild2) },
|
||||||
{ orgRevisionId: drafRevisionId, orgChild3Id: In(draftOrgIds.orgChild3) },
|
{ orgRevisionId: drafRevisionId, orgChild3Id: In(draftOrgIds.orgChild3) },
|
||||||
{ orgRevisionId: drafRevisionId, orgChild4Id: In(draftOrgIds.orgChild4) },
|
{ orgRevisionId: drafRevisionId, orgChild4Id: In(draftOrgIds.orgChild4) },
|
||||||
],
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (posMasterDraft.length <= 0)
|
||||||
|
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งในโครงสร้างร่าง");
|
||||||
|
|
||||||
|
// Clear current_holderId for positions that will have new holders
|
||||||
|
const nextHolderIds = posMasterDraft
|
||||||
|
.filter((x) => x.next_holderId != null)
|
||||||
|
.map((x) => x.next_holderId) as string[];
|
||||||
|
|
||||||
|
if (nextHolderIds.length > 0) {
|
||||||
|
// FIX: Fetch positions first before updating (to avoid race condition)
|
||||||
|
const posMastersToUpdate = await queryRunner.manager.find(PosMaster, {
|
||||||
|
where: {
|
||||||
|
orgRevisionId: currentRevisionId,
|
||||||
|
current_holderId: In(nextHolderIds),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (posMasterDraft.length <= 0)
|
// Save history BEFORE clearing current_holderId
|
||||||
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งในโครงสร้างร่าง");
|
const historyOps = posMastersToUpdate
|
||||||
|
.filter((x) => x.orgRootId != orgRootCurrent?.id)
|
||||||
// Clear current_holderId for positions that will have new holders
|
.map((pos) => ({
|
||||||
const nextHolderIds = posMasterDraft
|
|
||||||
.filter((x) => x.next_holderId != null)
|
|
||||||
.map((x) => x.next_holderId) as string[];
|
|
||||||
|
|
||||||
if (nextHolderIds.length > 0) {
|
|
||||||
// FIX: Fetch positions first before updating (to avoid race condition)
|
|
||||||
const posMastersToUpdate = await queryRunner.manager.find(PosMaster, {
|
|
||||||
where: {
|
|
||||||
orgRevisionId: currentRevisionId,
|
|
||||||
current_holderId: In(nextHolderIds),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Save history BEFORE clearing current_holderId
|
|
||||||
const historyOps = posMastersToUpdate
|
|
||||||
.filter((x) => x.orgRootId != orgRootCurrent?.id)
|
|
||||||
.map((pos) => ({
|
|
||||||
posMasterDnaId: pos.ancestorDNA,
|
|
||||||
profileId: null,
|
|
||||||
pm: null,
|
|
||||||
}));
|
|
||||||
await BatchSavePosMasterHistoryOfficer(queryRunner, historyOps);
|
|
||||||
|
|
||||||
// Now clear current_holderId
|
|
||||||
await queryRunner.manager.update(
|
|
||||||
PosMaster,
|
|
||||||
{
|
|
||||||
orgRevisionId: currentRevisionId,
|
|
||||||
current_holderId: In(nextHolderIds),
|
|
||||||
},
|
|
||||||
{ current_holderId: null, isSit: false },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2.2 Fetch current positions for comparison
|
|
||||||
// Get current organization IDs from the mappings
|
|
||||||
const currentOrgIds = {
|
|
||||||
orgRoot: [...allMappings.orgRoot.byDraftId.values()],
|
|
||||||
orgChild1: [...allMappings.orgChild1.byDraftId.values()],
|
|
||||||
orgChild2: [...allMappings.orgChild2.byDraftId.values()],
|
|
||||||
orgChild3: [...allMappings.orgChild3.byDraftId.values()],
|
|
||||||
orgChild4: [...allMappings.orgChild4.byDraftId.values()],
|
|
||||||
};
|
|
||||||
|
|
||||||
const posMasterCurrent = await this.posMasterRepository.find({
|
|
||||||
where: [
|
|
||||||
{ orgRevisionId: currentRevisionId, orgRootId: In(currentOrgIds.orgRoot) },
|
|
||||||
{ orgRevisionId: currentRevisionId, orgChild1Id: In(currentOrgIds.orgChild1) },
|
|
||||||
{ orgRevisionId: currentRevisionId, orgChild2Id: In(currentOrgIds.orgChild2) },
|
|
||||||
{ orgRevisionId: currentRevisionId, orgChild3Id: In(currentOrgIds.orgChild3) },
|
|
||||||
{ orgRevisionId: currentRevisionId, orgChild4Id: In(currentOrgIds.orgChild4) },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
// Build lookup map
|
|
||||||
const currentByDNA = new Map(posMasterCurrent.map((p) => [p.ancestorDNA, p]));
|
|
||||||
|
|
||||||
// 2.3 Batch DELETE: positions in current but not in draft
|
|
||||||
const toDelete = posMasterCurrent.filter(
|
|
||||||
(curr) => !posMasterDraft.some((d) => d.ancestorDNA === curr.ancestorDNA),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (toDelete.length > 0) {
|
|
||||||
const toDeleteIds = toDelete.map((p) => p.id);
|
|
||||||
|
|
||||||
// Cascade delete positions first
|
|
||||||
await queryRunner.manager.delete(Position, { posMasterId: In(toDeleteIds) });
|
|
||||||
|
|
||||||
// Then delete posMaster records
|
|
||||||
await queryRunner.manager.delete(PosMaster, toDeleteIds);
|
|
||||||
|
|
||||||
const deleteHistoryOps = toDelete.map((pos) => ({
|
|
||||||
posMasterDnaId: pos.ancestorDNA,
|
posMasterDnaId: pos.ancestorDNA,
|
||||||
profileId: null,
|
profileId: null,
|
||||||
pm: null,
|
pm: null,
|
||||||
}));
|
}));
|
||||||
await BatchSavePosMasterHistoryOfficer(queryRunner, deleteHistoryOps);
|
await BatchSavePosMasterHistoryOfficer(queryRunner, historyOps);
|
||||||
}
|
|
||||||
|
|
||||||
// 2.4 Process draft positions (UPDATE or INSERT)
|
// Now clear current_holderId
|
||||||
const toUpdate: PosMaster[] = [];
|
await queryRunner.manager.update(
|
||||||
const toInsert: any[] = [];
|
PosMaster,
|
||||||
|
{
|
||||||
// Track draft PosMaster ID to current PosMaster ID mapping for position sync
|
orgRevisionId: currentRevisionId,
|
||||||
// Type: Map<draftPosMasterId, [currentPosMasterId, nextHolderId]>
|
current_holderId: In(nextHolderIds),
|
||||||
const posMasterMapping: Map<string, [string, string | null | undefined]> = new Map();
|
},
|
||||||
|
{ current_holderId: null, isSit: false },
|
||||||
for (const draftPos of posMasterDraft) {
|
|
||||||
const current = currentByDNA.get(draftPos.ancestorDNA);
|
|
||||||
|
|
||||||
// Map organization IDs using new IDs from Part 1
|
|
||||||
const orgRootId = this.resolveOrgId(draftPos.orgRootId ?? null, allMappings.orgRoot);
|
|
||||||
const orgChild1Id = this.resolveOrgId(
|
|
||||||
draftPos.orgChild1Id ?? null,
|
|
||||||
allMappings.orgChild1,
|
|
||||||
);
|
|
||||||
const orgChild2Id = this.resolveOrgId(
|
|
||||||
draftPos.orgChild2Id ?? null,
|
|
||||||
allMappings.orgChild2,
|
|
||||||
);
|
|
||||||
const orgChild3Id = this.resolveOrgId(
|
|
||||||
draftPos.orgChild3Id ?? null,
|
|
||||||
allMappings.orgChild3,
|
|
||||||
);
|
|
||||||
const orgChild4Id = this.resolveOrgId(
|
|
||||||
draftPos.orgChild4Id ?? null,
|
|
||||||
allMappings.orgChild4,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (current) {
|
|
||||||
// UPDATE existing position
|
|
||||||
Object.assign(current, {
|
|
||||||
createdAt: draftPos.createdAt,
|
|
||||||
createdUserId: draftPos.createdUserId,
|
|
||||||
createdFullName: draftPos.createdFullName,
|
|
||||||
lastUpdatedAt: new Date(),
|
|
||||||
lastUpdateUserId: request.user.sub,
|
|
||||||
lastUpdateFullName: request.user.name,
|
|
||||||
posMasterNoPrefix: draftPos.posMasterNoPrefix,
|
|
||||||
posMasterNoSuffix: draftPos.posMasterNoSuffix,
|
|
||||||
posMasterNo: draftPos.posMasterNo,
|
|
||||||
posMasterOrder: draftPos.posMasterOrder,
|
|
||||||
orgRootId,
|
|
||||||
orgChild1Id,
|
|
||||||
orgChild2Id,
|
|
||||||
orgChild3Id,
|
|
||||||
orgChild4Id,
|
|
||||||
current_holderId: draftPos.next_holderId,
|
|
||||||
isSit: draftPos.isSit,
|
|
||||||
reason: draftPos.reason,
|
|
||||||
isDirector: draftPos.isDirector,
|
|
||||||
isStaff: draftPos.isStaff,
|
|
||||||
positionSign: draftPos.positionSign,
|
|
||||||
statusReport: "DONE",
|
|
||||||
isCondition: draftPos.isCondition,
|
|
||||||
conditionReason: draftPos.conditionReason,
|
|
||||||
});
|
|
||||||
toUpdate.push(current);
|
|
||||||
|
|
||||||
if (draftPos.next_holderId === null) {
|
|
||||||
await SavePosMasterHistoryOfficer(queryRunner, draftPos.ancestorDNA, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track mapping for position sync
|
|
||||||
posMasterMapping.set(draftPos.id, [current.id, draftPos.next_holderId]);
|
|
||||||
} else {
|
|
||||||
// INSERT new position
|
|
||||||
const newPosMaster = queryRunner.manager.create(PosMaster, {
|
|
||||||
...draftPos,
|
|
||||||
id: undefined,
|
|
||||||
orgRevisionId: currentRevisionId,
|
|
||||||
orgRootId,
|
|
||||||
orgChild1Id,
|
|
||||||
orgChild2Id,
|
|
||||||
orgChild3Id,
|
|
||||||
orgChild4Id,
|
|
||||||
current_holderId: draftPos.next_holderId,
|
|
||||||
statusReport: "DONE",
|
|
||||||
});
|
|
||||||
|
|
||||||
toInsert.push(newPosMaster);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Batch save updates and inserts
|
|
||||||
if (toUpdate.length > 0) {
|
|
||||||
await queryRunner.manager.save(toUpdate);
|
|
||||||
}
|
|
||||||
if (toInsert.length > 0) {
|
|
||||||
const saved = await queryRunner.manager.save(toInsert);
|
|
||||||
|
|
||||||
// Track mapping for newly inserted posMasters
|
|
||||||
// saved is an array, map each to its draft ID
|
|
||||||
if (Array.isArray(saved)) {
|
|
||||||
for (let i = 0; i < saved.length; i++) {
|
|
||||||
const draftPos = posMasterDraft.filter((d) => !currentByDNA.has(d.ancestorDNA))[i];
|
|
||||||
if (draftPos && saved[i]) {
|
|
||||||
posMasterMapping.set(draftPos.id, [saved[i].id, draftPos.next_holderId]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2.5 Sync positions table for all affected posMasters (BATCH operation for performance)
|
|
||||||
const positionSyncStats = await this.syncAllPositionsBatch(
|
|
||||||
queryRunner,
|
|
||||||
posMasterMapping,
|
|
||||||
drafRevisionId,
|
|
||||||
currentRevisionId,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Build comprehensive summary
|
|
||||||
const summary = {
|
|
||||||
message: "ย้ายโครงสร้างสำเร็จ",
|
|
||||||
organization: {
|
|
||||||
orgRoot: orgSyncStats.orgRoot,
|
|
||||||
orgChild1: orgSyncStats.orgChild1,
|
|
||||||
orgChild2: orgSyncStats.orgChild2,
|
|
||||||
orgChild3: orgSyncStats.orgChild3,
|
|
||||||
orgChild4: orgSyncStats.orgChild4,
|
|
||||||
},
|
|
||||||
positionMaster: {
|
|
||||||
deleted: toDelete.length,
|
|
||||||
updated: toUpdate.length,
|
|
||||||
inserted: toInsert.length,
|
|
||||||
},
|
|
||||||
position: positionSyncStats,
|
|
||||||
};
|
|
||||||
|
|
||||||
await queryRunner.commitTransaction();
|
|
||||||
return new HttpSuccess(summary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new HttpSuccess({});
|
// 2.2 Fetch current positions for comparison
|
||||||
|
// Get current organization IDs from the mappings
|
||||||
|
const currentOrgIds = {
|
||||||
|
orgRoot: [...allMappings.orgRoot.byDraftId.values()],
|
||||||
|
orgChild1: [...allMappings.orgChild1.byDraftId.values()],
|
||||||
|
orgChild2: [...allMappings.orgChild2.byDraftId.values()],
|
||||||
|
orgChild3: [...allMappings.orgChild3.byDraftId.values()],
|
||||||
|
orgChild4: [...allMappings.orgChild4.byDraftId.values()],
|
||||||
|
};
|
||||||
|
|
||||||
|
const posMasterCurrent = await this.posMasterRepository.find({
|
||||||
|
where: [
|
||||||
|
{ orgRevisionId: currentRevisionId, orgRootId: In(currentOrgIds.orgRoot) },
|
||||||
|
{ orgRevisionId: currentRevisionId, orgChild1Id: In(currentOrgIds.orgChild1) },
|
||||||
|
{ orgRevisionId: currentRevisionId, orgChild2Id: In(currentOrgIds.orgChild2) },
|
||||||
|
{ orgRevisionId: currentRevisionId, orgChild3Id: In(currentOrgIds.orgChild3) },
|
||||||
|
{ orgRevisionId: currentRevisionId, orgChild4Id: In(currentOrgIds.orgChild4) },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build lookup map
|
||||||
|
const currentByDNA = new Map(posMasterCurrent.map((p) => [p.ancestorDNA, p]));
|
||||||
|
|
||||||
|
// 2.3 Batch DELETE: positions in current but not in draft
|
||||||
|
const toDelete = posMasterCurrent.filter(
|
||||||
|
(curr) => !posMasterDraft.some((d) => d.ancestorDNA === curr.ancestorDNA),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (toDelete.length > 0) {
|
||||||
|
const toDeleteIds = toDelete.map((p) => p.id);
|
||||||
|
|
||||||
|
// Cascade delete positions first
|
||||||
|
await queryRunner.manager.delete(Position, { posMasterId: In(toDeleteIds) });
|
||||||
|
|
||||||
|
// Then delete posMaster records
|
||||||
|
await queryRunner.manager.delete(PosMaster, toDeleteIds);
|
||||||
|
|
||||||
|
const deleteHistoryOps = toDelete.map((pos) => ({
|
||||||
|
posMasterDnaId: pos.ancestorDNA,
|
||||||
|
profileId: null,
|
||||||
|
pm: null,
|
||||||
|
}));
|
||||||
|
await BatchSavePosMasterHistoryOfficer(queryRunner, deleteHistoryOps);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.4 Process draft positions (UPDATE or INSERT)
|
||||||
|
const toUpdate: PosMaster[] = [];
|
||||||
|
const toInsert: any[] = [];
|
||||||
|
|
||||||
|
// Track draft PosMaster ID to current PosMaster ID mapping for position sync
|
||||||
|
// Type: Map<draftPosMasterId, [currentPosMasterId, nextHolderId]>
|
||||||
|
const posMasterMapping: Map<string, [string, string | null | undefined]> = new Map();
|
||||||
|
|
||||||
|
for (const draftPos of posMasterDraft) {
|
||||||
|
const current = currentByDNA.get(draftPos.ancestorDNA);
|
||||||
|
|
||||||
|
// Map organization IDs using new IDs from Part 1
|
||||||
|
const orgRootId = this.resolveOrgId(draftPos.orgRootId ?? null, allMappings.orgRoot);
|
||||||
|
const orgChild1Id = this.resolveOrgId(draftPos.orgChild1Id ?? null, allMappings.orgChild1);
|
||||||
|
const orgChild2Id = this.resolveOrgId(draftPos.orgChild2Id ?? null, allMappings.orgChild2);
|
||||||
|
const orgChild3Id = this.resolveOrgId(draftPos.orgChild3Id ?? null, allMappings.orgChild3);
|
||||||
|
const orgChild4Id = this.resolveOrgId(draftPos.orgChild4Id ?? null, allMappings.orgChild4);
|
||||||
|
|
||||||
|
if (current) {
|
||||||
|
// UPDATE existing position
|
||||||
|
Object.assign(current, {
|
||||||
|
createdAt: draftPos.createdAt,
|
||||||
|
createdUserId: draftPos.createdUserId,
|
||||||
|
createdFullName: draftPos.createdFullName,
|
||||||
|
lastUpdatedAt: new Date(),
|
||||||
|
lastUpdateUserId: request.user.sub,
|
||||||
|
lastUpdateFullName: request.user.name,
|
||||||
|
posMasterNoPrefix: draftPos.posMasterNoPrefix,
|
||||||
|
posMasterNoSuffix: draftPos.posMasterNoSuffix,
|
||||||
|
posMasterNo: draftPos.posMasterNo,
|
||||||
|
posMasterOrder: draftPos.posMasterOrder,
|
||||||
|
orgRootId,
|
||||||
|
orgChild1Id,
|
||||||
|
orgChild2Id,
|
||||||
|
orgChild3Id,
|
||||||
|
orgChild4Id,
|
||||||
|
current_holderId: draftPos.next_holderId,
|
||||||
|
isSit: draftPos.isSit,
|
||||||
|
reason: draftPos.reason,
|
||||||
|
isDirector: draftPos.isDirector,
|
||||||
|
isStaff: draftPos.isStaff,
|
||||||
|
positionSign: draftPos.positionSign,
|
||||||
|
statusReport: "DONE",
|
||||||
|
isCondition: draftPos.isCondition,
|
||||||
|
conditionReason: draftPos.conditionReason,
|
||||||
|
});
|
||||||
|
toUpdate.push(current);
|
||||||
|
|
||||||
|
if (draftPos.next_holderId === null) {
|
||||||
|
await SavePosMasterHistoryOfficer(queryRunner, draftPos.ancestorDNA, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track mapping for position sync
|
||||||
|
posMasterMapping.set(draftPos.id, [current.id, draftPos.next_holderId]);
|
||||||
|
} else {
|
||||||
|
// INSERT new position
|
||||||
|
const newPosMaster = queryRunner.manager.create(PosMaster, {
|
||||||
|
...draftPos,
|
||||||
|
id: undefined,
|
||||||
|
orgRevisionId: currentRevisionId,
|
||||||
|
orgRootId,
|
||||||
|
orgChild1Id,
|
||||||
|
orgChild2Id,
|
||||||
|
orgChild3Id,
|
||||||
|
orgChild4Id,
|
||||||
|
current_holderId: draftPos.next_holderId,
|
||||||
|
statusReport: "DONE",
|
||||||
|
});
|
||||||
|
|
||||||
|
toInsert.push(newPosMaster);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batch save updates and inserts
|
||||||
|
if (toUpdate.length > 0) {
|
||||||
|
await queryRunner.manager.save(toUpdate);
|
||||||
|
}
|
||||||
|
if (toInsert.length > 0) {
|
||||||
|
const saved = await queryRunner.manager.save(toInsert);
|
||||||
|
|
||||||
|
// Track mapping for newly inserted posMasters
|
||||||
|
// saved is an array, map each to its draft ID
|
||||||
|
if (Array.isArray(saved)) {
|
||||||
|
for (let i = 0; i < saved.length; i++) {
|
||||||
|
const draftPos = posMasterDraft.filter((d) => !currentByDNA.has(d.ancestorDNA))[i];
|
||||||
|
if (draftPos && saved[i]) {
|
||||||
|
posMasterMapping.set(draftPos.id, [saved[i].id, draftPos.next_holderId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.5 Sync positions table for all affected posMasters (BATCH operation for performance)
|
||||||
|
const positionSyncStats = await this.syncAllPositionsBatch(
|
||||||
|
queryRunner,
|
||||||
|
posMasterMapping,
|
||||||
|
drafRevisionId,
|
||||||
|
currentRevisionId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Build comprehensive summary
|
||||||
|
const summary = {
|
||||||
|
message: "ย้ายโครงสร้างสำเร็จ",
|
||||||
|
organization: {
|
||||||
|
orgRoot: orgSyncStats.orgRoot,
|
||||||
|
orgChild1: orgSyncStats.orgChild1,
|
||||||
|
orgChild2: orgSyncStats.orgChild2,
|
||||||
|
orgChild3: orgSyncStats.orgChild3,
|
||||||
|
orgChild4: orgSyncStats.orgChild4,
|
||||||
|
},
|
||||||
|
positionMaster: {
|
||||||
|
deleted: toDelete.length,
|
||||||
|
updated: toUpdate.length,
|
||||||
|
inserted: toInsert.length,
|
||||||
|
},
|
||||||
|
position: positionSyncStats,
|
||||||
|
};
|
||||||
|
|
||||||
|
await queryRunner.commitTransaction();
|
||||||
|
return new HttpSuccess(summary);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error moving draft to current:", error);
|
console.error("Error moving draft to current:", error);
|
||||||
await queryRunner.rollbackTransaction();
|
await queryRunner.rollbackTransaction();
|
||||||
|
|
@ -8465,7 +8463,7 @@ export class OrganizationController extends Controller {
|
||||||
for (const draft of toInsert) {
|
for (const draft of toInsert) {
|
||||||
const newNode: any = queryRunner.manager.create(entityClass, {
|
const newNode: any = queryRunner.manager.create(entityClass, {
|
||||||
...draft,
|
...draft,
|
||||||
id: undefined,
|
id: draft.id,
|
||||||
orgRevisionId: currentRevisionId,
|
orgRevisionId: currentRevisionId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue