Skip to content

Commit 63d915a

Browse files
authored
(fix) Modify verify CR to conform to SAF HIE enpoint (#32)
1 parent 9d47e02 commit 63d915a

4 files changed

Lines changed: 127 additions & 21 deletions

File tree

packages/esm-patient-registration-app/src/patient-registration/client-registry-search/client-registry-search.component.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ const ClientRegistryLookupSection: React.FC<ClientRegistryLookupSectionProps> =
9797
identificationNumber: identifier,
9898
identificationType: identificationType,
9999
locationUuid,
100+
phoneNumber: '',
100101
};
101102
const response = await withTimeout(requestCustomOtp(payload));
102103
setSessionId(response.sessionId);
@@ -140,6 +141,7 @@ const ClientRegistryLookupSection: React.FC<ClientRegistryLookupSectionProps> =
140141
identificationNumber: identifier,
141142
identificationType: identificationType,
142143
locationUuid,
144+
phoneNumber: '',
143145
};
144146
setOtpVerified(true);
145147
onClientVerified?.(customOtpPayload);

packages/esm-patient-registration-app/src/patient-registration/client-registry/client-registry-search.component.tsx

Lines changed: 98 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
1-
import React, { useState } from 'react';
1+
import React, { useEffect, useMemo, useState } from 'react';
22
import { Button, TextInput, InlineLoading, InlineNotification, Dropdown, Modal, ModalBody } from '@carbon/react';
3-
import { showSnackbar, useSession } from '@openmrs/esm-framework';
3+
import { type PatientIdentifier, type PersonAttribute, showSnackbar, usePatient, useSession } from '@openmrs/esm-framework';
44
import { useFormikContext } from 'formik';
55
import styles from './client-registry-search.scss';
6-
import { requestCustomOtp, validateCustomOtp, fetchClientRegistryData } from './client-registry.resource';
6+
import {
7+
requestCustomOtp,
8+
validateCustomOtp,
9+
fetchClientRegistryData,
10+
fetchPatientAttributes,
11+
fetchPatientIdentifiers,
12+
} from './client-registry.resource';
713
import NewClientTab from './new-client/new-client-tab.component';
814
import ExistingClientTab from './existing-client/existing-client-tab.component';
9-
import { type IdentifierType, type HieClient, IDENTIFIER_TYPES } from './types';
15+
import {
16+
type IdentifierType,
17+
type HieClient,
18+
IDENTIFIER_TYPES,
19+
PersonAttributeTypeUuids,
20+
HieIdentificationType,
21+
IdentifierTypesUuids,
22+
} from './types';
1023

