Skip to content

Commit

Permalink
Add support for saving patient identifiers in forms
Browse files Browse the repository at this point in the history
  • Loading branch information
kajambiya committed Apr 24, 2024
1 parent 6c33bf8 commit 3385d3d
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 5 deletions.
24 changes: 23 additions & 1 deletion src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { openmrsFetch, openmrsObservableFetch } from '@openmrs/esm-framework';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { encounterRepresentation } from '../constants';
import { OpenmrsForm, ProgramEnrollmentPayload } from './types';
import { OpenmrsForm, PatientIdentifier, ProgramEnrollmentPayload } from './types';
import { isUuid } from '../utils/boolean-utils';

const BASE_WS_API_URL = '/ws/rest/v1/';
Expand Down Expand Up @@ -197,3 +197,25 @@ export function updateProgramEnrollment(
signal: abortController.signal,
});
}

export function savePatientIdentifier(identifier: PatientIdentifier, patientUuid: string) {
return openmrsFetch(`${BASE_WS_API_URL}patient/${patientUuid}/identifier`, {
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
body: identifier,
});
}

export function editPatientIdentifier(identifier: string, identifierUuid:string, patientUuid: string) {
return openmrsFetch(`${BASE_WS_API_URL}patient/${patientUuid}/identifier/${identifierUuid}`, {
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
body: {identifier: identifier},
});
}


9 changes: 9 additions & 0 deletions src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ export interface OHRIFormQuestionOptions {
isSearchable?: boolean;
workspaceName?: string;
buttonLabel?: string;
identifierType?: string;
}

export type SessionMode = 'edit' | 'enter' | 'view' | 'embedded-view';
Expand Down Expand Up @@ -344,6 +345,14 @@ export interface ProgramEnrollmentPayload {
location: string;
}

export interface PatientIdentifier {
uuid?: string;
identifier?: string;
identifierType?: string;
location?: string;
preferred?: boolean;
}

