diff --git a/src/app.ts b/src/app.ts index a44cace5..7f0e0220 100644 --- a/src/app.ts +++ b/src/app.ts @@ -11,8 +11,6 @@ import { AppDataSource } from "./database/data-source"; import { RegisterRoutes } from "./routes"; import { OrganizationController } from "./controllers/OrganizationController"; import logMiddleware from "./middlewares/logs"; -import { logMemoryStore } from "./utils/LogMemoryStore"; -import { orgStructureCache } from "./utils/OrgStructureCache"; import { CommandController } from "./controllers/CommandController"; import { ProfileSalaryController } from "./controllers/ProfileSalaryController"; import { DateSerializer } from "./interfaces/date-serializer"; @@ -22,12 +20,6 @@ import { initWebSocket } from "./services/webSocket"; async function main() { await AppDataSource.initialize(); - // Initialize LogMemoryStore after database is ready - logMemoryStore.initialize(); - - // Initialize OrgStructureCache after database is ready - orgStructureCache.initialize(); - // Setup custom Date serialization for local timezone DateSerializer.setupDateSerialization(); @@ -101,7 +93,7 @@ async function main() { }); // app.listen(APP_PORT, APP_HOST, () => console.log(`Listening on: http://localhost:${APP_PORT}`)); - const server = app.listen( + app.listen( APP_PORT, APP_HOST, () => ( @@ -119,46 +111,6 @@ async function main() { } runMessageQueue(); - - // Graceful Shutdown - const gracefulShutdown = async (signal: string) => { - console.log(`\n[APP] ${signal} received. Starting graceful shutdown...`); - - // Stop accepting new connections - server.close(() => { - console.log("[APP] HTTP server closed"); - }); - - // Force close after timeout - const shutdownTimeout = setTimeout(() => { - console.error("[APP] Forced shutdown after timeout"); - process.exit(1); - }, 30000); // 30 seconds timeout - - try { - // Close database connections - if (AppDataSource.isInitialized) { - await AppDataSource.destroy(); - console.log("[APP] Database connections closed"); - } - - // Destroy LogMemoryStore - logMemoryStore.destroy(); - console.log("[APP] LogMemoryStore destroyed"); - - clearTimeout(shutdownTimeout); - console.log("[APP] Graceful shutdown completed"); - process.exit(0); - } catch (error) { - console.error("[APP] Error during shutdown:", error); - clearTimeout(shutdownTimeout); - process.exit(1); - } - }; - - // Listen for shutdown signals - process.on("SIGTERM", () => gracefulShutdown("SIGTERM")); - process.on("SIGINT", () => gracefulShutdown("SIGINT")); } main(); diff --git a/src/controllers/OrganizationController.ts b/src/controllers/OrganizationController.ts index ad1f71d5..3050285d 100644 --- a/src/controllers/OrganizationController.ts +++ b/src/controllers/OrganizationController.ts @@ -46,12 +46,10 @@ import { getRoles, addUserRoles, } from "../keycloak"; -// import { getPositionCounts, getCounts, getRootCounts } from "../services/OrganizationService"; import { CreatePosMasterHistoryEmployee, CreatePosMasterHistoryOfficer, } from "../services/PositionService"; -import { orgStructureCache } from "../utils/OrgStructureCache"; @Route("api/v1/org") @Tags("Organization") @@ -1167,6 +1165,7 @@ export class OrganizationController extends Controller { async detailSuperAdmin(@Path() id: string, @Request() request: RequestWithUser) { const orgRevision = await this.orgRevisionRepository.findOne({ where: { id: id }, + relations: ["posMasters"], }); if (!orgRevision) return new HttpSuccess([]); @@ -1176,366 +1175,805 @@ export class OrganizationController extends Controller { where: { keycloak: request.user.sub, }, - select: ["id"], }); if (profile == null) return new HttpSuccess([]); - const posMaster = await this.posMasterRepository.findOne({ - where: - orgRevision.orgRevisionIsCurrent && !orgRevision.orgRevisionIsDraft - ? { - orgRevisionId: id, - current_holderId: profile.id, - } - : { - orgRevisionId: id, - next_holderId: profile.id, - }, - }); - if (!posMaster) return new HttpSuccess([]); - - rootId = posMaster.orgRootId; + if (!request.user.role.includes("SUPER_ADMIN")) { + if (orgRevision.orgRevisionIsDraft == true && orgRevision.orgRevisionIsCurrent == false) { + rootId = + orgRevision?.posMasters?.filter((x) => x.next_holderId == profile.id)[0]?.orgRootId || + null; + if (!rootId) return new HttpSuccess([]); + } else { + rootId = + orgRevision?.posMasters?.filter((x) => x.current_holderId == profile.id)[0] + ?.orgRootId || null; + if (!rootId) return new HttpSuccess([]); + } + } } - // OPTIMIZED: Check cache first - const cachedResponse = await orgStructureCache.get(id, rootId); - if (cachedResponse) { - return new HttpSuccess(cachedResponse); - } - - // OPTIMIZED: Get all position counts in ONE query (closed) - // const { orgRootMap, orgChild1Map, orgChild2Map, orgChild3Map, orgChild4Map, rootPosMap } = - // await getPositionCounts(id); - - // OPTIMIZED: Fetch orgRoot first, then fetch all child levels in parallel const orgRootData = await AppDataSource.getRepository(OrgRoot) .createQueryBuilder("orgRoot") .where("orgRoot.orgRevisionId = :id", { id }) .andWhere(rootId != null ? `orgRoot.id = :rootId` : "1=1", { rootId: rootId, }) + .select([ + "orgRoot.id", + "orgRoot.isDeputy", + "orgRoot.isCommission", + "orgRoot.orgRootName", + "orgRoot.orgRootShortName", + "orgRoot.orgRootCode", + "orgRoot.orgRootOrder", + "orgRoot.orgRootPhoneEx", + "orgRoot.orgRootPhoneIn", + "orgRoot.orgRootFax", + "orgRoot.orgRevisionId", + "orgRoot.orgRootRank", + "orgRoot.orgRootRankSub", + "orgRoot.DEPARTMENT_CODE", + "orgRoot.DIVISION_CODE", + "orgRoot.SECTION_CODE", + "orgRoot.JOB_CODE", + "orgRoot.responsibility", + "orgRoot.ancestorDNA", + ]) .orderBy("orgRoot.orgRootOrder", "ASC") .getMany(); - - const orgRootIds = orgRootData.map((orgRoot) => orgRoot.id); - - // OPTIMIZED: Fetch all child levels in parallel using orgRevisionId - // This is faster than sequential queries that depend on parent IDs - const [orgChild1AllData, orgChild2AllData, orgChild3AllData, orgChild4AllData] = - await Promise.all([ - AppDataSource.getRepository(OrgChild1) - .createQueryBuilder("orgChild1") - .where("orgChild1.orgRevisionId = :id", { id }) - .orderBy("orgChild1.orgChild1Order", "ASC") - .getMany(), - - AppDataSource.getRepository(OrgChild2) - .createQueryBuilder("orgChild2") - .where("orgChild2.orgRevisionId = :id", { id }) - .orderBy("orgChild2.orgChild2Order", "ASC") - .getMany(), - - AppDataSource.getRepository(OrgChild3) - .createQueryBuilder("orgChild3") - .where("orgChild3.orgRevisionId = :id", { id }) - .orderBy("orgChild3.orgChild3Order", "ASC") - .getMany(), - - AppDataSource.getRepository(OrgChild4) - .createQueryBuilder("orgChild4") - .where("orgChild4.orgRevisionId = :id", { id }) - .orderBy("orgChild4.orgChild4Order", "ASC") - .getMany(), - ]); - - // Filter child1 data by orgRootIds (maintains backward compatibility) + const orgRootIds = orgRootData.map((orgRoot) => orgRoot.id) || null; const orgChild1Data = orgRootIds && orgRootIds.length > 0 - ? orgChild1AllData.filter((orgChild1) => orgRootIds.includes(orgChild1.orgRootId)) + ? await AppDataSource.getRepository(OrgChild1) + .createQueryBuilder("orgChild1") + .where("orgChild1.orgRootId IN (:...ids)", { ids: orgRootIds }) + .select([ + "orgChild1.id", + "orgChild1.isOfficer", + "orgChild1.isInformation", + "orgChild1.orgChild1Name", + "orgChild1.orgChild1ShortName", + "orgChild1.orgChild1Code", + "orgChild1.orgChild1Order", + "orgChild1.orgChild1PhoneEx", + "orgChild1.orgChild1PhoneIn", + "orgChild1.orgChild1Fax", + "orgChild1.orgRootId", + "orgChild1.orgChild1Rank", + "orgChild1.orgChild1RankSub", + "orgChild1.DEPARTMENT_CODE", + "orgChild1.DIVISION_CODE", + "orgChild1.SECTION_CODE", + "orgChild1.JOB_CODE", + "orgChild1.responsibility", + "orgChild1.ancestorDNA", + ]) + .orderBy("orgChild1.orgChild1Order", "ASC") + .getMany() : []; - // Build maps for efficient filtering of deeper levels - const orgChild1Ids = orgChild1Data.map((orgChild1) => orgChild1.id); + const orgChild1Ids = orgChild1Data.map((orgChild1) => orgChild1.id) || null; const orgChild2Data = orgChild1Ids && orgChild1Ids.length > 0 - ? orgChild2AllData.filter((orgChild2) => orgChild1Ids.includes(orgChild2.orgChild1Id)) + ? await AppDataSource.getRepository(OrgChild2) + .createQueryBuilder("orgChild2") + .where("orgChild2.orgChild1Id IN (:...ids)", { ids: orgChild1Ids }) + .select([ + "orgChild2.id", + "orgChild2.orgChild2Name", + "orgChild2.orgChild2ShortName", + "orgChild2.orgChild2Code", + "orgChild2.orgChild2Order", + "orgChild2.orgChild2PhoneEx", + "orgChild2.orgChild2PhoneIn", + "orgChild2.orgChild2Fax", + "orgChild2.orgRootId", + "orgChild2.orgChild2Rank", + "orgChild2.orgChild2RankSub", + "orgChild2.DEPARTMENT_CODE", + "orgChild2.DIVISION_CODE", + "orgChild2.SECTION_CODE", + "orgChild2.JOB_CODE", + "orgChild2.orgChild1Id", + "orgChild2.responsibility", + "orgChild2.ancestorDNA", + ]) + .orderBy("orgChild2.orgChild2Order", "ASC") + .getMany() : []; - const orgChild2Ids = orgChild2Data.map((orgChild2) => orgChild2.id); + const orgChild2Ids = orgChild2Data.map((orgChild2) => orgChild2.id) || null; const orgChild3Data = orgChild2Ids && orgChild2Ids.length > 0 - ? orgChild3AllData.filter((orgChild3) => orgChild2Ids.includes(orgChild3.orgChild2Id)) + ? await AppDataSource.getRepository(OrgChild3) + .createQueryBuilder("orgChild3") + .where("orgChild3.orgChild2Id IN (:...ids)", { ids: orgChild2Ids }) + .select([ + "orgChild3.id", + "orgChild3.orgChild3Name", + "orgChild3.orgChild3ShortName", + "orgChild3.orgChild3Code", + "orgChild3.orgChild3Order", + "orgChild3.orgChild3PhoneEx", + "orgChild3.orgChild3PhoneIn", + "orgChild3.orgChild3Fax", + "orgChild3.orgRootId", + "orgChild3.orgChild3Rank", + "orgChild3.orgChild3RankSub", + "orgChild3.DEPARTMENT_CODE", + "orgChild3.DIVISION_CODE", + "orgChild3.SECTION_CODE", + "orgChild3.JOB_CODE", + "orgChild3.orgChild2Id", + "orgChild3.responsibility", + "orgChild3.ancestorDNA", + ]) + .orderBy("orgChild3.orgChild3Order", "ASC") + .getMany() : []; - const orgChild3Ids = orgChild3Data.map((orgChild3) => orgChild3.id); + const orgChild3Ids = orgChild3Data.map((orgChild3) => orgChild3.id) || null; const orgChild4Data = orgChild3Ids && orgChild3Ids.length > 0 - ? orgChild4AllData.filter((orgChild4) => orgChild3Ids.includes(orgChild4.orgChild3Id)) + ? await AppDataSource.getRepository(OrgChild4) + .createQueryBuilder("orgChild4") + .where("orgChild4.orgChild3Id IN (:...ids)", { ids: orgChild3Ids }) + .select([ + "orgChild4.id", + "orgChild4.orgChild4Name", + "orgChild4.orgChild4ShortName", + "orgChild4.orgChild4Code", + "orgChild4.orgChild4Order", + "orgChild4.orgChild4PhoneEx", + "orgChild4.orgChild4PhoneIn", + "orgChild4.orgChild4Fax", + "orgChild4.orgRootId", + "orgChild4.orgChild4Rank", + "orgChild4.orgChild4RankSub", + "orgChild4.DEPARTMENT_CODE", + "orgChild4.DIVISION_CODE", + "orgChild4.SECTION_CODE", + "orgChild4.JOB_CODE", + "orgChild4.orgChild3Id", + "orgChild4.responsibility", + "orgChild4.ancestorDNA", + ]) + .orderBy("orgChild4.orgChild4Order", "ASC") + .getMany() : []; - // OPTIMIZED: Build formatted data using pre-calculated counts (no nested queries!) - const formattedData = orgRootData.map((orgRoot) => { - // const rootCounts = getCounts(orgRootMap, orgRoot.id); - // const rootPosCounts = getRootCounts(rootPosMap, orgRoot.id); - - return { - orgTreeId: orgRoot.id, - orgLevel: 0, - orgName: orgRoot.orgRootName, - orgTreeName: orgRoot.orgRootName, - orgTreeShortName: orgRoot.orgRootShortName, - orgTreeCode: orgRoot.orgRootCode, - orgCode: orgRoot.orgRootCode + "00", - orgTreeRank: orgRoot.orgRootRank, - orgTreeRankSub: orgRoot.orgRootRankSub, - orgRootDnaId: orgRoot.ancestorDNA, - DEPARTMENT_CODE: orgRoot.DEPARTMENT_CODE, - DIVISION_CODE: orgRoot.DIVISION_CODE, - SECTION_CODE: orgRoot.SECTION_CODE, - JOB_CODE: orgRoot.JOB_CODE, - orgTreeOrder: orgRoot.orgRootOrder, - orgTreePhoneEx: orgRoot.orgRootPhoneEx, - orgTreePhoneIn: orgRoot.orgRootPhoneIn, - orgTreeFax: orgRoot.orgRootFax, - orgRevisionId: orgRoot.orgRevisionId, - orgRootName: orgRoot.orgRootName, - isDeputy: orgRoot.isDeputy, - isCommission: orgRoot.isCommission, - responsibility: orgRoot.responsibility, - labelName: - orgRoot.orgRootName + " " + orgRoot.orgRootCode + "00" + " " + orgRoot.orgRootShortName, - // totalPosition: rootCounts.totalPosition, - // totalPositionCurrentUse: rootCounts.totalPositionCurrentUse, - // totalPositionCurrentVacant: rootCounts.totalPositionCurrentVacant, - // totalPositionNextUse: rootCounts.totalPositionNextUse, - // totalPositionNextVacant: rootCounts.totalPositionNextVacant, - // totalRootPosition: rootPosCounts.totalRootPosition, - // totalRootPositionCurrentUse: rootPosCounts.totalRootPositionCurrentUse, - // totalRootPositionCurrentVacant: rootPosCounts.totalRootPositionCurrentVacant, - // totalRootPositionNextUse: rootPosCounts.totalRootPositionNextUse, - // totalRootPositionNextVacant: rootPosCounts.totalRootPositionNextVacant, - children: orgChild1Data - .filter((orgChild1) => orgChild1.orgRootId === orgRoot.id) - .map((orgChild1) => { - // const child1Counts = getCounts(orgChild1Map, orgChild1.id); - // const child1PosKey = `${orgRoot.id}-${orgChild1.id}`; - // const child1PosCounts = getRootCounts(rootPosMap, child1PosKey); - - return { - orgTreeId: orgChild1.id, - orgRootId: orgRoot.id, - orgLevel: 1, - orgName: `${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, - orgTreeName: orgChild1.orgChild1Name, - orgTreeShortName: orgChild1.orgChild1ShortName, - orgTreeCode: orgChild1.orgChild1Code, - orgCode: orgRoot.orgRootCode + orgChild1.orgChild1Code, - orgTreeRank: orgChild1.orgChild1Rank, - orgTreeRankSub: orgChild1.orgChild1RankSub, - orgRootDnaId: orgRoot.ancestorDNA, - orgChild1DnaId: orgChild1.ancestorDNA, - DEPARTMENT_CODE: orgChild1.DEPARTMENT_CODE, - DIVISION_CODE: orgChild1.DIVISION_CODE, - SECTION_CODE: orgChild1.SECTION_CODE, - JOB_CODE: orgChild1.JOB_CODE, - orgTreeOrder: orgChild1.orgChild1Order, - orgRootCode: orgRoot.orgRootCode, - orgTreePhoneEx: orgChild1.orgChild1PhoneEx, - orgTreePhoneIn: orgChild1.orgChild1PhoneIn, - orgTreeFax: orgChild1.orgChild1Fax, - orgRevisionId: orgRoot.orgRevisionId, - orgRootName: orgRoot.orgRootName, - responsibility: orgChild1.responsibility, - isOfficer: orgChild1.isOfficer, - isInformation: orgChild1.isInformation, - labelName: - orgChild1.orgChild1Name + - " " + - orgRoot.orgRootCode + - orgChild1.orgChild1Code + - " " + - orgChild1.orgChild1ShortName, - // totalPosition: child1Counts.totalPosition, - // totalPositionCurrentUse: child1Counts.totalPositionCurrentUse, - // totalPositionCurrentVacant: child1Counts.totalPositionCurrentVacant, - // totalPositionNextUse: child1Counts.totalPositionNextUse, - // totalPositionNextVacant: child1Counts.totalPositionNextVacant, - // totalRootPosition: child1PosCounts.totalRootPosition, - // totalRootPositionCurrentUse: child1PosCounts.totalRootPositionCurrentUse, - // totalRootPositionCurrentVacant: child1PosCounts.totalRootPositionCurrentVacant, - // totalRootPositionNextUse: child1PosCounts.totalRootPositionNextUse, - // totalRootPositionNextVacant: child1PosCounts.totalRootPositionNextVacant, - children: orgChild2Data - .filter((orgChild2) => orgChild2.orgChild1Id === orgChild1.id) - .map((orgChild2) => { - // const child2Counts = getCounts(orgChild2Map, orgChild2.id); - // const child2PosKey = `${orgRoot.id}-${orgChild1.id}-${orgChild2.id}`; - // const child2PosCounts = getRootCounts(rootPosMap, child2PosKey); - - return { - orgTreeId: orgChild2.id, - orgRootId: orgChild1.id, - orgLevel: 2, - orgName: `${orgChild2.orgChild2Name}/${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, - orgTreeName: orgChild2.orgChild2Name, - orgTreeShortName: orgChild2.orgChild2ShortName, - orgTreeCode: orgChild2.orgChild2Code, - orgCode: orgRoot.orgRootCode + orgChild2.orgChild2Code, - orgTreeRank: orgChild2.orgChild2Rank, - orgTreeRankSub: orgChild2.orgChild2RankSub, - orgRootDnaId: orgRoot.ancestorDNA, - orgChild1DnaId: orgChild1.ancestorDNA, - orgChild2DnaId: orgChild2.ancestorDNA, - DEPARTMENT_CODE: orgChild2.DEPARTMENT_CODE, - DIVISION_CODE: orgChild2.DIVISION_CODE, - SECTION_CODE: orgChild2.SECTION_CODE, - JOB_CODE: orgChild2.JOB_CODE, - orgTreeOrder: orgChild2.orgChild2Order, - orgRootCode: orgRoot.orgRootCode, - orgTreePhoneEx: orgChild2.orgChild2PhoneEx, - orgTreePhoneIn: orgChild2.orgChild2PhoneIn, - orgTreeFax: orgChild2.orgChild2Fax, - orgRevisionId: orgRoot.orgRevisionId, - orgRootName: orgRoot.orgRootName, - responsibility: orgChild2.responsibility, - labelName: - orgChild2.orgChild2Name + - " " + - orgRoot.orgRootCode + - orgChild2.orgChild2Code + - " " + - orgChild2.orgChild2ShortName, - // totalPosition: child2Counts.totalPosition, - // totalPositionCurrentUse: child2Counts.totalPositionCurrentUse, - // totalPositionCurrentVacant: child2Counts.totalPositionCurrentVacant, - // totalPositionNextUse: child2Counts.totalPositionNextUse, - // totalPositionNextVacant: child2Counts.totalPositionNextVacant, - // totalRootPosition: child2PosCounts.totalRootPosition, - // totalRootPositionCurrentUse: child2PosCounts.totalRootPositionCurrentUse, - // totalRootPositionCurrentVacant: child2PosCounts.totalRootPositionCurrentVacant, - // totalRootPositionNextUse: child2PosCounts.totalRootPositionNextUse, - // totalRootPositionNextVacant: child2PosCounts.totalRootPositionNextVacant, - children: orgChild3Data - .filter((orgChild3) => orgChild3.orgChild2Id === orgChild2.id) - .map((orgChild3) => { - // const child3Counts = getCounts(orgChild3Map, orgChild3.id); - // const child3PosKey = `${orgRoot.id}-${orgChild1.id}-${orgChild2.id}-${orgChild3.id}`; - // const child3PosCounts = getRootCounts(rootPosMap, child3PosKey); - - return { - orgTreeId: orgChild3.id, - orgRootId: orgChild2.id, - orgLevel: 3, - orgName: `${orgChild3.orgChild3Name}/${orgChild2.orgChild2Name}/${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, - orgTreeName: orgChild3.orgChild3Name, - orgTreeShortName: orgChild3.orgChild3ShortName, - orgTreeCode: orgChild3.orgChild3Code, - orgCode: orgRoot.orgRootCode + orgChild3.orgChild3Code, - orgTreeRank: orgChild3.orgChild3Rank, - orgTreeRankSub: orgChild3.orgChild3RankSub, - orgRootDnaId: orgRoot.ancestorDNA, - orgChild1DnaId: orgChild1.ancestorDNA, - orgChild2DnaId: orgChild2.ancestorDNA, - orgChild3DnaId: orgChild3.ancestorDNA, - DEPARTMENT_CODE: orgChild3.DEPARTMENT_CODE, - DIVISION_CODE: orgChild3.DIVISION_CODE, - SECTION_CODE: orgChild3.SECTION_CODE, - JOB_CODE: orgChild3.JOB_CODE, - orgTreeOrder: orgChild3.orgChild3Order, - orgRootCode: orgRoot.orgRootCode, - orgTreePhoneEx: orgChild3.orgChild3PhoneEx, - orgTreePhoneIn: orgChild3.orgChild3PhoneIn, - orgTreeFax: orgChild3.orgChild3Fax, - orgRevisionId: orgRoot.orgRevisionId, - orgRootName: orgRoot.orgRootName, - responsibility: orgChild3.responsibility, - labelName: - orgChild3.orgChild3Name + - " " + - orgRoot.orgRootCode + - orgChild3.orgChild3Code + - " " + - orgChild3.orgChild3ShortName, - // totalPosition: child3Counts.totalPosition, - // totalPositionCurrentUse: child3Counts.totalPositionCurrentUse, - // totalPositionCurrentVacant: child3Counts.totalPositionCurrentVacant, - // totalPositionNextUse: child3Counts.totalPositionNextUse, - // totalPositionNextVacant: child3Counts.totalPositionNextVacant, - // totalRootPosition: child3PosCounts.totalRootPosition, - // totalRootPositionCurrentUse: child3PosCounts.totalRootPositionCurrentUse, - // totalRootPositionCurrentVacant: - // child3PosCounts.totalRootPositionCurrentVacant, - // totalRootPositionNextUse: child3PosCounts.totalRootPositionNextUse, - // totalRootPositionNextVacant: child3PosCounts.totalRootPositionNextVacant, - children: orgChild4Data - .filter((orgChild4) => orgChild4.orgChild3Id === orgChild3.id) - .map((orgChild4) => { - // const child4Counts = getCounts(orgChild4Map, orgChild4.id); - // const child4PosKey = `${orgRoot.id}-${orgChild1.id}-${orgChild2.id}-${orgChild3.id}-${orgChild4.id}`; - // const child4PosCounts = getRootCounts(rootPosMap, child4PosKey); - - return { - orgTreeId: orgChild4.id, - orgRootId: orgChild3.id, - orgLevel: 4, - orgName: `${orgChild4.orgChild4Name}/${orgChild3.orgChild3Name}/${orgChild2.orgChild2Name}/${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, - orgTreeName: orgChild4.orgChild4Name, - orgTreeShortName: orgChild4.orgChild4ShortName, - orgTreeCode: orgChild4.orgChild4Code, - orgCode: orgRoot.orgRootCode + orgChild4.orgChild4Code, - orgTreeRank: orgChild4.orgChild4Rank, - orgTreeRankSub: orgChild4.orgChild4RankSub, - orgRootDnaId: orgRoot.ancestorDNA, - orgChild1DnaId: orgChild1.ancestorDNA, - orgChild2DnaId: orgChild2.ancestorDNA, - orgChild3DnaId: orgChild3.ancestorDNA, - orgChild4DnaId: orgChild4.ancestorDNA, - DEPARTMENT_CODE: orgChild4.DEPARTMENT_CODE, - DIVISION_CODE: orgChild4.DIVISION_CODE, - SECTION_CODE: orgChild4.SECTION_CODE, - JOB_CODE: orgChild4.JOB_CODE, - orgTreeOrder: orgChild4.orgChild4Order, - orgRootCode: orgRoot.orgRootCode, - orgTreePhoneEx: orgChild4.orgChild4PhoneEx, - orgTreePhoneIn: orgChild4.orgChild4PhoneIn, - orgTreeFax: orgChild4.orgChild4Fax, - orgRevisionId: orgRoot.orgRevisionId, - orgRootName: orgRoot.orgRootName, - responsibility: orgChild4.responsibility, - labelName: - orgChild4.orgChild4Name + - " " + - orgRoot.orgRootCode + - orgChild4.orgChild4Code + - " " + - orgChild4.orgChild4ShortName, - // totalPosition: child4Counts.totalPosition, - // totalPositionCurrentUse: child4Counts.totalPositionCurrentUse, - // totalPositionCurrentVacant: child4Counts.totalPositionCurrentVacant, - // totalPositionNextUse: child4Counts.totalPositionNextUse, - // totalPositionNextVacant: child4Counts.totalPositionNextVacant, - // totalRootPosition: child4PosCounts.totalRootPosition, - // totalRootPositionCurrentUse: - // child4PosCounts.totalRootPositionCurrentUse, - // totalRootPositionCurrentVacant: - // child4PosCounts.totalRootPositionCurrentVacant, - // totalRootPositionNextUse: child4PosCounts.totalRootPositionNextUse, - // totalRootPositionNextVacant: - // child4PosCounts.totalRootPositionNextVacant, - children: [], - }; - }), - }; - }), - }; - }), - }; + // const formattedData = orgRootData.map((orgRoot) => { + const formattedData = await Promise.all( + orgRootData.map(async (orgRoot) => { + return { + orgTreeId: orgRoot.id, + orgLevel: 0, + orgName: orgRoot.orgRootName, + orgTreeName: orgRoot.orgRootName, + orgTreeShortName: orgRoot.orgRootShortName, + orgTreeCode: orgRoot.orgRootCode, + orgCode: orgRoot.orgRootCode + "00", + orgTreeRank: orgRoot.orgRootRank, + orgTreeRankSub: orgRoot.orgRootRankSub, + orgRootDnaId: orgRoot.ancestorDNA, + DEPARTMENT_CODE: orgRoot.DEPARTMENT_CODE, + DIVISION_CODE: orgRoot.DIVISION_CODE, + SECTION_CODE: orgRoot.SECTION_CODE, + JOB_CODE: orgRoot.JOB_CODE, + orgTreeOrder: orgRoot.orgRootOrder, + orgTreePhoneEx: orgRoot.orgRootPhoneEx, + orgTreePhoneIn: orgRoot.orgRootPhoneIn, + orgTreeFax: orgRoot.orgRootFax, + orgRevisionId: orgRoot.orgRevisionId, + orgRootName: orgRoot.orgRootName, + isDeputy: orgRoot.isDeputy, + isCommission: orgRoot.isCommission, + responsibility: orgRoot.responsibility, + labelName: + orgRoot.orgRootName + " " + orgRoot.orgRootCode + "00" + " " + orgRoot.orgRootShortName, + totalPosition: await this.posMasterRepository.count({ + where: { orgRevisionId: orgRoot.orgRevisionId, orgRootId: orgRoot.id }, + }), + totalPositionCurrentUse: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + current_holderId: Not(IsNull()) || Not(""), + }, + }), + totalPositionCurrentVacant: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + current_holderId: IsNull() || "", + }, + }), + totalPositionNextUse: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + next_holderId: Not(IsNull()) || Not(""), + }, + }), + totalPositionNextVacant: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + next_holderId: IsNull() || "", + }, + }), + totalRootPosition: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: IsNull() || "", + orgChild2Id: IsNull() || "", + orgChild3Id: IsNull() || "", + orgChild4Id: IsNull() || "", + }, + }), + totalRootPositionCurrentUse: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: IsNull() || "", + orgChild2Id: IsNull() || "", + orgChild3Id: IsNull() || "", + orgChild4Id: IsNull() || "", + current_holderId: Not(IsNull()) || Not(""), + }, + }), + totalRootPositionCurrentVacant: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: IsNull() || "", + orgChild2Id: IsNull() || "", + orgChild3Id: IsNull() || "", + orgChild4Id: IsNull() || "", + current_holderId: IsNull() || "", + }, + }), + totalRootPositionNextUse: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: IsNull() || "", + orgChild2Id: IsNull() || "", + orgChild3Id: IsNull() || "", + orgChild4Id: IsNull() || "", + next_holderId: Not(IsNull()) || Not(""), + }, + }), + totalRootPositionNextVacant: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: IsNull() || "", + orgChild2Id: IsNull() || "", + orgChild3Id: IsNull() || "", + orgChild4Id: IsNull() || "", + next_holderId: IsNull() || "", + }, }), - }; - }); - // OPTIMIZED: Cache the result - await orgStructureCache.set(id, rootId, formattedData); + children: await Promise.all( + orgChild1Data + .filter((orgChild1) => orgChild1.orgRootId === orgRoot.id) + .map(async (orgChild1) => ({ + orgTreeId: orgChild1.id, + orgRootId: orgRoot.id, + orgLevel: 1, + orgName: `${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, + orgTreeName: orgChild1.orgChild1Name, + orgTreeShortName: orgChild1.orgChild1ShortName, + orgTreeCode: orgChild1.orgChild1Code, + orgCode: orgRoot.orgRootCode + orgChild1.orgChild1Code, + orgTreeRank: orgChild1.orgChild1Rank, + orgTreeRankSub: orgChild1.orgChild1RankSub, + orgRootDnaId: orgRoot.ancestorDNA, + orgChild1DnaId: orgChild1.ancestorDNA, + DEPARTMENT_CODE: orgChild1.DEPARTMENT_CODE, + DIVISION_CODE: orgChild1.DIVISION_CODE, + SECTION_CODE: orgChild1.SECTION_CODE, + JOB_CODE: orgChild1.JOB_CODE, + orgTreeOrder: orgChild1.orgChild1Order, + orgRootCode: orgRoot.orgRootCode, + orgTreePhoneEx: orgChild1.orgChild1PhoneEx, + orgTreePhoneIn: orgChild1.orgChild1PhoneIn, + orgTreeFax: orgChild1.orgChild1Fax, + orgRevisionId: orgRoot.orgRevisionId, + orgRootName: orgRoot.orgRootName, + responsibility: orgChild1.responsibility, + isOfficer: orgChild1.isOfficer, + isInformation: orgChild1.isInformation, + labelName: + orgChild1.orgChild1Name + + " " + + orgRoot.orgRootCode + + orgChild1.orgChild1Code + + " " + + orgChild1.orgChild1ShortName, + totalPosition: await this.posMasterRepository.count({ + where: { orgRevisionId: orgRoot.orgRevisionId, orgChild1Id: orgChild1.id }, + }), + totalPositionCurrentUse: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgChild1Id: orgChild1.id, + current_holderId: Not(IsNull()) || Not(""), + }, + }), + totalPositionCurrentVacant: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgChild1Id: orgChild1.id, + current_holderId: IsNull() || "", + }, + }), + totalPositionNextUse: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgChild1Id: orgChild1.id, + next_holderId: Not(IsNull()) || Not(""), + }, + }), + totalPositionNextVacant: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgChild1Id: orgChild1.id, + next_holderId: IsNull() || "", + }, + }), + totalRootPosition: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: orgChild1.id, + orgChild2Id: IsNull() || "", + orgChild3Id: IsNull() || "", + orgChild4Id: IsNull() || "", + }, + }), + totalRootPositionCurrentUse: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: orgChild1.id, + orgChild2Id: IsNull() || "", + orgChild3Id: IsNull() || "", + orgChild4Id: IsNull() || "", + current_holderId: Not(IsNull()) || Not(""), + }, + }), + totalRootPositionCurrentVacant: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: orgChild1.id, + orgChild2Id: IsNull() || "", + orgChild3Id: IsNull() || "", + orgChild4Id: IsNull() || "", + current_holderId: IsNull() || "", + }, + }), + totalRootPositionNextUse: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: orgChild1.id, + orgChild2Id: IsNull() || "", + orgChild3Id: IsNull() || "", + orgChild4Id: IsNull() || "", + next_holderId: Not(IsNull()) || Not(""), + }, + }), + totalRootPositionNextVacant: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: orgChild1.id, + orgChild2Id: IsNull() || "", + orgChild3Id: IsNull() || "", + orgChild4Id: IsNull() || "", + next_holderId: IsNull() || "", + }, + }), + + children: await Promise.all( + orgChild2Data + .filter((orgChild2) => orgChild2.orgChild1Id === orgChild1.id) + .map(async (orgChild2) => ({ + orgTreeId: orgChild2.id, + orgRootId: orgChild1.id, + orgLevel: 2, + orgName: `${orgChild2.orgChild2Name}/${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, + orgTreeName: orgChild2.orgChild2Name, + orgTreeShortName: orgChild2.orgChild2ShortName, + orgTreeCode: orgChild2.orgChild2Code, + orgCode: orgRoot.orgRootCode + orgChild2.orgChild2Code, + orgTreeRank: orgChild2.orgChild2Rank, + orgTreeRankSub: orgChild2.orgChild2RankSub, + orgRootDnaId: orgRoot.ancestorDNA, + orgChild1DnaId: orgChild1.ancestorDNA, + orgChild2DnaId: orgChild2.ancestorDNA, + DEPARTMENT_CODE: orgChild2.DEPARTMENT_CODE, + DIVISION_CODE: orgChild2.DIVISION_CODE, + SECTION_CODE: orgChild2.SECTION_CODE, + JOB_CODE: orgChild2.JOB_CODE, + orgTreeOrder: orgChild2.orgChild2Order, + orgRootCode: orgRoot.orgRootCode, + orgTreePhoneEx: orgChild2.orgChild2PhoneEx, + orgTreePhoneIn: orgChild2.orgChild2PhoneIn, + orgTreeFax: orgChild2.orgChild2Fax, + orgRevisionId: orgRoot.orgRevisionId, + orgRootName: orgRoot.orgRootName, + responsibility: orgChild2.responsibility, + labelName: + orgChild2.orgChild2Name + + " " + + orgRoot.orgRootCode + + orgChild2.orgChild2Code + + " " + + orgChild2.orgChild2ShortName, + totalPosition: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgChild2Id: orgChild2.id, + }, + }), + totalPositionCurrentUse: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgChild2Id: orgChild2.id, + current_holderId: Not(IsNull()) || Not(""), + }, + }), + totalPositionCurrentVacant: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgChild2Id: orgChild2.id, + current_holderId: IsNull() || "", + }, + }), + totalPositionNextUse: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgChild2Id: orgChild2.id, + next_holderId: Not(IsNull()) || Not(""), + }, + }), + totalPositionNextVacant: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgChild2Id: orgChild2.id, + next_holderId: IsNull() || "", + }, + }), + totalRootPosition: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: orgChild1.id, + orgChild2Id: orgChild2.id, + orgChild3Id: IsNull() || "", + orgChild4Id: IsNull() || "", + }, + }), + totalRootPositionCurrentUse: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: orgChild1.id, + orgChild2Id: orgChild2.id, + orgChild3Id: IsNull() || "", + orgChild4Id: IsNull() || "", + current_holderId: Not(IsNull()) || Not(""), + }, + }), + totalRootPositionCurrentVacant: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: orgChild1.id, + orgChild2Id: orgChild2.id, + orgChild3Id: IsNull() || "", + orgChild4Id: IsNull() || "", + current_holderId: IsNull() || "", + }, + }), + totalRootPositionNextUse: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: orgChild1.id, + orgChild2Id: orgChild2.id, + orgChild3Id: IsNull() || "", + orgChild4Id: IsNull() || "", + next_holderId: Not(IsNull()) || Not(""), + }, + }), + totalRootPositionNextVacant: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: orgChild1.id, + orgChild2Id: orgChild2.id, + orgChild3Id: IsNull() || "", + orgChild4Id: IsNull() || "", + next_holderId: IsNull() || "", + }, + }), + + children: await Promise.all( + orgChild3Data + .filter((orgChild3) => orgChild3.orgChild2Id === orgChild2.id) + .map(async (orgChild3) => ({ + orgTreeId: orgChild3.id, + orgRootId: orgChild2.id, + orgLevel: 3, + orgName: `${orgChild3.orgChild3Name}/${orgChild2.orgChild2Name}/${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, + orgTreeName: orgChild3.orgChild3Name, + orgTreeShortName: orgChild3.orgChild3ShortName, + orgTreeCode: orgChild3.orgChild3Code, + orgCode: orgRoot.orgRootCode + orgChild3.orgChild3Code, + orgTreeRank: orgChild3.orgChild3Rank, + orgTreeRankSub: orgChild3.orgChild3RankSub, + orgRootDnaId: orgRoot.ancestorDNA, + orgChild1DnaId: orgChild1.ancestorDNA, + orgChild2DnaId: orgChild2.ancestorDNA, + orgChild3DnaId: orgChild3.ancestorDNA, + DEPARTMENT_CODE: orgChild3.DEPARTMENT_CODE, + DIVISION_CODE: orgChild3.DIVISION_CODE, + SECTION_CODE: orgChild3.SECTION_CODE, + JOB_CODE: orgChild3.JOB_CODE, + orgTreeOrder: orgChild3.orgChild3Order, + orgRootCode: orgRoot.orgRootCode, + orgTreePhoneEx: orgChild3.orgChild3PhoneEx, + orgTreePhoneIn: orgChild3.orgChild3PhoneIn, + orgTreeFax: orgChild3.orgChild3Fax, + orgRevisionId: orgRoot.orgRevisionId, + orgRootName: orgRoot.orgRootName, + responsibility: orgChild3.responsibility, + labelName: + orgChild3.orgChild3Name + + " " + + orgRoot.orgRootCode + + orgChild3.orgChild3Code + + " " + + orgChild3.orgChild3ShortName, + totalPosition: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgChild3Id: orgChild3.id, + }, + }), + totalPositionCurrentUse: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgChild3Id: orgChild3.id, + current_holderId: Not(IsNull()) || Not(""), + }, + }), + totalPositionCurrentVacant: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgChild3Id: orgChild3.id, + current_holderId: IsNull() || "", + }, + }), + totalPositionNextUse: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgChild3Id: orgChild3.id, + next_holderId: Not(IsNull()) || Not(""), + }, + }), + totalPositionNextVacant: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgChild3Id: orgChild3.id, + next_holderId: IsNull() || "", + }, + }), + totalRootPosition: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: orgChild1.id, + orgChild2Id: orgChild2.id, + orgChild3Id: orgChild3.id, + orgChild4Id: IsNull() || "", + }, + }), + totalRootPositionCurrentUse: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: orgChild1.id, + orgChild2Id: orgChild2.id, + orgChild3Id: orgChild3.id, + orgChild4Id: IsNull() || "", + current_holderId: Not(IsNull()) || Not(""), + }, + }), + totalRootPositionCurrentVacant: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: orgChild1.id, + orgChild2Id: orgChild2.id, + orgChild3Id: orgChild3.id, + orgChild4Id: IsNull() || "", + current_holderId: IsNull() || "", + }, + }), + totalRootPositionNextUse: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: orgChild1.id, + orgChild2Id: orgChild2.id, + orgChild3Id: orgChild3.id, + orgChild4Id: IsNull() || "", + next_holderId: Not(IsNull()) || Not(""), + }, + }), + totalRootPositionNextVacant: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: orgChild1.id, + orgChild2Id: orgChild2.id, + orgChild3Id: orgChild3.id, + orgChild4Id: IsNull() || "", + next_holderId: IsNull() || "", + }, + }), + + children: await Promise.all( + orgChild4Data + .filter((orgChild4) => orgChild4.orgChild3Id === orgChild3.id) + .map(async (orgChild4) => ({ + orgTreeId: orgChild4.id, + orgRootId: orgChild3.id, + orgLevel: 4, + orgName: `${orgChild4.orgChild4Name}/${orgChild3.orgChild3Name}/${orgChild2.orgChild2Name}/${orgChild1.orgChild1Name}/${orgRoot.orgRootName}`, + orgTreeName: orgChild4.orgChild4Name, + orgTreeShortName: orgChild4.orgChild4ShortName, + orgTreeCode: orgChild4.orgChild4Code, + orgCode: orgRoot.orgRootCode + orgChild4.orgChild4Code, + orgTreeRank: orgChild4.orgChild4Rank, + orgTreeRankSub: orgChild4.orgChild4RankSub, + orgRootDnaId: orgRoot.ancestorDNA, + orgChild1DnaId: orgChild1.ancestorDNA, + orgChild2DnaId: orgChild2.ancestorDNA, + orgChild3DnaId: orgChild3.ancestorDNA, + orgChild4DnaId: orgChild4.ancestorDNA, + DEPARTMENT_CODE: orgChild4.DEPARTMENT_CODE, + DIVISION_CODE: orgChild4.DIVISION_CODE, + SECTION_CODE: orgChild4.SECTION_CODE, + JOB_CODE: orgChild4.JOB_CODE, + orgTreeOrder: orgChild4.orgChild4Order, + orgRootCode: orgRoot.orgRootCode, + orgTreePhoneEx: orgChild4.orgChild4PhoneEx, + orgTreePhoneIn: orgChild4.orgChild4PhoneIn, + orgTreeFax: orgChild4.orgChild4Fax, + orgRevisionId: orgRoot.orgRevisionId, + orgRootName: orgRoot.orgRootName, + responsibility: orgChild4.responsibility, + labelName: + orgChild4.orgChild4Name + + " " + + orgRoot.orgRootCode + + orgChild4.orgChild4Code + + " " + + orgChild4.orgChild4ShortName, + totalPosition: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgChild4Id: orgChild4.id, + }, + }), + totalPositionCurrentUse: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgChild4Id: orgChild4.id, + current_holderId: Not(IsNull()) || Not(""), + }, + }), + totalPositionCurrentVacant: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgChild4Id: orgChild4.id, + current_holderId: IsNull() || "", + }, + }), + totalPositionNextUse: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgChild4Id: orgChild4.id, + next_holderId: Not(IsNull()) || Not(""), + }, + }), + totalPositionNextVacant: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgChild4Id: orgChild4.id, + next_holderId: IsNull() || "", + }, + }), + totalRootPosition: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: orgChild1.id, + orgChild2Id: orgChild2.id, + orgChild3Id: orgChild3.id, + orgChild4Id: orgChild4.id, + }, + }), + totalRootPositionCurrentUse: await this.posMasterRepository.count( + { + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: orgChild1.id, + orgChild2Id: orgChild2.id, + orgChild3Id: orgChild3.id, + orgChild4Id: orgChild4.id, + current_holderId: Not(IsNull()) || Not(""), + }, + }, + ), + totalRootPositionCurrentVacant: + await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: orgChild1.id, + orgChild2Id: orgChild2.id, + orgChild3Id: orgChild3.id, + orgChild4Id: orgChild4.id, + current_holderId: IsNull() || "", + }, + }), + totalRootPositionNextUse: await this.posMasterRepository.count({ + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: orgChild1.id, + orgChild2Id: orgChild2.id, + orgChild3Id: orgChild3.id, + orgChild4Id: orgChild4.id, + next_holderId: Not(IsNull()) || Not(""), + }, + }), + totalRootPositionNextVacant: await this.posMasterRepository.count( + { + where: { + orgRevisionId: orgRoot.orgRevisionId, + orgRootId: orgRoot.id, + orgChild1Id: orgChild1.id, + orgChild2Id: orgChild2.id, + orgChild3Id: orgChild3.id, + orgChild4Id: orgChild4.id, + next_holderId: IsNull() || "", + }, + }, + ), + })), + ), + })), + ), + })), + ), + })), + ), + }; + }), + ); return new HttpSuccess(formattedData); } diff --git a/src/database/data-source.ts b/src/database/data-source.ts index e44252a1..db5afc0d 100644 --- a/src/database/data-source.ts +++ b/src/database/data-source.ts @@ -47,8 +47,8 @@ export const AppDataSource = new DataSource({ logging: true, // timezone: "Z", entities: - process.env.NODE_ENV !== "production" - ? ["src/entities/**/*.ts"] + process.env.NODE_ENV !== "production" + ? ["src/entities/**/*.ts"] : ["dist/entities/**/*{.ts,.js}"], migrations: process.env.NODE_ENV !== "production" @@ -56,16 +56,6 @@ export const AppDataSource = new DataSource({ : ["dist/migration/**/*{.ts,.js}"], subscribers: [], logger: new MyCustomLogger(), - // Connection pool settings to prevent connection exhaustion - extra: { - connectionLimit: +(process.env.DB_CONNECTION_LIMIT || 50), - maxIdle: +(process.env.DB_MAX_IDLE || 10), - idleTimeout: +(process.env.DB_IDLE_TIMEOUT || 60000), - timezone: "+07:00", // Bangkok timezone (UTC+7) - }, - // TypeORM pool settings - poolSize: +(process.env.DB_POOL_SIZE || 10), - maxQueryExecutionTime: +(process.env.DB_MAX_QUERY_TIME || 3000), }); // export default database; diff --git a/src/middlewares/logs.ts b/src/middlewares/logs.ts index 1bff2060..70020810 100644 --- a/src/middlewares/logs.ts +++ b/src/middlewares/logs.ts @@ -1,6 +1,9 @@ import { NextFunction, Request, Response } from "express"; import { Client } from "@elastic/elasticsearch"; -import { logMemoryStore } from "../utils/LogMemoryStore"; +import { AppDataSource } from "../database/data-source"; +import { PosMaster } from "../entities/PosMaster"; +import { OrgRevision } from "../entities/OrgRevision"; +import { Profile } from "../entities/Profile"; if (!process.env.ELASTICSEARCH_INDEX) { throw new Error("Require ELASTICSEARCH_INDEX to store log."); @@ -24,6 +27,9 @@ async function logMiddleware(req: Request, res: Response, next: NextFunction) { if (!req.url.startsWith("/api/")) return next(); let data: any; + const repoPosmaster = AppDataSource.getRepository(PosMaster); + const repoProfile = AppDataSource.getRepository(Profile); + const repoRevision = AppDataSource.getRepository(OrgRevision); const originalJson = res.json; @@ -36,6 +42,13 @@ async function logMiddleware(req: Request, res: Response, next: NextFunction) { req.app.locals.logData = {}; + const revision = await repoRevision.findOne({ + where: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }); + res.on("finish", async () => { try { if (!req.url.startsWith("/api/")) return; @@ -56,7 +69,7 @@ async function logMiddleware(req: Request, res: Response, next: NextFunction) { if (req.url.startsWith("/api/v1/org/profile/")) system = "registry"; if (req.url.startsWith("/api/v1/org/profile-employee/")) system = "registry"; if (req.url.startsWith("/api/v1/org/profile-temp/")) system = "registry"; - + if (req.url.startsWith("/api/v1/org/commandType/admin")) system = "admin"; if (req.url.startsWith("/api/v1/org/commandSys/")) system = "admin"; if (req.url.startsWith("/api/v1/org/commandSalary/")) system = "admin"; @@ -66,15 +79,16 @@ async function logMiddleware(req: Request, res: Response, next: NextFunction) { if (req.url.startsWith("/api/v1/org/keycloak/")) system = "registry"; const level = LOG_LEVEL_MAP[process.env.LOG_LEVEL ?? "debug"] || 4; - - // Get profile from cache - const profileByKeycloak = await logMemoryStore.getProfileByKeycloak( - req.app.locals.logData.userId, - ); - - // Get rootId from cache - const rootId = await logMemoryStore.getRootIdByProfileId(profileByKeycloak?.id); - // console.log("ancestorDNA:", rootId); + const profileByKeycloak = await repoProfile.findOne({ + where: { keycloak: req.app.locals.logData.userId }, + }); + const rootId = await repoPosmaster.findOne({ + where: { + current_holderId: profileByKeycloak?.id, + orgRevisionId: revision?.id, + }, + select: ["orgRootId"], + }); if (level === 1 && res.statusCode < 500) return; if (level === 2 && res.statusCode < 400) return; @@ -82,7 +96,7 @@ async function logMiddleware(req: Request, res: Response, next: NextFunction) { const obj = { logType: res.statusCode >= 500 ? "error" : res.statusCode >= 400 ? "warning" : "info", ip: req.ip, - rootId: rootId ?? null, + rootId: rootId?.orgRootId ?? null, systemName: system, startTimeStamp: timestamp, endTimeStamp: new Date().toISOString(), diff --git a/src/services/OrganizationService.ts b/src/services/OrganizationService.ts deleted file mode 100644 index a9b2b796..00000000 --- a/src/services/OrganizationService.ts +++ /dev/null @@ -1,278 +0,0 @@ -import { AppDataSource } from "../database/data-source"; -import { PosMaster } from "../entities/PosMaster"; - -// Helper function to get aggregated position counts -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, - } - ); -} diff --git a/src/utils/LogMemoryStore.ts b/src/utils/LogMemoryStore.ts deleted file mode 100644 index 456c06c8..00000000 --- a/src/utils/LogMemoryStore.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { AppDataSource } from "../database/data-source"; -import { OrgRevision } from "../entities/OrgRevision"; -import { Profile } from "../entities/Profile"; -import { PosMaster } from "../entities/PosMaster"; - -interface LogCacheData { - currentRevision: OrgRevision | null; - profileCache: Map; // keycloak → Profile - rootIdCache: Map; // profileId → rootId - updatedAt: Date; -} - -class LogMemoryStore { - private cache: LogCacheData = { - currentRevision: null, - profileCache: new Map(), - rootIdCache: new Map(), - updatedAt: new Date(), - }; - private readonly REFRESH_INTERVAL = 10 * 60 * 1000; // 10 minutes - private isRefreshing = false; - private isInitialized = false; - private refreshTimer: NodeJS.Timeout | null = null; - - constructor() { - // ไม่ refresh ทันที - รอให้เรียก initialize() หลัง TypeORM ready - } - - // เริ่มต้น cache หลังจาก TypeORM initialize เสร็จ - initialize() { - if (this.isInitialized) { - console.log("[LogMemoryStore] Already initialized"); - return; - } - - this.isInitialized = true; - this.refreshCache(); - this.refreshTimer = setInterval(() => { - this.refreshCache(); - }, this.REFRESH_INTERVAL); - - console.log( - "[LogMemoryStore] Initialized with", - this.REFRESH_INTERVAL / 1000, - "second refresh interval", - ); - } - - private async refreshCache() { - if (this.isRefreshing) { - console.log("[LogMemoryStore] Already refreshing, skipping..."); - return; - } - - this.isRefreshing = true; - try { - // Refresh revision cache - const repoRevision = AppDataSource.getRepository(OrgRevision); - const revision = await repoRevision.findOne({ - where: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }); - this.cache.currentRevision = revision; - - // Clear on-demand caches (they will be rebuilt as needed) - this.cache.profileCache.clear(); - this.cache.rootIdCache.clear(); - - this.cache.updatedAt = new Date(); - console.log("[LogMemoryStore] Cache refreshed at", this.cache.updatedAt.toISOString()); - } catch (error) { - console.error("[LogMemoryStore] Error refreshing cache:", error); - } finally { - this.isRefreshing = false; - } - } - - getCurrentRevision(): OrgRevision | null { - return this.cache.currentRevision; - } - - getLastUpdateTime(): Date { - return this.cache.updatedAt; - } - - /** - * Get Profile by keycloak ID with caching - */ - async getProfileByKeycloak(keycloak: string): Promise { - // Check cache first - if (this.cache.profileCache.has(keycloak)) { - return this.cache.profileCache.get(keycloak)!; - } - - // Fetch from database - const repoProfile = AppDataSource.getRepository(Profile); - const profile = await repoProfile.findOne({ - where: { keycloak }, - }); - - // Cache the result - if (profile) { - this.cache.profileCache.set(keycloak, profile); - } - - return profile; - } - - /** - * Get RootId by profileId with caching - */ - async getRootIdByProfileId(profileId: string | undefined): Promise { - if (!profileId) return null; - - // Check cache first - if (this.cache.rootIdCache.has(profileId)) { - return this.cache.rootIdCache.get(profileId)!; - } - - // Fetch from database - const repoPosmaster = AppDataSource.getRepository(PosMaster); - const revision = this.getCurrentRevision(); - // - const posMaster = await repoPosmaster.findOne({ - where: { - current_holderId: profileId, - orgRevisionId: revision?.id, - }, - relations: ["orgRoot"], - select: { - orgRoot: { - ancestorDNA: true, - }, - }, - }); - - const rootId = posMaster?.orgRoot?.ancestorDNA ?? null; - - // Cache the result - if (rootId) { - this.cache.rootIdCache.set(profileId, rootId); - } - - return rootId; - } - - // สำหรับ shutdown - destroy() { - if (this.refreshTimer) { - clearInterval(this.refreshTimer); - this.refreshTimer = null; - } - } -} - -export const logMemoryStore = new LogMemoryStore(); diff --git a/src/utils/OrgStructureCache.ts b/src/utils/OrgStructureCache.ts deleted file mode 100644 index 33fa19e2..00000000 --- a/src/utils/OrgStructureCache.ts +++ /dev/null @@ -1,96 +0,0 @@ -interface CacheEntry { - data: any; - cachedAt: Date; -} - -class OrgStructureCache { - private cache: Map = new Map(); - private readonly CACHE_TTL = 10 * 60 * 1000; // 10 minutes - private isInitialized = false; - private cleanupTimer: NodeJS.Timeout | null = null; - - constructor() { - // Don't auto-initialize - wait for AppDataSource to be ready - } - - initialize() { - if (this.isInitialized) return; - - this.isInitialized = true; - // Cleanup expired entries every 10 minutes - this.cleanupTimer = setInterval(() => { - this.cleanup(); - }, this.CACHE_TTL); - - console.log("[OrgStructureCache] Initialized"); - } - - private generateKey(revisionId: string, rootId?: string): string { - return `org-structure-${revisionId}-${rootId || "all"}`; - } - - private cleanup() { - const now = Date.now(); - let cleaned = 0; - - for (const [key, entry] of this.cache.entries()) { - const age = now - entry.cachedAt.getTime(); - if (age > this.CACHE_TTL) { - this.cache.delete(key); - cleaned++; - } - } - - if (cleaned > 0) { - console.log(`[OrgStructureCache] Cleaned ${cleaned} expired entries`); - } - } - - async get(revisionId: string, rootId?: string): Promise { - const key = this.generateKey(revisionId, rootId); - const entry = this.cache.get(key); - - if (!entry) { - return null; - } - - // Check if expired - const age = Date.now() - entry.cachedAt.getTime(); - if (age > this.CACHE_TTL) { - this.cache.delete(key); - return null; - } - - console.log(`[OrgStructureCache] HIT: ${key}`); - return entry.data; - } - - async set(revisionId: string, rootId: string | undefined, data: any): Promise { - const key = this.generateKey(revisionId, rootId); - this.cache.set(key, { - data, - cachedAt: new Date(), - }); - console.log(`[OrgStructureCache] SET: ${key}`); - } - - invalidate(revisionId: string): void { - // Invalidate all entries for this revision - for (const key of this.cache.keys()) { - if (key.startsWith(`org-structure-${revisionId}`)) { - this.cache.delete(key); - } - } - console.log(`[OrgStructureCache] INVALIDATED: ${revisionId}`); - } - - destroy() { - if (this.cleanupTimer) { - clearInterval(this.cleanupTimer); - this.cleanupTimer = null; - } - this.cache.clear(); - } -} - -export const orgStructureCache = new OrgStructureCache();