import { AppDataSource } from "../database/data-source"; import { PosMaster } from "../entities/PosMaster"; // Type definitions for position counts export interface PositionCountData { totalCount: number; currentVacantCount: number; nextVacantCount: number; } export interface PositionCountsByNode { orgRootMap: Map; orgChild1Map: Map; orgChild2Map: Map; orgChild3Map: Map; orgChild4Map: Map; } // Optimized function using SQL aggregation with GROUP BY // This is much more efficient than loading all records into memory export async function getPositionCountsAggregated(orgRevisionId: string): Promise { const posRepo = AppDataSource.getRepository(PosMaster); // Helper to build map from query results const buildMap = (results: any[]) => { const map = new Map(); for (const row of results) { if (row.nodeId) { map.set(row.nodeId, { totalCount: parseInt(row.totalCount) || 0, currentVacantCount: parseInt(row.currentVacantCount) || 0, nextVacantCount: parseInt(row.nextVacantCount) || 0, }); } } return map; }; // Execute all aggregation queries in parallel const [ orgRootResults, orgChild1Results, orgChild2Results, orgChild3Results, orgChild4Results, ] = await Promise.all([ // Level 0: orgRoot posRepo .createQueryBuilder("pos") .select("pos.orgRootId", "nodeId") .addSelect("COUNT(*)", "totalCount") .addSelect("SUM(CASE WHEN pos.current_holderId IS NULL OR pos.current_holderId = '' THEN 1 ELSE 0 END)", "currentVacantCount") .addSelect("SUM(CASE WHEN pos.next_holderId IS NULL OR pos.next_holderId = '' THEN 1 ELSE 0 END)", "nextVacantCount") .where("pos.orgRevisionId = :orgRevisionId", { orgRevisionId }) .andWhere("pos.orgRootId IS NOT NULL") .groupBy("pos.orgRootId") .getRawMany(), // Level 1: orgChild1 posRepo .createQueryBuilder("pos") .select("pos.orgChild1Id", "nodeId") .addSelect("COUNT(*)", "totalCount") .addSelect("SUM(CASE WHEN pos.current_holderId IS NULL OR pos.current_holderId = '' THEN 1 ELSE 0 END)", "currentVacantCount") .addSelect("SUM(CASE WHEN pos.next_holderId IS NULL OR pos.next_holderId = '' THEN 1 ELSE 0 END)", "nextVacantCount") .where("pos.orgRevisionId = :orgRevisionId", { orgRevisionId }) .andWhere("pos.orgChild1Id IS NOT NULL") .groupBy("pos.orgChild1Id") .getRawMany(), // Level 2: orgChild2 posRepo .createQueryBuilder("pos") .select("pos.orgChild2Id", "nodeId") .addSelect("COUNT(*)", "totalCount") .addSelect("SUM(CASE WHEN pos.current_holderId IS NULL OR pos.current_holderId = '' THEN 1 ELSE 0 END)", "currentVacantCount") .addSelect("SUM(CASE WHEN pos.next_holderId IS NULL OR pos.next_holderId = '' THEN 1 ELSE 0 END)", "nextVacantCount") .where("pos.orgRevisionId = :orgRevisionId", { orgRevisionId }) .andWhere("pos.orgChild2Id IS NOT NULL") .groupBy("pos.orgChild2Id") .getRawMany(), // Level 3: orgChild3 posRepo .createQueryBuilder("pos") .select("pos.orgChild3Id", "nodeId") .addSelect("COUNT(*)", "totalCount") .addSelect("SUM(CASE WHEN pos.current_holderId IS NULL OR pos.current_holderId = '' THEN 1 ELSE 0 END)", "currentVacantCount") .addSelect("SUM(CASE WHEN pos.next_holderId IS NULL OR pos.next_holderId = '' THEN 1 ELSE 0 END)", "nextVacantCount") .where("pos.orgRevisionId = :orgRevisionId", { orgRevisionId }) .andWhere("pos.orgChild3Id IS NOT NULL") .groupBy("pos.orgChild3Id") .getRawMany(), // Level 4: orgChild4 posRepo .createQueryBuilder("pos") .select("pos.orgChild4Id", "nodeId") .addSelect("COUNT(*)", "totalCount") .addSelect("SUM(CASE WHEN pos.current_holderId IS NULL OR pos.current_holderId = '' THEN 1 ELSE 0 END)", "currentVacantCount") .addSelect("SUM(CASE WHEN pos.next_holderId IS NULL OR pos.next_holderId = '' THEN 1 ELSE 0 END)", "nextVacantCount") .where("pos.orgRevisionId = :orgRevisionId", { orgRevisionId }) .andWhere("pos.orgChild4Id IS NOT NULL") .groupBy("pos.orgChild4Id") .getRawMany(), ]); return { orgRootMap: buildMap(orgRootResults), orgChild1Map: buildMap(orgChild1Results), orgChild2Map: buildMap(orgChild2Results), orgChild3Map: buildMap(orgChild3Results), orgChild4Map: buildMap(orgChild4Results), }; } // Legacy function - kept for backward compatibility // DEPRECATED: Use getPositionCountsAggregated instead export async function getPositionCounts(orgRevisionId: string) { // Query all posMaster data for this revision with aggregation const rawData = await AppDataSource.getRepository(PosMaster) .createQueryBuilder("pos") .select([ "pos.orgRootId", "pos.orgChild1Id", "pos.orgChild2Id", "pos.orgChild3Id", "pos.orgChild4Id", "pos.current_holderId", "pos.next_holderId", ]) .where("pos.orgRevisionId = :orgRevisionId", { orgRevisionId }) .getMany(); // Helper to check if value is null or empty string const isNull = (val: any) => val === null || val === ""; // Build maps for each level const orgRootMap = new Map(); const orgChild1Map = new Map(); const orgChild2Map = new Map(); const orgChild3Map = new Map(); const orgChild4Map = new Map(); // Nested maps for root positions (positions at specific levels) const rootPosMap = new Map(); for (const pos of rawData) { const orgRootId = pos.orgRootId || "NULL"; const orgChild1Id = pos.orgChild1Id || "NULL"; const orgChild2Id = pos.orgChild2Id || "NULL"; const orgChild3Id = pos.orgChild3Id || "NULL"; const orgChild4Id = pos.orgChild4Id || "NULL"; // Level 0 (orgRoot) counts if (!orgRootMap.has(orgRootId)) { orgRootMap.set(orgRootId, { totalPosition: 0, totalPositionCurrentUse: 0, totalPositionCurrentVacant: 0, totalPositionNextUse: 0, totalPositionNextVacant: 0, }); } const rootCounts = orgRootMap.get(orgRootId); rootCounts.totalPosition++; if (!isNull(pos.current_holderId)) rootCounts.totalPositionCurrentUse++; else rootCounts.totalPositionCurrentVacant++; if (!isNull(pos.next_holderId)) rootCounts.totalPositionNextUse++; else rootCounts.totalPositionNextVacant++; // Level 1 (orgChild1) counts if (!isNull(pos.orgChild1Id)) { if (!orgChild1Map.has(orgChild1Id)) { orgChild1Map.set(orgChild1Id, { totalPosition: 0, totalPositionCurrentUse: 0, totalPositionCurrentVacant: 0, totalPositionNextUse: 0, totalPositionNextVacant: 0, }); } const child1Counts = orgChild1Map.get(orgChild1Id); child1Counts.totalPosition++; if (!isNull(pos.current_holderId)) child1Counts.totalPositionCurrentUse++; else child1Counts.totalPositionCurrentVacant++; if (!isNull(pos.next_holderId)) child1Counts.totalPositionNextUse++; else child1Counts.totalPositionNextVacant++; } // Level 2 (orgChild2) counts if (!isNull(pos.orgChild2Id)) { if (!orgChild2Map.has(orgChild2Id)) { orgChild2Map.set(orgChild2Id, { totalPosition: 0, totalPositionCurrentUse: 0, totalPositionCurrentVacant: 0, totalPositionNextUse: 0, totalPositionNextVacant: 0, }); } const child2Counts = orgChild2Map.get(orgChild2Id); child2Counts.totalPosition++; if (!isNull(pos.current_holderId)) child2Counts.totalPositionCurrentUse++; else child2Counts.totalPositionCurrentVacant++; if (!isNull(pos.next_holderId)) child2Counts.totalPositionNextUse++; else child2Counts.totalPositionNextVacant++; } // Level 3 (orgChild3) counts if (!isNull(pos.orgChild3Id)) { if (!orgChild3Map.has(orgChild3Id)) { orgChild3Map.set(orgChild3Id, { totalPosition: 0, totalPositionCurrentUse: 0, totalPositionCurrentVacant: 0, totalPositionNextUse: 0, totalPositionNextVacant: 0, }); } const child3Counts = orgChild3Map.get(orgChild3Id); child3Counts.totalPosition++; if (!isNull(pos.current_holderId)) child3Counts.totalPositionCurrentUse++; else child3Counts.totalPositionCurrentVacant++; if (!isNull(pos.next_holderId)) child3Counts.totalPositionNextUse++; else child3Counts.totalPositionNextVacant++; } // Level 4 (orgChild4) counts if (!isNull(pos.orgChild4Id)) { if (!orgChild4Map.has(orgChild4Id)) { orgChild4Map.set(orgChild4Id, { totalPosition: 0, totalPositionCurrentUse: 0, totalPositionCurrentVacant: 0, totalPositionNextUse: 0, totalPositionNextVacant: 0, }); } const child4Counts = orgChild4Map.get(orgChild4Id); child4Counts.totalPosition++; if (!isNull(pos.current_holderId)) child4Counts.totalPositionCurrentUse++; else child4Counts.totalPositionCurrentVacant++; if (!isNull(pos.next_holderId)) child4Counts.totalPositionNextUse++; else child4Counts.totalPositionNextVacant++; } // Root position counts (positions at specific hierarchy levels) // For orgRoot level (all children are null) const rootLevelKey = orgRootId; if (!rootPosMap.has(rootLevelKey)) { rootPosMap.set(rootLevelKey, { totalRootPosition: 0, totalRootPositionCurrentUse: 0, totalRootPositionCurrentVacant: 0, totalRootPositionNextUse: 0, totalRootPositionNextVacant: 0, }); } if (isNull(pos.orgChild1Id) && isNull(pos.orgChild2Id) && isNull(pos.orgChild3Id) && isNull(pos.orgChild4Id)) { const rootLevelCounts = rootPosMap.get(rootLevelKey); rootLevelCounts.totalRootPosition++; if (!isNull(pos.current_holderId)) rootLevelCounts.totalRootPositionCurrentUse++; else rootLevelCounts.totalRootPositionCurrentVacant++; if (!isNull(pos.next_holderId)) rootLevelCounts.totalRootPositionNextUse++; else rootLevelCounts.totalRootPositionNextVacant++; } // For orgChild1 level if (!isNull(pos.orgChild1Id)) { const child1LevelKey = `${orgRootId}-${pos.orgChild1Id}`; if (!rootPosMap.has(child1LevelKey)) { rootPosMap.set(child1LevelKey, { totalRootPosition: 0, totalRootPositionCurrentUse: 0, totalRootPositionCurrentVacant: 0, totalRootPositionNextUse: 0, totalRootPositionNextVacant: 0, }); } if (isNull(pos.orgChild2Id) && isNull(pos.orgChild3Id) && isNull(pos.orgChild4Id)) { const child1LevelCounts = rootPosMap.get(child1LevelKey); child1LevelCounts.totalRootPosition++; if (!isNull(pos.current_holderId)) child1LevelCounts.totalRootPositionCurrentUse++; else child1LevelCounts.totalRootPositionCurrentVacant++; if (!isNull(pos.next_holderId)) child1LevelCounts.totalRootPositionNextUse++; else child1LevelCounts.totalRootPositionNextVacant++; } } // For orgChild2 level if (!isNull(pos.orgChild2Id)) { const child2LevelKey = `${orgRootId}-${pos.orgChild1Id}-${pos.orgChild2Id}`; if (!rootPosMap.has(child2LevelKey)) { rootPosMap.set(child2LevelKey, { totalRootPosition: 0, totalRootPositionCurrentUse: 0, totalRootPositionCurrentVacant: 0, totalRootPositionNextUse: 0, totalRootPositionNextVacant: 0, }); } if (isNull(pos.orgChild3Id) && isNull(pos.orgChild4Id)) { const child2LevelCounts = rootPosMap.get(child2LevelKey); child2LevelCounts.totalRootPosition++; if (!isNull(pos.current_holderId)) child2LevelCounts.totalRootPositionCurrentUse++; else child2LevelCounts.totalRootPositionCurrentVacant++; if (!isNull(pos.next_holderId)) child2LevelCounts.totalRootPositionNextUse++; else child2LevelCounts.totalRootPositionNextVacant++; } } // For orgChild3 level if (!isNull(pos.orgChild3Id)) { const child3LevelKey = `${orgRootId}-${pos.orgChild1Id}-${pos.orgChild2Id}-${orgChild3Id}`; if (!rootPosMap.has(child3LevelKey)) { rootPosMap.set(child3LevelKey, { totalRootPosition: 0, totalRootPositionCurrentUse: 0, totalRootPositionCurrentVacant: 0, totalRootPositionNextUse: 0, totalRootPositionNextVacant: 0, }); } if (isNull(pos.orgChild4Id)) { const child3LevelCounts = rootPosMap.get(child3LevelKey); child3LevelCounts.totalRootPosition++; if (!isNull(pos.current_holderId)) child3LevelCounts.totalRootPositionCurrentUse++; else child3LevelCounts.totalRootPositionCurrentVacant++; if (!isNull(pos.next_holderId)) child3LevelCounts.totalRootPositionNextUse++; else child3LevelCounts.totalRootPositionNextVacant++; } } // For orgChild4 level if (!isNull(pos.orgChild4Id)) { const child4LevelKey = `${orgRootId}-${pos.orgChild1Id}-${pos.orgChild2Id}-${orgChild3Id}-${pos.orgChild4Id}`; if (!rootPosMap.has(child4LevelKey)) { rootPosMap.set(child4LevelKey, { totalRootPosition: 0, totalRootPositionCurrentUse: 0, totalRootPositionCurrentVacant: 0, totalRootPositionNextUse: 0, totalRootPositionNextVacant: 0, }); } const child4LevelCounts = rootPosMap.get(child4LevelKey); child4LevelCounts.totalRootPosition++; if (!isNull(pos.current_holderId)) child4LevelCounts.totalRootPositionCurrentUse++; else child4LevelCounts.totalRootPositionCurrentVacant++; if (!isNull(pos.next_holderId)) child4LevelCounts.totalRootPositionNextUse++; else child4LevelCounts.totalRootPositionNextVacant++; } } return { orgRootMap, orgChild1Map, orgChild2Map, orgChild3Map, orgChild4Map, rootPosMap, }; } // Helper function to get counts from maps with defaults export function getCounts(map: Map, key: string) { return ( map.get(key) || { totalPosition: 0, totalPositionCurrentUse: 0, totalPositionCurrentVacant: 0, totalPositionNextUse: 0, totalPositionNextVacant: 0, } ); } // Helper function to get root position counts export function getRootCounts(map: Map, key: string) { return ( map.get(key) || { totalRootPosition: 0, totalRootPositionCurrentUse: 0, totalRootPositionCurrentVacant: 0, totalRootPositionNextUse: 0, totalRootPositionNextVacant: 0, } ); } // Helper function to get position counts from aggregated maps with defaults export function getPositionCount( map: Map, key: string | null | undefined ): PositionCountData { if (!key) return { totalCount: 0, currentVacantCount: 0, nextVacantCount: 0 }; return ( map.get(key) || { totalCount: 0, currentVacantCount: 0, nextVacantCount: 0, } ); }