From c5c4c4609d2935b7b3b0ccf8d11cca614c8851ab Mon Sep 17 00:00:00 2001 From: Ana Garcia 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 563737f..78835d4 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; @@ -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 10b4834..7d67985 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 { - 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 8913f1e..fd8dfe0 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 { + getForIncidentManagementTeam(): FutureData { 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 38e8647..f2b975b 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 { + getForIncidentManagementTeam(): FutureData { 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 2cf1bd0..ba851be 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 { + return d2Events.reduce( + (acc: Record, 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 085c659..158ea68 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; }; -type IncidentManagementTeamMemberUsername = string; +export type IncidentManagementTeamRole = { + id: Id; + roleId: Id; + reportsToUsername: Maybe; +}; + +type IncidentManagementTeamMember = { + username: string; + teamRoles: IncidentManagementTeamRole[]; +}; interface IncidentManagementTeamAttrsInAggregateRoot { - teamHierarchy: IncidentManagementTeamMemberUsername[]; + teamHierarchy: IncidentManagementTeamMember[]; } export class IncidentManagementTeamInAggregateRoot extends Struct() {} diff --git a/src/domain/repositories/TeamMemberRepository.ts b/src/domain/repositories/TeamMemberRepository.ts index 26f9476..f490f26 100644 --- a/src/domain/repositories/TeamMemberRepository.ts +++ b/src/domain/repositories/TeamMemberRepository.ts @@ -7,5 +7,5 @@ export interface TeamMemberRepository { get(id: Id): FutureData; getIncidentManagers(): FutureData; getRiskAssessors(): FutureData; - getForIncidentManagementTeamMembers(): FutureData; + getForIncidentManagementTeam(): FutureData; } diff --git a/src/domain/usecases/GetAllRolesUseCase.ts b/src/domain/usecases/GetAllRolesUseCase.ts new file mode 100644 index 0000000..b81c379 --- /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 { + return this.roleRepository.getAll(); + } +} diff --git a/src/domain/usecases/GetTeamMembersForIncidentManagementTeamUseCase.ts b/src/domain/usecases/GetTeamMembersForIncidentManagementTeamUseCase.ts new file mode 100644 index 0000000..fd7c6b5 --- /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 { + 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 0d55112..39eda05 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> { - 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 73e581c..4ff1a9a 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 { 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 b95d377..fe73e1a 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(""); 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 ): IMTeamHierarchyOption[] {