@kenyaemr/esm-patient-registration-app 8.1.1-pre.129 → 8.1.2-pre.152

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/.turbo/turbo-build.log +21 -23
  2. package/dist/108.js +1 -1
  3. package/dist/130.js +1 -1
  4. package/dist/130.js.map +1 -1
  5. package/dist/173.js +2 -0
  6. package/dist/{895.js.LICENSE.txt → 173.js.LICENSE.txt} +25 -0
  7. package/dist/173.js.map +1 -0
  8. package/dist/236.js +1 -0
  9. package/dist/240.js +1 -0
  10. package/dist/261.js +1 -0
  11. package/dist/271.js +1 -1
  12. package/dist/272.js +1 -0
  13. package/dist/319.js +1 -1
  14. package/dist/336.js +1 -0
  15. package/dist/371.js +1 -0
  16. package/dist/371.js.map +1 -0
  17. package/dist/378.js +1 -0
  18. package/dist/460.js +1 -1
  19. package/dist/501.js +1 -1
  20. package/dist/501.js.map +1 -1
  21. package/dist/539.js +1 -0
  22. package/dist/566.js +1 -0
  23. package/dist/574.js +1 -1
  24. package/dist/623.js +1 -0
  25. package/dist/623.js.map +1 -0
  26. package/dist/644.js +1 -1
  27. package/dist/652.js +1 -0
  28. package/dist/657.js +1 -0
  29. package/dist/657.js.map +1 -0
  30. package/dist/673.js +1 -0
  31. package/dist/705.js +1 -0
  32. package/dist/711.js +1 -0
  33. package/dist/727.js +1 -0
  34. package/dist/737.js +1 -0
  35. package/dist/744.js +1 -0
  36. package/dist/757.js +1 -1
  37. package/dist/759.js +1 -0
  38. package/dist/759.js.map +1 -0
  39. package/dist/76.js +1 -1
  40. package/dist/788.js +1 -1
  41. package/dist/807.js +1 -1
  42. package/dist/833.js +1 -1
  43. package/dist/899.js +1 -0
  44. package/dist/kenyaemr-esm-patient-registration-app.js +1 -1
  45. package/dist/kenyaemr-esm-patient-registration-app.js.buildmanifest.json +445 -93
  46. package/dist/kenyaemr-esm-patient-registration-app.js.map +1 -1
  47. package/dist/main.js +1 -1
  48. package/dist/main.js.LICENSE.txt +25 -0
  49. package/dist/main.js.map +1 -1
  50. package/dist/routes.json +1 -1
  51. package/package-lock.json +2052 -1699
  52. package/package.json +4 -4
  53. package/src/client-registry/hie-client-registry/dependants/dependants.component.tsx +46 -0
  54. package/src/client-registry/hie-client-registry/hie-client-registry.component.tsx +38 -8
  55. package/src/client-registry/hie-client-registry/hie-resource.ts +126 -21
  56. package/src/client-registry/hie-client-registry/hie-types.ts +102 -0
  57. package/src/client-registry/hie-client-registry/modal/confirm-hie.modal.tsx +78 -62
  58. package/src/client-registry/hie-client-registry/modal/confirm-hie.scss +55 -3
  59. package/src/client-registry/hie-client-registry/modal/hie-otp-verification-form.component.tsx +88 -0
  60. package/src/client-registry/hie-client-registry/modal/hie-patient-detail-preview.component.tsx +77 -0
  61. package/src/client-registry/hie-client-registry/patient-info/patient-info.component.tsx +17 -0
  62. package/src/config-schema.ts +30 -2
  63. package/src/patient-registration/field/address/address-search.component.tsx +5 -2
  64. package/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx +1 -1
  65. package/src/patient-registration/field/dob/dob.component.tsx +1 -1
  66. package/src/patient-registration/field/gender/gender-field.component.tsx +6 -2
  67. package/src/patient-registration/field/id/identifier-selection-overlay.component.tsx +3 -3
  68. package/src/patient-registration/field/name/name-field.component.tsx +2 -2
  69. package/src/patient-registration/field/obs/obs-field.component.tsx +9 -5
  70. package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +3 -3
  71. package/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx +22 -11
  72. package/src/patient-registration/field/person-attributes/location-person-attribute-field.component.tsx +1 -1
  73. package/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx +12 -4
  74. package/src/patient-registration/form-manager.test.ts +4 -1
  75. package/src/patient-registration/form-manager.ts +0 -1
  76. package/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx +52 -62
  77. package/src/patient-registration/mpi/mpi-patient.resource.ts +21 -0
  78. package/src/patient-registration/patient-registration-hooks.ts +90 -25
  79. package/src/patient-registration/patient-registration-utils.test.ts +33 -0
  80. package/src/patient-registration/patient-registration-utils.ts +63 -13
  81. package/src/patient-registration/patient-registration.component.tsx +17 -2
  82. package/src/patient-registration/patient-registration.test.tsx +442 -56
  83. package/src/patient-registration/section/demographics/demographics-section.component.tsx +3 -3
  84. package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +1 -1
  85. package/src/patient-registration/section/patient-relationships/relationships.resource.tsx +28 -28
  86. package/src/widgets/cancel-patient-edit.modal.tsx +2 -0
  87. package/src/widgets/cancel-patient-edit.scss +29 -0
  88. package/src/widgets/delete-identifier-confirmation.modal.tsx +2 -0
  89. package/src/widgets/delete-identifier-confirmation.scss +29 -0
  90. package/translations/am.json +1 -0
  91. package/translations/ar.json +6 -4
  92. package/translations/de.json +118 -0
  93. package/translations/en.json +17 -0
  94. package/translations/es.json +2 -0
  95. package/translations/fr.json +1 -0
  96. package/translations/he.json +1 -0
  97. package/translations/hi.json +118 -0
  98. package/translations/hi_IN.json +118 -0
  99. package/translations/id.json +118 -0
  100. package/translations/it.json +118 -0
  101. package/translations/km.json +1 -0
  102. package/translations/ne.json +118 -0
  103. package/translations/pt.json +118 -0
  104. package/translations/pt_BR.json +118 -0
  105. package/translations/qu.json +118 -0
  106. package/translations/si.json +118 -0
  107. package/translations/sw.json +118 -0
  108. package/translations/sw_KE.json +118 -0
  109. package/translations/tr.json +118 -0
  110. package/translations/tr_TR.json +118 -0
  111. package/translations/uk.json +118 -0
  112. package/translations/vi.json +118 -0
  113. package/translations/zh.json +3 -1
  114. package/translations/zh_CN.json +2 -0
  115. package/dist/250.js +0 -1
  116. package/dist/250.js.map +0 -1
  117. package/dist/66.js +0 -1
  118. package/dist/66.js.map +0 -1
  119. package/dist/662.js +0 -1
  120. package/dist/662.js.map +0 -1
  121. package/dist/753.js +0 -1
  122. package/dist/753.js.map +0 -1
  123. package/dist/895.js +0 -2
  124. package/dist/895.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
