diff --git a/src/api/api.ts b/src/api/api.ts index dc28bf1c2..28aa7b87b 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -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/'; @@ -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}, + }); +} + + diff --git a/src/api/types.ts b/src/api/types.ts index 0786bc893..9b63ed5ea 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -191,6 +191,7 @@ export interface OHRIFormQuestionOptions { isSearchable?: boolean; workspaceName?: string; buttonLabel?: string; + identifierType?: string; } export type SessionMode = 'edit' | 'enter' | 'view' | 'embedded-view'; @@ -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 diff --git a/src/components/encounter/ohri-encounter-form.component.tsx b/src/components/encounter/ohri-encounter-form.component.tsx index e2b2a6638..e20af9135 100644 --- a/src/components/encounter/ohri-encounter-form.component.tsx +++ b/src/components/encounter/ohri-encounter-form.component.tsx @@ -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, @@ -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'; @@ -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; @@ -80,6 +83,7 @@ export const OHRIEncounterForm: React.FC = ({ isSubmitting, setIsSubmitting, }) => { + const { t } = useTranslation(); const [fields, setFields] = useState>([]); const [encounterLocation, setEncounterLocation] = useState(null); const [encounterDate, setEncounterDate] = useState(formSessionDate); @@ -110,6 +114,7 @@ export const OHRIEncounterForm: React.FC = ({ setEncounterDate, setEncounterProvider, setEncounterLocation, + patientIdentifier: {}, initValues: initValues, obsGroupCounter: obsGroupCounter, setObsGroupCounter: setObsGroupCounter, @@ -527,6 +532,10 @@ export const OHRIEncounterForm: React.FC = ({ } 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; @@ -544,6 +553,42 @@ export const OHRIEncounterForm: React.FC = ({ 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 = ( diff --git a/src/hooks/useInitialValues.ts b/src/hooks/useInitialValues.ts index 5f93ece09..c5ebc5ef9 100644 --- a/src/hooks/useInitialValues.ts +++ b/src/hooks/useInitialValues.ts @@ -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 ?? {}); diff --git a/src/ohri-form-context.tsx b/src/ohri-form-context.tsx index bb7f23f16..e81398f7c 100644 --- a/src/ohri-form-context.tsx +++ b/src/ohri-form-context.tsx @@ -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; @@ -30,6 +37,7 @@ export interface EncounterContext { setEncounterLocation(value: any): void; initValues?: Record; setObsGroupCounter?: any; + patientIdentifier?: PatientIdentifier; } export const OHRIFormContext = React.createContext(undefined); diff --git a/src/registry/inbuilt-components/inbuiltFieldSubmissionHandlers.ts b/src/registry/inbuilt-components/inbuiltFieldSubmissionHandlers.ts index e18648327..5177f962d 100644 --- a/src/registry/inbuilt-components/inbuiltFieldSubmissionHandlers.ts +++ b/src/registry/inbuilt-components/inbuiltFieldSubmissionHandlers.ts @@ -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'; /** @@ -34,4 +35,9 @@ export const inbuiltFieldSubmissionHandlers: Array {} +export type FormSchemaTransformerRegistration = ComponentRegistration export interface FormsRegistryStoreState { controls: CustomControlRegistration[]; diff --git a/src/submission-handlers/patientIdentifierHandler.ts b/src/submission-handlers/patientIdentifierHandler.ts new file mode 100644 index 000000000..56eb20277 --- /dev/null +++ b/src/submission-handlers/patientIdentifierHandler.ts @@ -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, + 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) => { + return null; + }, + +}; diff --git a/src/utils/patientIdentifierProcessor.ts b/src/utils/patientIdentifierProcessor.ts new file mode 100644 index 000000000..95e13f7b2 --- /dev/null +++ b/src/utils/patientIdentifierProcessor.ts @@ -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; + }); +} \ No newline at end of file