@kenyaemr/esm-pcs-app 5.4.5-pre.1092
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/.turbo/turbo-build.log +6 -0
- package/dist/1072.js +1 -0
- package/dist/1072.js.map +1 -0
- package/dist/1102.js +1 -0
- package/dist/1102.js.map +1 -0
- package/dist/1105.js +1 -0
- package/dist/1105.js.map +1 -0
- package/dist/1148.js +1 -0
- package/dist/1148.js.map +1 -0
- package/dist/1278.js +1 -0
- package/dist/1278.js.map +1 -0
- package/dist/1442.js +1 -0
- package/dist/1442.js.map +1 -0
- package/dist/15.js +1 -0
- package/dist/15.js.map +1 -0
- package/dist/1620.js +1 -0
- package/dist/1620.js.map +1 -0
- package/dist/1789.js +1 -0
- package/dist/1789.js.map +1 -0
- package/dist/1818.js +1 -0
- package/dist/1818.js.map +1 -0
- package/dist/1831.js +1 -0
- package/dist/1831.js.map +1 -0
- package/dist/1844.js +1 -0
- package/dist/1844.js.map +1 -0
- package/dist/1854.js +1 -0
- package/dist/1854.js.map +1 -0
- package/dist/1946.js +1 -0
- package/dist/1946.js.map +1 -0
- package/dist/2007.js +1 -0
- package/dist/2007.js.map +1 -0
- package/dist/2115.js +1 -0
- package/dist/2115.js.map +1 -0
- package/dist/2152.js +1 -0
- package/dist/2152.js.map +1 -0
- package/dist/2202.js +1 -0
- package/dist/2202.js.map +1 -0
- package/dist/2394.js +1 -0
- package/dist/2394.js.map +1 -0
- package/dist/2530.js +1 -0
- package/dist/2530.js.map +1 -0
- package/dist/2534.js +1 -0
- package/dist/2534.js.map +1 -0
- package/dist/2537.js +1 -0
- package/dist/2537.js.map +1 -0
- package/dist/2556.js +1 -0
- package/dist/2556.js.map +1 -0
- package/dist/2648.js +1 -0
- package/dist/2648.js.map +1 -0
- package/dist/2795.js +1 -0
- package/dist/2795.js.map +1 -0
- package/dist/2827.js +1 -0
- package/dist/2827.js.map +1 -0
- package/dist/2916.js +1 -0
- package/dist/2916.js.map +1 -0
- package/dist/2924.js +1 -0
- package/dist/2924.js.map +1 -0
- package/dist/2947.js +1 -0
- package/dist/2947.js.map +1 -0
- package/dist/3022.js +1 -0
- package/dist/3022.js.map +1 -0
- package/dist/3285.js +1 -0
- package/dist/3285.js.map +1 -0
- package/dist/3335.js +1 -0
- package/dist/3335.js.map +1 -0
- package/dist/3378.js +1 -0
- package/dist/3378.js.map +1 -0
- package/dist/3381.js +1 -0
- package/dist/3381.js.map +1 -0
- package/dist/3504.js +1 -0
- package/dist/3504.js.map +1 -0
- package/dist/3530.js +1 -0
- package/dist/3530.js.map +1 -0
- package/dist/356.js +1 -0
- package/dist/356.js.map +1 -0
- package/dist/3569.js +1 -0
- package/dist/3569.js.map +1 -0
- package/dist/3723.js +1 -0
- package/dist/3723.js.map +1 -0
- package/dist/3745.js +1 -0
- package/dist/3745.js.map +1 -0
- package/dist/3813.js +1 -0
- package/dist/3813.js.map +1 -0
- package/dist/393.js +6 -0
- package/dist/393.js.map +1 -0
- package/dist/3953.js +1 -0
- package/dist/3953.js.map +1 -0
- package/dist/3989.js +1 -0
- package/dist/3989.js.map +1 -0
- package/dist/412.js +1 -0
- package/dist/412.js.map +1 -0
- package/dist/4178.js +1 -0
- package/dist/4178.js.map +1 -0
- package/dist/4413.js +1 -0
- package/dist/4413.js.map +1 -0
- package/dist/4487.js +11 -0
- package/dist/4487.js.map +1 -0
- package/dist/4537.js +1 -0
- package/dist/4537.js.map +1 -0
- package/dist/4588.js +1 -0
- package/dist/4588.js.map +1 -0
- package/dist/4637.js +1 -0
- package/dist/4637.js.map +1 -0
- package/dist/4650.js +1 -0
- package/dist/4650.js.map +1 -0
- package/dist/466.js +1 -0
- package/dist/466.js.map +1 -0
- package/dist/4817.js +1 -0
- package/dist/4817.js.map +1 -0
- package/dist/4818.js +1 -0
- package/dist/4818.js.map +1 -0
- package/dist/500.js +1 -0
- package/dist/500.js.map +1 -0
- package/dist/5086.js +1 -0
- package/dist/5086.js.map +1 -0
- package/dist/5117.js +1 -0
- package/dist/5117.js.map +1 -0
- package/dist/5279.js +1 -0
- package/dist/5279.js.map +1 -0
- package/dist/5366.js +1 -0
- package/dist/5366.js.map +1 -0
- package/dist/5598.js +1 -0
- package/dist/5598.js.map +1 -0
- package/dist/5676.js +1 -0
- package/dist/5676.js.map +1 -0
- package/dist/5731.js +5 -0
- package/dist/5731.js.map +1 -0
- package/dist/5792.js +1 -0
- package/dist/5792.js.map +1 -0
- package/dist/5825.js +1 -0
- package/dist/5825.js.map +1 -0
- package/dist/5932.js +1 -0
- package/dist/5932.js.map +1 -0
- package/dist/5941.js +1 -0
- package/dist/5941.js.map +1 -0
- package/dist/5967.js +1 -0
- package/dist/5967.js.map +1 -0
- package/dist/6104.js +1 -0
- package/dist/6104.js.map +1 -0
- package/dist/624.js +1 -0
- package/dist/624.js.map +1 -0
- package/dist/6259.js +1 -0
- package/dist/6259.js.map +1 -0
- package/dist/6273.js +1 -0
- package/dist/6273.js.map +1 -0
- package/dist/6337.js +1 -0
- package/dist/6337.js.map +1 -0
- package/dist/6427.js +1 -0
- package/dist/6427.js.map +1 -0
- package/dist/6478.js +7 -0
- package/dist/6478.js.map +1 -0
- package/dist/6508.js +1 -0
- package/dist/6508.js.map +1 -0
- package/dist/6561.js +1 -0
- package/dist/6561.js.map +1 -0
- package/dist/6566.js +1 -0
- package/dist/6566.js.map +1 -0
- package/dist/6567.js +1 -0
- package/dist/6567.js.map +1 -0
- package/dist/6634.js +1 -0
- package/dist/6634.js.map +1 -0
- package/dist/7022.js +1 -0
- package/dist/7022.js.map +1 -0
- package/dist/7085.js +1 -0
- package/dist/7085.js.map +1 -0
- package/dist/7209.js +1 -0
- package/dist/7209.js.map +1 -0
- package/dist/7291.js +1 -0
- package/dist/7291.js.map +1 -0
- package/dist/7360.js +1 -0
- package/dist/7360.js.map +1 -0
- package/dist/7453.js +1 -0
- package/dist/7453.js.map +1 -0
- package/dist/7454.js +1 -0
- package/dist/7454.js.map +1 -0
- package/dist/7478.js +1 -0
- package/dist/7478.js.map +1 -0
- package/dist/7778.js +1 -0
- package/dist/7778.js.map +1 -0
- package/dist/7782.js +1 -0
- package/dist/7782.js.map +1 -0
- package/dist/8028.js +1 -0
- package/dist/8028.js.map +1 -0
- package/dist/8031.js +1 -0
- package/dist/8031.js.map +1 -0
- package/dist/8196.js +1 -0
- package/dist/8196.js.map +1 -0
- package/dist/820.js +1 -0
- package/dist/820.js.map +1 -0
- package/dist/8240.js +1 -0
- package/dist/8240.js.map +1 -0
- package/dist/8472.js +1 -0
- package/dist/8472.js.map +1 -0
- package/dist/8607.js +1 -0
- package/dist/8607.js.map +1 -0
- package/dist/8793.js +1 -0
- package/dist/8793.js.map +1 -0
- package/dist/8924.js +1 -0
- package/dist/8924.js.map +1 -0
- package/dist/8990.js +1 -0
- package/dist/8990.js.map +1 -0
- package/dist/9060.js +1 -0
- package/dist/9060.js.map +1 -0
- package/dist/9061.js +1 -0
- package/dist/9061.js.map +1 -0
- package/dist/9087.js +1 -0
- package/dist/9087.js.map +1 -0
- package/dist/9165.js +1 -0
- package/dist/9165.js.map +1 -0
- package/dist/9274.js +1 -0
- package/dist/9274.js.map +1 -0
- package/dist/9425.js +1 -0
- package/dist/9520.js +1 -0
- package/dist/9520.js.map +1 -0
- package/dist/9547.js +1 -0
- package/dist/9547.js.map +1 -0
- package/dist/9612.js +1 -0
- package/dist/9612.js.map +1 -0
- package/dist/9771.js +1 -0
- package/dist/9771.js.map +1 -0
- package/dist/9774.js +1 -0
- package/dist/9774.js.map +1 -0
- package/dist/9806.js +1 -0
- package/dist/9806.js.map +1 -0
- package/dist/9885.js +1 -0
- package/dist/9885.js.map +1 -0
- package/dist/992.js +1 -0
- package/dist/992.js.map +1 -0
- package/dist/9941.js +1 -0
- package/dist/9941.js.map +1 -0
- package/dist/997.js +1 -0
- package/dist/997.js.map +1 -0
- package/dist/kenyaemr-esm-pcs-app.js +3 -0
- package/dist/kenyaemr-esm-pcs-app.js.buildmanifest.json +3173 -0
- package/dist/kenyaemr-esm-pcs-app.js.map +1 -0
- package/dist/main.js +3 -0
- package/dist/main.js.map +1 -0
- package/dist/routes.json +1 -0
- package/package.json +50 -0
- package/rspack.config.js +1 -0
- package/src/add-dependant/add-dependant.scss +29 -0
- package/src/add-dependant/add-dependant.workspace.test.ts +65 -0
- package/src/add-dependant/add-dependant.workspace.tsx +249 -0
- package/src/config-schema.ts +67 -0
- package/src/declarations.d.ts +2 -0
- package/src/empty-svg.component.tsx +565 -0
- package/src/index.ts +34 -0
- package/src/link-hie-dependant/link-hie-dependant.scss +55 -0
- package/src/link-hie-dependant/link-hie-dependant.workspace.tsx +207 -0
- package/src/linked/dependant-row.component.test.tsx +134 -0
- package/src/linked/dependant-row.component.tsx +122 -0
- package/src/linked/dependants-list.component.tsx +60 -0
- package/src/linked/linked-participant-body.component.tsx +82 -0
- package/src/linked/linked-participant.component.test.tsx +134 -0
- package/src/linked/linked-participant.component.tsx +142 -0
- package/src/linked/participant-details.component.tsx +89 -0
- package/src/linked/use-study-sync-snackbars.ts +41 -0
- package/src/modals/delink-participant.modal.tsx +96 -0
- package/src/modals/link-dependant.modal.tsx +244 -0
- package/src/modals/link-participant.modal.tsx +143 -0
- package/src/modals/link-participant.scss +48 -0
- package/src/pcs.scss +241 -0
- package/src/resources/hie-contacts.ts +61 -0
- package/src/resources/identifiers.ts +52 -0
- package/src/resources/link-dependant.resource.test.ts +445 -0
- package/src/resources/link-dependant.resource.ts +268 -0
- package/src/resources/link-participant.resource.test.ts +336 -0
- package/src/resources/link-participant.resource.ts +452 -0
- package/src/resources/patient-create.resource.ts +296 -0
- package/src/resources/patient-search.resource.ts +106 -0
- package/src/resources/pcs.resource.test.ts +36 -0
- package/src/resources/pcs.resource.ts +182 -0
- package/src/resources/use-identifier-type-uuids.ts +38 -0
- package/src/routes.json +54 -0
- package/src/search/participant-results.component.tsx +96 -0
- package/src/search/participant-tile.component.tsx +133 -0
- package/src/search/pcs-empty-state.component.tsx +31 -0
- package/src/search/pcs-results-skeleton.component.tsx +21 -0
- package/src/search/pcs-search.component.test.tsx +169 -0
- package/src/search/pcs-search.component.tsx +228 -0
- package/src/types/index.ts +140 -0
- package/translations/am.json +120 -0
- package/translations/en.json +120 -0
- package/translations/fr.json +122 -0
- package/translations/sw.json +120 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { getSessionLocation, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
import { differenceInYears, parseISO } from 'date-fns';
|
|
3
|
+
import { generateIdentifier, openmrsId, openmrsIdSource, sanitizeName } from './identifiers';
|
|
4
|
+
import { type IdentifierTypeUuids, type PcsDependantDemographics, type PcsHieDependant } from '../types';
|
|
5
|
+
|
|
6
|
+
interface PatientIdentifier {
|
|
7
|
+
identifier: string;
|
|
8
|
+
identifierType: string;
|
|
9
|
+
location: string;
|
|
10
|
+
preferred: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* True when the dependant is under 18, or their birth date is unknown or unparseable.
|
|
15
|
+
*
|
|
16
|
+
* Load-bearing: minors in the HIE registry are identified by their SHA number, not a national ID.
|
|
17
|
+
* A national ID present on a minor's record belongs to the household head (parent), so attaching
|
|
18
|
+
* it to the child would write the parent's identity onto the child's record.
|
|
19
|
+
*/
|
|
20
|
+
const isMinorDependent = (birthDate?: string): boolean => {
|
|
21
|
+
if (!birthDate || birthDate === 'Unknown') {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
return differenceInYears(new Date(), parseISO(birthDate)) < 18;
|
|
27
|
+
} catch (error) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The identifiers to register a dependant with, taken straight from the flattened HIE contact.
|
|
34
|
+
*
|
|
35
|
+
* Express-workflow reaches the same result by building FHIR `identifiers` extensions and then
|
|
36
|
+
* reading them back out to resolve each code to a type uuid; nothing between those two steps is
|
|
37
|
+
* observable, so this maps the fields directly.
|
|
38
|
+
*/
|
|
39
|
+
export function buildDependantIdentifiers(
|
|
40
|
+
dependant: PcsHieDependant,
|
|
41
|
+
uuids: IdentifierTypeUuids,
|
|
42
|
+
location: string,
|
|
43
|
+
): Array<PatientIdentifier> {
|
|
44
|
+
const identifiers: Array<PatientIdentifier> = [];
|
|
45
|
+
const add = (identifier: string | undefined, identifierType: string | undefined) => {
|
|
46
|
+
if (identifier && identifierType) {
|
|
47
|
+
identifiers.push({ identifier, identifierType, location, preferred: false });
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// `household-number` is deliberately absent: it holds the parent's own CR number.
|
|
52
|
+
if (!isMinorDependent(dependant.birthDate)) {
|
|
53
|
+
add(dependant.nationalId, uuids.nationalIdUUID);
|
|
54
|
+
}
|
|
55
|
+
add(dependant.birthCertificate, uuids.birthCertificateUUID);
|
|
56
|
+
add(dependant.shaNumber, uuids.shaNumberUUID);
|
|
57
|
+
add(dependant.shaIdNumber, uuids.crIdentificationNumberUUID);
|
|
58
|
+
|
|
59
|
+
// The contact's own id IS the dependant's CR number when sha-id-number was not among the
|
|
60
|
+
// extensions, so it stands in rather than being lost.
|
|
61
|
+
if (!dependant.shaIdNumber && String(dependant.id ?? '').startsWith('CR')) {
|
|
62
|
+
add(dependant.id, uuids.crIdentificationNumberUUID);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return identifiers;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Given/middle/family, preferring the HIE contact's own parts over splitting the display name. */
|
|
69
|
+
function splitDependantName(dependant: PcsHieDependant) {
|
|
70
|
+
const parts = (dependant.name ?? '').trim().split(' ').filter(Boolean);
|
|
71
|
+
const given: Array<string> = dependant.contactData?.name?.given ?? parts.slice(0, -1);
|
|
72
|
+
const family = dependant.contactData?.name?.family || (parts.length > 1 ? parts[parts.length - 1] : '');
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
givenName: given[0] || parts[0] || '',
|
|
76
|
+
middleName: given.slice(1).join(' '),
|
|
77
|
+
familyName: family,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface CreatePatientBody {
|
|
82
|
+
person: {
|
|
83
|
+
names: Array<{ preferred: boolean; givenName: string; middleName: string; familyName: string }>;
|
|
84
|
+
gender: string;
|
|
85
|
+
birthdate: string | null;
|
|
86
|
+
birthdateEstimated: boolean;
|
|
87
|
+
attributes: Array<{ attributeType: string; value: string }>;
|
|
88
|
+
addresses: Array<Record<string, string>>;
|
|
89
|
+
};
|
|
90
|
+
identifiers: Array<PatientIdentifier>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const EMPTY_ADDRESS = { address1: '', cityVillage: '', country: '', postalCode: '', stateProvince: '' };
|
|
94
|
+
|
|
95
|
+
/** Posts the registration and returns the created patient. */
|
|
96
|
+
async function postPatient(body: CreatePatientBody) {
|
|
97
|
+
const { data } = await openmrsFetch<{ uuid: string }>(`${restBaseUrl}/patient`, {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
headers: { 'Content-Type': 'application/json' },
|
|
100
|
+
body,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return data;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The OpenMRS ID is minted for every patient and is always the preferred identifier; the HIE
|
|
108
|
+
* identifiers ride alongside it, and are what let a later search find this record again.
|
|
109
|
+
*/
|
|
110
|
+
async function withOpenmrsId(identifiers: Array<PatientIdentifier>, location: string) {
|
|
111
|
+
const generated = await generateIdentifier(openmrsIdSource);
|
|
112
|
+
|
|
113
|
+
if (!generated?.data?.identifier) {
|
|
114
|
+
throw new Error('Failed to generate OpenMRS identifier');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return [
|
|
118
|
+
...identifiers,
|
|
119
|
+
{ identifier: generated.data.identifier, identifierType: openmrsId, location, preferred: true },
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function requireSessionLocation() {
|
|
124
|
+
const location = await getSessionLocation();
|
|
125
|
+
|
|
126
|
+
if (!location?.uuid) {
|
|
127
|
+
throw new Error('Could not determine session location');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return location.uuid;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Registers a dependant the HIE lists under their parent but who has no record here yet. */
|
|
134
|
+
export async function createDependantPatient(
|
|
135
|
+
dependant: PcsHieDependant,
|
|
136
|
+
uuids: IdentifierTypeUuids,
|
|
137
|
+
parentPhoneNumber?: string,
|
|
138
|
+
) {
|
|
139
|
+
const location = await requireSessionLocation();
|
|
140
|
+
const { givenName, middleName, familyName } = splitDependantName(dependant);
|
|
141
|
+
// `getDependentsFromContacts` reports a missing date as 'Unknown', which must not reach the
|
|
142
|
+
// payload as a birthdate — it is what makes `birthdateEstimated` true.
|
|
143
|
+
const birthdate = dependant.birthDate && dependant.birthDate !== 'Unknown' ? dependant.birthDate : null;
|
|
144
|
+
|
|
145
|
+
return postPatient({
|
|
146
|
+
person: {
|
|
147
|
+
names: [
|
|
148
|
+
{
|
|
149
|
+
preferred: true,
|
|
150
|
+
givenName: sanitizeName(givenName) || 'Unknown',
|
|
151
|
+
middleName: sanitizeName(middleName) || '',
|
|
152
|
+
familyName: sanitizeName(familyName) || 'Unknown',
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
gender: dependant.gender?.charAt(0).toUpperCase() || 'U',
|
|
156
|
+
birthdate,
|
|
157
|
+
birthdateEstimated: !birthdate,
|
|
158
|
+
// The parent's number, not the dependant's — a child rarely has one of their own.
|
|
159
|
+
attributes: parentPhoneNumber ? [{ attributeType: uuids.phoneAttributeTypeUUID, value: parentPhoneNumber }] : [],
|
|
160
|
+
addresses: [{ ...EMPTY_ADDRESS, country: 'Kenya' }],
|
|
161
|
+
},
|
|
162
|
+
identifiers: await withOpenmrsId(buildDependantIdentifiers(dependant, uuids, location), location),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Registers a patient from plain demographics — a PCS participant's own details, or the
|
|
168
|
+
* add-dependant form's. There is no HIE contact behind these, so only what was given is written.
|
|
169
|
+
*/
|
|
170
|
+
export async function createPatientFromDemographics(
|
|
171
|
+
demographics: PcsDependantDemographics,
|
|
172
|
+
nationalIdUUID: string,
|
|
173
|
+
phoneAttributeTypeUUID: string,
|
|
174
|
+
) {
|
|
175
|
+
const location = await requireSessionLocation();
|
|
176
|
+
const identifiers: Array<PatientIdentifier> = [];
|
|
177
|
+
|
|
178
|
+
// Carrying the national ID forward is what lets `findExistingLocalPatient` match this patient
|
|
179
|
+
// later — and makes OpenMRS reject the create if they are already registered, rather than
|
|
180
|
+
// silently producing a duplicate.
|
|
181
|
+
if (demographics.nationalId) {
|
|
182
|
+
identifiers.push({
|
|
183
|
+
identifier: demographics.nationalId,
|
|
184
|
+
identifierType: nationalIdUUID,
|
|
185
|
+
location,
|
|
186
|
+
preferred: false,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return postPatient({
|
|
191
|
+
person: {
|
|
192
|
+
names: [
|
|
193
|
+
{
|
|
194
|
+
preferred: true,
|
|
195
|
+
givenName: sanitizeName(demographics.firstName) || 'Unknown',
|
|
196
|
+
middleName: sanitizeName(demographics.middleName ?? '') || '',
|
|
197
|
+
familyName: sanitizeName(demographics.lastName) || 'Unknown',
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
gender: demographics.sex,
|
|
201
|
+
birthdate: demographics.dateOfBirth ?? null,
|
|
202
|
+
birthdateEstimated: !demographics.dateOfBirth,
|
|
203
|
+
attributes: demographics.phone ? [{ attributeType: phoneAttributeTypeUUID, value: demographics.phone }] : [],
|
|
204
|
+
addresses: [EMPTY_ADDRESS],
|
|
205
|
+
},
|
|
206
|
+
identifiers: await withOpenmrsId(identifiers, location),
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** The HIE identifier codes, mapped onto the identifier types they are stored as here. */
|
|
211
|
+
function resolveIdentifierType(code: string | undefined, uuids: IdentifierTypeUuids): string | undefined {
|
|
212
|
+
const map: Record<string, string> = {
|
|
213
|
+
'sha-number': uuids.shaNumberUUID,
|
|
214
|
+
'national-id': uuids.nationalIdUUID,
|
|
215
|
+
'passport-number': uuids.passportUUID,
|
|
216
|
+
'birth-certificate': uuids.birthCertificateUUID,
|
|
217
|
+
'sha-id-number': uuids.crIdentificationNumberUUID,
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
return code ? map[code] : undefined;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Given/middle/family from a FHIR name, falling back to splitting its display text. */
|
|
224
|
+
function splitHumanName(name?: fhir.HumanName) {
|
|
225
|
+
const given = name?.given ?? [];
|
|
226
|
+
|
|
227
|
+
if (given.length > 0 || name?.family) {
|
|
228
|
+
return {
|
|
229
|
+
givenName: given[0] ?? '',
|
|
230
|
+
middleName: given.slice(1).join(' '),
|
|
231
|
+
familyName: name?.family ?? '',
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const parts = (name?.text ?? '').trim().split(' ').filter(Boolean);
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
givenName: parts[0] ?? '',
|
|
239
|
+
middleName: parts.slice(1, -1).join(' '),
|
|
240
|
+
familyName: parts.length > 1 ? parts[parts.length - 1] : '',
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Registers a patient from their HIE record — the path taken when the authorized patient is being
|
|
246
|
+
* linked to a participant but has never been seen at this facility.
|
|
247
|
+
*/
|
|
248
|
+
export async function createPatientFromHiePatient(hiePatient: any, uuids: IdentifierTypeUuids) {
|
|
249
|
+
const location = await requireSessionLocation();
|
|
250
|
+
|
|
251
|
+
const identifiers: Array<PatientIdentifier> = (hiePatient?.identifier ?? [])
|
|
252
|
+
.map((id: any) => ({
|
|
253
|
+
identifier: id.value,
|
|
254
|
+
identifierType: resolveIdentifierType(id.type?.coding?.[0]?.code, uuids),
|
|
255
|
+
location,
|
|
256
|
+
preferred: false,
|
|
257
|
+
}))
|
|
258
|
+
.filter((id: PatientIdentifier) => id.identifier && id.identifierType);
|
|
259
|
+
|
|
260
|
+
// The HIE resource id is the patient's CR number.
|
|
261
|
+
if (hiePatient?.id) {
|
|
262
|
+
identifiers.push({
|
|
263
|
+
identifier: hiePatient.id,
|
|
264
|
+
identifierType: uuids.crIdentificationNumberUUID,
|
|
265
|
+
location,
|
|
266
|
+
preferred: false,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const { givenName, middleName, familyName } = splitHumanName(hiePatient?.name?.[0]);
|
|
271
|
+
const phone = hiePatient?.telecom?.find((contact: any) => contact.system === 'phone')?.value;
|
|
272
|
+
const addresses = (hiePatient?.address ?? []).map((address: any) => ({
|
|
273
|
+
...EMPTY_ADDRESS,
|
|
274
|
+
cityVillage: address.city || '',
|
|
275
|
+
country: address.country || '',
|
|
276
|
+
}));
|
|
277
|
+
|
|
278
|
+
return postPatient({
|
|
279
|
+
person: {
|
|
280
|
+
names: [
|
|
281
|
+
{
|
|
282
|
+
preferred: true,
|
|
283
|
+
givenName: sanitizeName(givenName) || 'Unknown',
|
|
284
|
+
middleName: sanitizeName(middleName) || '',
|
|
285
|
+
familyName: sanitizeName(familyName) || 'Unknown',
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
gender: hiePatient?.gender?.charAt(0).toUpperCase() || 'U',
|
|
289
|
+
birthdate: hiePatient?.birthDate ?? null,
|
|
290
|
+
birthdateEstimated: !hiePatient?.birthDate,
|
|
291
|
+
attributes: phone ? [{ attributeType: uuids.phoneAttributeTypeUUID, value: phone }] : [],
|
|
292
|
+
addresses: addresses.length > 0 ? addresses : [EMPTY_ADDRESS],
|
|
293
|
+
},
|
|
294
|
+
identifiers: await withOpenmrsId(identifiers, location),
|
|
295
|
+
});
|
|
296
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
|
|
3
|
+
const CUSTOM_PATIENT_REPRESENTATION =
|
|
4
|
+
'custom:(patientId,uuid,identifiers,display,patientIdentifier:(uuid,identifier),person:(gender,age,birthdate,birthdateEstimated,personName,addresses,display,dead,deathDate),attributes:(value,attributeType:(uuid,display)))';
|
|
5
|
+
|
|
6
|
+
/** National ID first, then SHA, then birth certificate — most to least likely to be on file here. */
|
|
7
|
+
const IDENTIFIER_PRIORITY: Record<string, number> = {
|
|
8
|
+
'national-id': 1,
|
|
9
|
+
'sha-number': 2,
|
|
10
|
+
'birth-certificate': 3,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The identifiers to search a patient by. A dependant carries theirs in `extension` entries
|
|
15
|
+
* rather than an `identifier` array, which is why the two are read differently.
|
|
16
|
+
*/
|
|
17
|
+
export const extractPatientIdentifiers = (patient: any, isDependent = false) => {
|
|
18
|
+
const identifiers: Array<{ value: string; type: string }> = [];
|
|
19
|
+
|
|
20
|
+
if (isDependent) {
|
|
21
|
+
const identifierExtensions = patient?.extension?.filter((ext: any) => ext.url === 'identifiers') || [];
|
|
22
|
+
|
|
23
|
+
identifierExtensions.forEach((ext: any) => {
|
|
24
|
+
const code = ext.valueIdentifier?.type?.coding?.[0]?.code;
|
|
25
|
+
// 'household-number' holds the household head's (parent's) own CR number, not an identifier
|
|
26
|
+
// unique to this dependent. Searching by it would match the parent's local patient record.
|
|
27
|
+
if (ext.valueIdentifier?.value && code && code !== 'household-number') {
|
|
28
|
+
identifiers.push({ value: ext.valueIdentifier.value, type: code });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return identifiers;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (Array.isArray(patient?.identifier)) {
|
|
36
|
+
patient.identifier.forEach((id: any) => {
|
|
37
|
+
if (id.value && id.type?.coding?.[0]?.code) {
|
|
38
|
+
identifiers.push({ value: id.value, type: id.type.coding[0].code });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return identifiers;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Looks a patient up locally by one identifier. Falls back to a `q=` search when no type is
|
|
48
|
+
* given, filtering the results down to a real identifier match — `q=` matches on name too.
|
|
49
|
+
*/
|
|
50
|
+
export const searchLocalPatientByIdentifier = async (identifierValue: string, identifierType?: string) => {
|
|
51
|
+
if (!identifierValue) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const byIdentifier = await openmrsFetch(
|
|
57
|
+
`${restBaseUrl}/patient?identifier=${encodeURIComponent(identifierValue)}&v=${CUSTOM_PATIENT_REPRESENTATION}`,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (byIdentifier?.data?.results?.length > 0) {
|
|
61
|
+
return byIdentifier.data.results[0];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!identifierType) {
|
|
65
|
+
const byQuery = await openmrsFetch(
|
|
66
|
+
`${restBaseUrl}/patient?q=${encodeURIComponent(identifierValue)}&v=${CUSTOM_PATIENT_REPRESENTATION}`,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const matching = (byQuery?.data?.results ?? []).filter((patient: any) =>
|
|
70
|
+
patient.identifiers?.some(
|
|
71
|
+
(id: any) => id.identifier === identifierValue || id.display?.includes(identifierValue),
|
|
72
|
+
),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
if (matching.length > 0) {
|
|
76
|
+
return matching[0];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return null;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/** The local record for a patient, or null when none of their identifiers resolve to one. */
|
|
87
|
+
export const findExistingLocalPatient = async (patient: any, isDependent = false) => {
|
|
88
|
+
const identifiers = extractPatientIdentifiers(patient, isDependent);
|
|
89
|
+
|
|
90
|
+
if (identifiers.length === 0) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const prioritized = [...identifiers].sort(
|
|
95
|
+
(a, b) => (IDENTIFIER_PRIORITY[a.type] || 999) - (IDENTIFIER_PRIORITY[b.type] || 999),
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
for (const identifier of prioritized) {
|
|
99
|
+
const existing = await searchLocalPatientByIdentifier(identifier.value, identifier.type).catch(() => null);
|
|
100
|
+
if (existing) {
|
|
101
|
+
return existing;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return null;
|
|
106
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { buildParticipantSearchUrl, hasAnyFilter } from './pcs.resource';
|
|
3
|
+
|
|
4
|
+
const url = (filters: Partial<{ name: string; village: string; phone: string; motherId: string }>, opts = {}) =>
|
|
5
|
+
buildParticipantSearchUrl({ name: '', village: '', phone: '', ...filters }, opts)!;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Pins the request we send to `pbids-participants`. The module owns the response semantics —
|
|
9
|
+
* name tiering, phone normalisation, AND-combination, paging — and the cases that covered
|
|
10
|
+
* those went with the mock that used to stand in for it.
|
|
11
|
+
*/
|
|
12
|
+
describe('pbids participant search request', () => {
|
|
13
|
+
it('omits blank filters and holds an unfiltered request', () => {
|
|
14
|
+
// The API rejects an unfiltered request with a 400, so a null URL holds the SWR fetch.
|
|
15
|
+
expect(hasAnyFilter({ name: ' ', village: '', phone: '' })).toBe(false);
|
|
16
|
+
expect(buildParticipantSearchUrl({ name: '', village: '', phone: '' })).toBeNull();
|
|
17
|
+
expect(url({ name: 'Mary Akinyi' })).toContain(
|
|
18
|
+
'/pbids-participants?name=Mary+Akinyi&limit=50&startIndex=0&fuzzy=true',
|
|
19
|
+
);
|
|
20
|
+
expect(url({ name: 'a', village: 'KAMBI' })).toContain('village=KAMBI');
|
|
21
|
+
expect(url({ name: 'a' })).not.toContain('village=');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('treats motherId as a filter in its own right', () => {
|
|
25
|
+
expect(hasAnyFilter({ name: '', village: '', phone: '', motherId: '901-1-1-2' })).toBe(true);
|
|
26
|
+
expect(url({ motherId: '901-1-1-2' })).toContain('motherId=901-1-1-2');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('passes limit, startIndex and fuzzy through', () => {
|
|
30
|
+
const paged = url({ name: 'ODONGO' }, { limit: 3, startIndex: 6, fuzzy: false });
|
|
31
|
+
|
|
32
|
+
expect(paged).toContain('limit=3');
|
|
33
|
+
expect(paged).toContain('startIndex=6');
|
|
34
|
+
expect(paged).toContain('fuzzy=false');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import useSWR from 'swr';
|
|
2
|
+
import { getPatientName, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
3
|
+
import { getNationalIdFromPatient, getPhoneFromFhirPatient } from './identifiers';
|
|
4
|
+
import {
|
|
5
|
+
type PcsApiError,
|
|
6
|
+
type PcsCompound,
|
|
7
|
+
type PcsMother,
|
|
8
|
+
type PcsParticipant,
|
|
9
|
+
type PcsParticipantFilters,
|
|
10
|
+
type PcsParticipantSearchResponse,
|
|
11
|
+
type PcsSearchSubject,
|
|
12
|
+
} from '../types';
|
|
13
|
+
|
|
14
|
+
/** The API's own defaults, restated so the URL we build is explicit about them. */
|
|
15
|
+
const DEFAULT_LIMIT = 50;
|
|
16
|
+
const DEFAULT_START_INDEX = 0;
|
|
17
|
+
|
|
18
|
+
interface ParticipantSearchOptions {
|
|
19
|
+
limit?: number;
|
|
20
|
+
startIndex?: number;
|
|
21
|
+
fuzzy?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The default query for a newly authorized patient: their name, plus their phone when on file. */
|
|
25
|
+
export function toPcsParticipantFilters(subject: PcsSearchSubject): PcsParticipantFilters {
|
|
26
|
+
return {
|
|
27
|
+
name: subject.name ?? '',
|
|
28
|
+
village: '',
|
|
29
|
+
phone: subject.phoneNumber ?? '',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The API rejects an unfiltered request with a 400, so hold it rather than provoke one. */
|
|
34
|
+
export function hasAnyFilter(filters: PcsParticipantFilters | null): boolean {
|
|
35
|
+
return Boolean(
|
|
36
|
+
filters && (filters.name.trim() || filters.village.trim() || filters.phone.trim() || filters.motherId?.trim()),
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Builds the participant search URL. Blank filters are omitted — the API AND-combines
|
|
42
|
+
* whatever it is given. Returns null when nothing is set, which doubles as SWR's
|
|
43
|
+
* "don't fetch yet" key.
|
|
44
|
+
*/
|
|
45
|
+
export function buildParticipantSearchUrl(
|
|
46
|
+
filters: PcsParticipantFilters | null,
|
|
47
|
+
{ limit = DEFAULT_LIMIT, startIndex = DEFAULT_START_INDEX, fuzzy = true }: ParticipantSearchOptions = {},
|
|
48
|
+
): string | null {
|
|
49
|
+
if (!hasAnyFilter(filters)) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const params = new URLSearchParams();
|
|
54
|
+
(['name', 'village', 'phone', 'motherId'] as const).forEach((filter) => {
|
|
55
|
+
const value = filters![filter]?.trim();
|
|
56
|
+
if (value) {
|
|
57
|
+
params.set(filter, value);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
params.set('limit', String(limit));
|
|
61
|
+
params.set('startIndex', String(startIndex));
|
|
62
|
+
params.set('fuzzy', String(fuzzy));
|
|
63
|
+
|
|
64
|
+
return `${restBaseUrl}/pbids-participants?${params}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function fetchParticipants(url: string): Promise<PcsParticipantSearchResponse> {
|
|
68
|
+
const { data } = await openmrsFetch<PcsParticipantSearchResponse>(url);
|
|
69
|
+
return data;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** An id PCS does not know returns 404 with an `ApiError` body, which SWR surfaces as an error. */
|
|
73
|
+
async function fetchParticipantById(url: string): Promise<PcsParticipant> {
|
|
74
|
+
const { data } = await openmrsFetch<PcsParticipant>(url);
|
|
75
|
+
return data;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Fetches the one participant a patient is already linked to. A single record comes back in
|
|
80
|
+
* the same shape as one entry of the search results.
|
|
81
|
+
*/
|
|
82
|
+
export function usePcsParticipant(individualId: string | null) {
|
|
83
|
+
const url = individualId ? `${restBaseUrl}/pbids-participants/${encodeURIComponent(individualId)}` : null;
|
|
84
|
+
|
|
85
|
+
const { data, isLoading, error, mutate } = useSWR(url, fetchParticipantById, {
|
|
86
|
+
revalidateOnFocus: false,
|
|
87
|
+
revalidateOnReconnect: false,
|
|
88
|
+
shouldRetryOnError: false,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return { participant: data ?? null, isLoading, error, mutate };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The participants whose mother is `motherIndividualId` — a linked client's dependants.
|
|
96
|
+
* Same endpoint and response shape as the search, just a different filter.
|
|
97
|
+
*/
|
|
98
|
+
export function usePcsDependants(motherIndividualId: string | null) {
|
|
99
|
+
const url = buildParticipantSearchUrl(
|
|
100
|
+
motherIndividualId ? { name: '', village: '', phone: '', motherId: motherIndividualId } : null,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const { data, isLoading, error, mutate } = useSWR(url, fetchParticipants, {
|
|
104
|
+
revalidateOnFocus: false,
|
|
105
|
+
revalidateOnReconnect: false,
|
|
106
|
+
shouldRetryOnError: false,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return { dependants: data?.results ?? [], totalCount: data?.totalCount ?? 0, isLoading, error, mutate };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Searches the PCS registry. The URL is the SWR key, so editing a filter refetches and an
|
|
114
|
+
* unchanged query dedupes.
|
|
115
|
+
*/
|
|
116
|
+
export function usePcsParticipantSearch(filters: PcsParticipantFilters | null, options?: ParticipantSearchOptions) {
|
|
117
|
+
const url = buildParticipantSearchUrl(filters, options);
|
|
118
|
+
|
|
119
|
+
const { data, isLoading, error } = useSWR(url, fetchParticipants, {
|
|
120
|
+
revalidateOnFocus: false,
|
|
121
|
+
revalidateOnReconnect: false,
|
|
122
|
+
shouldRetryOnError: false,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
participants: data?.results ?? [],
|
|
127
|
+
totalCount: data?.totalCount ?? 0,
|
|
128
|
+
isLoading,
|
|
129
|
+
error,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Surfaces the API's own wording. `openmrsFetch` attaches the failure body — an `ApiError`
|
|
135
|
+
* — to the thrown error, and its `message` is more useful than a generic HTTP string.
|
|
136
|
+
*/
|
|
137
|
+
export function getPcsErrorMessage(error: unknown): string | undefined {
|
|
138
|
+
const apiError = (error as { responseBody?: Partial<PcsApiError> } | undefined)?.responseBody;
|
|
139
|
+
return apiError?.message || (error as Error | undefined)?.message;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** A downed VPN to the SQL Server is the common failure, and reads very differently to a bug. */
|
|
143
|
+
export function isPcsUnavailable(error: unknown): boolean {
|
|
144
|
+
return (error as { responseBody?: Partial<PcsApiError> } | undefined)?.responseBody?.status === 503;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function formatParticipantName(participant: PcsParticipant): string {
|
|
148
|
+
return [participant.firstName, participant.middleName, participant.lastName].filter(Boolean).join(' ');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function formatMotherName(mother: PcsMother): string {
|
|
152
|
+
return [mother.firstName, mother.middleName, mother.lastName].filter(Boolean).join(' ');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function formatCompoundHeadName(compound: PcsCompound): string {
|
|
156
|
+
return [compound.headFirstName, compound.headMiddleName, compound.headLastName].filter(Boolean).join(' ');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Contacts are a list; the first one carrying anything identifying is what the tile shows. */
|
|
160
|
+
export function getPrimaryContact(participant: PcsParticipant) {
|
|
161
|
+
return participant.contacts?.find((contact) => contact.phone || contact.nationalId) ?? participant.contacts?.[0];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Normalizes either side of the search results (local or HIE) into a PCS search subject. */
|
|
165
|
+
export function toPcsSearchSubject(
|
|
166
|
+
patient: fhir.Patient,
|
|
167
|
+
source: PcsSearchSubject['source'],
|
|
168
|
+
nationalIdUUID?: string,
|
|
169
|
+
hiePatient?: fhir.Patient,
|
|
170
|
+
): PcsSearchSubject {
|
|
171
|
+
return {
|
|
172
|
+
id: patient.id!,
|
|
173
|
+
source,
|
|
174
|
+
patient,
|
|
175
|
+
hiePatient,
|
|
176
|
+
name: getPatientName(patient),
|
|
177
|
+
gender: patient.gender,
|
|
178
|
+
birthDate: patient.birthDate,
|
|
179
|
+
nationalId: getNationalIdFromPatient(patient, nationalIdUUID),
|
|
180
|
+
phoneNumber: getPhoneFromFhirPatient(patient) ?? null,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { useConfig } from '@openmrs/esm-framework';
|
|
3
|
+
import { type IdentifierTypeUuids } from '../types';
|
|
4
|
+
import { type PcsConfig } from '../config-schema';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The identifier and attribute types a created patient's HIE identifiers are written against,
|
|
8
|
+
* gathered in one place so every caller passes the same set.
|
|
9
|
+
*/
|
|
10
|
+
export function useIdentifierTypeUuids(): IdentifierTypeUuids {
|
|
11
|
+
const {
|
|
12
|
+
nationalIdUUID,
|
|
13
|
+
shaNumberUUID,
|
|
14
|
+
passportUUID,
|
|
15
|
+
birthCertificateUUID,
|
|
16
|
+
crIdentificationNumberUUID,
|
|
17
|
+
phoneAttributeTypeUUID,
|
|
18
|
+
} = useConfig<PcsConfig>();
|
|
19
|
+
|
|
20
|
+
return useMemo(
|
|
21
|
+
() => ({
|
|
22
|
+
nationalIdUUID,
|
|
23
|
+
shaNumberUUID,
|
|
24
|
+
passportUUID,
|
|
25
|
+
birthCertificateUUID,
|
|
26
|
+
crIdentificationNumberUUID,
|
|
27
|
+
phoneAttributeTypeUUID,
|
|
28
|
+
}),
|
|
29
|
+
[
|
|
30
|
+
nationalIdUUID,
|
|
31
|
+
shaNumberUUID,
|
|
32
|
+
passportUUID,
|
|
33
|
+
birthCertificateUUID,
|
|
34
|
+
crIdentificationNumberUUID,
|
|
35
|
+
phoneAttributeTypeUUID,
|
|
36
|
+
],
|
|
37
|
+
);
|
|
38
|
+
}
|