- import { BrowserRouter } from 'react-router-dom';
3
- import { render, screen } from '@testing-library/react';
2
+ import { screen } from '@testing-library/react';
4
3
  import userEvent from '@testing-library/user-event';
4
+ import { renderWithRouter } from 'tools';
5
5
  import { Autosuggest } from './autosuggest.component';
6
6
 
7
7
  const mockPersons = [
@@ -33,18 +33,16 @@ const mockHandleSuggestionSelected = jest.fn((field, value) => [field, value]);
33
33
 
34
34
  describe('Autosuggest', () => {
35
35
  it('renders a search box', () => {
36
- render(
37
- <BrowserRouter>
38
- <Autosuggest
39
- getSearchResults={mockGetSearchResults}
40
- getDisplayValue={(item) => item.display}
41
- getFieldValue={(item) => item.uuid}
42
- id="person"
43
- labelText=""
44
- onSuggestionSelected={mockHandleSuggestionSelected}
45
- placeholder="Find Person"
46
- />
47
- </BrowserRouter>,
36
+ renderWithRouter(
37
+ <Autosuggest
38
+ getSearchResults={mockGetSearchResults}
39
+ getDisplayValue={(item) => item.display}
40
+ getFieldValue={(item) => item.uuid}
41
+ id="person"
42
+ labelText=""
43
+ onSuggestionSelected={mockHandleSuggestionSelected}
44
+ placeholder="Find Person"
45
+ />,
48
46
  );
49
47
 
50
48
  expect(screen.getByRole('searchbox')).toBeInTheDocument();
@@ -54,18 +52,16 @@ describe('Autosuggest', () => {
54
52
  it('renders matching search results in a list when the user types a query', async () => {
55
53
  const user = userEvent.setup();
56
54
 
57
- render(
58
- <BrowserRouter>
59
- <Autosuggest
60
- getSearchResults={mockGetSearchResults}
61
- getDisplayValue={(item) => item.display}
62
- getFieldValue={(item) => item.uuid}
63
- id="person"
64
- labelText=""
65
- onSuggestionSelected={mockHandleSuggestionSelected}
66
- placeholder="Find Person"
67
- />
68
- </BrowserRouter>,
55
+ renderWithRouter(
56
+ <Autosuggest
57
+ getSearchResults={mockGetSearchResults}
58
+ getDisplayValue={(item) => item.display}
59
+ getFieldValue={(item) => item.uuid}
60
+ id="person"
61
+ labelText=""
62
+ onSuggestionSelected={mockHandleSuggestionSelected}
63
+ placeholder="Find Person"
64
+ />,
69
65
  );
70
66
 
71
67
  const searchbox = screen.getByRole('searchbox');
@@ -82,18 +78,16 @@ describe('Autosuggest', () => {
82
78
  it('clears the list of suggestions when a suggestion is selected', async () => {
83
79
  const user = userEvent.setup();
84
80
 
85
- render(
86
- <BrowserRouter>
87
- <Autosuggest
88
- getSearchResults={mockGetSearchResults}
89
- getDisplayValue={(item) => item.display}
90
- getFieldValue={(item) => item.uuid}
91
- id="person"
92
- labelText=""
93
- onSuggestionSelected={mockHandleSuggestionSelected}
94
- placeholder="Find Person"
95
- />
96
- </BrowserRouter>,
81
+ renderWithRouter(
82
+ <Autosuggest
83
+ getSearchResults={mockGetSearchResults}
84
+ getDisplayValue={(item) => item.display}
85
+ getFieldValue={(item) => item.uuid}
86
+ id="person"
87
+ labelText=""
88
+ onSuggestionSelected={mockHandleSuggestionSelected}
89
+ placeholder="Find Person"
90
+ />,
97
91
  );
98
92
 
99
93
  let list = screen.queryByRole('list');
@@ -117,18 +111,16 @@ describe('Autosuggest', () => {
117
111
  it('changes suggestions when a search input is changed', async () => {
118
112
  const user = userEvent.setup();
119
113
 
120
- render(
121
- <BrowserRouter>
122
- <Autosuggest
123
- getSearchResults={mockGetSearchResults}
124
- getDisplayValue={(item) => item.display}
125
- getFieldValue={(item) => item.uuid}
126
- id="person"
127
- labelText=""
128
- onSuggestionSelected={mockHandleSuggestionSelected}
129
- placeholder="Find Person"
130
- />
131
- </BrowserRouter>,
114
+ renderWithRouter(
115
+ <Autosuggest
116
+ getSearchResults={mockGetSearchResults}
117
+ getDisplayValue={(item) => item.display}
118
+ getFieldValue={(item) => item.uuid}
119
+ id="person"
120
+ labelText=""
121
+ onSuggestionSelected={mockHandleSuggestionSelected}
122
+ placeholder="Find Person"
123
+ />,
132
124
  );
133
125
 
134
126
  let list = screen.queryByRole('list');
@@ -149,18 +141,16 @@ describe('Autosuggest', () => {
149
141
  it('hides the list of suggestions when the user clicks outside of the component', async () => {
150
142
  const user = userEvent.setup();
151
143
 
152
- render(
153
- <BrowserRouter>
154
- <Autosuggest
155
- getSearchResults={mockGetSearchResults}
156
- getDisplayValue={(item) => item.display}
157
- getFieldValue={(item) => item.uuid}
158
- id="person"
159
- labelText=""
160
- onSuggestionSelected={mockHandleSuggestionSelected}
161
- placeholder="Find Person"
162
- />
163
- </BrowserRouter>,
144
+ renderWithRouter(
145
+ <Autosuggest
146
+ getSearchResults={mockGetSearchResults}
147
+ getDisplayValue={(item) => item.display}
148
+ getFieldValue={(item) => item.uuid}
149
+ id="person"
150
+ labelText=""
151
+ onSuggestionSelected={mockHandleSuggestionSelected}
152
+ placeholder="Find Person"
153
+ />,
164
154
  );
165
155
 
166
156
  const input = screen.getByRole('searchbox');
@@ -0,0 +1,21 @@
1
+ import useSWR from 'swr';
2
+
3
+ const fetcher = (url: string) => {
4
+ const headers = new Headers();
5
+ headers.append('Content-Type', 'application/json');
6
+ headers.append('Authorization', `Basic ${btoa('kemr:password')}`);
7
+ return fetch(url, { headers }).then((res) => res.json());
8
+ };
9
+
10
+ export function useMpiPatient(patientId: string) {
11
+ const url = `https://hiedhs.intellisoftkenya.com/fhir/Patient?_id=${patientId}`;
12
+
13
+ const { data: patient, error: error, isLoading: isLoading } = useSWR<{ data: fhir.Bundle }, Error>(url, fetcher);
14
+ const patientInfo = patient?.['entry']?.[0]?.resource;
15
+
16
+ return {
17
+ isLoading,
18
+ patient: patientInfo,
19
+ error,
20
+ };
21
+ }
@@ -1,15 +1,16 @@
1
+ import { type Dispatch, useEffect, useMemo, useState } from 'react';
1
2
  import {
2
3
  type FetchResponse,
4
+ type OpenmrsResource,
3
5
  getSynchronizationItems,
4
6
  openmrsFetch,
5
- type OpenmrsResource,
6
7
  restBaseUrl,
7
8
  useConfig,
8
9
  usePatient,
9
10
  } from '@openmrs/esm-framework';
10
11
  import last from 'lodash-es/last';
11
12
  import camelCase from 'lodash-es/camelCase';
12
- import { type Dispatch, useEffect, useMemo, useState } from 'react';
13
+ import dayjs from 'dayjs';
13
14
  import useSWR from 'swr';
14
15
  import { v4 } from 'uuid';
15
16
  import { type RegistrationConfig } from '../config-schema';
@@ -31,15 +32,25 @@ import {
31
32
  import {
32
33
  getAddressFieldValuesFromFhirPatient,
33
34
  getFormValuesFromFhirPatient,
35
+ getIdentifierFieldValuesFromFhirPatient,
34
36
  getPatientUuidMapFromFhirPatient,
35
37
  getPhonePersonAttributeValueFromFhirPatient,
36
38
  latestFirstEncounter,
37
39
  } from './patient-registration-utils';
38
40
  import { useInitialPatientRelationships } from './section/patient-relationships/relationships.resource';
39
- import dayjs from 'dayjs';
41
+ import { useMpiPatient } from './mpi/mpi-patient.resource';
42
+
43
+ interface DeathInfoResults {
44
+ uuid: string;
45
+ display: string;
46
+ causeOfDeath: OpenmrsResource | null;
47
+ dead: boolean;
48
+ deathDate: string;
49
+ causeOfDeathNonCoded: string | null;
50
+ }
40
51
 
41
- export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch<FormValues>] {
42
- const { freeTextFieldConceptUuid } = useConfig<RegistrationConfig>();
52
+ export function useInitialFormValuesLocal(patientUuid: string): [FormValues, Dispatch<FormValues>] {
53
+ const { freeTextFieldConceptUuid, fieldConfigurations } = useConfig<RegistrationConfig>()
43
54
  const { martialStatus, education, occupation, educationLoad } = useConcepts();
44
55
  const { isLoading: isLoadingPatientToEdit, patient: patientToEdit } = usePatient(patientUuid);
45
56
  const { data: deathInfo, isLoading: isLoadingDeathInfo } = useInitialPersonDeathInfo(patientUuid);
@@ -89,7 +100,7 @@ export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch
89
100
  ...initialFormValues,
90
101
  ...getFormValuesFromFhirPatient(patientToEdit),
91
102
  address: getAddressFieldValuesFromFhirPatient(patientToEdit),
92
- ...getPhonePersonAttributeValueFromFhirPatient(patientToEdit),
103
+ ...getPhonePersonAttributeValueFromFhirPatient(patientToEdit, fieldConfigurations.phone.personAttributeUuid),
93
104
  birthdateEstimated: !/^\d{4}-\d{2}-\d{2}$/.test(patientToEdit.birthDate),
94
105
  yearsEstimated,
95
106
  monthsEstimated,
@@ -107,7 +118,13 @@ export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch
107
118
  setInitialFormValues(registration._patientRegistrationData.formValues);
108
119
  }
109
120
  })();
110
- }, [isLoadingPatientToEdit, patientToEdit, patientUuid]);
121
+ }, [
122
+ initialFormValues,
123
+ isLoadingPatientToEdit,
124
+ patientToEdit,
125
+ patientUuid,
126
+ fieldConfigurations.phone.personAttributeUuid,
127
+ ]);
111
128
 
112
129
  // Set initial patient death info
113
130
  useEffect(() => {
@@ -126,9 +143,9 @@ export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch
126
143
  nonCodedCauseOfDeath: deathInfo.causeOfDeathNonCoded,
127
144
  }));
128
145
  }
129
- }, [isLoadingDeathInfo, deathInfo, setInitialFormValues]);
130
- // Setting authentication token
146
+ }, [isLoadingDeathInfo, deathInfo, setInitialFormValues, freeTextFieldConceptUuid]);
131
147
 
148
+ // Setting authentication token
132
149
  useEffect(() => {
133
150
  if (!isLoadingToken && token) {
134
151
  setInitialFormValues((initialFormValues) => ({ ...initialFormValues, token: String(token.access_token) }));
@@ -179,7 +196,7 @@ export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch
179
196
  if (!isLoadingObs) {
180
197
  setInitialFormValues((initialFormValues) => ({ ...initialFormValues, obs: obs, observation: observations }));
181
198
  }
182
- }, [isLoadingObs]);
199
+ }, [isLoadingObs, obs, observations]);
183
200
 
184
201
  // Set Initial encounter
185
202
 
@@ -190,11 +207,68 @@ export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch
190
207
  concepts: [...occupation, ...martialStatus, ...education],
191
208
  }));
192
209
  }
193
- }, [educationLoad]);
210
+ }, [educationLoad, martialStatus, education, occupation]);
194
211
 
195
212
  return [initialFormValues, setInitialFormValues];
196
213
  }
197
214
 
215
+ export function useMpiInitialFormValues(patientUuid: string): [FormValues, Dispatch<FormValues>] {
216
+ const { fieldConfigurations } = useConfig<RegistrationConfig>();
217
+ const { isLoading: isLoadingMpiPatient, patient: mpiPatient } = useMpiPatient(patientUuid);
218
+
219
+ const [initialMPIFormValues, setInitialMPIFormValues] = useState<FormValues>({
220
+ patientUuid: v4(),
221
+ givenName: '',
222
+ middleName: '',
223
+ familyName: '',
224
+ additionalGivenName: '',
225
+ additionalMiddleName: '',
226
+ additionalFamilyName: '',
227
+ addNameInLocalLanguage: false,
228
+ gender: '',
229
+ birthdate: null,
230
+ yearsEstimated: 0,
231
+ monthsEstimated: 0,
232
+ birthdateEstimated: false,
233
+ telephoneNumber: '',
234
+ isDead: false,
235
+ deathDate: undefined,
236
+ deathTime: undefined,
237
+ deathTimeFormat: 'AM',
238
+ deathCause: '',
239
+ nonCodedCauseOfDeath: '',
240
+ relationships: [],
241
+ identifiers: {},
242
+ address: {},
243
+ });
244
+
245
+ useEffect(() => {
246
+ (async () => {
247
+ if (mpiPatient) {
248
+ // const identifiers = await getIdentifierFieldValuesFromFhirPatient(
249
+ // mpiPatient.data,
250
+ // fieldConfigurations.identifier,
251
+ // );
252
+
253
+ const values = {
254
+ ...initialMPIFormValues,
255
+ ...getFormValuesFromFhirPatient(mpiPatient),
256
+ address: getAddressFieldValuesFromFhirPatient(mpiPatient),
257
+ attributes: getPhonePersonAttributeValueFromFhirPatient(
258
+ mpiPatient,
259
+ fieldConfigurations.phone.personAttributeUuid,
260
+ ),
261
+ };
262
+ setInitialMPIFormValues(values);
263
+ }
264
+ })();
265
+
266
+ // eslint-disable-next-line react-hooks/exhaustive-deps
267
+ }, [mpiPatient, isLoadingMpiPatient]);
268
+
269
+ return [initialMPIFormValues, setInitialMPIFormValues];
270
+ }
271
+
198
272
  export function useInitialAddressFieldValues(patientUuid: string, fallback = {}): [object, Dispatch<object>] {
199
273
  const { isLoading, patient } = usePatient(patientUuid);
200
274
  const [initialAddressFieldValues, setInitialAddressFieldValues] = useState<object>(fallback);
@@ -211,7 +285,7 @@ export function useInitialAddressFieldValues(patientUuid: string, fallback = {})
211
285
  setInitialAddressFieldValues(registration?._patientRegistrationData.initialAddressFieldValues ?? fallback);
212
286
  }
213
287
  })();
214
- }, [isLoading, patient, patientUuid]);
288
+ }, [fallback, initialAddressFieldValues, isLoading, patient, patientUuid]);
215
289
 
