@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.
- package/dist/4300.js +1 -1
- package/dist/4395.js +1 -0
- package/dist/4395.js.map +1 -0
- package/dist/5239.js +1 -1
- package/dist/5239.js.LICENSE.txt +10 -0
- package/dist/5239.js.map +1 -1
- package/dist/6276.js +1 -1
- package/dist/6276.js.map +1 -1
- package/dist/6687.js +1 -0
- package/dist/6687.js.map +1 -0
- package/dist/6996.js +1 -0
- package/dist/6996.js.map +1 -0
- package/dist/7125.js +2 -0
- package/dist/7125.js.map +1 -0
- package/dist/7821.js +1 -0
- package/dist/7821.js.map +1 -0
- package/dist/8414.js +1 -0
- package/dist/8414.js.map +1 -0
- package/dist/8434.js +1 -1
- package/dist/8434.js.map +1 -1
- package/dist/8882.js +1 -0
- package/dist/8882.js.map +1 -0
- package/dist/9898.js +1 -0
- package/dist/9898.js.map +1 -0
- package/dist/9933.js +2 -0
- package/dist/{1909.js.LICENSE.txt → 9933.js.LICENSE.txt} +9 -0
- package/dist/9933.js.map +1 -0
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-patient-registration-app.js +1 -1
- package/dist/openmrs-esm-patient-registration-app.js.buildmanifest.json +226 -150
- package/dist/openmrs-esm-patient-registration-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/index.ts +9 -0
- package/src/patient-registration/client-registry/client-registry-search.component.tsx +233 -0
- package/src/patient-registration/client-registry/client-registry.resource.ts +79 -0
- package/src/patient-registration/client-registry/map-client-registry-to-form-utils.ts +190 -0
- package/src/patient-registration/client-registry-search/client-registry-dependant-details.component.tsx +237 -0
- package/src/patient-registration/client-registry-search/client-registry-details.component.tsx +111 -0
- package/src/patient-registration/client-registry-search/client-registry-patient-details.component.tsx +234 -0
- package/src/patient-registration/client-registry-search/client-registry-search.component.tsx +234 -0
- package/src/patient-registration/client-registry-search/client-registry-verification-tag.component.tsx +78 -0
- package/src/patient-registration/client-registry-search/client-registry.resource.ts +135 -0
- package/src/patient-registration/client-registry-search/client-registry.types.ts +243 -0
- package/src/patient-registration/client-registry-search/map-client-registry-to-form-utils.ts +590 -0
- package/src/patient-registration/field/field.component.tsx +3 -0
- package/src/patient-registration/field/id/id-field.component.tsx +1 -1
- package/src/patient-registration/field/id/id-field.test.tsx +1 -1
- package/src/patient-registration/form-manager.test.ts +1 -0
- package/src/patient-registration/patient-registration.component.tsx +25 -9
- package/src/patient-registration/patient-registration.resource.ts +4 -3
- package/src/patient-registration/patient-registration.scss +4 -0
- package/src/routes.json +12 -1
- package/src/widgets/client-registry-verification.modal.tsx +27 -0
- package/translations/en.json +4 -0
- package/dist/1909.js +0 -2
- package/dist/1909.js.map +0 -1
- package/dist/320.js +0 -2
- package/dist/320.js.LICENSE.txt +0 -8
- package/dist/320.js.map +0 -1
- package/dist/3474.js +0 -2
- package/dist/3474.js.LICENSE.txt +0 -8
- package/dist/3474.js.map +0 -1
- package/dist/627.js +0 -1
- package/dist/627.js.map +0 -1
- package/dist/7071.js +0 -1
- package/dist/7071.js.map +0 -1
- package/dist/729.js +0 -2
- package/dist/729.js.map +0 -1
- /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.
|
|
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
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
|
+
}
|