/**
* A form schema transformer is used to bridge the gap caused by different variations of form schemas
* in the OpenMRS JSON schema-based form-entry world. It fine-tunes custom schemas to be compliant
Expand Down
47 changes: 46 additions & 1 deletion src/components/encounter/ohri-encounter-form.component.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from 'react';
import { SessionLocation, showToast, useLayoutType, Visit } from '@openmrs/esm-framework';
import { SessionLocation, showToast, useLayoutType, Visit, showSnackbar } from '@openmrs/esm-framework';
import { ConceptFalse, ConceptTrue } from '../../constants';
import {
OHRIFormField,
Expand All @@ -9,6 +9,7 @@ import {
SessionMode,
ValidationResult,
RepeatObsGroupCounter,
PatientIdentifier,
} from '../../api/types';
import OHRIFormPage from '../page/ohri-form-page.component';
import { OHRIFormContext } from '../../ohri-form-context';
Expand All @@ -32,6 +33,8 @@ import { useEncounterRole } from '../../hooks/useEncounterRole';
import { useConcepts } from '../../hooks/useConcepts';
import { useFormFieldHandlers } from '../../hooks/useFormFieldHandlers';
import { useFormFieldValidators } from '../../hooks/useFormFieldValidators';
import { saveIdentifier } from '../../utils/patientIdentifierProcessor';
import { useTranslation } from 'react-i18next';

interface OHRIEncounterFormProps {
formJson: OHRIFormSchema;
Expand Down Expand Up @@ -80,6 +83,7 @@ export const OHRIEncounterForm: React.FC<OHRIEncounterFormProps> = ({
isSubmitting,
setIsSubmitting,
}) => {
const { t } = useTranslation();
const [fields, setFields] = useState<Array<OHRIFormField>>([]);
const [encounterLocation, setEncounterLocation] = useState(null);
const [encounterDate, setEncounterDate] = useState(formSessionDate);
Expand Down Expand Up @@ -110,6 +114,7 @@ export const OHRIEncounterForm: React.FC<OHRIEncounterFormProps> = ({
setEncounterDate,
setEncounterProvider,
setEncounterLocation,
patientIdentifier: {},
initValues: initValues,
obsGroupCounter: obsGroupCounter,
setObsGroupCounter: setObsGroupCounter,
Expand Down Expand Up @@ -527,6 +532,10 @@ export const OHRIEncounterForm: React.FC<OHRIEncounterFormProps> = ({
}

if (encounterForSubmission.obs?.length || encounterForSubmission.orders?.length) {
//Save Identifiers if any
if (Object.keys(encounterContext?.patientIdentifier).length) {
saveIdentifier(patient, encounterContext.patientIdentifier);
}
const ac = new AbortController();
return saveEncounter(ac, encounterForSubmission, encounter?.uuid).then((response) => {
const encounter = response.data;
Expand All @@ -544,6 +553,42 @@ export const OHRIEncounterForm: React.FC<OHRIEncounterFormProps> = ({
return Promise.all(saveAttachmentPromises).then(() => response);
});
}

if (encounterForSubmission.obs?.length || encounterForSubmission.orders?.length) {
if (Object.keys(encounterContext?.patientIdentifier).length) {
return saveIdentifier(patient, encounterContext.patientIdentifier)
.then(() => saveEncounterWithAttachments(encounterForSubmission))
.catch((error) => {
showSnackbar({
title: t('errorDescriptionTitle', 'Error on saving form'),
subtitle: t('errorDescription', error),
kind: 'error',
isLowContrast: false,
});
});
} else {
return saveEncounterWithAttachments(encounterForSubmission);
}
}
};

const saveEncounterWithAttachments = (encounterForSubmission: OpenmrsEncounter) => {
const ac = new AbortController();
return saveEncounter(ac, encounterForSubmission, encounter?.uuid).then((response) => {
const encounter = response.data;
const fileFields = fields?.filter((field) => field?.questionOptions.rendering === 'file');
const saveAttachmentPromises = fileFields.map((field) => {
return saveAttachment(
encounter?.patient.uuid,
field,
field?.questionOptions.concept,
new Date().toISOString(),
encounter?.uuid,
ac,
);
});
return Promise.all(saveAttachmentPromises).then(() => response);
});
};

const onFieldChange = (
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useInitialValues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function useInitialValues(
const [isEncounterBindingComplete, setIsEncounterBindingComplete] = useState(
encounterContext.sessionMode === 'enter',
);
const encounterContextInitializableTypes = ['encounterProvider', 'encounterDatetime', 'encounterLocation'];
const encounterContextInitializableTypes = ['encounterProvider', 'encounterDatetime', 'encounterLocation', 'patientIdentifier'];

useEffect(() => {
const asyncItemsKeys = Object.keys(asyncInitValues ?? {});
Expand Down
10 changes: 9 additions & 1 deletion src/ohri-form-context.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { LayoutType } from '@openmrs/esm-framework';
import React from 'react';
import { RepeatObsGroupCounter, OHRIFormField, OpenmrsEncounter, SessionMode, SubmissionHandler } from './api/types';
import {
RepeatObsGroupCounter,
OHRIFormField,
OpenmrsEncounter,
SessionMode,
SubmissionHandler,
PatientIdentifier,
} from './api/types';

type OHRIFormContextProps = {
values: Record<string, any>;
Expand Down Expand Up @@ -30,6 +37,7 @@ export interface EncounterContext {
setEncounterLocation(value: any): void;
initValues?: Record<string, any>;
setObsGroupCounter?: any;
patientIdentifier?: PatientIdentifier;
}

export const OHRIFormContext = React.createContext<OHRIFormContextProps | undefined>(undefined);
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ObsSubmissionHandler } from '../../submission-handlers/base-handlers';
import { EncounterDatetimeHandler } from '../../submission-handlers/encounterDatetimeHandler';
import { EncounterLocationSubmissionHandler } from '../../submission-handlers/encounterLocationHandler';
import { EncounterProviderHandler } from '../../submission-handlers/encounterProviderHandler';
import { PatientIdentifierHandler } from '../../submission-handlers/patientIdentifierHandler';
import { RegistryItem } from '../registry';

/**
Expand Down Expand Up @@ -34,4 +35,9 @@ export const inbuiltFieldSubmissionHandlers: Array<RegistryItem<SubmissionHandle
component: EncounterProviderHandler,
type: 'encounterProvider',
},
{
name: 'PatientIdentifierHandler',
component: PatientIdentifierHandler,
type: 'patientIdentifier',
},
];
2 changes: 1 addition & 1 deletion src/registry/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export interface FieldSubmissionHandlerRegistration extends ComponentRegistratio
type: string;
}

export interface FormSchemaTransformerRegistration extends ComponentRegistration<FormSchemaTransformer> {}
export type FormSchemaTransformerRegistration = ComponentRegistration<FormSchemaTransformer>

export interface FormsRegistryStoreState {
controls: CustomControlRegistration[];
Expand Down
33 changes: 33 additions & 0 deletions src/submission-handlers/patientIdentifierHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { SubmissionHandler } from '..';
import { OpenmrsEncounter, OHRIFormField } from '../api/types';
import { EncounterContext } from '../ohri-form-context';
import { getPatientLatestIdentifier } from '../utils/patientIdentifierProcessor';

export const PatientIdentifierHandler: SubmissionHandler = {
handleFieldSubmission: (field: OHRIFormField, value: any, context: EncounterContext) => {
const patientIdentifier = {
identifier: value,
identifierType: field.questionOptions.identifierType,
location:context.location
};
context.patientIdentifier = patientIdentifier
return value;
},
getInitialValue: (
encounter: OpenmrsEncounter,
field: OHRIFormField,
allFormFields: Array<OHRIFormField>,
context: EncounterContext,
) => {
const patientIdentifier = getPatientLatestIdentifier(context.patient, field.questionOptions.identifierType);
return patientIdentifier?.value;
},

getDisplayValue: (field: OHRIFormField, value: any) => {
return value;
},
getPreviousValue: (field: OHRIFormField, encounter: OpenmrsEncounter, allFormFields: Array<OHRIFormField>) => {
return null;
},

};
21 changes: 21 additions & 0 deletions src/utils/patientIdentifierProcessor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { PatientIdentifier } from "../api/types";
import {editPatientIdentifier, savePatientIdentifier} from "../api/api";

export const saveIdentifier = (patient: fhir.Patient, patientIdentifier:PatientIdentifier) =>{
const identifier = getPatientLatestIdentifier(patient, patientIdentifier.identifierType);
if(identifier){
return identifier.value !== patientIdentifier.identifier && editPatientIdentifier(patientIdentifier.identifier, identifier.id, patient.id);
}else{
return savePatientIdentifier(patientIdentifier, patient.id);
}
}

export const getPatientLatestIdentifier = (patient: fhir.Patient, identifierType: string) => {
const patientIdentifiers = patient.identifier;
return patientIdentifiers.find(identifier => {
if (identifier.type.coding && identifier.type.coding[0].code === identifierType) {
return true;
}
return false;
});
}

0 comments on commit 3385d3d

Please sign in to comment.