216
290
  return [initialAddressFieldValues, setInitialAddressFieldValues];
217
291
  }
@@ -232,7 +306,7 @@ export function usePatientUuidMap(
232
306
  setPatientUuidMap(registration?._patientRegistrationData.initialAddressFieldValues ?? fallback),
233
307
  );
234
308
  }
235
- }, [isLoadingPatientToEdit, patientToEdit, patientUuid]);
309
+ }, [fallback, isLoadingPatientToEdit, patientToEdit, patientUuid, patientUuidMap]);
236
310
 
237
311
  useEffect(() => {
238
312
  if (attributes) {
@@ -287,7 +361,7 @@ export function useInitialPatientIdentifiers(patientUuid: string): {
287
361
  data: identifiers,
288
362
  isLoading,
289
363
  };
290
- }, [data, error]);
364
+ }, [data?.data?.results, isLoading]);
291
365
 
292
366
  return result;
293
367
  }
@@ -323,19 +397,10 @@ function useInitialPersonAttributes(personUuid: string) {
323
397
  data: data?.data?.results,
324
398
  isLoading,
325
399
  };
326
- }, [data, error]);
400
+ }, [data?.data?.results, isLoading]);
327
401
  return result;
328
402
  }
329
403
 
330
- interface DeathInfoResults {
331
- uuid: string;
332
- display: string;
333
- causeOfDeath: OpenmrsResource | null;
334
- dead: boolean;
335
- deathDate: string;
336
- causeOfDeathNonCoded: string | null;
337
- }
338
-
339
404
  function useInitialPersonDeathInfo(personUuid: string) {
340
405
  const { data, error, isLoading } = useSWR<FetchResponse<DeathInfoResults>, Error>(
341
406
  !!personUuid
@@ -349,7 +414,7 @@ function useInitialPersonDeathInfo(personUuid: string) {
349
414
  data: data?.data,
350
415
  isLoading,
351
416
  };
352
- }, [data, error]);
417
+ }, [data?.data, isLoading]);
353
418
  return result;