1124
export interface ClientRegistryLookupSectionProps {
1225
onClientVerified?: () => void;
@@ -23,7 +36,7 @@ const ClientRegistryLookupSection: React.FC<ClientRegistryLookupSectionProps> =
2336
}) => {
2437
const { setFieldValue } = useFormikContext<any>();
2538
const [identifierType, setIdentifierType] = useState<IdentifierType>('National ID');
26-
const [identifierValue, setIdentifierValue] = useState('');
39+
const [identifierValue, setIdentifierValue] = useState<string>('');
2740
const [otp, setOtp] = useState('');
2841
const [otpSent, setOtpSent] = useState(false);
2942
const [otpVerified, setOtpVerified] = useState<boolean>(false);
@@ -33,7 +46,22 @@ const ClientRegistryLookupSection: React.FC<ClientRegistryLookupSectionProps> =
3346
const [error, setError] = useState<string>('');
3447
const { sessionLocation } = useSession();
3548
const [client, setClient] = useState<HieClient>();
36-
const locationUuid = sessionLocation?.uuid;
49+
const locationUuid = sessionLocation?.uuid ?? '';
50+
const [patientAttributes, setPatientAttributes] = useState<PersonAttribute[]>([]);
51+
const [patientIdentifiers, setPatientIdentifiers] = useState<PatientIdentifier[]>([]);
52+
const { patient } = usePatient();
53+
54+
useEffect(() => {
55+
if (patient) {
56+
getPatientAttributes(patient.id);
57+
getPatientIdentifiers(patient.id);
58+
}
59+
}, [patient]);
60+
61+
const phoneNumber = useMemo(
62+
() => getPatientAttribute(patientAttributes, PersonAttributeTypeUuids.CONTACT_PHONE_NUMBER_UUID),
63+
[patientAttributes],
64+
);
3765

3866
async function withTimeout<T>(promise: Promise<T>, ms = 10000): Promise<T> {
3967
const controller = new AbortController();
@@ -90,6 +118,10 @@ const ClientRegistryLookupSection: React.FC<ClientRegistryLookupSectionProps> =
90118
setError('Please enter a valid ID value');
91119
return;
92120
}
121+
if (!phoneNumber) {
122+
setError('No phone number set. Please add the phone number patient attribute');
123+
return;
124+
}
93125

94126
setIsSendingOtp(true);
95127
setError('');
@@ -98,7 +130,8 @@ const ClientRegistryLookupSection: React.FC<ClientRegistryLookupSectionProps> =
98130
const payload = {
99131
identificationNumber: identifierValue,
100132
identificationType: identifierType,
101-
locationUuid,
133+
phoneNumber: phoneNumber ? (phoneNumber.value ?? '') : '',
134+
locationUuid: locationUuid ?? '',
102135
};
103136

104137
const response = await withTimeout(requestCustomOtp(payload));
@@ -159,6 +192,57 @@ const ClientRegistryLookupSection: React.FC<ClientRegistryLookupSectionProps> =
159192
const registerOnAfyaYangu = () => {
160193
window.open('https://afyayangu.go.ke/', '_blank');
161194
};
195+
async function getPatientAttributes(patientUuid: string) {
196+
const data = await fetchPatientAttributes(patientUuid);
197+
if (data) {
198+
setPatientAttributes(data);
199+
}
200+
}
201+
function getPatientAttribute(patientAttributes: PersonAttribute[], attributeTypeUuid: string) {
202+
if (!patientAttributes || !attributeTypeUuid) {
203+
return null;
204+
}
205+
return patientAttributes.find((a) => {
206+
return a.attributeType?.uuid === attributeTypeUuid;
207+
});
208+
}
209+
async function getPatientIdentifiers(patientUuid: string) {
210+
const data = await fetchPatientIdentifiers(patientUuid);
211+
if (data) {
212+
setPatientIdentifiers(data);
213+
}
214+
}
215+
function handleIdentifierChange(value: { selectedItem: IdentifierType }) {
216+
const selectedValue = value.selectedItem;
217+
setIdentifierType(selectedValue);
218+
const identifier = getPatientIdentifier(selectedValue);
219+
setIdentifierValue(identifier?.identifier ?? '');
220+
}
221+
function getPatientIdentifier(selectedIdentifierType: IdentifierType) {
222+
let identifier: PatientIdentifier | undefined;
223+
switch (selectedIdentifierType) {
224+
case HieIdentificationType.NationalID:
225+
identifier = getIdentifier(patientIdentifiers, IdentifierTypesUuids.NATIONAL_ID_UUID);
226+
break;
227+
case HieIdentificationType.MandateNumber:
228+
identifier = getIdentifier(patientIdentifiers, IdentifierTypesUuids.MANDATE_NUMBER_UUID);
229+
break;
230+
case HieIdentificationType.AlienID:
231+
identifier = getIdentifier(patientIdentifiers, IdentifierTypesUuids.ALIEN_ID_UUID);
232+
break;
233+
case HieIdentificationType.RefugeeID:
234+
identifier = getIdentifier(patientIdentifiers, IdentifierTypesUuids.REFUGEE_ID_UUID);
235+
break;
236+
default:
237+
identifier = getIdentifier(patientIdentifiers, IdentifierTypesUuids.NATIONAL_ID_UUID);
238+
}
239+
return identifier;
240+
}
241+
function getIdentifier(patientIdentifiers: PatientIdentifier[], identifierTypeUuid: string) {
242+
return patientIdentifiers.find((i) => {
243+
return i.identifierType?.uuid === identifierTypeUuid;
244+
});
245+
}
162246

163247
return (
164248
<Modal
@@ -189,20 +273,16 @@ const ClientRegistryLookupSection: React.FC<ClientRegistryLookupSectionProps> =
189273
label="Identifier Type"
190274
titleText="Select Identifier Type"
191275
items={IDENTIFIER_TYPES}
192-
selectedItem={identifierType}
193-
onChange={({ selectedItem }) => setIdentifierType(selectedItem as IdentifierType)}
276+
onChange={handleIdentifierChange}
194277
disabled={otpSent}
195278
/>
196279
</div>
197-
198280
<div className={styles.formControl}>
199281
<TextInput
200282
id="identifier-value"
201283
labelText={`${identifierType} Value`}
202284
value={identifierValue}
203-
onChange={(e) => setIdentifierValue(e.target.value)}
204-
disabled={otpSent}
205-
placeholder={`Enter ${identifierType.toLowerCase()} value`}
285+
readonly={true}
206286
/>
207287
</div>
208288
</div>
@@ -246,6 +326,11 @@ const ClientRegistryLookupSection: React.FC<ClientRegistryLookupSectionProps> =
246326
Back
247327
</Button>
248328
</div>
329+
<div className={styles.actionBtn}>
330+
<Button kind="primary" onClick={() => setOtpVerified(true)}>
331+
Skip OTP
332+
</Button>
333+
</div>
249334
</div>
250335
</>
251336
) : (

packages/esm-patient-registration-app/src/patient-registration/client-registry/client-registry.resource.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { openmrsFetch, type PatientIdentifier, type PersonAttribute, restBaseUrl } from '@openmrs/esm-framework';
12
import { getHieBaseUrl } from '../../utils/get-base-url';
23
import {
34
type ClientRegistrySearchRequest,
@@ -10,7 +11,7 @@ import {
1011
export type ClientRegistrySearchResponse = any[];
1112

1213
async function postJson<T>(url: string, payload: unknown): Promise<T> {
13-
const response = await fetch(url, {
14+
const response = await openmrsFetch(url, {
1415
method: 'POST',
1516
headers: { 'Content-Type': 'application/json' },
1617
body: JSON.stringify(payload),
@@ -31,6 +32,7 @@ export async function requestCustomOtp(payload: RequestCustomOtpDto): Promise<Re
3132
identificationNumber: payload.identificationNumber,
3233
identificationType: payload.identificationType,
3334
locationUuid: payload.locationUuid,
35+
phoneNumber: payload.phoneNumber,
3436
};
3537
return postJson<RequestCustomOtpResponse>(url, formattedPayload);
3638
}
@@ -58,3 +60,25 @@ export async function fetchClientRegistryData(
5860
};
5961
return postJson<ClientRegistrySearchResponse>(url, formattedPayload);
6062
}
63+
64+
export async function fetchPatientAttributes(patientUuid: string) {
65+
const url = `${restBaseUrl}/person/${patientUuid}/attribute`;
66+
const resp = await openmrsFetch(url);
67+
const data: { results: PersonAttribute[] } = await resp.json();
68+
if (data && data['results']) {
69+
return data['results'];
70+
} else {
71+
return [];
72+
}
73+
}
74+
75+
export async function fetchPatientIdentifiers(patientUuid: string) {
76+
const url = `${restBaseUrl}/patient/${patientUuid}/identifier`;
77+
const resp = await openmrsFetch(url);
78+
const data: { results: PatientIdentifier[] } = await resp.json();
79+
if (data && data['results']) {
80+
return data['results'];
81+
} else {
82+
return [];
83+
}
84+
}

packages/esm-patient-registration-app/src/patient-registration/client-registry/types/index.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ export type RequestCustomOtpDto = {
258258
identificationNumber: string | number;
259259
identificationType: string;
260260
locationUuid: string;
261+
phoneNumber: string;
261262
};
262263

263264
export interface RequestCustomOtpResponse {
@@ -491,13 +492,7 @@ export const RelationshipTypeUuids = {
491492

492493
export type IdentifierType = 'National ID' | 'Alien ID' | 'Passport' | 'Mandate Number' | 'Refugee ID';
493494

494-
export const IDENTIFIER_TYPES: IdentifierType[] = [
495-
'National ID',
496-
'Alien ID',
497-
'Passport',
498-
'Mandate Number',
499-
'Refugee ID',
500-
];
495+
export const IDENTIFIER_TYPES: IdentifierType[] = ['National ID', 'Alien ID', 'Mandate Number', 'Refugee ID'];
501496

502497
export interface CreateRelationshipDto {
503498
personA: string;

0 commit comments

Comments
 (0)