From 50dbe520b56313cb3d0248940402f44fd1507ad5 Mon Sep 17 00:00:00 2001 From: Chukwudumebi Onwuli <37223065+deeonwuli@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:55:30 +0100 Subject: [PATCH 1/5] fix: update ou parameter to get tracked entities in v42 --- src/data/Dhis2RelationshipTypes.ts | 37 ++++++++++++++++++++----- src/data/Dhis2TrackedEntityInstances.ts | 14 +++++++--- src/domain/entities/OrgUnit.ts | 5 ++-- src/utils/d2-api.ts | 5 ++++ 4 files changed, 48 insertions(+), 13 deletions(-) diff --git a/src/data/Dhis2RelationshipTypes.ts b/src/data/Dhis2RelationshipTypes.ts index 02f4ef16..190e2ceb 100644 --- a/src/data/Dhis2RelationshipTypes.ts +++ b/src/data/Dhis2RelationshipTypes.ts @@ -13,19 +13,24 @@ import { getTrackedEntities, TrackedEntityGetRequest } from "./Dhis2TrackedEntit import { TrackerRelationship, RelationshipItem, TrackedEntitiesApiRequest } from "../domain/entities/TrackedEntity"; import { buildOrgUnitsParameter } from "../domain/entities/OrgUnit"; import { EventsAPIResponse } from "../domain/entities/DhisDataPackage"; +import { getVersion, getMajorVersion } from "../utils/d2-api"; type RelationshipTypesById = Record>; export type RelationshipOrgUnitFilter = TrackedEntityOURequestApi["ouMode"]; -export function buildOrgUnitMode(ouMode: RelationshipOrgUnitFilter, orgUnits?: Ref[]) { +export function buildOrgUnitMode(apiVersion: string, ouMode: RelationshipOrgUnitFilter, orgUnits?: Ref[]) { + const majorVersion = getMajorVersion(apiVersion); const isOuReq = ouMode === "SELECTED" || ouMode === "CHILDREN" || ouMode === "DESCENDANTS"; - //issue: v41 - orgUnitMode/ouMode; v38-40 ouMode; ouMode to be deprecated + //issue: v42 - orgUnitMode and orgUnits; v41 - orgUnitMode/ouMode and orgUnit; v38-40 ouMode; ouMode to be deprecated //can't use both orgUnitMode and ouMode in v41 if (!isOuReq) { return { ouMode }; } else if (orgUnits && orgUnits.length > 0) { - return { ouMode, orgUnit: buildOrgUnitsParameter(orgUnits) }; + const orgUnitsParam = buildOrgUnitsParameter(majorVersion, orgUnits); + if (majorVersion >= 42) return { orgUnitMode: ouMode, orgUnits: orgUnitsParam }; + else if (majorVersion === 41) return { ouMode: ouMode, orgUnit: orgUnitsParam }; + else return { ouMode, orgUnit: orgUnitsParam }; } else { throw new Error(`No orgUnits selected for ouMode ${ouMode}`); } @@ -169,6 +174,8 @@ export async function getRelationshipMetadata( api: D2Api, filters?: ProgramFilters ): Promise { + const apiVersion = await getVersion(api); + const { trackedEntityTypes, relationshipTypes: allRelationshipTypes, @@ -201,8 +208,22 @@ export async function getRelationshipMetadata( if (!isProgramAssociatedWithSomeConstraint) return; - const fromConstraint = await getConstraint(api, trackedEntityTypes, programs, type.fromConstraint, filters); - const toConstraint = await getConstraint(api, trackedEntityTypes, programs, type.toConstraint, filters); + const fromConstraint = await getConstraint( + api, + apiVersion, + trackedEntityTypes, + programs, + type.fromConstraint, + filters + ); + const toConstraint = await getConstraint( + api, + apiVersion, + trackedEntityTypes, + programs, + type.toConstraint, + filters + ); return fromConstraint && toConstraint ? { id: relType.id, name: relType.name, constraints: { from: fromConstraint, to: toConstraint } } @@ -217,6 +238,7 @@ type ProgramInfo = NamedRef & { programStages: NamedRef[] }; const getConstraint = memoizeAsync( async ( api: D2Api, + apiVersion: string, trackedEntityTypes: NamedRef[], programs: ProgramInfo[], constraint: D2RelationshipConstraint, @@ -235,7 +257,7 @@ const getConstraint = memoizeAsync( switch (constraint.relationshipEntity) { case "TRACKED_ENTITY_INSTANCE": - return getConstraintForTypeTei(api, filters, trackedEntityTypes, constraint); + return getConstraintForTypeTei(api, apiVersion, filters, trackedEntityTypes, constraint); case "PROGRAM_STAGE_INSTANCE": { if (constraint.program) { const program = programsById[constraint.program.id]; @@ -251,6 +273,7 @@ const getConstraint = memoizeAsync( async function getConstraintForTypeTei( api: D2Api, + apiVersion: string, filters: ProgramFilters | undefined, trackedEntityTypes: NamedRef[], constraint: D2RelationshipConstraint @@ -258,7 +281,7 @@ async function getConstraintForTypeTei( const { ouMode = "CAPTURE", organisationUnits = [] } = filters || {}; const trackedEntityTypesById = _.keyBy(trackedEntityTypes, obj => obj.id); - const ouModeQuery = buildOrgUnitMode(ouMode, organisationUnits); + const ouModeQuery = buildOrgUnitMode(apiVersion, ouMode, organisationUnits); const query = { ...ouModeQuery, diff --git a/src/data/Dhis2TrackedEntityInstances.ts b/src/data/Dhis2TrackedEntityInstances.ts index 441bbaa2..8d6f1af4 100644 --- a/src/data/Dhis2TrackedEntityInstances.ts +++ b/src/data/Dhis2TrackedEntityInstances.ts @@ -28,8 +28,9 @@ import { TrackedEntitiesApiRequest, TrackedEntitiesResponse, TrackedEntity } fro import { Params } from "@eyeseetea/d2-api/api/common"; import { ImportDataPackageOptions } from "../domain/repositories/InstanceRepository"; import { MULTI_TEXT_OPTION_DELIMITER } from "../domain/helpers/ExcelBuilder"; +import { getVersion } from "../utils/d2-api"; -export interface GetOptions { +type GetOptions = { api: D2Api; program: Ref; orgUnits: Ref[]; @@ -37,7 +38,7 @@ export interface GetOptions { enrollmentStartDate?: Moment; enrollmentEndDate?: Moment; relationshipsOuFilter?: RelationshipOrgUnitFilter; -} +}; type TrackerParams = Params & Omit; @@ -60,6 +61,8 @@ export async function getTrackedEntityInstances(options: GetOptions): Promise; async function getTeisFromApi(options: { api: D2Api; + apiVersion: string; program: Program; orgUnits: Ref[]; page: number; @@ -512,7 +517,8 @@ async function getTeisFromApi(options: { enrollmentEndDate?: Moment; ouMode: RelationshipOrgUnitFilter; }): Promise { - const { api, program, orgUnits, page, pageSize, enrollmentStartDate, enrollmentEndDate, ouMode } = options; + const { api, apiVersion, program, orgUnits, page, pageSize, enrollmentStartDate, enrollmentEndDate, ouMode } = + options; const fields: TeiKey[] = [ "trackedEntity", @@ -524,7 +530,7 @@ async function getTeisFromApi(options: { "geometry", ]; - const ouModeQuery = buildOrgUnitMode(ouMode, orgUnits); + const ouModeQuery = buildOrgUnitMode(apiVersion, ouMode, orgUnits); const filters: TrackedEntityGetRequest = { ...ouModeQuery, diff --git a/src/domain/entities/OrgUnit.ts b/src/domain/entities/OrgUnit.ts index 3e7d8f35..0ef84322 100644 --- a/src/domain/entities/OrgUnit.ts +++ b/src/domain/entities/OrgUnit.ts @@ -7,6 +7,7 @@ export interface OrgUnit { level: number; } -export function buildOrgUnitsParameter(orgUnitsIds: Ref[]): string { - return orgUnitsIds.map(({ id }) => id).join(";"); +export function buildOrgUnitsParameter(apiVersion: number, orgUnitsIds: Ref[]): string { + const separator = apiVersion >= 42 ? "," : ";"; + return orgUnitsIds.map(({ id }) => id).join(separator); } diff --git a/src/utils/d2-api.ts b/src/utils/d2-api.ts index d9eec508..32af67d2 100644 --- a/src/utils/d2-api.ts +++ b/src/utils/d2-api.ts @@ -15,3 +15,8 @@ export function getD2APiFromInstance(instance: DhisInstance) { backend: "fetch", }); } + +export async function getVersion(api: D2Api): Promise { + const { version } = await api.system.info.getData(); + return version; +} From b79e8b71c370936a39c3a03a18863088d8fd296d Mon Sep 17 00:00:00 2001 From: Chukwudumebi Onwuli <37223065+deeonwuli@users.noreply.github.com> Date: Thu, 5 Mar 2026 08:25:23 +0100 Subject: [PATCH 2/5] fix: remove obsolete condition for majorVersion 41 in buildOrgUnitMode function --- src/data/Dhis2RelationshipTypes.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/data/Dhis2RelationshipTypes.ts b/src/data/Dhis2RelationshipTypes.ts index 190e2ceb..a8ede5d1 100644 --- a/src/data/Dhis2RelationshipTypes.ts +++ b/src/data/Dhis2RelationshipTypes.ts @@ -29,7 +29,6 @@ export function buildOrgUnitMode(apiVersion: string, ouMode: RelationshipOrgUnit } else if (orgUnits && orgUnits.length > 0) { const orgUnitsParam = buildOrgUnitsParameter(majorVersion, orgUnits); if (majorVersion >= 42) return { orgUnitMode: ouMode, orgUnits: orgUnitsParam }; - else if (majorVersion === 41) return { ouMode: ouMode, orgUnit: orgUnitsParam }; else return { ouMode, orgUnit: orgUnitsParam }; } else { throw new Error(`No orgUnits selected for ouMode ${ouMode}`); From b010d066d3470faf101879326b07ccbae9491cc2 Mon Sep 17 00:00:00 2001 From: Chukwudumebi Onwuli <37223065+deeonwuli@users.noreply.github.com> Date: Tue, 24 Mar 2026 10:08:42 +0100 Subject: [PATCH 3/5] fix: refactor buildOrgUnitsParameter function for apiVersion 42 compatibility --- src/data/Dhis2RelationshipTypes.ts | 6 +++++- src/domain/entities/OrgUnit.ts | 7 +------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/data/Dhis2RelationshipTypes.ts b/src/data/Dhis2RelationshipTypes.ts index a8ede5d1..5ed7035e 100644 --- a/src/data/Dhis2RelationshipTypes.ts +++ b/src/data/Dhis2RelationshipTypes.ts @@ -11,7 +11,6 @@ import { promiseMap } from "../utils/promises"; import { getUid } from "./dhis2-uid"; import { getTrackedEntities, TrackedEntityGetRequest } from "./Dhis2TrackedEntityInstances"; import { TrackerRelationship, RelationshipItem, TrackedEntitiesApiRequest } from "../domain/entities/TrackedEntity"; -import { buildOrgUnitsParameter } from "../domain/entities/OrgUnit"; import { EventsAPIResponse } from "../domain/entities/DhisDataPackage"; import { getVersion, getMajorVersion } from "../utils/d2-api"; @@ -19,6 +18,11 @@ type RelationshipTypesById = Record= 42 ? "," : ";"; + return orgUnitsIds.map(({ id }) => id).join(separator); +} + export function buildOrgUnitMode(apiVersion: string, ouMode: RelationshipOrgUnitFilter, orgUnits?: Ref[]) { const majorVersion = getMajorVersion(apiVersion); const isOuReq = ouMode === "SELECTED" || ouMode === "CHILDREN" || ouMode === "DESCENDANTS"; diff --git a/src/domain/entities/OrgUnit.ts b/src/domain/entities/OrgUnit.ts index 0ef84322..17ac2128 100644 --- a/src/domain/entities/OrgUnit.ts +++ b/src/domain/entities/OrgUnit.ts @@ -1,4 +1,4 @@ -import { Id, Ref } from "./ReferenceObject"; +import { Id } from "./ReferenceObject"; export interface OrgUnit { id: Id; @@ -6,8 +6,3 @@ export interface OrgUnit { name: string; level: number; } - -export function buildOrgUnitsParameter(apiVersion: number, orgUnitsIds: Ref[]): string { - const separator = apiVersion >= 42 ? "," : ";"; - return orgUnitsIds.map(({ id }) => id).join(separator); -} From 65abbfb3d0ae1be6f985c632ca6e1bd6de0d8079 Mon Sep 17 00:00:00 2001 From: Chukwudumebi Onwuli <37223065+deeonwuli@users.noreply.github.com> Date: Tue, 24 Mar 2026 10:20:02 +0100 Subject: [PATCH 4/5] fix: refactor getVersion function to use memoization for improved performance --- src/utils/d2-api.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/utils/d2-api.ts b/src/utils/d2-api.ts index 32af67d2..992baaab 100644 --- a/src/utils/d2-api.ts +++ b/src/utils/d2-api.ts @@ -1,6 +1,7 @@ import _ from "lodash"; import { DhisInstance } from "../domain/entities/DhisInstance"; import { D2Api } from "../types/d2-api"; +import { memoizeAsync } from "./cache"; export function getMajorVersion(version: string): number { const apiVersion = _.get(version.split("."), 1); @@ -16,7 +17,7 @@ export function getD2APiFromInstance(instance: DhisInstance) { }); } -export async function getVersion(api: D2Api): Promise { +export const getVersion = memoizeAsync(async (api: D2Api): Promise => { const { version } = await api.system.info.getData(); return version; -} +}); From 83a6492595ea0ded10d64d59e6698d5d5a4f535d Mon Sep 17 00:00:00 2001 From: Chukwudumebi Onwuli <37223065+deeonwuli@users.noreply.github.com> Date: Tue, 24 Mar 2026 10:51:35 +0100 Subject: [PATCH 5/5] fix: add v42 compatibility to getExistingTeis --- src/data/Dhis2RelationshipTypes.ts | 3 ++- src/data/Dhis2TrackedEntityInstances.ts | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/data/Dhis2RelationshipTypes.ts b/src/data/Dhis2RelationshipTypes.ts index 5ed7035e..979ea35c 100644 --- a/src/data/Dhis2RelationshipTypes.ts +++ b/src/data/Dhis2RelationshipTypes.ts @@ -29,7 +29,8 @@ export function buildOrgUnitMode(apiVersion: string, ouMode: RelationshipOrgUnit //issue: v42 - orgUnitMode and orgUnits; v41 - orgUnitMode/ouMode and orgUnit; v38-40 ouMode; ouMode to be deprecated //can't use both orgUnitMode and ouMode in v41 if (!isOuReq) { - return { ouMode }; + if (majorVersion >= 42) return { orgUnitMode: ouMode }; + else return { ouMode }; } else if (orgUnits && orgUnits.length > 0) { const orgUnitsParam = buildOrgUnitsParameter(majorVersion, orgUnits); if (majorVersion >= 42) return { orgUnitMode: ouMode, orgUnits: orgUnitsParam }; diff --git a/src/data/Dhis2TrackedEntityInstances.ts b/src/data/Dhis2TrackedEntityInstances.ts index 8d6f1af4..34c50793 100644 --- a/src/data/Dhis2TrackedEntityInstances.ts +++ b/src/data/Dhis2TrackedEntityInstances.ts @@ -466,8 +466,11 @@ function getMultiTextValue(options: { } async function getExistingTeis(api: D2Api): Promise { + const apiVersion = await getVersion(api); + const ouModeParam = buildOrgUnitMode(apiVersion, "CAPTURE"); + const query = { - ouMode: "CAPTURE", + ...ouModeParam, pageSize: 1000, totalPages: true, fields: "trackedEntity",