From c5c4c4609d2935b7b3b0ccf8d11cca614c8851ab Mon Sep 17 00:00:00 2001
From: Ana Garcia <ana@eyeseetea.com>
Date: Thu, 24 Oct 2024 14:35:38 +0200
Subject: [PATCH] Merge domain entities in useIMTeamBuilder to create
 presentation entity

---
 src/CompositionRoot.ts                        | 10 ++
 .../DiseaseOutbreakEventD2Repository.ts       | 57 +++++++----
 .../repositories/TeamMemberD2Repository.ts    |  2 +-
 .../test/TeamMemberTestRepository.ts          |  2 +-
 .../utils/IncidentManagementTeamMapper.ts     | 89 ++++++++++++++++--
 .../DiseaseOutbreakEventAggregateRoot.ts      | 13 ++-
 .../repositories/TeamMemberRepository.ts      |  2 +-
 src/domain/usecases/GetAllRolesUseCase.ts     | 11 +++
 ...MembersForIncidentManagementTeamUseCase.ts | 11 +++
 .../GetIncidentManagementTeamById.ts          |  2 +-
 .../GetIncidentManagementTeamWithOptions.ts   |  2 +-
 .../useIMTeamBuilder.ts                       | 94 +++++++++++++++++--
 12 files changed, 249 insertions(+), 46 deletions(-)
 create mode 100644 src/domain/usecases/GetAllRolesUseCase.ts
 create mode 100644 src/domain/usecases/GetTeamMembersForIncidentManagementTeamUseCase.ts

diff --git a/src/CompositionRoot.ts b/src/CompositionRoot.ts
index 563737f8..78835d4a 100644
--- a/src/CompositionRoot.ts
+++ b/src/CompositionRoot.ts
@@ -56,6 +56,8 @@ import { SystemD2Repository } from "./data/repositories/SystemD2Repository";
 import { SystemTestRepository } from "./data/repositories/test/SystemTestRepository";
 import { GetOverviewCardsUseCase } from "./domain/usecases/GetOverviewCardsUseCase";
 import { GetDiseaseOutbreakEventAggregateRootByIdUseCase } from "./domain/usecases/GetDiseaseOutbreakEventAggregateRootByIdUseCase";
+import { GetAllRolesUseCase } from "./domain/usecases/GetAllRolesUseCase";
+import { GetTeamMembersForIncidentManagementTeamUseCase } from "./domain/usecases/GetTeamMembersForIncidentManagementTeamUseCase";
 
 export type CompositionRoot = ReturnType<typeof getCompositionRoot>;
 
@@ -117,6 +119,14 @@ function getCompositionRoot(repositories: Repositories) {
         charts: {
             getCases: new GetChartConfigByTypeUseCase(repositories.chartConfigRepository),
         },
+        teamMembers: {
+            getForIncidentManagementTeam: new GetTeamMembersForIncidentManagementTeamUseCase(
+                repositories.teamMemberRepository
+            ),
+        },
+        roles: {
+            getAll: new GetAllRolesUseCase(repositories.roleRepository),
+        },
     };
 }
 