354
419
  }
355
420
 
@@ -0,0 +1,33 @@
1
+ import { filterOutUndefinedPatientIdentifiers } from './patient-registration-utils';
2
+
3
+ describe('filterOutUndefinedPatientIdentifiers', () => {
4
+ const getIdentifiers = (autoGeneration = true, manualEntryEnabled = false) => ({
5
+ OpenMRSId: {
6
+ autoGeneration: autoGeneration,
7
+ identifierName: 'OpenMRS ID',
8
+ identifierTypeUuid: '05a29f94-c0ed-11e2-94be-8c13b969e334',
9
+ identifierValue: undefined,
10
+ initialValue: '100GEJ',
11
+ preferred: true,
12
+ required: true,
13
+ selectedSource: {
14
+ uuid: '01af8526-cea4-4175-aa90-340acb411771',
15
+ name: 'Generator for OpenMRS ID',
16
+ autoGenerationOption: {
17
+ manualEntryEnabled: manualEntryEnabled,
18
+ automaticGenerationEnabled: autoGeneration,
19
+ },
20
+ },
21
+ },
22
+ });
23
+
24
+ it('should fitler out undefined identifiers', () => {
25
+ const filteredIdentifiers = filterOutUndefinedPatientIdentifiers(getIdentifiers());
26
+ expect(filteredIdentifiers.OpenMRSId).not.toBeDefined();
27
+ });
28
+
29
+ it('should retain auto-generated identifiers with manual entry', () => {
30
+ const filteredIdentifiers = filterOutUndefinedPatientIdentifiers(getIdentifiers(true, true));
31
+ expect(filteredIdentifiers.OpenMRSId).toBeDefined();
32
+ });
33
+ });
@@ -1,6 +1,6 @@
1
1
  import * as Yup from 'yup';
