@ampath/esm-patient-registration-app 9.2.0-next.14 → 9.2.0-next.16

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 (71) hide show
  1. package/dist/4300.js +1 -1
  2. package/dist/4395.js +1 -0
  3. package/dist/4395.js.map +1 -0
  4. package/dist/5239.js +1 -1
  5. package/dist/5239.js.LICENSE.txt +10 -0
  6. package/dist/5239.js.map +1 -1
  7. package/dist/6276.js +1 -1
  8. package/dist/6276.js.map +1 -1
  9. package/dist/6687.js +1 -0
  10. package/dist/6687.js.map +1 -0
  11. package/dist/6996.js +1 -0
  12. package/dist/6996.js.map +1 -0
  13. package/dist/7125.js +2 -0
  14. package/dist/7125.js.map +1 -0
  15. package/dist/7821.js +1 -0
  16. package/dist/7821.js.map +1 -0
  17. package/dist/8414.js +1 -0
  18. package/dist/8414.js.map +1 -0
  19. package/dist/8434.js +1 -1
  20. package/dist/8434.js.map +1 -1
  21. package/dist/8882.js +1 -0
  22. package/dist/8882.js.map +1 -0
  23. package/dist/9898.js +1 -0
  24. package/dist/9898.js.map +1 -0
  25. package/dist/9933.js +2 -0
  26. package/dist/{1909.js.LICENSE.txt → 9933.js.LICENSE.txt} +9 -0
  27. package/dist/9933.js.map +1 -0
  28. package/dist/main.js +1 -1
  29. package/dist/main.js.map +1 -1
  30. package/dist/openmrs-esm-patient-registration-app.js +1 -1
  31. package/dist/openmrs-esm-patient-registration-app.js.buildmanifest.json +226 -150
  32. package/dist/openmrs-esm-patient-registration-app.js.map +1 -1
  33. package/dist/routes.json +1 -1
  34. package/package.json +1 -1
  35. package/src/index.ts +9 -0
  36. package/src/patient-registration/client-registry/client-registry-search.component.tsx +233 -0
  37. package/src/patient-registration/client-registry/client-registry.resource.ts +79 -0
  38. package/src/patient-registration/client-registry/map-client-registry-to-form-utils.ts +190 -0
  39. package/src/patient-registration/client-registry-search/client-registry-dependant-details.component.tsx +237 -0
  40. package/src/patient-registration/client-registry-search/client-registry-details.component.tsx +111 -0
  41. package/src/patient-registration/client-registry-search/client-registry-patient-details.component.tsx +234 -0
  42. package/src/patient-registration/client-registry-search/client-registry-search.component.tsx +234 -0
  43. package/src/patient-registration/client-registry-search/client-registry-verification-tag.component.tsx +78 -0
  44. package/src/patient-registration/client-registry-search/client-registry.resource.ts +135 -0
  45. package/src/patient-registration/client-registry-search/client-registry.types.ts +243 -0
  46. package/src/patient-registration/client-registry-search/map-client-registry-to-form-utils.ts +590 -0
  47. package/src/patient-registration/field/field.component.tsx +3 -0
  48. package/src/patient-registration/field/id/id-field.component.tsx +1 -1
  49. package/src/patient-registration/field/id/id-field.test.tsx +1 -1
  50. package/src/patient-registration/form-manager.test.ts +1 -0
  51. package/src/patient-registration/patient-registration.component.tsx +25 -9
  52. package/src/patient-registration/patient-registration.resource.ts +4 -3
  53. package/src/patient-registration/patient-registration.scss +4 -0
  54. package/src/routes.json +12 -1
  55. package/src/widgets/client-registry-verification.modal.tsx +27 -0
  56. package/translations/en.json +4 -0
  57. package/dist/1909.js +0 -2
  58. package/dist/1909.js.map +0 -1
  59. package/dist/320.js +0 -2
  60. package/dist/320.js.LICENSE.txt +0 -8
  61. package/dist/320.js.map +0 -1
  62. package/dist/3474.js +0 -2
  63. package/dist/3474.js.LICENSE.txt +0 -8
  64. package/dist/3474.js.map +0 -1
  65. package/dist/627.js +0 -1
  66. package/dist/627.js.map +0 -1
  67. package/dist/7071.js +0 -1
  68. package/dist/7071.js.map +0 -1
  69. package/dist/729.js +0 -2
  70. package/dist/729.js.map +0 -1
  71. /package/dist/{729.js.LICENSE.txt → 7125.js.LICENSE.txt} +0 -0
