Merge branch 'fix/org-chart' into develop
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m34s

* fix/org-chart:
  fix: bug query
This commit is contained in:
Warunee Tamkoo 2026-02-19 15:44:19 +07:00
commit 92e20966d0
2 changed files with 1461 additions and 978 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,121 @@
import { AppDataSource } from "../database/data-source";
import { PosMaster } from "../entities/PosMaster";
// Helper function to get aggregated position counts
// Type definitions for position counts
export interface PositionCountData {
totalCount: number;
currentVacantCount: number;
nextVacantCount: number;
}
export interface PositionCountsByNode {
orgRootMap: Map<string, PositionCountData>;
orgChild1Map: Map<string, PositionCountData>;
orgChild2Map: Map<string, PositionCountData>;
orgChild3Map: Map<string, PositionCountData>;
orgChild4Map: Map<string, PositionCountData>;
}
// 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<PositionCountsByNode> {
const posRepo = AppDataSource.getRepository(PosMaster);
// Helper to build map from query results
const buildMap = (results: any[]) => {
const map = new Map<string, PositionCountData>();
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)
@ -276,3 +390,18 @@ export function getRootCounts(map: Map<string, any>, key: string) {
}
);
}
// Helper function to get position counts from aggregated maps with defaults
export function getPositionCount(
map: Map<string, PositionCountData>,
key: string | null | undefined
): PositionCountData {
if (!key) return { totalCount: 0, currentVacantCount: 0, nextVacantCount: 0 };
return (
map.get(key) || {
totalCount: 0,
currentVacantCount: 0,
nextVacantCount: 0,
}
);
}