diff --git a/src/data/repositories/DiseaseOutbreakEventD2Repository.ts b/src/data/repositories/DiseaseOutbreakEventD2Repository.ts
index 10b4834d..7d679853 100644
--- a/src/data/repositories/DiseaseOutbreakEventD2Repository.ts
+++ b/src/data/repositories/DiseaseOutbreakEventD2Repository.ts
@@ -284,29 +284,46 @@ export class DiseaseOutbreakEventD2Repository implements DiseaseOutbreakEventRep
     }
 
     private getIncidentManagementTeamInAggregateRoot(
-        diseaseOutbreakId: Id
+        diseaseOutbreakEventId: Id
     ): FutureData<IncidentManagementTeamInAggregateRoot> {
-        return apiToFuture(
-            this.api.tracker.events.get({
-                program: RTSL_ZEBRA_PROGRAM_ID,
-                orgUnit: RTSL_ZEBRA_ORG_UNIT_ID,
-                trackedEntity: diseaseOutbreakId,
-                programStage: RTSL_ZEBRA_INCIDENT_MANAGEMENT_TEAM_BUILDER_PROGRAM_STAGE_ID,
-                fields: {
-                    dataValues: {
-                        dataElement: dataElementFields,
-                        value: true,
-                    },
-                    trackedEntity: true,
-                    event: true,
-                },
-            })
+        return getProgramStage(
+            this.api,
+            RTSL_ZEBRA_INCIDENT_MANAGEMENT_TEAM_BUILDER_PROGRAM_STAGE_ID
         )
-            .flatMap(response =>
-                assertOrError(response.instances, `Incident management team not found`)
+            .flatMap(incidentManagementTeamBuilderResponse =>
+                assertOrError(
+                    incidentManagementTeamBuilderResponse.objects[0],
+                    `Incident management team builder program stage not found`
+                )
             )
-            .flatMap(d2Events => {
-                return Future.success(mapD2EventsToIncidentManagementTeamInAggregateRoot(d2Events));
+            .flatMap(programStageDataElementsMetadata => {
+                return apiToFuture(
+                    this.api.tracker.events.get({
+                        program: RTSL_ZEBRA_PROGRAM_ID,
+                        orgUnit: RTSL_ZEBRA_ORG_UNIT_ID,
+                        trackedEntity: diseaseOutbreakEventId,
+                        programStage: RTSL_ZEBRA_INCIDENT_MANAGEMENT_TEAM_BUILDER_PROGRAM_STAGE_ID,
+                        fields: {
+                            dataValues: {
+                                dataElement: dataElementFields,
+                                value: true,
+                            },
+                            trackedEntity: true,
+                            event: true,
+                        },
+                    })
+                )
+                    .flatMap(response =>
+                        assertOrError(response.instances, `Incident management team not found`)
+                    )
+                    .flatMap(d2Events => {
+                        return Future.success(
+                            mapD2EventsToIncidentManagementTeamInAggregateRoot(
+                                d2Events,
+                                programStageDataElementsMetadata.programStageDataElements
+                            )
+                        );
+                    });
             });
     }
 
diff --git a/src/data/repositories/TeamMemberD2Repository.ts b/src/data/repositories/TeamMemberD2Repository.ts
index 8913f1e4..fd8dfe08 100644
--- a/src/data/repositories/TeamMemberD2Repository.ts
+++ b/src/data/repositories/TeamMemberD2Repository.ts
@@ -38,7 +38,7 @@ export class TeamMemberD2Repository implements TeamMemberRepository {
         return this.getTeamMembersByUserGroup(RTSL_ZEBRA_RISKASSESSOR);
     }
 
-    getForIncidentManagementTeamMembers(): FutureData<TeamMember[]> {
+    getForIncidentManagementTeam(): FutureData<TeamMember[]> {
         return this.getTeamMembersByUserGroup(RTSL_ZEBRA_INCIDENT_MANAGEMENT_TEAM_MEMBERS);
     }
 
diff --git a/src/data/repositories/test/TeamMemberTestRepository.ts b/src/data/repositories/test/TeamMemberTestRepository.ts
index 38e86470..f2b975b6 100644
--- a/src/data/repositories/test/TeamMemberTestRepository.ts
+++ b/src/data/repositories/test/TeamMemberTestRepository.ts
@@ -36,7 +36,7 @@ export class TeamMemberTestRepository implements TeamMemberRepository {
         return Future.success([teamMember]);
     }
 
-    getForIncidentManagementTeamMembers(): FutureData<TeamMember[]> {
+    getForIncidentManagementTeam(): FutureData<TeamMember[]> {
         const teamMember: TeamMember = new TeamMember({
             id: "incidentManagementTeamMember",
             username: "incidentManagementTeamMember",
diff --git a/src/data/repositories/utils/IncidentManagementTeamMapper.ts b/src/data/repositories/utils/IncidentManagementTeamMapper.ts
index 2cf1bd02..ba851bea 100644
--- a/src/data/repositories/utils/IncidentManagementTeamMapper.ts
+++ b/src/data/repositories/utils/IncidentManagementTeamMapper.ts
@@ -16,7 +16,10 @@ import {
     RTSL_ZEBRA_INCIDENT_MANAGEMENT_TEAM_BUILDER_IDS_WITHOUT_ROLES,
 } from "../consts/IncidentManagementTeamBuilderConstants";
 import { D2ProgramStageDataElementsMetadata } from "./MetadataHelper";
-import { IncidentManagementTeamInAggregateRoot } from "../../../domain/entities/disease-outbreak-event/DiseaseOutbreakEventAggregateRoot";
+import {
+    IncidentManagementTeamInAggregateRoot,
+    IncidentManagementTeamRole,
+} from "../../../domain/entities/disease-outbreak-event/DiseaseOutbreakEventAggregateRoot";
 
 export function mapD2EventsToIncidentManagementTeam(
     diseaseOutbreakId: Id,
@@ -192,17 +195,85 @@ function getValueFromIncidentManagementTeamMember(
 }
 
 export function mapD2EventsToIncidentManagementTeamInAggregateRoot(
-    d2Events: D2TrackerEvent[]
+    d2Events: D2TrackerEvent[],
+    incidentManagementTeamProgramStageDataElements: D2ProgramStageDataElementsMetadata[]
 ): IncidentManagementTeamInAggregateRoot {
-    const incidentManagementTeamMembers = d2Events.map(event => {
-        const teamMemberAssignedUsername = getValueById(
-            event.dataValues,
-            RTSL_ZEBRA_INCIDENT_MANAGEMENT_TEAM_BUILDER_IDS_WITHOUT_ROLES.teamMemberAssigned
-        );
-        return teamMemberAssignedUsername;
-    });
+    const incidentManagementTeamRolesByUsername = getIncidentManagementTeamRolesByUsername(
+        d2Events,
+        incidentManagementTeamProgramStageDataElements
+    );
+
+    const incidentManagementTeamMembers = Object.keys(incidentManagementTeamRolesByUsername).map(
+        username => {
+            const teamRoles = incidentManagementTeamRolesByUsername[username];
+            return teamRoles
+                ? {
+                      username: username,
+                      teamRoles: teamRoles,
+                  }
+                : null;
+        }
+    );
 
     return new IncidentManagementTeamInAggregateRoot({
         teamHierarchy: _c(incidentManagementTeamMembers).compact().toArray(),
     });
 }
+
+function getIncidentManagementTeamRolesByUsername(
+    d2Events: D2TrackerEvent[],
+    incidentManagementTeamProgramStageDataElements: D2ProgramStageDataElementsMetadata[]
+): Record<string, IncidentManagementTeamRole[]> {
+    return d2Events.reduce(
+        (acc: Record<string, IncidentManagementTeamRole[]>, event: D2TrackerEvent) => {
+            const teamMemberAssignedUsername = getValueById(
+                event.dataValues,
+                RTSL_ZEBRA_INCIDENT_MANAGEMENT_TEAM_BUILDER_IDS_WITHOUT_ROLES.teamMemberAssigned
+            );
+
+            if (!teamMemberAssignedUsername) {
+                return acc;
+            }
+
+            const teamRoles = incidentManagementTeamProgramStageDataElements.reduce(
+                (accTeamRoles: IncidentManagementTeamRole[], programStage) => {
+                    const roleId = programStage.dataElement.id;
+                    const reportsToUsername = getValueById(
+                        event.dataValues,
+                        RTSL_ZEBRA_INCIDENT_MANAGEMENT_TEAM_BUILDER_IDS_WITHOUT_ROLES.reportsToUsername
+                    );
+
+                    if (getValueById(event.dataValues, roleId) === "true") {
+                        return [
+                            ...accTeamRoles,
+                            {
+                                id: event.event,
+                                roleId: roleId,
+                                reportsToUsername: reportsToUsername,
+                            },
+                        ];
+                    }
+
+                    return accTeamRoles;
+                },
+                []
+            );
+
+            if (acc[teamMemberAssignedUsername]) {
+                return {
+                    ...acc,
+                    [teamMemberAssignedUsername]: [
+                        ...(acc[teamMemberAssignedUsername] || []),
+                        ...teamRoles,
+                    ],
+                };
+            } else {
+                return {
+                    ...acc,
+                    [teamMemberAssignedUsername]: teamRoles,
+                };
+            }
+        },
+        {}
+    );
+}
diff --git a/src/domain/entities/disease-outbreak-event/DiseaseOutbreakEventAggregateRoot.ts b/src/domain/entities/disease-outbreak-event/DiseaseOutbreakEventAggregateRoot.ts
index 085c6591..158ea68f 100644
--- a/src/domain/entities/disease-outbreak-event/DiseaseOutbreakEventAggregateRoot.ts
+++ b/src/domain/entities/disease-outbreak-event/DiseaseOutbreakEventAggregateRoot.ts
@@ -33,10 +33,19 @@ export type DiseaseOutbreakEventAggregateRootBaseAttrs = NamedRef & {
     notes: Maybe<string>;
 };
 
-type IncidentManagementTeamMemberUsername = string;
+export type IncidentManagementTeamRole = {
+    id: Id;
+    roleId: Id;
+    reportsToUsername: Maybe<string>;
+};
+
+type IncidentManagementTeamMember = {
+    username: string;
+    teamRoles: IncidentManagementTeamRole[];
+};
 
 interface IncidentManagementTeamAttrsInAggregateRoot {
-    teamHierarchy: IncidentManagementTeamMemberUsername[];
+    teamHierarchy: IncidentManagementTeamMember[];
 }
 
 export class IncidentManagementTeamInAggregateRoot extends Struct<IncidentManagementTeamAttrsInAggregateRoot>() {}
diff --git a/src/domain/repositories/TeamMemberRepository.ts b/src/domain/repositories/TeamMemberRepository.ts
index 26f94769..f490f266 100644
--- a/src/domain/repositories/TeamMemberRepository.ts
+++ b/src/domain/repositories/TeamMemberRepository.ts
@@ -7,5 +7,5 @@ export interface TeamMemberRepository {
     get(id: Id): FutureData<TeamMember>;
     getIncidentManagers(): FutureData<TeamMember[]>;
     getRiskAssessors(): FutureData<TeamMember[]>;
-    getForIncidentManagementTeamMembers(): FutureData<TeamMember[]>;
+    getForIncidentManagementTeam(): FutureData<TeamMember[]>;
 }
diff --git a/src/domain/usecases/GetAllRolesUseCase.ts b/src/domain/usecases/GetAllRolesUseCase.ts
new file mode 100644
index 00000000..b81c379b
--- /dev/null
+++ b/src/domain/usecases/GetAllRolesUseCase.ts
@@ -0,0 +1,11 @@
+import { FutureData } from "../../data/api-futures";
+import { Role } from "../entities/incident-management-team/Role";
+import { RoleRepository } from "../repositories/RoleRepository";
+
+export class GetAllRolesUseCase {
+    constructor(private roleRepository: RoleRepository) {}
+
+    public execute(): FutureData<Role[]> {
+        return this.roleRepository.getAll();
+    }
+}
diff --git a/src/domain/usecases/GetTeamMembersForIncidentManagementTeamUseCase.ts b/src/domain/usecases/GetTeamMembersForIncidentManagementTeamUseCase.ts
new file mode 100644
index 00000000..fd7c6b5a
--- /dev/null
+++ b/src/domain/usecases/GetTeamMembersForIncidentManagementTeamUseCase.ts
@@ -0,0 +1,11 @@
+import { FutureData } from "../../data/api-futures";
+import { TeamMember } from "../entities/incident-management-team/TeamMember";
+import { TeamMemberRepository } from "../repositories/TeamMemberRepository";
+
+export class GetTeamMembersForIncidentManagementTeamUseCase {
+    constructor(private teamMemberRepository: TeamMemberRepository) {}
+
+    public execute(): FutureData<TeamMember[]> {
+        return this.teamMemberRepository.getForIncidentManagementTeam();
+    }
+}
diff --git a/src/domain/usecases/utils/incident-management-team/GetIncidentManagementTeamById.ts b/src/domain/usecases/utils/incident-management-team/GetIncidentManagementTeamById.ts
index 0d55112d..39eda058 100644
--- a/src/domain/usecases/utils/incident-management-team/GetIncidentManagementTeamById.ts
+++ b/src/domain/usecases/utils/incident-management-team/GetIncidentManagementTeamById.ts
@@ -12,7 +12,7 @@ export function getIncidentManagementTeamById(
         diseaseOutbreakEventRepository: DiseaseOutbreakEventRepository;
     }
 ): FutureData<Maybe<IncidentManagementTeam>> {
-    return repositories.teamMemberRepository.getAll().flatMap(teamMembers => {
+    return repositories.teamMemberRepository.getForIncidentManagementTeam().flatMap(teamMembers => {
         return repositories.diseaseOutbreakEventRepository.getIncidentManagementTeam(
             diseaseOutbreakId,
             teamMembers
diff --git a/src/domain/usecases/utils/incident-management-team/GetIncidentManagementTeamWithOptions.ts b/src/domain/usecases/utils/incident-management-team/GetIncidentManagementTeamWithOptions.ts
index 73e581c3..4ff1a9ad 100644
--- a/src/domain/usecases/utils/incident-management-team/GetIncidentManagementTeamWithOptions.ts
+++ b/src/domain/usecases/utils/incident-management-team/GetIncidentManagementTeamWithOptions.ts
@@ -22,7 +22,7 @@ export function getIncidentManagementTeamWithOptions(
 ): FutureData<IncidentManagementTeamMemberFormData> {
     return Future.joinObj({
         roles: repositories.roleRepository.getAll(),
-        teamMembers: repositories.teamMemberRepository.getForIncidentManagementTeamMembers(),
+        teamMembers: repositories.teamMemberRepository.getForIncidentManagementTeam(),
         incidentManagers: repositories.teamMemberRepository.getIncidentManagers(),
         incidentManagementTeam: getIncidentManagementTeamById(eventTrackerDetails.id, repositories),
     }).flatMap(({ roles, teamMembers, incidentManagers, incidentManagementTeam }) => {
diff --git a/src/webapp/pages/incident-management-team-builder/useIMTeamBuilder.ts b/src/webapp/pages/incident-management-team-builder/useIMTeamBuilder.ts
index b95d3772..fe73e1a7 100644
--- a/src/webapp/pages/incident-management-team-builder/useIMTeamBuilder.ts
+++ b/src/webapp/pages/incident-management-team-builder/useIMTeamBuilder.ts
@@ -12,6 +12,9 @@ import { IncidentManagementTeam } from "../../../domain/entities/incident-manage
 import { TeamMember, TeamRole } from "../../../domain/entities/incident-management-team/TeamMember";
 import { INCIDENT_MANAGER_ROLE } from "../../../data/repositories/consts/IncidentManagementTeamBuilderConstants";
 import _c from "../../../domain/entities/generic/Collection";
+import { IncidentManagementTeamInAggregateRoot } from "../../../domain/entities/disease-outbreak-event/DiseaseOutbreakEventAggregateRoot";
+import { Role } from "../../../domain/entities/incident-management-team/Role";
+import { Future } from "../../../domain/entities/generic/Future";
 
 type GlobalMessage = {
     text: string;
@@ -57,15 +60,32 @@ export function useIMTeamBuilder(id: Id): State {
     const [searchTerm, setSearchTerm] = useState<string>("");
 
     const getIncidentManagementTeam = useCallback(() => {
-        compositionRoot.diseaseOutbreakEvent.get.execute(id).run(
-            diseaseOutbreakEvent => {
-                const incidentManagementTeam = diseaseOutbreakEvent?.incidentManagementTeam;
-                setIncidentManagementTeam(incidentManagementTeam);
-                setIncidentManagementTeamHierarchyItems(
-                    mapIncidentManagementTeamToIncidentManagementTeamHierarchyItems(
-                        incidentManagementTeam?.teamHierarchy
-                    )
-                );
+        Future.joinObj({
+            diseaseOutbreakEventAggregateRoot:
+                compositionRoot.diseaseOutbreakEvent.getAggregateRoot.execute(id),
+            teamMembers: compositionRoot.teamMembers.getForIncidentManagementTeam.execute(),
+            roles: compositionRoot.roles.getAll.execute(),
+        }).run(
+            ({ diseaseOutbreakEventAggregateRoot, teamMembers, roles }) => {
+                if (diseaseOutbreakEventAggregateRoot.incidentManagementTeam) {
+                    const incidentManagementTeam = buildIncidentManagementTeam(
+                        id,
+                        diseaseOutbreakEventAggregateRoot.incidentManagementTeam,
+                        teamMembers,
+                        roles
+                    );
+                    setIncidentManagementTeam(incidentManagementTeam);
+                    setIncidentManagementTeamHierarchyItems(
+                        mapIncidentManagementTeamToIncidentManagementTeamHierarchyItems(
+                            incidentManagementTeam?.teamHierarchy
+                        )
+                    );
+                } else {
+                    setGlobalMessage({
+                        text: `Incident Management Team not found`,
+                        type: "error",
+                    });
+                }
             },
             err => {
                 console.debug(err);
@@ -75,7 +95,12 @@ export function useIMTeamBuilder(id: Id): State {
                 });
             }
         );
-    }, [compositionRoot.diseaseOutbreakEvent.get, id]);
+    }, [
+        compositionRoot.diseaseOutbreakEvent.getAggregateRoot,
+        compositionRoot.roles.getAll,
+        compositionRoot.teamMembers.getForIncidentManagementTeam,
+        id,
+    ]);
 
     useEffect(() => {
         getIncidentManagementTeam();
@@ -240,6 +265,55 @@ export function useIMTeamBuilder(id: Id): State {
     };
 }
 
+function buildIncidentManagementTeam(
+    diseaseOutbreakId: Id,
+    diseaseOutbreakEventIncidentManagementTeam: IncidentManagementTeamInAggregateRoot,
+    teamMembers: TeamMember[],
+    roles: Role[]
+): IncidentManagementTeam {
+    const imTeamMembers: TeamMember[] = _c(
+        diseaseOutbreakEventIncidentManagementTeam.teamHierarchy.map(imTeamMember => {
+            const teamMember = teamMembers.find(tm => tm.username === imTeamMember.username);
+            if (teamMember) {
+                const teamRoles: TeamRole[] = _c(
+                    imTeamMember.teamRoles.map(teamRole => {
+                        const role = roles.find(r => r.id === teamRole.roleId);
+                        if (role) {
+                            return {
+                                id: teamRole.id,
+                                name: role.name,
+                                diseaseOutbreakId: diseaseOutbreakId,
+                                roleId: teamRole.roleId,
+                                reportsToUsername: teamRole.reportsToUsername,
+                            };
+                        }
+                    })
+                )
+                    .compact()
+                    .toArray();
+
+                return new TeamMember({
+                    id: teamMember.id,
+                    name: teamMember.name,
+                    username: teamMember.username,
+                    phone: teamMember.phone,
+                    email: teamMember.email,
+                    status: teamMember.status,
+                    photo: teamMember.photo,
+                    teamRoles: teamRoles,
+                    workPosition: teamMember.workPosition,
+                });
+            }
+        })
+    )
+        .compact()
+        .toArray();
+
+    return new IncidentManagementTeam({
+        teamHierarchy: imTeamMembers,
+    });
+}
+
 function mapIncidentManagementTeamToIncidentManagementTeamHierarchyItems(
     incidentManagementTeamHierarchy: Maybe<TeamMember[]>
 ): IMTeamHierarchyOption[] {