Merge branch 'feat/org-move-draf-current' into develop
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m47s

* feat/org-move-draf-current:
  fix time out
This commit is contained in:
Warunee Tamkoo 2026-02-11 13:11:10 +07:00
commit 17760212d1

View file

@ -8199,25 +8199,13 @@ export class OrganizationController extends Controller {
}
}
// 2.5 Sync positions table for all affected posMasters
const positionSyncStats: { deleted: number; updated: number; inserted: number } = {
deleted: 0,
updated: 0,
inserted: 0,
};
for (const [draftPosMasterId, [currentPosMasterId, nextHolderId]] of posMasterMapping) {
const stats = await this.syncPositionsForPosMaster(
// 2.5 Sync positions table for all affected posMasters (BATCH operation for performance)
const positionSyncStats = await this.syncAllPositionsBatch(
queryRunner,
draftPosMasterId,
currentPosMasterId,
posMasterMapping,
drafRevisionId,
currentRevisionId,
nextHolderId,
);
positionSyncStats.deleted += stats.deleted;
positionSyncStats.updated += stats.updated;
positionSyncStats.inserted += stats.inserted;
}
// Build comprehensive summary
const summary = {
@ -8514,13 +8502,15 @@ export class OrganizationController extends Controller {
/**
* Helper function: Sync positions for a PosMaster
* Handles DELETE/UPDATE/INSERT for positions associated with a posMaster
*
* @deprecated Kept as fallback - use syncAllPositionsBatch for better performance
*/
private async syncPositionsForPosMaster(
queryRunner: any,
draftPosMasterId: string,
currentPosMasterId: string,
draftRevisionId: string,
currentRevisionId: string,
_draftRevisionId: string,
_currentRevisionId: string,
nextHolderId: string | null | undefined,
): Promise<{ deleted: number; updated: number; inserted: number }> {
// Fetch draft and current positions for this posMaster
@ -8609,4 +8599,171 @@ export class OrganizationController extends Controller {
return { deleted: toDelete.length, updated: updatedCount, inserted: insertedCount };
}
/**
* Batch version: Sync positions for ALL posMasters in a single operation
* This significantly reduces database round trips for large organizations
*/
private async syncAllPositionsBatch(
queryRunner: any,
posMasterMapping: Map<string, [string, string | null | undefined]>,
_draftRevisionId: string,
_currentRevisionId: string,
): 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);
if (draftPosMasterIds.length === 0) {
return { deleted: 0, updated: 0, inserted: 0 };
}
// Fetch ALL positions for ALL posMasters in just 2 queries
const [allDraftPositions, allCurrentPositions] = await Promise.all([
queryRunner.manager.find(Position, {
where: {
posMasterId: In(draftPosMasterIds),
},
order: { orderNo: "ASC" },
}),
queryRunner.manager.find(Position, {
where: {
posMasterId: In(currentPosMasterIds),
},
}),
]);
// Group positions by posMasterId for processing
const draftPositionsByMaster = new Map<string, any[]>();
for (const pos of allDraftPositions) {
if (!draftPositionsByMaster.has(pos.posMasterId)) {
draftPositionsByMaster.set(pos.posMasterId, []);
}
draftPositionsByMaster.get(pos.posMasterId)!.push(pos);
}
const currentPositionsByMaster = new Map<string, any[]>();
for (const pos of allCurrentPositions) {
if (!currentPositionsByMaster.has(pos.posMasterId)) {
currentPositionsByMaster.set(pos.posMasterId, []);
}
currentPositionsByMaster.get(pos.posMasterId)!.push(pos);
}
// Collect all operations
const allToDelete: string[] = [];
const allToUpdate: Array<{ id: string; data: any }> = [];
const allToInsert: Array<any> = [];
const profileUpdates: Map<string, any> = new Map();
// Process each posMaster mapping
for (const [draftPosMasterId, [currentPosMasterId, nextHolderId]] of posMasterMapping) {
const draftPositions = draftPositionsByMaster.get(draftPosMasterId) || [];
const currentPositions = currentPositionsByMaster.get(currentPosMasterId) || [];
// If no draft positions, mark all current positions for deletion
if (draftPositions.length === 0) {
allToDelete.push(...currentPositions.map((p: any) => p.id));
continue;
}
// Build map for tracking
const currentByOrderNo = new Map(currentPositions.map((p: any) => [p.orderNo, p]));
const draftOrderNos = new Set(draftPositions.map((p: any) => p.orderNo));
// Mark for deletion: current positions not in draft (by orderNo)
for (const currentPos of currentPositions) {
if (!draftOrderNos.has(currentPos.orderNo)) {
allToDelete.push(currentPos.id);
}
}
// Process UPDATE and INSERT
for (const draftPos of draftPositions) {
const current = currentByOrderNo.get(draftPos.orderNo);
if (current) {
// UPDATE existing position - collect for batch update
allToUpdate.push({
id: current.id,
data: {
positionName: draftPos.positionName,
positionField: draftPos.positionField,
posTypeId: draftPos.posTypeId,
posLevelId: draftPos.posLevelId,
posExecutiveId: draftPos.posExecutiveId,
positionExecutiveField: draftPos.positionExecutiveField,
positionArea: draftPos.positionArea,
isSpecial: draftPos.isSpecial,
orderNo: draftPos.orderNo,
positionIsSelected: draftPos.positionIsSelected,
lastUpdateFullName: draftPos.lastUpdateFullName,
lastUpdatedAt: new Date(),
},
});
} else {
// INSERT new position - collect for batch insert
allToInsert.push({
...draftPos,
id: undefined,
posMasterId: currentPosMasterId,
});
}
// Collect profile update for the selected position
if (nextHolderId != null && draftPos.positionIsSelected) {
profileUpdates.set(nextHolderId, {
position: draftPos.positionName,
posTypeId: draftPos.posTypeId,
posLevelId: draftPos.posLevelId,
});
}
}
}
// Execute bulk operations
let deletedCount = 0;
let updatedCount = 0;
let insertedCount = 0;
// Bulk DELETE
if (allToDelete.length > 0) {
await queryRunner.manager.delete(Position, allToDelete);
deletedCount = allToDelete.length;
}
// Bulk UPDATE (batch by 500 to avoid query size limits)
if (allToUpdate.length > 0) {
const batchSize = 500;
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))
);
}
updatedCount = allToUpdate.length;
}
// Bulk INSERT
if (allToInsert.length > 0) {
const batchSize = 500;
for (let i = 0; i < allToInsert.length; i += batchSize) {
const batch = allToInsert.slice(i, i + batchSize);
await queryRunner.manager.save(Position, batch);
}
insertedCount = allToInsert.length;
}
// Bulk UPDATE profiles
if (profileUpdates.size > 0) {
const profileUpdateEntries = Array.from(profileUpdates.entries());
await Promise.all(
profileUpdateEntries.map(([profileId, data]) =>
queryRunner.manager.update(Profile, profileId, data)
)
);
}
return { deleted: deletedCount, updated: updatedCount, inserted: insertedCount };
}
}