2
2
  import camelCase from 'lodash-es/camelCase';
3
- import { parseDate } from '@openmrs/esm-framework';
3
+ import { openmrsFetch, restBaseUrl, parseDate } from '@openmrs/esm-framework';
4
4
  import {
5
5
  type AddressValidationSchemaType,
6
6
  type Encounter,
@@ -110,15 +110,14 @@ export function getFormValuesFromFhirPatient(patient: fhir.Patient) {
110
110
  const result = {} as FormValues;
111
111
  const patientName = patient.name[0];
112
112
  const additionalPatientName = patient.name[1];
113
-
114
113
  result.patientUuid = patient.id;
115
- result.givenName = patientName?.given[0];
116
- result.middleName = patientName?.given[1];
114
+ result.givenName = patientName?.given?.[0];
115
+ result.middleName = patientName?.given?.[1];
117
116
  result.familyName = patientName?.family;
118
117
  result.addNameInLocalLanguage = !!additionalPatientName ? true : undefined;
119
- result.additionalGivenName = additionalPatientName?.given[0];
120
- result.additionalMiddleName = additionalPatientName?.given[1];
121
- result.additionalFamilyName = additionalPatientName?.family;
118
+ result.additionalGivenName = additionalPatientName?.given?.[0] ?? undefined;
119
+ result.additionalMiddleName = additionalPatientName?.given?.[1] ?? undefined;
120
+ result.additionalFamilyName = additionalPatientName?.family ?? undefined;
122
121
 
123
122
  result.gender = patient.gender;
124
123
  result.birthdate = patient.birthDate ? parseDate(patient.birthDate) : undefined;
@@ -192,18 +191,69 @@ export function getPatientIdentifiersFromFhirPatient(patient: fhir.Patient): Arr
192
191
  });
