From 3fd20253c60efbf1f4d923f6c569dc210a852821 Mon Sep 17 00:00:00 2001 From: kajambiya Date: Fri, 17 Nov 2023 18:02:21 +0300 Subject: [PATCH 1/4] extend mother-to-child linkage post submission handler to allow editing --- packages/esm-ohri-pmtct-app/src/api/api.ts | 11 +++++++ packages/esm-ohri-pmtct-app/src/index.ts | 2 +- .../mother-child-linkage-action.ts | 22 ++++++++++---- .../src/utils/pmtct-helpers.ts | 30 +++++++++++++++++++ .../src/utils/ptracker-forms-helpers.ts | 11 ------- 5 files changed, 59 insertions(+), 17 deletions(-) create mode 100644 packages/esm-ohri-pmtct-app/src/utils/pmtct-helpers.ts delete mode 100644 packages/esm-ohri-pmtct-app/src/utils/ptracker-forms-helpers.ts diff --git a/packages/esm-ohri-pmtct-app/src/api/api.ts b/packages/esm-ohri-pmtct-app/src/api/api.ts index 1fd351fc1..c007b59f3 100644 --- a/packages/esm-ohri-pmtct-app/src/api/api.ts +++ b/packages/esm-ohri-pmtct-app/src/api/api.ts @@ -82,6 +82,17 @@ export function getEstimatedDeliveryDate(patientUuid: string, pTrackerId: string }); } +export function getIdentifierInfo(identifier: string){ + return openmrsFetch( + `${BASE_WS_API_URL}patient?identifier=${identifier}&v=custom:(identifiers:(identifier,identifierType:(uuid,display)),person:(display))`, + ).then(({ data }) => { + if (data) { + return data; + } + return null; + }); +} + export function fetchMotherHIVStatus(patientUuid: string, pTrackerId: string) { return openmrsFetch( `${BASE_WS_API_URL}reportingrest/dataSet/${motherHivStatusReport}?person_uuid=${patientUuid}&ptracker_id=${pTrackerId}`, diff --git a/packages/esm-ohri-pmtct-app/src/index.ts b/packages/esm-ohri-pmtct-app/src/index.ts index ca7ce6ed5..dcd864864 100644 --- a/packages/esm-ohri-pmtct-app/src/index.ts +++ b/packages/esm-ohri-pmtct-app/src/index.ts @@ -15,7 +15,7 @@ import { OHRIWelcomeSection, createConditionalDashboardGroup, } from '@ohri/openmrs-esm-ohri-commons-lib'; -import { generateInfantPTrackerId } from './utils/ptracker-forms-helpers'; +import { generateInfantPTrackerId } from './utils/pmtct-helpers'; export const importTranslation = require.context('../translations', false, /.json$/, 'lazy'); diff --git a/packages/esm-ohri-pmtct-app/src/post-submission-actions/mother-child-linkage-action.ts b/packages/esm-ohri-pmtct-app/src/post-submission-actions/mother-child-linkage-action.ts index 1320c0613..298293804 100644 --- a/packages/esm-ohri-pmtct-app/src/post-submission-actions/mother-child-linkage-action.ts +++ b/packages/esm-ohri-pmtct-app/src/post-submission-actions/mother-child-linkage-action.ts @@ -4,6 +4,7 @@ import { Patient, PatientIdentifier } from '../api/types'; import { findObsByConcept, findChildObsInTree, getObsValueCoded } from '../utils/obs-encounter-utils'; import { updatePatientPtracker } from './current-ptracker-action'; import { getConfig } from '@openmrs/esm-framework'; +import { getIdentifierAssignee } from '../utils/pmtct-helpers'; // necessary data points about an infact captured at birth const infantDetailsGroup = '1c70c490-cafa-4c95-9fdd-a30b62bb78b8'; @@ -21,7 +22,7 @@ export const MotherToChildLinkageSubmissionAction: PostSubmissionAction = { const encounter = encounters[0]; const encounterLocation = encounter.location['uuid']; // only do this the first time the form is entered - if (sessionMode !== 'enter') { + if (sessionMode === 'view') { return; } @@ -29,7 +30,7 @@ export const MotherToChildLinkageSubmissionAction: PostSubmissionAction = { await updatePatientPtracker(encounter, encounterLocation, patient.id); const infantsToCreate = await Promise.all( findObsByConcept(encounter, infantDetailsGroup).map(async (obsGroup) => - constructPatientObjectFromObsData(obsGroup, encounterLocation, preferredIdentifierSource), + constructPatientObjectFromObsData(obsGroup, encounterLocation, preferredIdentifierSource, sessionMode), ), ); const newInfantsToCreate = await Promise.all(infantsToCreate.filter((infant) => infant !== null)); @@ -53,11 +54,23 @@ async function constructPatientObjectFromObsData( obsGroup, encounterLocation: string, preferredIdentifierSource: string, + sessionMode: string, ): Promise { // check if infant is alive const lifeStatusAtBirth = findChildObsInTree(obsGroup, infantLifeStatus); + // the infant is alive hence eligible for registration if (getObsValueCoded(lifeStatusAtBirth) == aliveStatus) { - // the infant is alive hence eligible for registration + + const pTrackerId = findChildObsInTree(obsGroup, infantPTrackerId)?.value; + const existingpTrackerAssignee = await getIdentifierAssignee(pTrackerId, PtrackerIdentifierType); + if(existingpTrackerAssignee){ + if(sessionMode === 'enter'){ + throw new Error(`P Tracker Id (${pTrackerId}) already assigned to patient (${existingpTrackerAssignee})`); + } + else{ + return null; + } + } const patient: Patient = { identifiers: [], person: { @@ -77,8 +90,7 @@ async function constructPatientObjectFromObsData( causeOfDeath: '', }, }; - // PTracker ID - const pTrackerId = findChildObsInTree(obsGroup, infantPTrackerId)?.value; + if (pTrackerId) { patient.identifiers = [ { diff --git a/packages/esm-ohri-pmtct-app/src/utils/pmtct-helpers.ts b/packages/esm-ohri-pmtct-app/src/utils/pmtct-helpers.ts new file mode 100644 index 000000000..b778cc754 --- /dev/null +++ b/packages/esm-ohri-pmtct-app/src/utils/pmtct-helpers.ts @@ -0,0 +1,30 @@ +import { getIdentifierInfo } from "../api/api"; + +export const generateInfantPTrackerId = ( + fieldId: string, + motherPtrackerId: string + ): string | undefined => { + if (!fieldId || !motherPtrackerId) return; + return fieldId === "infantPtrackerid" + ? motherPtrackerId + "1" + : fieldId.includes("_") + ? motherPtrackerId.concat(fieldId.split("_")[1]) + : undefined; + }; + + export const getIdentifierAssignee = async (identifier: string, identifierType: string) => { + return getIdentifierInfo(identifier).then( (data) => { + if(data){ + for(const result of data.results){ + for (const identifierObj of result.identifiers){ + if (identifierObj.identifier === identifier && identifierObj.identifierType.uuid === identifierType) { + return result.person.display; + } + } + } + return ''; + } + return ''; + }) + + } \ No newline at end of file diff --git a/packages/esm-ohri-pmtct-app/src/utils/ptracker-forms-helpers.ts b/packages/esm-ohri-pmtct-app/src/utils/ptracker-forms-helpers.ts deleted file mode 100644 index ce5e0d6ee..000000000 --- a/packages/esm-ohri-pmtct-app/src/utils/ptracker-forms-helpers.ts +++ /dev/null @@ -1,11 +0,0 @@ -export const generateInfantPTrackerId = ( - fieldId: string, - motherPtrackerId: string - ): string | undefined => { - if (!fieldId || !motherPtrackerId) return; - return fieldId === "infantPtrackerid" - ? motherPtrackerId + "1" - : fieldId.includes("_") - ? motherPtrackerId.concat(fieldId.split("_")[1]) - : undefined; - }; \ No newline at end of file From cf824f2c9e23a8d72f6d88eeb51eef2bd31b911a Mon Sep 17 00:00:00 2001 From: kajambiya Date: Fri, 24 Nov 2023 18:44:34 +0300 Subject: [PATCH 2/4] adjust generateInfantPTrackerId function --- .../src/utils/pmtct-helpers.ts | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/packages/esm-ohri-pmtct-app/src/utils/pmtct-helpers.ts b/packages/esm-ohri-pmtct-app/src/utils/pmtct-helpers.ts index b778cc754..84032691d 100644 --- a/packages/esm-ohri-pmtct-app/src/utils/pmtct-helpers.ts +++ b/packages/esm-ohri-pmtct-app/src/utils/pmtct-helpers.ts @@ -1,30 +1,26 @@ -import { getIdentifierInfo } from "../api/api"; +import { getIdentifierInfo } from '../api/api'; -export const generateInfantPTrackerId = ( - fieldId: string, - motherPtrackerId: string - ): string | undefined => { - if (!fieldId || !motherPtrackerId) return; - return fieldId === "infantPtrackerid" - ? motherPtrackerId + "1" - : fieldId.includes("_") - ? motherPtrackerId.concat(fieldId.split("_")[1]) - : undefined; - }; +export const generateInfantPTrackerId = (fieldId: string, motherPtrackerId: string): string | undefined => { + if (!fieldId || !motherPtrackerId) return; + return fieldId === 'infantPtrackerid' + ? motherPtrackerId + '1' + : fieldId.includes('_') + ? motherPtrackerId.concat((Number(fieldId.split('_')[1]) + 1).toString()) + : undefined; +}; - export const getIdentifierAssignee = async (identifier: string, identifierType: string) => { - return getIdentifierInfo(identifier).then( (data) => { - if(data){ - for(const result of data.results){ - for (const identifierObj of result.identifiers){ - if (identifierObj.identifier === identifier && identifierObj.identifierType.uuid === identifierType) { - return result.person.display; - } +export const getIdentifierAssignee = async (identifier: string, identifierType: string) => { + return getIdentifierInfo(identifier).then((data) => { + if (data) { + for (const result of data.results) { + for (const identifierObj of result.identifiers) { + if (identifierObj.identifier === identifier && identifierObj.identifierType.uuid === identifierType) { + return result.person.display; } } - return ''; } return ''; - }) - - } \ No newline at end of file + } + return ''; + }); +}; From cdc69375528091bff842531dbe903dd663686a5a Mon Sep 17 00:00:00 2001 From: kajambiya Date: Mon, 27 Nov 2023 12:09:58 +0300 Subject: [PATCH 3/4] fix minor linter issues --- packages/esm-ohri-pmtct-app/src/api/api.ts | 5 ++--- .../mother-child-linkage-action.ts | 12 +++++------- .../esm-ohri-pmtct-app/src/utils/pmtct-helpers.ts | 2 +- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/esm-ohri-pmtct-app/src/api/api.ts b/packages/esm-ohri-pmtct-app/src/api/api.ts index c007b59f3..a3b9e5e97 100644 --- a/packages/esm-ohri-pmtct-app/src/api/api.ts +++ b/packages/esm-ohri-pmtct-app/src/api/api.ts @@ -82,7 +82,7 @@ export function getEstimatedDeliveryDate(patientUuid: string, pTrackerId: string }); } -export function getIdentifierInfo(identifier: string){ +export function getIdentifierInfo(identifier: string) { return openmrsFetch( `${BASE_WS_API_URL}patient?identifier=${identifier}&v=custom:(identifiers:(identifier,identifierType:(uuid,display)),person:(display))`, ).then(({ data }) => { @@ -104,7 +104,7 @@ export function fetchMotherHIVStatus(patientUuid: string, pTrackerId: string) { }); } -export function fetchChildLatestFinalOutcome(childUuid: string, conceptUuid: string, encounterTypeUuid){ +export function fetchChildLatestFinalOutcome(childUuid: string, conceptUuid: string, encounterTypeUuid) { let params = `patient=${childUuid}&code=${conceptUuid}${ encounterTypeUuid ? `&encounter.type=${encounterTypeUuid}` : '' }`; @@ -113,7 +113,6 @@ export function fetchChildLatestFinalOutcome(childUuid: string, conceptUuid: str return openmrsFetch(`/ws/fhir2/R4/Observation?${params}`).then(({ data }) => { return data.entry?.length ? data.entry[0].resource.valueCodeableConcept.coding[0]?.display : null; }); - } // Get family relationships from patient uuid diff --git a/packages/esm-ohri-pmtct-app/src/post-submission-actions/mother-child-linkage-action.ts b/packages/esm-ohri-pmtct-app/src/post-submission-actions/mother-child-linkage-action.ts index 298293804..0371352b8 100644 --- a/packages/esm-ohri-pmtct-app/src/post-submission-actions/mother-child-linkage-action.ts +++ b/packages/esm-ohri-pmtct-app/src/post-submission-actions/mother-child-linkage-action.ts @@ -21,7 +21,7 @@ export const MotherToChildLinkageSubmissionAction: PostSubmissionAction = { applyAction: async function ({ patient, encounters, sessionMode }) { const encounter = encounters[0]; const encounterLocation = encounter.location['uuid']; - // only do this the first time the form is entered + // only do this in enter or edit mode if (sessionMode === 'view') { return; } @@ -58,16 +58,14 @@ async function constructPatientObjectFromObsData( ): Promise { // check if infant is alive const lifeStatusAtBirth = findChildObsInTree(obsGroup, infantLifeStatus); - // the infant is alive hence eligible for registration + // the infant is alive hence eligible for registration if (getObsValueCoded(lifeStatusAtBirth) == aliveStatus) { - const pTrackerId = findChildObsInTree(obsGroup, infantPTrackerId)?.value; const existingpTrackerAssignee = await getIdentifierAssignee(pTrackerId, PtrackerIdentifierType); - if(existingpTrackerAssignee){ - if(sessionMode === 'enter'){ + if (existingpTrackerAssignee) { + if (sessionMode === 'enter') { throw new Error(`P Tracker Id (${pTrackerId}) already assigned to patient (${existingpTrackerAssignee})`); - } - else{ + } else { return null; } } diff --git a/packages/esm-ohri-pmtct-app/src/utils/pmtct-helpers.ts b/packages/esm-ohri-pmtct-app/src/utils/pmtct-helpers.ts index 84032691d..45241e1c4 100644 --- a/packages/esm-ohri-pmtct-app/src/utils/pmtct-helpers.ts +++ b/packages/esm-ohri-pmtct-app/src/utils/pmtct-helpers.ts @@ -9,7 +9,7 @@ export const generateInfantPTrackerId = (fieldId: string, motherPtrackerId: stri : undefined; }; -export const getIdentifierAssignee = async (identifier: string, identifierType: string) => { +export const getIdentifierAssignee = (identifier: string, identifierType: string) => { return getIdentifierInfo(identifier).then((data) => { if (data) { for (const result of data.results) { From 9ad294cdefb63e021f8ce27e73851d51db4d1fa7 Mon Sep 17 00:00:00 2001 From: kajambiya Date: Mon, 27 Nov 2023 15:56:19 +0300 Subject: [PATCH 4/4] Add check for existing linkage --- packages/esm-ohri-pmtct-app/src/api/api.ts | 2 +- .../mother-child-linkage-action.ts | 90 +++++++++++-------- .../src/utils/pmtct-helpers.ts | 6 +- 3 files changed, 57 insertions(+), 41 deletions(-) diff --git a/packages/esm-ohri-pmtct-app/src/api/api.ts b/packages/esm-ohri-pmtct-app/src/api/api.ts index a3b9e5e97..59e1cf6cc 100644 --- a/packages/esm-ohri-pmtct-app/src/api/api.ts +++ b/packages/esm-ohri-pmtct-app/src/api/api.ts @@ -84,7 +84,7 @@ export function getEstimatedDeliveryDate(patientUuid: string, pTrackerId: string export function getIdentifierInfo(identifier: string) { return openmrsFetch( - `${BASE_WS_API_URL}patient?identifier=${identifier}&v=custom:(identifiers:(identifier,identifierType:(uuid,display)),person:(display))`, + `${BASE_WS_API_URL}patient?identifier=${identifier}&v=custom:(identifiers:(identifier,identifierType:(uuid,display)),person:(uuid,display))`, ).then(({ data }) => { if (data) { return data; diff --git a/packages/esm-ohri-pmtct-app/src/post-submission-actions/mother-child-linkage-action.ts b/packages/esm-ohri-pmtct-app/src/post-submission-actions/mother-child-linkage-action.ts index 0371352b8..0ad40f182 100644 --- a/packages/esm-ohri-pmtct-app/src/post-submission-actions/mother-child-linkage-action.ts +++ b/packages/esm-ohri-pmtct-app/src/post-submission-actions/mother-child-linkage-action.ts @@ -5,6 +5,7 @@ import { findObsByConcept, findChildObsInTree, getObsValueCoded } from '../utils import { updatePatientPtracker } from './current-ptracker-action'; import { getConfig } from '@openmrs/esm-framework'; import { getIdentifierAssignee } from '../utils/pmtct-helpers'; +import { fetchPatientRelationships } from '@ohri/openmrs-esm-ohri-commons-lib'; // necessary data points about an infact captured at birth const infantDetailsGroup = '1c70c490-cafa-4c95-9fdd-a30b62bb78b8'; @@ -30,7 +31,7 @@ export const MotherToChildLinkageSubmissionAction: PostSubmissionAction = { await updatePatientPtracker(encounter, encounterLocation, patient.id); const infantsToCreate = await Promise.all( findObsByConcept(encounter, infantDetailsGroup).map(async (obsGroup) => - constructPatientObjectFromObsData(obsGroup, encounterLocation, preferredIdentifierSource, sessionMode), + constructPatientObjectFromObsData(obsGroup, encounterLocation, preferredIdentifierSource, sessionMode, patient), ), ); const newInfantsToCreate = await Promise.all(infantsToCreate.filter((infant) => infant !== null)); @@ -55,41 +56,54 @@ async function constructPatientObjectFromObsData( encounterLocation: string, preferredIdentifierSource: string, sessionMode: string, + parent: fhir.Patient, ): Promise { // check if infant is alive const lifeStatusAtBirth = findChildObsInTree(obsGroup, infantLifeStatus); // the infant is alive hence eligible for registration if (getObsValueCoded(lifeStatusAtBirth) == aliveStatus) { const pTrackerId = findChildObsInTree(obsGroup, infantPTrackerId)?.value; - const existingpTrackerAssignee = await getIdentifierAssignee(pTrackerId, PtrackerIdentifierType); - if (existingpTrackerAssignee) { - if (sessionMode === 'enter') { - throw new Error(`P Tracker Id (${pTrackerId}) already assigned to patient (${existingpTrackerAssignee})`); - } else { - return null; + if (pTrackerId) { + const existingpTrackerAssignee = await getIdentifierAssignee(pTrackerId, PtrackerIdentifierType); + if (existingpTrackerAssignee) { + if (sessionMode === 'enter') { + throw new Error( + `PTracker Id (${pTrackerId}) already assigned to patient (${existingpTrackerAssignee.display})`, + ); + } else { + //In edit mode, only throw error if the patient with the existing PTracker is not linked with the current mother + const parentRelationships = await fetchPatientRelationships(parent.id); + const isAlreadyLinked = parentRelationships.some( + (relationship) => relationship.personB.uuid === existingpTrackerAssignee.uuid, + ); + if (!isAlreadyLinked) { + throw new Error( + `PTracker Id (${pTrackerId}) already assigned to patient (${existingpTrackerAssignee.display})`, + ); + } + return null; + } } - } - const patient: Patient = { - identifiers: [], - person: { - names: [ - { - givenName: 'TBD', - middleName: 'TBD', - familyName: 'TBD', - preferred: true, - }, - ], - gender: inferGenderFromObs(findChildObsInTree(obsGroup, infantGender)), - birthdate: findChildObsInTree(obsGroup, infantDOB)?.value, - birthdateEstimated: false, - dead: false, - deathDate: null, - causeOfDeath: '', - }, - }; + const patient: Patient = { + identifiers: [], + person: { + names: [ + { + givenName: 'TBD', + middleName: 'TBD', + familyName: 'TBD', + preferred: true, + }, + ], + gender: inferGenderFromObs(findChildObsInTree(obsGroup, infantGender)), + birthdate: findChildObsInTree(obsGroup, infantDOB)?.value, + birthdateEstimated: false, + dead: false, + deathDate: null, + causeOfDeath: '', + }, + }; - if (pTrackerId) { patient.identifiers = [ { identifier: pTrackerId, @@ -98,16 +112,18 @@ async function constructPatientObjectFromObsData( preferred: false, }, ]; + // generate the preferred identifier + const preferredIdentifier: PatientIdentifier = { + identifier: await (await generateIdentifier(preferredIdentifierSource)).data.identifier, + identifierType: OpenmrsClassicIdentifierType, + location: encounterLocation, + preferred: true, + }; + patient.identifiers.push(preferredIdentifier); + return patient; } - // generate the preferred identifier - const preferredIdentifier: PatientIdentifier = { - identifier: await (await generateIdentifier(preferredIdentifierSource)).data.identifier, - identifierType: OpenmrsClassicIdentifierType, - location: encounterLocation, - preferred: true, - }; - patient.identifiers.push(preferredIdentifier); - return patient; + } else { + throw new Error('Please provide child PTracker Id'); } return null; } diff --git a/packages/esm-ohri-pmtct-app/src/utils/pmtct-helpers.ts b/packages/esm-ohri-pmtct-app/src/utils/pmtct-helpers.ts index 45241e1c4..1ed579cc9 100644 --- a/packages/esm-ohri-pmtct-app/src/utils/pmtct-helpers.ts +++ b/packages/esm-ohri-pmtct-app/src/utils/pmtct-helpers.ts @@ -15,12 +15,12 @@ export const getIdentifierAssignee = (identifier: string, identifierType: string for (const result of data.results) { for (const identifierObj of result.identifiers) { if (identifierObj.identifier === identifier && identifierObj.identifierType.uuid === identifierType) { - return result.person.display; + return result.person; } } } - return ''; + return {}; } - return ''; + return {}; }); };