diff --git a/i18n/en.pot b/i18n/en.pot index 0090bc99..383565f7 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2024-10-09T10:56:24.909Z\n" -"PO-Revision-Date: 2024-10-09T10:56:24.909Z\n" +"POT-Creation-Date: 2024-11-07T10:23:53.316Z\n" +"PO-Revision-Date: 2024-11-07T10:23:53.316Z\n" msgid "Low" msgstr "" @@ -84,9 +84,18 @@ msgstr "" msgid "Cancel" msgstr "" +msgid "Edit Action Plan" +msgstr "" + +msgid "Event completed" +msgstr "" + msgid "Edit Details" msgstr "" +msgid "Complete Event" +msgstr "" + msgid "Notes" msgstr "" @@ -96,6 +105,9 @@ msgstr "" msgid "Currently assigned:" msgstr "" +msgid "Error loading current Incident Management Team" +msgstr "" + msgid "Create Event" msgstr "" @@ -186,13 +198,31 @@ msgstr "" msgid "Risk Assessment Questionnaire saved successfully" msgstr "" +msgid "Incident Action Plan saved successfully" +msgstr "" + +msgid "Incident Response Actions saved successfully" +msgstr "" + msgid "Incident Management Team Member saved successfully" msgstr "" +msgid "Create an incident action plan" +msgstr "" + +msgid "No plan has been created for this incident" +msgstr "" + +msgid "Create IAP" +msgstr "" + msgid "Incident Action Plan" msgstr "" -msgid "Cholera in NW Province, June 2023" +msgid "Team" +msgstr "" + +msgid "Edit Team" msgstr "" msgid "Incident Management Team Builder" @@ -204,10 +234,16 @@ msgstr "" msgid "Assign Role" msgstr "" +msgid "Delete Roles" +msgstr "" + msgid "Delete Role" msgstr "" -msgid "Delete team role" +msgid "Confirm deletion" +msgstr "" + +msgid "Delete" msgstr "" msgid "Resources" diff --git a/i18n/es.po b/i18n/es.po index 9edfa351..d1247b77 100644 --- a/i18n/es.po +++ b/i18n/es.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2024-10-09T10:56:24.909Z\n" +"POT-Creation-Date: 2024-10-15T13:56:24.806Z\n" "PO-Revision-Date: 2018-10-25T09:02:35.143Z\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -83,6 +83,9 @@ msgstr "" msgid "Cancel" msgstr "" +msgid "Edit Action Plan" +msgstr "" + msgid "Edit Details" msgstr "" @@ -185,12 +188,30 @@ msgstr "" msgid "Risk Assessment Questionnaire saved successfully" msgstr "" +msgid "Incident Action Plan saved successfully" +msgstr "" + +msgid "Incident Response Actions saved successfully" +msgstr "" + +msgid "Create an incident action plan" +msgstr "" + +msgid "No plan has been created for this incident" +msgstr "" + +msgid "Create IAP" +msgstr "" + msgid "Incident Management Team Member saved successfully" msgstr "" msgid "Incident Action Plan" msgstr "" +msgid "Incident Management Team Builder" +msgstr "" + msgid "Cholera in NW Province, June 2023" msgstr "" diff --git a/package.json b/package.json index 32c84ab4..2a9982b4 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "zebra", + "name": "zebra-perf-test", "description": "Zambia Emergency Bridge for Response Application", "version": "0.0.1", "license": "GPL-3.0", @@ -32,6 +32,7 @@ "dotenv": "^16.4.5", "font-awesome": "4.7.0", "moment": "^2.30.1", + "moment-timezone": "^0.5.46", "purify-ts": "1.2.0", "purify-ts-extra-codec": "0.6.0", "react": "^18.2.0", @@ -116,8 +117,8 @@ "script-map-outbreak-to-alerts": "npx ts-node -r dotenv/config src/scripts/mapDiseaseOutbreakToAlerts.ts" }, "manifest.webapp": { - "name": "ZEBRA", - "description": "Zambia Emergency Bridge for Response Application", + "name": "ZEBRA-PERFTEST", + "description": "Zambia Emergency Bridge for Response Application-PERFTEST", "icons": { "48": "icon.png" }, diff --git a/src/CompositionRoot.ts b/src/CompositionRoot.ts index 78835d4a..0db15c8d 100644 --- a/src/CompositionRoot.ts +++ b/src/CompositionRoot.ts @@ -7,14 +7,13 @@ import { UserRepository } from "./domain/repositories/UserRepository"; import { GetCurrentUserUseCase } from "./domain/usecases/GetCurrentUserUseCase"; import { GetDiseaseOutbreakByIdUseCase } from "./domain/usecases/GetDiseaseOutbreakByIdUseCase"; import { D2Api } from "./types/d2-api"; -import { OptionsRepository } from "./domain/repositories/OptionsRepository"; + import { TeamMemberRepository } from "./domain/repositories/TeamMemberRepository"; import { OrgUnitRepository } from "./domain/repositories/OrgUnitRepository"; -import { OptionsD2Repository } from "./data/repositories/OptionsD2Repository"; + import { TeamMemberD2Repository } from "./data/repositories/TeamMemberD2Repository"; import { OrgUnitD2Repository } from "./data/repositories/OrgUnitD2Repository"; import { AlertD2Repository } from "./data/repositories/AlertD2Repository"; -import { OptionsTestRepository } from "./data/repositories/test/OptionsTestRepository"; import { TeamMemberTestRepository } from "./data/repositories/test/TeamMemberTestRepository"; import { OrgUnitTestRepository } from "./data/repositories/test/OrgUnitTestRepository"; import { GetAllDiseaseOutbreaksUseCase } from "./domain/usecases/GetAllDiseaseOutbreaksUseCase"; @@ -22,11 +21,14 @@ import { MapDiseaseOutbreakToAlertsUseCase } from "./domain/usecases/MapDiseaseO import { AlertRepository } from "./domain/repositories/AlertRepository"; import { AlertTestRepository } from "./data/repositories/test/AlertTestRepository"; import { Get717PerformanceUseCase } from "./domain/usecases/Get717PerformanceUseCase"; -import { GetEntityWithOptionsUseCase } from "./domain/usecases/GetEntityWithOptionsUseCase"; +import { GetConfigurableFormUseCase } from "./domain/usecases/GetConfigurableFormUseCase"; import { SaveEntityUseCase } from "./domain/usecases/SaveEntityUseCase"; import { RiskAssessmentRepository } from "./domain/repositories/RiskAssessmentRepository"; import { RiskAssessmentD2Repository } from "./data/repositories/RiskAssessmentD2Repository"; import { RiskAssessmentTestRepository } from "./data/repositories/test/RiskAssessmentTestRepository"; +import { IncidentActionRepository } from "./domain/repositories/IncidentActionRepository"; +import { IncidentActionD2Repository } from "./data/repositories/IncidentActionD2Repository"; +import { IncidentActionTestRepository } from "./data/repositories/test/IncidentActionTestRepository"; import { MapConfigRepository } from "./domain/repositories/MapConfigRepository"; import { MapConfigD2Repository } from "./data/repositories/MapConfigD2Repository"; import { MapConfigTestRepository } from "./data/repositories/test/MapConfigTestRepository"; @@ -42,10 +44,12 @@ import { AlertSyncDataStoreTestRepository } from "./data/repositories/test/Alert import { AlertSyncRepository } from "./domain/repositories/AlertSyncRepository"; import { DataStoreClient } from "./data/DataStoreClient"; import { GetTotalCardCountsUseCase } from "./domain/usecases/GetTotalCardCountsUseCase"; +import { GetIncidentActionByIdUseCase } from "./domain/usecases/GetIncidentActionByIdUseCase"; +import { UpdateIncidentResponseActionUseCase } from "./domain/usecases/UpdateIncidentResponseActionUseCase"; import { RoleRepository } from "./domain/repositories/RoleRepository"; import { RoleD2Repository } from "./data/repositories/RoleD2Repository"; import { RoleTestRepository } from "./data/repositories/test/RoleTestRepository"; -import { DeleteIncidentManagementTeamMemberRoleUseCase } from "./domain/usecases/DeleteIncidentManagementTeamMemberRoleUseCase"; +import { DeleteIncidentManagementTeamMemberRolesUseCase } from "./domain/usecases/DeleteIncidentManagementTeamMemberRolesUseCase"; import { ChartConfigRepository } from "./domain/repositories/ChartConfigRepository"; import { GetChartConfigByTypeUseCase } from "./domain/usecases/GetChartConfigByTypeUseCase"; import { ChartConfigTestRepository } from "./data/repositories/test/ChartConfigTestRepository"; @@ -58,6 +62,11 @@ import { GetOverviewCardsUseCase } from "./domain/usecases/GetOverviewCardsUseCa import { GetDiseaseOutbreakEventAggregateRootByIdUseCase } from "./domain/usecases/GetDiseaseOutbreakEventAggregateRootByIdUseCase"; import { GetAllRolesUseCase } from "./domain/usecases/GetAllRolesUseCase"; import { GetTeamMembersForIncidentManagementTeamUseCase } from "./domain/usecases/GetTeamMembersForIncidentManagementTeamUseCase"; +import { GetConfigurationsUseCase } from "./domain/usecases/GetConfigurationsUseCase"; +import { ConfigurationsRepository } from "./domain/repositories/ConfigurationsRepository"; +import { ConfigurationsD2Repository } from "./data/repositories/ConfigurationsD2Repository"; +import { ConfigurationsTestRepository } from "./data/repositories/test/ConfigurationsTestRepository"; +import { CompleteEventTrackerUseCase } from "./domain/usecases/CompleteEventTrackerUseCase"; export type CompositionRoot = ReturnType; @@ -66,20 +75,21 @@ type Repositories = { diseaseOutbreakEventRepository: DiseaseOutbreakEventRepository; alertRepository: AlertRepository; alertSyncRepository: AlertSyncRepository; - optionsRepository: OptionsRepository; teamMemberRepository: TeamMemberRepository; orgUnitRepository: OrgUnitRepository; riskAssessmentRepository: RiskAssessmentRepository; + incidentActionRepository: IncidentActionRepository; mapConfigRepository: MapConfigRepository; performanceOverviewRepository: PerformanceOverviewRepository; roleRepository: RoleRepository; chartConfigRepository: ChartConfigRepository; systemRepository: SystemRepository; + configurationsRepository: ConfigurationsRepository; }; function getCompositionRoot(repositories: Repositories) { return { - getWithOptions: new GetEntityWithOptionsUseCase(repositories), + getConfigurableForm: new GetConfigurableFormUseCase(repositories), save: new SaveEntityUseCase(repositories), users: { getCurrent: new GetCurrentUserUseCase(repositories.usersRepository), @@ -89,14 +99,22 @@ function getCompositionRoot(repositories: Repositories) { getAll: new GetAllDiseaseOutbreaksUseCase(repositories.diseaseOutbreakEventRepository), mapDiseaseOutbreakEventToAlerts: new MapDiseaseOutbreakToAlertsUseCase( repositories.alertRepository, - repositories.alertSyncRepository, - repositories.optionsRepository + repositories.alertSyncRepository ), - deleteIncidentManagementTeamMemberRole: - new DeleteIncidentManagementTeamMemberRoleUseCase(repositories), + deleteIncidentManagementTeamMemberRoles: + new DeleteIncidentManagementTeamMemberRolesUseCase(repositories), getAggregateRoot: new GetDiseaseOutbreakEventAggregateRootByIdUseCase( repositories.diseaseOutbreakEventRepository ), + getConfigurations: new GetConfigurationsUseCase( + repositories.configurationsRepository, + repositories.teamMemberRepository + ), + complete: new CompleteEventTrackerUseCase(repositories), + }, + incidentActionPlan: { + get: new GetIncidentActionByIdUseCase(repositories), + updateResponseAction: new UpdateIncidentResponseActionUseCase(repositories), }, performanceOverview: { getPerformanceOverviewMetrics: new GetAllPerformanceOverviewMetricsUseCase( @@ -133,19 +151,20 @@ function getCompositionRoot(repositories: Repositories) { export function getWebappCompositionRoot(api: D2Api) { const dataStoreClient = new DataStoreClient(api); const repositories: Repositories = { - usersRepository: new UserD2Repository(api), + usersRepository: new UserD2Repository(api, dataStoreClient), diseaseOutbreakEventRepository: new DiseaseOutbreakEventD2Repository(api), alertRepository: new AlertD2Repository(api), alertSyncRepository: new AlertSyncDataStoreRepository(api), - optionsRepository: new OptionsD2Repository(api), teamMemberRepository: new TeamMemberD2Repository(api), orgUnitRepository: new OrgUnitD2Repository(api), riskAssessmentRepository: new RiskAssessmentD2Repository(api), + incidentActionRepository: new IncidentActionD2Repository(api), mapConfigRepository: new MapConfigD2Repository(api), performanceOverviewRepository: new PerformanceOverviewD2Repository(api, dataStoreClient), roleRepository: new RoleD2Repository(api), chartConfigRepository: new ChartConfigD2Repository(dataStoreClient), systemRepository: new SystemD2Repository(api), + configurationsRepository: new ConfigurationsD2Repository(api), }; return getCompositionRoot(repositories); @@ -157,15 +176,16 @@ export function getTestCompositionRoot() { diseaseOutbreakEventRepository: new DiseaseOutbreakEventTestRepository(), alertRepository: new AlertTestRepository(), alertSyncRepository: new AlertSyncDataStoreTestRepository(), - optionsRepository: new OptionsTestRepository(), teamMemberRepository: new TeamMemberTestRepository(), orgUnitRepository: new OrgUnitTestRepository(), riskAssessmentRepository: new RiskAssessmentTestRepository(), + incidentActionRepository: new IncidentActionTestRepository(), mapConfigRepository: new MapConfigTestRepository(), performanceOverviewRepository: new PerformanceOverviewTestRepository(), roleRepository: new RoleTestRepository(), chartConfigRepository: new ChartConfigTestRepository(), systemRepository: new SystemTestRepository(), + configurationsRepository: new ConfigurationsTestRepository(), }; return getCompositionRoot(repositories); diff --git a/src/data/repositories/ConfigurationsD2Repository.ts b/src/data/repositories/ConfigurationsD2Repository.ts new file mode 100644 index 00000000..2282a4b0 --- /dev/null +++ b/src/data/repositories/ConfigurationsD2Repository.ts @@ -0,0 +1,285 @@ +import { D2Api, MetadataPick } from "@eyeseetea/d2-api/2.36"; +import { ConfigurationsRepository as ConfigurationsRepository } from "../../domain/repositories/ConfigurationsRepository"; +import { Option } from "../../domain/entities/Ref"; +import { apiToFuture, FutureData } from "../api-futures"; +import _ from "../../domain/entities/generic/Collection"; +import { getHazardTypeByCode } from "./consts/DiseaseOutbreakConstants"; +import { Future } from "../../domain/entities/generic/Future"; +import { SelectableOptions } from "../../domain/entities/AppConfigurations"; +import { RiskAssessmentGrading } from "../../domain/entities/risk-assessment/RiskAssessmentGrading"; + +const optionSetCode: Record = { + dataSources: "RTSL_ZEB_OS_DATA_SOURCE", + hazardTypes: "RTSL_ZEB_OS_HAZARD_TYPE", + hazardTypesByCode: "RTSL_ZEB_OS_HAZARD_TYPE", + mainSyndromes: "AGENTS", + suspectedDiseases: "RTSL_ZEB_OS_DISEASE", + notificationSources: "RTSL_ZEB_OS_SOURCE", + incidentStatus: "RTSL_ZEB_OS_INCIDENT_STATUS", + populationAtRisk: "RTSL_ZEB_OS_POPULATION_AT_RISK", + lowMediumHigh: "RTSL_ZEB_OS_LMH", + geographicalSpread: "RTSL_ZEB_OS_GEOGRAPHICAL_SPREAD", + capability: "RTSL_ZEB_OS_CAPABILITY", + capacity: "RTSL_ZEB_OS_CAPACITY", + likelihood: "RTSL_ZEB_OS_LIKELIHOOD", + consequences: "RTSL_ZEB_OS_CONSEQUENCES", + iapType: "RTSL_ZEB_OS_IAP_TYPE", + phoecLevel: "RTSL_ZEB_OS_PHOEC_ACT_LEVEL", + status: "RTSL_ZEB_OS_STATUS", + verification: "RTSL_ZEB_OS_VERIFICATION", +}; + +export class ConfigurationsD2Repository implements ConfigurationsRepository { + constructor(private api: D2Api) {} + + getSelectableOptions(): FutureData { + return apiToFuture( + this.api.metadata.get({ + optionSets: { fields: optionSetsFields }, + }) + ).flatMap(optionsResponse => { + const selectableOptions = this.createEmptySelectableOptions(); + Object.entries(optionSetCode).map(([key, value]) => { + if (key === "dataSources") { + const dataSources = optionsResponse.optionSets.find( + optionSet => optionSet.code === value + ); + if (dataSources) + selectableOptions.eventTrackerConfigurations.dataSources = + this.mapD2OptionSetToOptions(dataSources); + } else if (key === "hazardTypes") { + const hazardTypes = optionsResponse.optionSets.find( + optionSet => optionSet.code === value + ); + if (hazardTypes) { + const hazardOptions = this.mapD2OptionSetToOptions(hazardTypes); + selectableOptions.eventTrackerConfigurations.hazardTypes = + this.getHazardTypes(hazardOptions); + } + } else if (key === "mainSyndromes") { + const mainSyndromes = optionsResponse.optionSets.find( + optionSet => optionSet.code === value + ); + if (mainSyndromes) + selectableOptions.eventTrackerConfigurations.mainSyndromes = + this.mapD2OptionSetToOptions(mainSyndromes); + } else if (key === "suspectedDiseases") { + const suspectedDiseases = optionsResponse.optionSets.find( + optionSet => optionSet.code === value + ); + if (suspectedDiseases) + selectableOptions.eventTrackerConfigurations.suspectedDiseases = + this.mapD2OptionSetToOptions(suspectedDiseases); + } else if (key === "notificationSources") { + const notificationSources = optionsResponse.optionSets.find( + optionSet => optionSet.code === value + ); + if (notificationSources) + selectableOptions.eventTrackerConfigurations.notificationSources = + this.mapD2OptionSetToOptions(notificationSources); + } else if (key === "incidentStatus") { + const incidentStatus = optionsResponse.optionSets.find( + optionSet => optionSet.code === value + ); + if (incidentStatus) + selectableOptions.eventTrackerConfigurations.incidentStatus = + this.mapD2OptionSetToOptions(incidentStatus); + } else if (key === "populationAtRisk") { + const populationAtRisk = optionsResponse.optionSets.find( + optionSet => optionSet.code === value + ); + if (populationAtRisk) + selectableOptions.riskAssessmentGradingConfigurations.populationAtRisk = + populationAtRisk.options.map(populationAtRisk => { + return RiskAssessmentGrading.getOptionTypeByCodePopulation( + populationAtRisk.code + ); + }); + } else if (key === "lowMediumHigh") { + const lowMediumHighResponse = optionsResponse.optionSets.find( + optionSet => optionSet.code === value + ); + if (lowMediumHighResponse) { + selectableOptions.riskAssessmentGradingConfigurations.lowMediumHigh = + lowMediumHighResponse.options.map(lowMediumHigh => { + return RiskAssessmentGrading.getOptionTypeByCodeWeighted( + lowMediumHigh.code + ); + }); + + const lmhOptions = this.mapD2OptionSetToOptions(lowMediumHighResponse); + selectableOptions.riskAssessmentSummaryConfigurations.overallRiskGlobal = + lmhOptions; + selectableOptions.riskAssessmentSummaryConfigurations.overallRiskNational = + lmhOptions; + selectableOptions.riskAssessmentSummaryConfigurations.overallRiskRegional = + lmhOptions; + selectableOptions.riskAssessmentSummaryConfigurations.overAllConfidencGlobal = + lmhOptions; + selectableOptions.riskAssessmentSummaryConfigurations.overAllConfidencNational = + lmhOptions; + selectableOptions.riskAssessmentSummaryConfigurations.overAllConfidencRegional = + lmhOptions; + selectableOptions.riskAssessmentQuestionnaireConfigurations.risk = + lmhOptions; + } + } else if (key === "geographicalSpread") { + const geographicalSpreadOptions = optionsResponse.optionSets.find( + optionSet => optionSet.code === value + ); + if (geographicalSpreadOptions) + selectableOptions.riskAssessmentGradingConfigurations.geographicalSpread = + geographicalSpreadOptions.options.map(geographicalSpread => { + return RiskAssessmentGrading.getOptionTypeByCodeGeographicalSpread( + geographicalSpread.code + ); + }); + } else if (key === "capability") { + const capabilityOptions = optionsResponse.optionSets.find( + optionSet => optionSet.code === value + ); + if (capabilityOptions) + selectableOptions.riskAssessmentGradingConfigurations.capability = + capabilityOptions.options.map(capability => { + return RiskAssessmentGrading.getOptionTypeByCodeCapability( + capability.code + ); + }); + } else if (key === "capacity") { + const capacityOptions = optionsResponse.optionSets.find( + optionSet => optionSet.code === value + ); + if (capacityOptions) + selectableOptions.riskAssessmentGradingConfigurations.capacity = + capacityOptions.options.map(capacity => { + return RiskAssessmentGrading.getOptionTypeByCodeCapacity( + capacity.code + ); + }); + } else if (key === "likelihood") { + const likelihoodOptions = optionsResponse.optionSets.find( + optionSet => optionSet.code === value + ); + if (likelihoodOptions) + selectableOptions.riskAssessmentQuestionnaireConfigurations.likelihood = + this.mapD2OptionSetToOptions(likelihoodOptions); + } else if (key === "consequences") { + const consequencesOptions = optionsResponse.optionSets.find( + optionSet => optionSet.code === value + ); + if (consequencesOptions) + selectableOptions.riskAssessmentQuestionnaireConfigurations.consequences = + this.mapD2OptionSetToOptions(consequencesOptions); + } else if (key === "iapType") { + const iapTypes = optionsResponse.optionSets.find( + optionSet => optionSet.code === value + ); + if (iapTypes) + selectableOptions.incidentActionPlanConfigurations.iapType = + this.mapD2OptionSetToOptions(iapTypes); + } else if (key === "phoecLevel") { + const phoecLevels = optionsResponse.optionSets.find( + optionSet => optionSet.code === value + ); + if (phoecLevels) + selectableOptions.incidentActionPlanConfigurations.phoecLevel = + this.mapD2OptionSetToOptions(phoecLevels); + } else if (key === "status") { + const statuses = optionsResponse.optionSets.find( + optionSet => optionSet.code === value + ); + if (statuses) + selectableOptions.incidentResponseActionConfigurations.status = + this.mapD2OptionSetToOptions(statuses); + } else if (key === "verification") { + const verifications = optionsResponse.optionSets.find( + optionSet => optionSet.code === value + ); + if (verifications) + selectableOptions.incidentResponseActionConfigurations.verification = + this.mapD2OptionSetToOptions(verifications); + } + }); + + return Future.success(selectableOptions); + }); + } + + private createEmptySelectableOptions(): SelectableOptions { + const selectableOptions: SelectableOptions = { + eventTrackerConfigurations: { + dataSources: [], + hazardTypes: [], + mainSyndromes: [], + suspectedDiseases: [], + notificationSources: [], + incidentStatus: [], + incidentManagers: [], + }, + riskAssessmentGradingConfigurations: { + populationAtRisk: [], + lowMediumHigh: [], + geographicalSpread: [], + capability: [], + capacity: [], + }, + riskAssessmentSummaryConfigurations: { + overAllConfidencGlobal: [], + overAllConfidencNational: [], + overAllConfidencRegional: [], + overallRiskGlobal: [], + overallRiskNational: [], + overallRiskRegional: [], + riskAssessors: [], + }, + riskAssessmentQuestionnaireConfigurations: { + consequences: [], + likelihood: [], + risk: [], + }, + incidentActionPlanConfigurations: { + iapType: [], + phoecLevel: [], + }, + incidentResponseActionConfigurations: { + searchAssignRO: [], + status: [], + verification: [], + }, + }; + return selectableOptions; + } + + private mapD2OptionSetToOptions(optionSet: D2OptionSet): Option[] { + return optionSet.options.map( + (option): Option => ({ + id: option.code, + name: option.name, + }) + ); + } + + private getHazardTypes(hazardTypesByCode: Option[]): Option[] { + return _(hazardTypesByCode) + .compactMap(hazardType => { + const hazardTypeId = getHazardTypeByCode(hazardType.id); + if (hazardTypeId) { + return { + id: hazardTypeId, + name: hazardType.name, + }; + } + }) + .toArray(); + } +} + +export const optionSetsFields = { + name: true, + code: true, + options: { id: true, name: true, code: true }, +} as const; + +export type D2OptionSet = MetadataPick<{ + optionSets: { fields: typeof optionSetsFields }; +}>["optionSets"][number]; diff --git a/src/data/repositories/DiseaseOutbreakEventD2Repository.ts b/src/data/repositories/DiseaseOutbreakEventD2Repository.ts index 7d679853..282014be 100644 --- a/src/data/repositories/DiseaseOutbreakEventD2Repository.ts +++ b/src/data/repositories/DiseaseOutbreakEventD2Repository.ts @@ -2,7 +2,7 @@ import { D2Api, MetadataPick } from "../../types/d2-api"; import { DiseaseOutbreakEventRepository } from "../../domain/repositories/DiseaseOutbreakEventRepository"; import { apiToFuture, FutureData } from "../api-futures"; import { DiseaseOutbreakEventBaseAttrs } from "../../domain/entities/disease-outbreak-event/DiseaseOutbreakEvent"; -import { Id, ConfigLabel } from "../../domain/entities/Ref"; +import { Id } from "../../domain/entities/Ref"; import { mapDiseaseOutbreakEventToTrackedEntityAttributes, mapTrackedEntityAttributesToDiseaseOutbreak, @@ -29,6 +29,7 @@ import { DiseaseOutbreakEventAggregateRoot, IncidentManagementTeamInAggregateRoot, } from "../../domain/entities/disease-outbreak-event/DiseaseOutbreakEventAggregateRoot"; +import { D2TrackerEnrollment } from "@eyeseetea/d2-api/api/trackerEnrollments"; export class DiseaseOutbreakEventD2Repository implements DiseaseOutbreakEventRepository { constructor(private api: D2Api) {} @@ -100,8 +101,44 @@ export class DiseaseOutbreakEventD2Repository implements DiseaseOutbreakEventRep ); } - getConfigStrings(): FutureData { - throw new Error("Method not implemented."); + complete(id: Id): FutureData { + return apiToFuture( + this.api.tracker.enrollments.get({ + fields: { + enrollment: true, + enrolledAt: true, + occurredAt: true, + }, + trackedEntity: id, + enrolledBefore: new Date().toISOString(), + program: RTSL_ZEBRA_PROGRAM_ID, + orgUnit: RTSL_ZEBRA_ORG_UNIT_ID, + }) + ).flatMap(enrollmentResponse => { + const currentEnrollment = enrollmentResponse.instances[0]; + const currentEnrollmentId = currentEnrollment?.enrollment; + if (!currentEnrollment || !currentEnrollmentId) { + return Future.error(new Error(`Enrollment not found for Event Tracker`)); + } + + const enrollment: D2TrackerEnrollment = { + ...currentEnrollment, + orgUnit: RTSL_ZEBRA_ORG_UNIT_ID, + program: RTSL_ZEBRA_PROGRAM_ID, + trackedEntity: id, + status: "COMPLETED", + }; + + return apiToFuture( + this.api.tracker.post({ importStrategy: "UPDATE" }, { enrollments: [enrollment] }) + ).flatMap(response => { + if (response.status !== "OK") { + return Future.error( + new Error(`Error completing disease outbreak event : ${response.message}`) + ); + } else return Future.success(undefined); + }); + }); } getIncidentManagementTeam( @@ -208,61 +245,36 @@ export class DiseaseOutbreakEventD2Repository implements DiseaseOutbreakEventRep }); } - deleteIncidentManagementTeamMemberRole( + deleteIncidentManagementTeamMemberRoles( diseaseOutbreakId: Id, - incidentManagementTeamRoleId: Id + incidentManagementTeamRoleIds: Id[] ): FutureData { - return apiToFuture( - this.api.tracker.events.get({ - fields: { - dataValues: { - dataElement: { id: true, code: true }, - value: true, - }, - enrollment: true, - status: true, - occurredAt: true, - }, - trackedEntity: diseaseOutbreakId, - event: incidentManagementTeamRoleId, + const d2IncidentManagementTeamRolesToDelete: D2TrackerEvent[] = + incidentManagementTeamRoleIds.map(id => ({ + event: id, + status: "COMPLETED", program: RTSL_ZEBRA_PROGRAM_ID, + programStage: RTSL_ZEBRA_INCIDENT_MANAGEMENT_TEAM_BUILDER_PROGRAM_STAGE_ID, orgUnit: RTSL_ZEBRA_ORG_UNIT_ID, - }) - ) - .flatMap(response => - assertOrError( - response.instances[0], - `Incident management team builder event not found` - ) - ) - .flatMap(d2Event => { - const d2IncidentManagementTeamRoleToDelete: D2TrackerEvent = { - event: incidentManagementTeamRoleId, - status: d2Event.status, - program: RTSL_ZEBRA_PROGRAM_ID, - programStage: RTSL_ZEBRA_INCIDENT_MANAGEMENT_TEAM_BUILDER_PROGRAM_STAGE_ID, - enrollment: d2Event.enrollment, - orgUnit: RTSL_ZEBRA_ORG_UNIT_ID, - occurredAt: d2Event.occurredAt, - dataValues: d2Event.dataValues, - trackedEntity: diseaseOutbreakId, - }; + occurredAt: "", + dataValues: [], + trackedEntity: diseaseOutbreakId, + })); - return apiToFuture( - this.api.tracker.post( - { importStrategy: "DELETE" }, - { events: [d2IncidentManagementTeamRoleToDelete] } - ) - ).flatMap(deleteResponse => { - if (deleteResponse.status === "ERROR") { - return Future.error( - new Error(`Error deleting Incident Management Team Member Role`) - ); - } else { - return Future.success(undefined); - } - }); - }); + return apiToFuture( + this.api.tracker.post( + { importStrategy: "DELETE" }, + { events: d2IncidentManagementTeamRolesToDelete } + ) + ).flatMap(deleteResponse => { + if (deleteResponse.status === "ERROR") { + return Future.error( + new Error(`Error deleting Incident Management Team Member Role`) + ); + } else { + return Future.success(undefined); + } + }); } getAggregateRoot(id: Id): FutureData { @@ -310,6 +322,7 @@ export class DiseaseOutbreakEventD2Repository implements DiseaseOutbreakEventRep }, trackedEntity: true, event: true, + updatedAt: true, }, }) ) diff --git a/src/data/repositories/IncidentActionD2Repository.ts b/src/data/repositories/IncidentActionD2Repository.ts new file mode 100644 index 00000000..92424d7e --- /dev/null +++ b/src/data/repositories/IncidentActionD2Repository.ts @@ -0,0 +1,256 @@ +import { D2Api } from "../../types/d2-api"; +import { Maybe } from "../../utils/ts-utils"; +import { apiToFuture, FutureData } from "../api-futures"; +import { Id } from "../../domain/entities/Ref"; +import { + RTSL_ZEBRA_INCIDENT_ACTION_PLAN_PROGRAM_STAGE_ID, + RTSL_ZEBRA_INCIDENT_RESPONSE_ACTION_PROGRAM_STAGE_ID, + RTSL_ZEBRA_ORG_UNIT_ID, + RTSL_ZEBRA_PROGRAM_ID, +} from "./consts/DiseaseOutbreakConstants"; +import { + IncidentActionRepository, + UpdateIncidentResponseActionOptions, +} from "../../domain/repositories/IncidentActionRepository"; +import { + mapDataElementsToIncidentActionPlan, + mapDataElementsToIncidentResponseActions, + mapIncidentActionToDataElements, +} from "./utils/IncidentActionMapper"; +import { ActionPlanFormData, ResponseActionFormData } from "../../domain/entities/ConfigurableForm"; +import { getProgramStage } from "./utils/MetadataHelper"; +import { Future } from "../../domain/entities/generic/Future"; +import { Status, Verification } from "../../domain/entities/incident-action-plan/ResponseAction"; +import { assertOrError } from "./utils/AssertOrError"; +import { D2TrackerEvent } from "@eyeseetea/d2-api/api/trackerEvents"; +import { statusCodeMap, verificationCodeMap } from "./consts/IncidentActionConstants"; + +export const incidentActionPlanIds = { + iapType: "wr1I51WTHhl", + phoecLevel: "KgTXZonQEsm", + criticalInfoRequirements: "sgZ6MgzCI7m", + planningAssumptions: "RZviL2uz1Wa", + responseObjectives: "giq2C0lvCza", + responseStrategies: "lbcbEZ8bEpK", + expectedResults: "sB1N7Nkm5Y1", + responseActivitiesNarrative: "RnWk88dYOXN", +} as const; + +export type IncidentActionPlanDataValues = { + lastUpdated: Maybe; + id: string; + iapType: Maybe; + phoecLevel: Maybe; + criticalInfoRequirements: Maybe; + planningAssumptions: Maybe; + responseObjectives: Maybe; + responseStrategies: Maybe; + expectedResults: Maybe; + responseActivitiesNarrative: Maybe; +}; + +export const incidentResponseActionsIds = { + mainTask: "k3FiTDWD18d", + subActivities: "i728CZUYlRB", + subPillar: "BQhCqEHOyej", + searchAssignRO: "Z9a067KbV5J", + dueDate: "i2M51y9qBoC", + timeLine: "xvWvQ3K1GVA", + status: "mUR4eNxgAwg", + verification: "M62NkbKXhqZ", +}; + +export type IncidentResponseActionDataValues = { + id: string; + mainTask: Maybe; + subActivities: Maybe; + subPillar: Maybe; + searchAssignRO: Maybe; + dueDate: Maybe; + timeLine: Maybe; + status: Maybe; + verification: Maybe; +}; + +export class IncidentActionD2Repository implements IncidentActionRepository { + constructor(private api: D2Api) {} + + private fields = { + event: true, + updatedAt: true, + dataValues: { + dataElement: { id: true, code: true }, + value: true, + }, + trackedEntity: true, + }; + + getIncidentActionPlan(diseaseOutbreakId: 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_ACTION_PLAN_PROGRAM_STAGE_ID, + fields: this.fields, + }) + ).map(events => { + if (!events.instances[0]?.event) return undefined; + + const plan: IncidentActionPlanDataValues = mapDataElementsToIncidentActionPlan( + events.instances[0].event, + events.instances[0].dataValues, + events.instances[0].updatedAt + ); + + return plan; + }); + } + + getIncidentResponseActions( + diseaseOutbreakId: 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_RESPONSE_ACTION_PROGRAM_STAGE_ID, + fields: this.fields, + }) + ).map(events => { + if (events.instances.length === 0) return []; + + const responseActions: IncidentResponseActionDataValues[] = + mapDataElementsToIncidentResponseActions(events.instances); + + return responseActions; + }); + } + + saveIncidentAction( + formData: ActionPlanFormData | ResponseActionFormData, + diseaseOutbreakId: Id + ): FutureData { + const programStageId = this.getProgramStageByFormType(formData.type); + + return getProgramStage(this.api, programStageId).flatMap(incidentResponse => { + const incidentDataElements = incidentResponse.objects[0]?.programStageDataElements; + + if (!incidentDataElements) + return Future.error(new Error(`${formData.type} Program Stage metadata not found`)); + + //Get the enrollment Id for the disease outbreak + return apiToFuture( + this.api.tracker.enrollments.get({ + fields: { + enrollment: true, + }, + trackedEntity: diseaseOutbreakId, + enrolledBefore: new Date().toISOString(), + program: RTSL_ZEBRA_PROGRAM_ID, + orgUnit: RTSL_ZEBRA_ORG_UNIT_ID, + }) + ).flatMap(enrollmentResponse => { + const enrollmentId = enrollmentResponse.instances[0]?.enrollment; + if (!enrollmentId) { + return Future.error(new Error(`Enrollment not found for Disease Outbreak`)); + } + + const events = mapIncidentActionToDataElements( + formData, + programStageId, + diseaseOutbreakId, + enrollmentId, + incidentDataElements + ); + + return apiToFuture( + this.api.tracker.post( + { importStrategy: "CREATE_AND_UPDATE" }, + { events: Array.isArray(events) ? events : [events] } + ) + ).flatMap(saveResponse => { + if (saveResponse.status === "ERROR" || !diseaseOutbreakId) { + return Future.error(new Error(`Error saving Incident Action`)); + } else { + return Future.success(undefined); + } + }); + }); + }); + } + + updateIncidentResponseAction(options: UpdateIncidentResponseActionOptions): FutureData { + const { diseaseOutbreakId, eventId, responseAction } = options; + + return apiToFuture( + this.api.tracker.events.get({ + program: RTSL_ZEBRA_PROGRAM_ID, + orgUnit: RTSL_ZEBRA_ORG_UNIT_ID, + trackedEntity: diseaseOutbreakId, + programStage: RTSL_ZEBRA_INCIDENT_RESPONSE_ACTION_PROGRAM_STAGE_ID, + event: eventId, + fields: { + enrollment: true, + dataValues: { + dataElement: true, + value: true, + }, + }, + }) + ) + .flatMap(response => assertOrError(response.instances[0], "Event")) + .flatMap(event => { + const enrollmentId = event.enrollment; + if (!enrollmentId) { + return Future.error(new Error(`Enrollment not found for response action`)); + } + + const valueCodeMaps = { ...statusCodeMap, ...verificationCodeMap }; + + const eventToPost: D2TrackerEvent = { + event: eventId, + program: RTSL_ZEBRA_PROGRAM_ID, + programStage: RTSL_ZEBRA_INCIDENT_RESPONSE_ACTION_PROGRAM_STAGE_ID, + orgUnit: RTSL_ZEBRA_ORG_UNIT_ID, + enrollment: enrollmentId, + occurredAt: new Date().toISOString(), + trackedEntity: diseaseOutbreakId, + status: "ACTIVE", + dataValues: [ + { + dataElement: + incidentResponseActionsIds[ + responseAction.type as keyof typeof incidentResponseActionsIds + ], + value: valueCodeMaps[ + responseAction.value as keyof typeof valueCodeMaps + ], + }, + ], + }; + + return apiToFuture( + this.api.tracker.post({ importStrategy: "UPDATE" }, { events: [eventToPost] }) + ).flatMap(saveResponse => { + if (saveResponse.status === "ERROR") { + return Future.error(new Error(`Error saving Incident Response Action`)); + } else { + return Future.success(undefined); + } + }); + }); + } + + private getProgramStageByFormType(formType: string) { + switch (formType) { + case "incident-action-plan": + return RTSL_ZEBRA_INCIDENT_ACTION_PLAN_PROGRAM_STAGE_ID; + case "incident-response-action": + return RTSL_ZEBRA_INCIDENT_RESPONSE_ACTION_PROGRAM_STAGE_ID; + default: + throw new Error("Incident Action Form type not supported"); + } + } +} diff --git a/src/data/repositories/OptionsD2Repository.ts b/src/data/repositories/OptionsD2Repository.ts deleted file mode 100644 index deaab05c..00000000 --- a/src/data/repositories/OptionsD2Repository.ts +++ /dev/null @@ -1,225 +0,0 @@ -import _ from "../../domain/entities/generic/Collection"; -import { D2Api, MetadataPick } from "../../types/d2-api"; -import { Code, Option } from "../../domain/entities/Ref"; -import { apiToFuture, FutureData } from "../api-futures"; -import { OptionsRepository } from "../../domain/repositories/OptionsRepository"; -import { assertOrError } from "./utils/AssertOrError"; -import { getHazardTypeByCode } from "./consts/DiseaseOutbreakConstants"; - -import { - Capability1, - Capability2, - HighCapacity, - HighGeographicalSpread, - HighPopulationAtRisk, - HighWeightedOption, - LowCapacity, - LowGeographicalSpread, - LowPopulationAtRisk, - LowWeightedOption, - MediumCapacity, - MediumGeographicalSpread, - MediumPopulationAtRisk, - MediumWeightedOption, - RiskAssessmentGrading, -} from "../../domain/entities/risk-assessment/RiskAssessmentGrading"; -import { Future } from "../../domain/entities/generic/Future"; - -const MAIN_SYNDROME_OPTION_SET_CODE = "AGENTS"; -const SUSPECTED_DISEASE_OPTION_SET_CODE = "RTSL_ZEB_OS_DISEASE"; -const NOTIFICATION_SOURCE_OPTION_SET_CODE = "RTSL_ZEB_OS_SOURCE"; - -export class OptionsD2Repository implements OptionsRepository { - constructor(private api: D2Api) {} - private likelihoodOptions: Map = new Map(); - private consequencesOptions: Map = new Map(); - private lowMediumHighOptions: Map = new Map(); - - getMainSyndrome(optionCode: Code): FutureData