193
192
  }
194
193
 
195
- export function getPhonePersonAttributeValueFromFhirPatient(patient: fhir.Patient) {
194
+ export async function getIdentifierFieldValuesFromFhirPatient(
195
+ patient: fhir.Patient,
196
+ identifierConfig,
197
+ ): Promise<{ [identifierFieldName: string]: PatientIdentifierValue }> {
198
+ const identifiers: FormValues['identifiers'] = {};
199
+ const promises: Promise<void>[] = [];
200
+
201
+ for (const identifier of patient.identifier) {
202
+ for (const config of identifierConfig) {
203
+ if (config.fhirIdentifierSystem !== identifier.system) {
204
+ continue;
205
+ }
206
+
207
+ const url = `${restBaseUrl}/patientidentifiertype/${config.openmrsIdentifierTypeUuid}`;
208
+
209
+ promises.push(
210
+ openmrsFetch(url)
211
+ .then((response) => {
212
+ if (!response.data?.name) {
213
+ return;
214
+ }
215
+ identifiers[response.data.name] = {
216
+ identifierUuid: null,
217
+ preferred: false,
218
+ initialValue: identifier.value,
219
+ identifierValue: identifier.value,
220
+ identifierTypeUuid: config.identifierTypeUuid,
221
+ identifierName: response.data.name,
222
+ required: false,
223
+ selectedSource: null,
224
+ autoGeneration: false,
225
+ };
226
+ })
227
+ .catch((error) => {
228
+ console.error(`Error fetching identifier type for ${config.identifierTypeUuid}:`, error);
229
+ }),
230
+ );
231
+ }
232
+ }
233
+ await Promise.all(promises);
234
+ return identifiers;
235
+ }
236
+
237
+ export function getPhonePersonAttributeValueFromFhirPatient(patient: fhir.Patient, phoneUuid) {
196
238
  const result = {};
197
- if (patient.telecom) {
198
- result['phone'] = patient.telecom[0].value;
239
+
240
+ if (patient.telecom && Array.isArray(patient.telecom)) {
241
+ const phoneEntry = patient.telecom.find((entry) => entry.system === 'phone');
242
+ if (phoneEntry) {
243
+ result[phoneUuid] = phoneEntry.value;
244
+ }
199
245
  }
246
+
200
247
  return result;
201
248
  }
202
249
 
203
- export const filterOutUndefinedPatientIdentifiers = (patientIdentifiers) =>
250
+ type IdentifierMap = { [identifierFieldName: string]: PatientIdentifierValue };
251
+ export const filterOutUndefinedPatientIdentifiers = (patientIdentifiers: IdentifierMap): IdentifierMap =>
204
252
  Object.fromEntries(
205
- Object.entries<PatientIdentifierValue>(patientIdentifiers).filter(
206
- ([key, value]) => value.identifierValue !== undefined,
253
+ Object.entries(patientIdentifiers).filter(
254
+ ([key, value]) =>
255
+ (value.autoGeneration && value.selectedSource.autoGenerationOption.manualEntryEnabled) ||
256
+ value.identifierValue !== undefined,
207
257
  ),
208
258
  );
209
259
 
@@ -9,6 +9,7 @@ import {
9
9
  createErrorHandler,
10
10
  interpolateUrl,
11
11
  showSnackbar,
12
+ useAppContext,
12
13
  useConfig,
13
14
  usePatient,
14
15
  usePatientPhoto,
@@ -20,7 +21,12 @@ import { PatientRegistrationContext } from './patient-registration-context';
20
21
  import { type SavePatientForm, SavePatientTransactionManager } from './form-manager';
21
22
  import { DummyDataInput } from './input/dummy-data/dummy-data-input.component';
22
23
  import { cancelRegistration, filterOutUndefinedPatientIdentifiers, scrollIntoView } from './patient-registration-utils';
23
- import { useInitialAddressFieldValues, useInitialFormValues, usePatientUuidMap } from './patient-registration-hooks';
24
+ import {
25
+ useInitialAddressFieldValues,
26
+ useMpiInitialFormValues,
27
+ useInitialFormValuesLocal,
28
+ usePatientUuidMap,
29
+ } from './patient-registration-hooks';
24
30
  import { ResourcesContext } from '../offline.resources';
25
31
  import { builtInSections, type RegistrationConfig, type SectionDefinition } from '../config-schema';
26
32
  import { SectionWrapper } from './section/section-wrapper.component';
@@ -44,10 +50,12 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
44
50
  const config = useConfig() as RegistrationConfig;
45
51
  const [target, setTarget] = useState<undefined | string>();
46
52
  const { patientUuid: uuidOfPatientToEdit } = useParams();
53
+ const sourcePatientId = new URLSearchParams(search).get('sourceRecord');
47
54
  const { isLoading: isLoadingPatientToEdit, patient: patientToEdit } = usePatient(uuidOfPatientToEdit);
48
55
  const { t } = useTranslation();
49
56
  const [capturePhotoProps, setCapturePhotoProps] = useState<CapturePhotoProps | null>(null);
50
- const [initialFormValues, setInitialFormValues] = useInitialFormValues(uuidOfPatientToEdit);
57
+ const [initialFormValues, setInitialFormValues] = useInitialFormValuesLocal(uuidOfPatientToEdit);
58
+ const [initialMPIFormValues, setInitialMPIFormValues] = useMpiInitialFormValues(sourcePatientId);
51
59
  const [initialAddressFieldValues] = useInitialAddressFieldValues(uuidOfPatientToEdit);
52
60
  const [patientUuidMap] = usePatientUuidMap(uuidOfPatientToEdit);
53
61
  const location = currentSession?.sessionLocation?.uuid;
@@ -61,6 +69,13 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
61
69
  inEditMode ? initialFormValues.identifiers['nationalUniquePatientIdentifier']?.identifierValue : false,
62
70
  );
63
71
 
72
+ useEffect(() => {
73
+ if (initialMPIFormValues) {
74
+ setInitialFormValues(initialMPIFormValues);
75
+ }
76
+ // eslint-disable-next-line react-hooks/exhaustive-deps
77
+ }, [initialMPIFormValues, setInitialMPIFormValues]);
78
+
64
79
  useEffect(() => {
65
80
  exportedInitialFormValuesForTesting = initialFormValues;
66
81
  }, [initialFormValues]);