package/dist/routes.json CHANGED
@@ -1 +1 @@
1
- {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":">=2.2.0"},"pages":[{"component":"root","route":"patient-registration","online":true,"offline":true},{"component":"editPatient","routeRegex":"patient\\/([a-zA-Z0-9\\-]+)\\/edit","online":true,"offline":true}],"extensions":[{"component":"addPatientLink","name":"add-patient-action","slot":"top-nav-actions-slot","online":true,"offline":true,"order":30},{"component":"patientPhotoExtension","name":"patient-photo-widget","slot":"patient-photo-slot","online":true,"offline":true},{"component":"editPatientDetailsButton","name":"edit-patient-details-button","slot":"patient-actions-slot","online":true,"offline":true},{"component":"editPatientDetailsButton","name":"edit-patient-details-button","slot":"patient-search-actions-slot","online":true,"offline":true}],"modals":[{"name":"cancel-patient-edit-modal","component":"cancelPatientEditModal"},{"name":"delete-identifier-confirmation-modal","component":"deleteIdentifierConfirmationModal"}],"version":"9.2.0-next.14"}
1
+ {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":">=2.2.0"},"pages":[{"component":"root","route":"patient-registration","online":true,"offline":true},{"component":"editPatient","routeRegex":"patient\\/([a-zA-Z0-9\\-]+)\\/edit","online":true,"offline":true}],"extensions":[{"component":"addPatientLink","name":"add-patient-action","slot":"top-nav-actions-slot","online":true,"offline":true,"order":30},{"component":"patientPhotoExtension","name":"patient-photo-widget","slot":"patient-photo-slot","online":true,"offline":true},{"component":"editPatientDetailsButton","name":"edit-patient-details-button","slot":"patient-actions-slot","online":true,"offline":true},{"component":"editPatientDetailsButton","name":"edit-patient-details-button","slot":"patient-search-actions-slot","online":true,"offline":true},{"component":"clientRegistryVerificationTag","name":"client-registry-verification-tag","slot":"patient-banner-tags-slot","online":true,"offline":true}],"modals":[{"name":"cancel-patient-edit-modal","component":"cancelPatientEditModal"},{"name":"delete-identifier-confirmation-modal","component":"deleteIdentifierConfirmationModal"},{"name":"client-registry-verification-modal","component":"clientRegistryVerificationModal"}],"version":"9.2.0-next.16"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ampath/esm-patient-registration-app",
3
- "version": "9.2.0-next.14",
3
+ "version": "9.2.0-next.16",
4
4
  "description": "Patient registration microfrontend for O3",
5
5
  "browser": "dist/openmrs-esm-patient-registration-app.js",
6
6
  "main": "src/index.ts",
package/src/index.ts CHANGED
@@ -45,6 +45,10 @@ export const editPatient = getAsyncLifecycle(() => import('./root.component'), o
45
45
  export const addPatientLink = getSyncLifecycle(addPatientLinkComponent, options);
46
46
 
47
47
  export const cancelPatientEditModal = getAsyncLifecycle(() => import('./widgets/cancel-patient-edit.modal'), options);
48
+ export const clientRegistryVerificationModal = getAsyncLifecycle(
49
+ () => import('./widgets/client-registry-verification.modal'),
50
+ options,
51
+ );
48
52
 
49
53
  export const patientPhotoExtension = getAsyncLifecycle(() => import('./patient-photo.extension'), options);
50
54
 
@@ -57,3 +61,8 @@ export const deleteIdentifierConfirmationModal = getAsyncLifecycle(
57
61
  () => import('./widgets/delete-identifier-confirmation.modal'),
58
62
  options,
59
63
  );
64
+
65
+ export const clientRegistryVerificationTag = getAsyncLifecycle(
66
+ () => import('./patient-registration/client-registry-search/client-registry-verification-tag.component'),
67
+ options,
68
+ );
@@ -0,0 +1,233 @@
1
+ import React, { useState } from 'react';
2
+ import { Button, TextInput, InlineLoading, InlineNotification, Dropdown } from '@carbon/react';
3
+ import { showSnackbar, useSession } from '@openmrs/esm-framework';
4
+ import { useFormikContext } from 'formik';
5
+ import styles from '../patient-registration.scss';
6
+ import { requestCustomOtp, validateCustomOtp, fetchClientRegistryData } from './client-registry.resource';
7
+ import { applyClientRegistryMapping } from './map-client-registry-to-form-utils';
8
+
9
+ export interface ClientRegistryLookupSectionProps {
10
+ onClientVerified?: () => void;
11
+ }
12
+
13
+ export type IdentifierType = 'National ID' | 'Alien ID' | 'Passport' | 'Mandate Number' | 'Refugee ID';
14
+
15
+ export const IDENTIFIER_TYPES: IdentifierType[] = [
16
+ 'National ID',
17
+ 'Alien ID',
18
+ 'Passport',
19
+ 'Mandate Number',
20
+ 'Refugee ID',
21
+ ];
22
+
23
+ const ClientRegistryLookupSection: React.FC<ClientRegistryLookupSectionProps> = ({ onClientVerified }) => {
24
+ const { setFieldValue } = useFormikContext<any>();
25
+ const [identifierType, setIdentifierType] = useState<IdentifierType>('National ID');
26
+ const [identifierValue, setIdentifierValue] = useState('');
27
+ const [otp, setOtp] = useState('');
28
+ const [otpSent, setOtpSent] = useState(false);
29
+ const [otpVerified, setOtpVerified] = useState(false);
30
+ const [loading, setLoading] = useState(false);
31
+ const [sessionId, setSessionId] = useState('');
32
+ const [error, setError] = useState<string>('');
33
+ const { sessionLocation } = useSession();
34
+ const locationUuid = sessionLocation?.uuid;
35
+
36
+ async function withTimeout<T>(promise: Promise<T>, ms = 10000): Promise<T> {
37
+ const controller = new AbortController();
38
+ const timeout = setTimeout(() => controller.abort(), ms);
39
+ try {
40
+ const response = await promise;
41
+ return response;
42
+ } catch (err: any) {
43
+ if (err.name === 'AbortError') throw new Error('Request timeout');
44
+ throw err;
45
+ } finally {
46
+ clearTimeout(timeout);
47
+ }
48
+ }
49
+
50
+ const handleFetchCR = async () => {
51
+ setLoading(true);
52
+ setError('');
53
+
54
+ try {
55
+ const payload = {
56
+ identificationNumber: identifierValue,
57
+ identificationType: identifierType,
58
+ locationUuid,
59
+ };
60
+
61
+ const result = await withTimeout(fetchClientRegistryData(payload));
62
+ const patients = Array.isArray(result) ? result : [];
63
+
64
+ if (patients.length === 0) throw new Error('No matching patient found in Client Registry.');
65
+
66
+ const patient = patients[0];
67
+ applyClientRegistryMapping(patient, setFieldValue);
68
+
69
+ showSnackbar({
70
+ kind: 'success',
71
+ title: 'Client Data Loaded',
72
+ subtitle: `Patient ${patient.first_name} ${patient.last_name} fetched successfully.`,
73
+ });
74
+ } catch (err: any) {
75
+ const errorMessage = err.message || 'Failed to fetch client data';
76
+ setError(errorMessage);
77
+ showSnackbar({
78
+ kind: 'error',
79
+ title: 'Fetch Failed',
80
+ subtitle: errorMessage,
81
+ });
82
+ } finally {
83
+ setLoading(false);
84
+ }
85
+ };
86
+
87
+ const handleSendOtp = async () => {
88
+ if (!identifierValue.trim()) {
89
+ setError('Please enter a valid ID value');
90
+ return;
91
+ }
92
+
93
+ setLoading(true);
94
+ setError('');
95
+
96
+ try {
97
+ const payload = {
98
+ identificationNumber: identifierValue,
99
+ identificationType: identifierType,
100
+ locationUuid,
101
+ };
102
+
103
+ const response = await withTimeout(requestCustomOtp(payload));
104
+ setSessionId(response.sessionId);
105
+ setOtpSent(true);
106
+
107
+ showSnackbar({
108
+ kind: 'success',
109
+ title: 'OTP sent successfully',
110
+ subtitle: `A code was sent to ${response.maskedPhone}`,
111
+ });
112
+ } catch (err: any) {
113
+ const errorMessage = err.message || 'Failed to send OTP';
114
+ setError(errorMessage);
115
+ showSnackbar({
116
+ kind: 'error',
117
+ title: 'Error sending OTP',
118
+ subtitle: errorMessage,
119
+ });
120
+ } finally {
121
+ setLoading(false);
122
+ }
123
+ };
124
+
125
+ const handleVerifyOtp = async () => {
126
+ if (!otp.trim()) {
127
+ setError('Please enter the OTP code');
128
+ return;
129
+ }
130
+
131
+ setLoading(true);
132
+ setError('');
133
+
134
+ try {
135
+ const payload = { sessionId, otp, locationUuid };
136
+ await withTimeout(validateCustomOtp(payload));
137
+
138
+ setOtpVerified(true);
139
+ onClientVerified?.();
140
+
141
+ showSnackbar({
142
+ kind: 'success',
143
+ title: 'OTP Verified',
144
+ subtitle: 'You can now fetch data from Client Registry.',
145
+ });
146
+ } catch (err: any) {
147
+ const errorMessage = err.message || 'OTP verification failed';
148
+ setError(errorMessage);
149
+ showSnackbar({
150
+ kind: 'error',
151
+ title: 'OTP Verification Failed',
152
+ subtitle: errorMessage,
153
+ });
154
+ } finally {
155
+ setLoading(false);
156
+ }
157
+ };
158
+
159
+ return (
160
+ <div className={styles.section}>
161
+ <h4 className={styles.sectionTitle}>Client Registry Verification</h4>
162
+
163
+ {error && (
164
+ <div className={styles.notificationSpacing}>
165
+ <InlineNotification title="Error" subtitle={error} kind="error" lowContrast />
166
+ </div>
167
+ )}
168
+
169
+ <div className={styles.fieldGroup}>
170
+ <Dropdown
171
+ id="identifier-type-dropdown"
172
+ label="Identifier Type"
173
+ titleText="Select Identifier Type"
174
+ items={IDENTIFIER_TYPES}
175
+ selectedItem={identifierType}
176
+ onChange={({ selectedItem }) => setIdentifierType(selectedItem as IdentifierType)}
177
+ disabled={otpSent}
178
+ />
179
+ </div>
180
+
181
+ <div className={styles.fieldGroup}>
182
+ <TextInput
183
+ id="identifier-value"
184
+ labelText={`${identifierType} Value`}
185
+ value={identifierValue}
186
+ onChange={(e) => setIdentifierValue(e.target.value)}
187
+ disabled={otpSent}
188
+ placeholder={`Enter ${identifierType.toLowerCase()} value`}
189
+ />
190
+ </div>
191
+
192
+ <div style={{ marginTop: '0.75rem' }}>
193
+ {!otpSent ? (
194
+ <Button kind="secondary" onClick={handleSendOtp} disabled={loading}>
195
+ {loading ? <InlineLoading description="Sending..." /> : 'Send OTP'}
196
+ </Button>
197
+ ) : (
198
+ <>
199
+ <div style={{ marginTop: '0.75rem' }}>
200
+ <TextInput
201
+ id="otp-input"
202
+ labelText="Enter OTP"
203
+ value={otp}
204
+ onChange={(e) => setOtp(e.target.value)}
205
+ disabled={otpVerified}
206
+ placeholder="Enter the code sent to your phone"
207
+ />
208
+ </div>
209
+
210
+ <div style={{ marginTop: '0.5rem', display: 'flex', gap: '0.5rem' }}>
211
+ {!otpVerified ? (
212
+ <Button size="sm" kind="secondary" onClick={handleVerifyOtp} disabled={loading}>
213
+ {loading ? <InlineLoading description="Verifying..." /> : 'Verify OTP'}
214
+ </Button>
215
+ ) : (
216
+ <Button kind="primary" onClick={handleFetchCR} disabled={loading}>
217
+ {loading ? <InlineLoading description="Fetching..." /> : 'Fetch Client Registry Data'}
218
+ </Button>
219
+ )}
220
+ {!otpVerified && (
221
+ <Button size="sm" kind="tertiary" onClick={() => setOtpSent(false)}>
222
+ Change ID
223
+ </Button>
224
+ )}
225
+ </div>
226
+ </>
227
+ )}
228
+ </div>
229
+ </div>
230
+ );
231
+ };
232
+
233
+ export default ClientRegistryLookupSection;
@@ -0,0 +1,79 @@
1
+ const HIE_BASE_URL = 'https://ngx.ampath.or.ke/hie';
2
+
3
+ export type RequestCustomOtpDto = {
4
+ identificationNumber: string | number;
5
+ identificationType: string;
6
+ locationUuid: string;
7
+ };
8
+
9
+ export interface RequestCustomOtpResponse {
10
+ message: string;
11
+ sessionId: string;
12
+ maskedPhone: string;
13
+ }
14
+
15
+ export interface ValidateHieCustomOtpDto {
16
+ sessionId: string;
17
+ otp: number | string;
18
+ locationUuid: string;
19
+ }
20
+
21
+ export interface ValidateCustomOtpResponse {
22
+ message: string;
23
+ isValid?: boolean;
24
+ }
25
+
26
+ export type ClientRegistrySearchRequest = {
27
+ identificationNumber: string | number;
28
+ identificationType: string;
29
+ locationUuid: string;
30
+ };
31
+
32
+ export type ClientRegistrySearchResponse = any[];
33
+
34
+ async function postJson<T>(url: string, payload: unknown): Promise<T> {
35
+ const response = await fetch(url, {
36
+ method: 'POST',
37
+ headers: { 'Content-Type': 'application/json' },
38
+ body: JSON.stringify(payload),
39
+ });
40
+
41
+ if (!response.ok) {
42
+ const errorText = await response.text();
43
+ throw new Error(`Request failed with ${response.status}: ${errorText}`);
44
+ }
45
+
46
+ return response.json() as Promise<T>;
47
+ }
48
+
49
+ export async function requestCustomOtp(payload: RequestCustomOtpDto): Promise<RequestCustomOtpResponse> {
50
+ const url = `${HIE_BASE_URL}/client/send-custom-otp`;
51
+ const formattedPayload = {
52
+ identificationNumber: payload.identificationNumber,
53
+ identificationType: payload.identificationType,
54
+ locationUuid: payload.locationUuid,
55
+ };
56
+ return postJson<RequestCustomOtpResponse>(url, formattedPayload);
57
+ }
58
+
59
+ export async function validateCustomOtp(payload: ValidateHieCustomOtpDto): Promise<ValidateCustomOtpResponse> {
60
+ const url = `${HIE_BASE_URL}/client/validate-custom-otp`;
61
+ const formattedPayload = {
62
+ sessionId: payload.sessionId,
63
+ otp: payload.otp,
64
+ locationUuid: payload.locationUuid,
65
+ };
66
+ return postJson<ValidateCustomOtpResponse>(url, formattedPayload);
67
+ }
68
+
69
+ export async function fetchClientRegistryData(
70
+ payload: ClientRegistrySearchRequest,
71
+ ): Promise<ClientRegistrySearchResponse> {
72
+ const url = `${HIE_BASE_URL}/client/search`;
73
+ const formattedPayload = {
74
+ identificationNumber: payload.identificationNumber,
75
+ identificationType: payload.identificationType,
76
+ locationUuid: payload.locationUuid,
77
+ };
78
+ return postJson<ClientRegistrySearchResponse>(url, formattedPayload);
79
+ }
@@ -0,0 +1,190 @@
1
+ interface PatientData {
2
+ [key: string]: any;
3
+ }
4
+
5
+ interface Dependant {
6
+ relationship: string;
7
+ result: Array<{
8
+ first_name: string;
9
+ middle_name: string;
10
+ last_name: string;
11
+ gender: string;
12
+ date_of_birth: string;
13
+ identification_number: string;
14
+ identification_type: string;
15
+ }>;
16
+ }
17
+
18
+ interface AlternativeContact {
19
+ contact_type: string;
20
+ contact_id: string;
21
+ contact_name: string;
22
+ relationship: string;
23
+ remarks: string;
24
+ }
25
+
26
+ const fieldMapping: Record<string, string | { path: string; transform?: (value: any) => any }> = {
27
+ givenName: 'first_name',
28
+ familyName: 'last_name',
29
+ middleName: 'middle_name',
30
+ birthdate: 'date_of_birth',
31
+ phone: 'phone',
32
+ email: 'email',
33
+ identificationNumber: 'identification_number',
34
+ identificationType: 'identification_type',
35
+ county: 'county',
36
+ subCounty: 'sub_county',
37
+ ward: 'ward',
38
+ village: 'village_estate',
39
+ postalAddress: 'postal_address',
40
+ address: 'address',
41
+ gender: {
42
+ path: 'gender',
43
+ transform: (value) => {
44
+ if (!value) return '';
45
+ const genderMap: Record<string, string> = {
46
+ male: 'male',
47
+ m: 'male',
48
+ female: 'female',
49
+ f: 'female',
50
+ other: 'other',
51
+ unknown: 'unknown',
52
+ };
53
+ return genderMap[value.toLowerCase()] || value.toLowerCase();
54
+ },
55
+ },
56
+ 'academicOccupation.highestLevelEducation': {
57
+ path: 'employment_type',
58
+ transform: (value) => value || '',
59
+ },
60
+
61
+ // Occupation field - also using employment_type (you might want to separate these)
62
+ 'academicOccupation.occupation': {
63
+ path: 'employment_type',
64
+ transform: (value) => value || '',
65
+ },
66
+
67
+ // Civil status/marital status
68
+ civilStatus: {
69
+ path: 'civil_status',
70
+ transform: (value) => value || '',
71
+ },
72
+ };
73
+
74
+ function mapNextOfKin(patient: PatientData, setFieldValue: (field: string, value: any) => void) {
75
+ const alternativeContacts: AlternativeContact[] = patient.alternative_contacts || [];
76
+
77
+ // Find spouse as next of kin (highest priority)
78
+ const spouseContact = alternativeContacts.find((contact) => contact.relationship.toLowerCase() === 'spouse');
79
+
80
+ // If no spouse, find any next of kin
81
+ const nextOfKinContact =
82
+ spouseContact ||
83
+ alternativeContacts.find(
84
+ (contact) =>
85
+ contact.remarks.toLowerCase().includes('next of kin') ||
86
+ contact.relationship.toLowerCase().includes('next of kin'),
87
+ );
88
+
89
+ if (nextOfKinContact) {
90
+ setFieldValue('nextOfKin.nextOfKinName', nextOfKinContact.contact_name);
91
+ setFieldValue('nextOfKin.nextOfKinRelationship', nextOfKinContact.relationship);
92
+ setFieldValue('nextOfKin.nextOfKinPhoneNumber', nextOfKinContact.contact_id);
93
+ setFieldValue('nextOfKin.nextOfKinResidence', patient.village_estate || patient.county || '');
94
+ } else {
95
+ setFieldValue('nextOfKin.nextOfKinName', '');
96
+ setFieldValue('nextOfKin.nextOfKinRelationship', '');
97
+ setFieldValue('nextOfKin.nextOfKinPhoneNumber', '');
98
+ setFieldValue('nextOfKin.nextOfKinResidence', '');
99
+ }
100
+ }
101
+
102
+ function mapRelationships(patient: PatientData, setFieldValue: (field: string, value: any) => void) {
103
+ const dependants: Dependant[] = patient.dependants || [];
104
+ const relationships: any[] = [];
105
+
106
+ dependants.forEach((dependant, index) => {
107
+ if (dependant.result && dependant.result.length > 0) {
108
+ const person = dependant.result[0];
109
+ relationships.push({
110
+ relationshipType: dependant.relationship, // "Child"
111
+ relatedPersonName: `${person.first_name} ${person.middle_name || ''} ${person.last_name}`.trim(),
112
+ relatedPersonUuid: '', // Not available in CR
113
+ relationshipTypeUuid: '', // Will need to map to OpenMRS relationship type UUID
114
+ // Additional fields that might be needed
115
+ relativeName: `${person.first_name} ${person.middle_name || ''} ${person.last_name}`.trim(),
116
+ relativePhone: '', // Not available in dependants
117
+ relationship: dependant.relationship,
118
+ birthdate: person.date_of_birth,
119
+ gender: person.gender,
120
+ identifier: person.identification_number,
121
+ });
122
+ }
123
+ });
124
+ setFieldValue('relationships', relationships);
125
+ }
126
+
127
+ function mapAddresses(patient: PatientData, setFieldValue: (field: string, value: any) => void) {
128
+ // county
129
+ if (patient['county']) {
130
+ setFieldValue('address.countyDistrict', patient['county']);
131
+ }
132
+ //subcounty
133
+ if (patient['sub_county']) {
134
+ setFieldValue('address.stateProvince', patient['sub_county']);
135
+ }
136
+ if (patient['ward']) {
137
+ //ward
138
+ setFieldValue('address.address4', patient['ward']);
139
+ }
140
+ }
141
+
142
+ function mapContactDetails(patient: PatientData, setFieldValue: (field: string, value: any) => void) {
143
+ if (patient['email']) {
144
+ setFieldValue('attributes.2f65dbcb-3e58-45a3-8be7-fd1dc9aa0faa', patient['email']);
145
+ }
146
+ if (patient['phone']) {
147
+ setFieldValue('attributes.72a759a8-1359-11df-a1f1-0026b9348838', patient['phone']);
148
+ }
149
+ }
150
+
151
+ function mapIdentifiers(patient: PatientData, setFieldValue: (field: string, value: any) => void) {
152
+ if (patient['id']) {
153
+ const crIdentifier = {
154
+ identifierTypeUuid: 'e88dc246-3614-4ee3-8141-1f2a83054e72',
155
+ initialValue: patient['id'],
156
+ identifierValue: patient['id'],
157
+ identifierName: 'CR',
158
+ selectedSource: null,
159
+ autoGeneration: false,
160
+ preferred: false,
161
+ required: true,
162
+ };
163
+ setFieldValue('identifiers.cr', crIdentifier);
164
+ }
165
+ }
166
+
167
+ export function applyClientRegistryMapping(patient: PatientData, setFieldValue: (field: string, value: any) => void) {
168
+ Object.entries(fieldMapping).forEach(([formField, mapping]) => {
169
+ let crField: string;
170
+ let transformFn: (value: any) => any = (v) => v;
171
+
172
+ if (typeof mapping === 'string') {
173
+ crField = mapping;
174
+ } else {
175
+ crField = mapping.path;
176
+ transformFn = mapping.transform || transformFn;
177
+ }
178
+
179
+ if (crField && patient[crField] !== undefined && patient[crField] !== null && patient[crField] !== '') {
180
+ const value = transformFn(patient[crField]);
181
+ setFieldValue(formField, value);
182
+ }
183
+ });
184
+
185
+ mapNextOfKin(patient, setFieldValue);
186
+ mapRelationships(patient, setFieldValue);
187
+ mapAddresses(patient, setFieldValue);
188
+ mapContactDetails(patient, setFieldValue);
189
+ mapIdentifiers(patient, setFieldValue);
190
+ }