@kenyaemr/esm-admin-app 5.4.2-pre.2641 → 5.4.2-pre.2656

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/routes.json CHANGED
@@ -1 +1 @@
1
- {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"kenyaemrCharts":"^1.6.7"},"extensions":[{"component":"adminLeftPanelLink","name":"admin-left-panel-link","slot":"admin-left-panel-slot"},{"component":"userManagementLeftPannelLink","name":"user-management-left-panel-link","slot":"admin-left-panel-slot"},{"component":"etlAdministrationLeftPannelLink","name":"etl-administration-left-panel-link","slot":"admin-left-panel-slot"},{"component":"locationsLeftPanelLink","name":"locations-left-panel-link","slot":"admin-left-panel-slot"},{"component":"facilitySetupLeftPanelLink","name":"facility-setup-left-panel-link","slot":"admin-left-panel-slot"},{"component":"providerBanner","name":"provider-banner","slot":"provider-banner-info-slot","order":1}],"workspaces":[{"name":"manage-user-workspace","component":"manageUserWorkspace","title":"Manage User Workspace","type":"other-form","canMaximize":true,"width":"extra-wide"},{"name":"user-role-scope-workspace","component":"userRoleScopeWorkspace","title":"User Rple Scope Workspace","type":"other-form","canMaximize":true,"width":"extra-wide"},{"name":"add-location-workspace","title":"Add Location","component":"addLocation","type":"workspace"},{"name":"search-location-workspace","title":"Search Location","component":"searchLocationWorkspace","type":"workspace"},{"name":"hwr-sync-workspace","title":"HWR Sync Workspace","component":"hwrSyncWorkspace","type":"other-form"},{"name":"hwr-sync-modal","title":"HWR Sync Modal","component":"hwrSyncModal","type":"modal"}],"modals":[{"component":"operationConfirmationModal","name":"operation-confirmation-modal"},{"component":"hwrConfirmationModal","name":"hwr-confirmation-modal"},{"component":"hwrEmptyModal","name":"hwr-empty-modal"},{"component":"hwrSyncModal","name":"hwr-syncing-modal"}],"pages":[{"component":"root","route":"admin"}],"version":"5.4.2-pre.2641"}
1
+ {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"kenyaemrCharts":"^1.6.7"},"extensions":[{"component":"adminLeftPanelLink","name":"admin-left-panel-link","slot":"admin-left-panel-slot"},{"component":"userManagementLeftPannelLink","name":"user-management-left-panel-link","slot":"admin-left-panel-slot"},{"component":"etlAdministrationLeftPannelLink","name":"etl-administration-left-panel-link","slot":"admin-left-panel-slot"},{"component":"locationsLeftPanelLink","name":"locations-left-panel-link","slot":"admin-left-panel-slot"},{"component":"facilitySetupLeftPanelLink","name":"facility-setup-left-panel-link","slot":"admin-left-panel-slot"},{"component":"providerBanner","name":"provider-banner","slot":"provider-banner-info-slot","order":1}],"workspaces":[{"name":"manage-user-workspace","component":"manageUserWorkspace","title":"Manage User Workspace","type":"other-form","canMaximize":true,"width":"extra-wide"},{"name":"user-role-scope-workspace","component":"userRoleScopeWorkspace","title":"User Rple Scope Workspace","type":"other-form","canMaximize":true,"width":"extra-wide"},{"name":"add-location-workspace","title":"Add Location","component":"addLocation","type":"workspace"},{"name":"search-location-workspace","title":"Search Location","component":"searchLocationWorkspace","type":"workspace"},{"name":"hwr-sync-workspace","title":"HWR Sync Workspace","component":"hwrSyncWorkspace","type":"other-form"},{"name":"hwr-sync-modal","title":"HWR Sync Modal","component":"hwrSyncModal","type":"modal"}],"modals":[{"component":"operationConfirmationModal","name":"operation-confirmation-modal"},{"component":"hwrConfirmationModal","name":"hwr-confirmation-modal"},{"component":"hwrEmptyModal","name":"hwr-empty-modal"},{"component":"hwrSyncModal","name":"hwr-syncing-modal"}],"pages":[{"component":"root","route":"admin"}],"version":"5.4.2-pre.2656"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenyaemr/esm-admin-app",
3
- "version": "5.4.2-pre.2641",
3
+ "version": "5.4.2-pre.2656",
4
4
  "description": "Facilitates the management of ETL tables",
5
5
  "keywords": [
6
6
  "openmrs"
@@ -3,7 +3,7 @@ import { ExtensionSlot } from '@openmrs/esm-framework';
3
3
  import capitalize from 'lodash-es/capitalize';
4
4
  import React from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
- import { type PractitionerResponse } from '../../types';
6
+ import { CustomHIEPractitionerResponse, type PractitionerResponse } from '../../types';
7
7
  import styles from './hwr-confirmation.modal.scss';
8
8
  import { formatDateTime } from '../../utils/utils';
9
9
 
@@ -24,26 +24,19 @@ const HealthWorkerInfo: React.FC<HealthWorkerInfoProps> = ({ label, value }) =>
24
24
  interface HWRConfirmModalProps {
25
25
  onConfirm: () => void;
26
26
  close: () => void;
27
- healthWorker: PractitionerResponse;
27
+ healthWorker: CustomHIEPractitionerResponse;
28
28
  }
29
29
 
30
30
  const HWRConfirmModal: React.FC<HWRConfirmModalProps> = ({ close, onConfirm, healthWorker }) => {
31
31
  const { t } = useTranslation();
32
- const passportNumber = healthWorker?.link
33
- ?.find(
34
- (link: { relation: string; url: string }) =>
35
- link.relation === 'self' && link.url.includes('identifierType=Passport'),
36
- )
37
- ?.url.split('identifierNumber=')[1]
38
- ?.split('&')[0];
39
32
 
40
- const practitioner = healthWorker?.entry?.[0]?.resource;
33
+ const { membership, licenses, contacts, identifiers, professional_details } = healthWorker?.message || {};
41
34
 
42
- const licenseRenewalDate = practitioner?.identifier?.find((id) =>
43
- id.type?.coding?.some((code) => code.code === 'license-number'),
44
- )?.period?.end;
35
+ const mostRecentLicense = licenses
36
+ ?.filter((l) => l.license_end)
37
+ .sort((a, b) => new Date(b.license_end).getTime() - new Date(a.license_end).getTime())[0];
45
38
 
46
- const isLicenseValid = licenseRenewalDate ? new Date(licenseRenewalDate) > new Date() : false;
39
+ const isLicenseValid = mostRecentLicense?.license_end ? new Date(mostRecentLicense.license_end) > new Date() : false;
47
40
 
48
41
  return (
49
42
  <>
@@ -62,57 +55,83 @@ const HWRConfirmModal: React.FC<HWRConfirmModalProps> = ({ close, onConfirm, hea
62
55
  className={styles.healthWorkerPhoto}
63
56
  name="patient-photo-slot"
64
57
  state={{
65
- patientName: practitioner?.name?.[0]?.text || '',
58
+ patientName: membership?.full_name || '',
66
59
  }}
67
60
  />
68
61
  <div style={{ width: '100%', marginLeft: '0.625rem' }}>
69
62
  <HealthWorkerInfo
70
63
  label={t('healthWorkerName', 'Health worker name')}
71
- value={practitioner?.name?.[0]?.text}
64
+ value={membership?.full_name || '--'}
72
65
  />
66
+
73
67
  <HealthWorkerInfo
74
68
  label={t('providerUniqueIdentifier', 'Provider unique identifier')}
75
- value={practitioner?.id}
69
+ value={membership?.id || '--'}
76
70
  />
77
- {practitioner?.telecom?.map((telecom, index) => (
78
- <HealthWorkerInfo key={index} label={capitalize(telecom?.system)} value={telecom?.value || '--'} />
79
- ))}
80
-
81
- {practitioner?.identifier?.map((identifier, index) => (
82
- <HealthWorkerInfo
83
- key={index}
84
- label={identifier.type?.coding?.map((code) => code.display).join(' ') || '--'}
85
- value={identifier.value || '--'}
86
- />
87
- ))}
88
-
89
- {passportNumber && (
90
- <HealthWorkerInfo label={t('passportNumber', 'Passport Number')} value={passportNumber} />
91
- )}
92
71
 
93
72
  <HealthWorkerInfo
94
- label={t('renewalDate', 'Renewal Date')}
95
- value={formatDateTime(licenseRenewalDate) || '--'}
73
+ label={t('registrationId', 'Registration ID')}
74
+ value={membership?.registration_id || '--'}
96
75
  />
97
76
 
98
77
  <HealthWorkerInfo
99
- label={t('licensingBody', 'Licensing Body')}
100
- value={
101
- practitioner?.qualification?.[0]?.extension?.find(
102
- (ext) => ext.url === 'https://hwr-kenyahie/StructureDefinition/licensing-body',
103
- )?.valueCodeableConcept?.coding?.[0]?.display || '--'
104
- }
78
+ label={t('externalReferenceId', 'External Reference ID')}
79
+ value={membership?.external_reference_id || '--'}
105
80
  />
81
+
82
+ <HealthWorkerInfo label={t('gender', 'Gender')} value={membership?.gender || '--'} />
83
+
84
+ <HealthWorkerInfo label={t('status', 'Status')} value={membership?.status || '--'} />
85
+
86
+ {contacts?.phone && <HealthWorkerInfo label={t('phone', 'Phone')} value={contacts.phone} />}
87
+
88
+ {contacts?.email && <HealthWorkerInfo label={t('email', 'Email')} value={contacts.email} />}
89
+
90
+ {identifiers?.identification_number && (
91
+ <HealthWorkerInfo
92
+ label={identifiers.identification_type || t('identificationNumber', 'Identification Number')}
93
+ value={identifiers.identification_number}
94
+ />
95
+ )}
96
+
97
+ <HealthWorkerInfo label={t('licensingBody', 'Licensing Body')} value={membership?.licensing_body || '--'} />
98
+
106
99
  <HealthWorkerInfo
107
- label={t('qualification', 'Qualification')}
108
- value={
109
- practitioner?.qualification?.[0]?.code?.coding?.[0]?.display ||
110
- practitioner?.extension?.find((ext) => ext.url === 'https://ts.kenya-hie.health/Codesystem/specialty')
111
- ?.valueCodeableConcept?.coding?.[0]?.display ||
112
- '--'
113
- }
100
+ label={t('specialty', 'Specialty')}
101
+ value={membership?.specialty || professional_details?.specialty || '--'}
114
102
  />
115
103
 
104
+ {professional_details?.professional_cadre && (
105
+ <HealthWorkerInfo
106
+ label={t('professionalCadre', 'Professional Cadre')}
107
+ value={professional_details.professional_cadre}
108
+ />
109
+ )}
110
+
111
+ {professional_details?.practice_type && (
112
+ <HealthWorkerInfo label={t('practiceType', 'Practice Type')} value={professional_details.practice_type} />
113
+ )}
114
+
115
+ {licenses && licenses.length > 0 && (
116
+ <>
117
+ <div style={{ marginTop: '1rem', marginBottom: '0.5rem' }}>
118
+ <strong>{t('licenses', 'Licenses')}</strong>
119
+ </div>
120
+ {licenses.map((license, index) => (
121
+ <div key={index} style={{ marginBottom: '0.5rem', paddingLeft: '1rem' }}>
122
+ <HealthWorkerInfo
123
+ label={`${license.license_type} ${t('license', 'License')}`}
124
+ value={license.external_reference_id || '--'}
125
+ />
126
+ <HealthWorkerInfo
127
+ label={t('validity', 'Validity')}
128
+ value={`${formatDateTime(license.license_start)} - ${formatDateTime(license.license_end)}`}
129
+ />
130
+ </div>
131
+ ))}
132
+ </>
133
+ )}
134
+
116
135
  <HealthWorkerInfo
117
136
  label={t('licenseValid', 'License Validity')}
118
137
  value={
@@ -4,7 +4,7 @@ import { Button, Column, Search, ComboBox, InlineLoading } from '@carbon/react';
4
4
  import styles from './hwr-sync.modal.scss';
5
5
  import { useConfig, showSnackbar, formatDate, parseDate, showToast, restBaseUrl } from '@openmrs/esm-framework';
6
6
  import { mutate } from 'swr';
7
- import { type PractitionerResponse, type ProviderResponse } from '../../types';
7
+ import { CustomHIEPractitionerResponse, type PractitionerResponse, type ProviderResponse } from '../../types';
8
8
  import { ConfigObject } from '../../config-schema';
9
9
  import { searchHealthCareWork } from '../hook/searchHealthCareWork';
10
10
  import { createProviderAttribute, updateProviderAttributes } from './hwr-sync.resource';
@@ -71,48 +71,44 @@ const HWRSyncModal: React.FC<HWRSyncModalProps> = ({ close, provider }) => {
71
71
  const handleSync = async () => {
72
72
  try {
73
73
  setSyncLoading(true);
74
- const healthWorker: PractitionerResponse = await searchHealthCareWork(
74
+ const healthWorker: CustomHIEPractitionerResponse = await searchHealthCareWork(
75
75
  searchHWR.identifierType,
76
76
  searchHWR.identifier,
77
77
  searchHWR.regulator,
78
78
  );
79
79
 
80
- const resource = healthWorker.entry[0]?.resource;
80
+ if (!healthWorker?.message) {
81
+ throw new Error(t('noResults', 'No results found'));
82
+ }
83
+
84
+ const { membership, licenses, contacts, identifiers } = healthWorker.message;
85
+
86
+ const mostRecentLicense = licenses
87
+ ?.filter((l) => l.license_end)
88
+ .sort((a, b) => new Date(b.license_end).getTime() - new Date(a.license_end).getTime())[0];
81
89
 
82
90
  const extractedAttributes = {
83
- licenseNumber: resource?.identifier?.find((id) =>
84
- id.type?.coding?.some((code) => code.code === 'license-number'),
85
- )?.value,
86
- regNumber: resource?.identifier?.find((id) =>
87
- id.type?.coding?.some((code) => code.code === 'board-registration-number'),
88
- )?.value,
89
- licenseDate: formatDate(
90
- new Date(
91
- resource?.identifier?.find((id) =>
92
- id.type?.coding?.some((code) => code.code === 'license-number'),
93
- )?.period?.end,
94
- ),
95
- ),
96
- phoneNumber: resource?.telecom?.find((contact) => contact.system === 'phone')?.value,
97
- email: resource?.telecom?.find((contact) => contact.system === 'email')?.value,
98
- qualification:
99
- resource?.qualification?.[0]?.code?.coding?.[0]?.display ||
100
- resource?.extension?.find((ext) => ext.url === 'https://ts.kenya-hie.health/Codesystem/specialty')
101
- ?.valueCodeableConcept?.coding?.[0]?.display,
102
- nationalId: resource?.identifier?.find((id) => id.type?.coding?.some((code) => code.code === 'national-id'))
103
- ?.value,
104
- providerUniqueIdentifier: resource?.id,
91
+ licenseNumber: membership.external_reference_id,
92
+ regNumber: membership.registration_id,
93
+ licenseDate: mostRecentLicense?.license_end ? formatDate(new Date(mostRecentLicense.license_end)) : null,
94
+ phoneNumber: contacts.phone,
95
+ email: contacts.email,
96
+ qualification: membership.specialty,
97
+ nationalId: identifiers.identification_number,
98
+ providerUniqueIdentifier: membership.id,
105
99
  };
106
100
 
107
101
  const updatableAttributes = [
108
102
  { attributeType: licenseNumberUuid, value: extractedAttributes.licenseNumber },
109
103
  { attributeType: licenseBodyUuid, value: extractedAttributes.regNumber },
110
- { attributeType: licenseExpiryDateUuid, value: parseDate(extractedAttributes.licenseDate) },
104
+ {
105
+ attributeType: licenseExpiryDateUuid,
106
+ value: extractedAttributes.licenseDate ? parseDate(extractedAttributes.licenseDate) : null,
107
+ },
111
108
  { attributeType: phoneNumberUuid, value: extractedAttributes.phoneNumber },
112
109
  { attributeType: qualificationUuid, value: extractedAttributes.qualification },
113
110
  {
114
111
  attributeType: providerHieFhirReference,
115
- // Include regulator info in the health worker reference
116
112
  value: JSON.stringify({
117
113
  ...healthWorker,
118
114
  searchParameters: {
@@ -46,7 +46,7 @@ import {
46
46
  import UserManagementFormSchema from '../userManagementFormSchema';
47
47
  import { ChevronLeft, Query, ChevronRight } from '@carbon/react/icons';
48
48
  import { useSystemUserRoleConfigSetting } from '../../hook/useSystemRoleSetting';
49
- import { type PractitionerResponse, Provider, User } from '../../../types';
49
+ import { type CustomHIEPractitionerResponse, type PractitionerResponse, Provider, User } from '../../../types';
50
50
  import { searchHealthCareWork } from '../../hook/searchHealthCareWork';
51
51
  import { ROLE_CATEGORIES, SECTIONS, today } from '../../../constants';
52
52
  import { ConfigObject } from '../../../config-schema';
@@ -256,15 +256,45 @@ const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
256
256
  }
257
257
  }, [isDirty, promptBeforeClosing]);
258
258
 
259
+ const setPractitionerValues = (healthWorker: CustomHIEPractitionerResponse) => {
260
+ const { membership, licenses, contacts, identifiers } = healthWorker.message;
261
+
262
+ setValue('givenName', membership.first_name || '');
263
+ setValue('middleName', membership.middle_name || '');
264
+ setValue('familyName', membership.last_name || '');
265
+
266
+ setValue('nationalId', identifiers.identification_number || '');
267
+ setValue('providerLicense', membership.external_reference_id || '');
268
+ setValue('registrationNumber', membership.registration_id || '');
269
+ setValue('providerUniqueIdentifier', membership.id || '');
270
+
271
+ setValue('phoneNumber', contacts.phone || '');
272
+ setValue('email', contacts.email || '');
273
+
274
+ setValue('qualification', membership.specialty || '');
275
+
276
+ setValue('passportNumber', '');
277
+
278
+ const mostRecentLicense = licenses
279
+ .filter((l) => l.license_end)
280
+ .sort((a, b) => new Date(b.license_end).getTime() - new Date(a.license_end).getTime())[0];
281
+
282
+ setValue(
283
+ 'licenseExpiryDate',
284
+ mostRecentLicense?.license_end ? parseDate(mostRecentLicense.license_end) : parseDate(t('unknown', 'Unknown')),
285
+ );
286
+ };
287
+
259
288
  const handleSearch = async () => {
260
289
  try {
261
290
  setSearchHWR({ ...searchHWR, isHWRLoading: true });
262
- const fetchedHealthWorker: PractitionerResponse = await searchHealthCareWork(
291
+ const fetchedHealthWorker: CustomHIEPractitionerResponse = await searchHealthCareWork(
263
292
  searchHWR.identifierType,
264
293
  searchHWR.identifier,
265
294
  searchHWR.regulator,
266
295
  );
267
- if (!fetchedHealthWorker?.entry || fetchedHealthWorker.entry.length === 0) {
296
+
297
+ if (!fetchedHealthWorker?.message) {
268
298
  showModal('hwr-empty-modal', { errorCode: t('noResults', 'No results found') });
269
299
  return;
270
300
  }
@@ -273,73 +303,7 @@ const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
273
303
  healthWorker: fetchedHealthWorker,
274
304
  onConfirm: () => {
275
305
  dispose();
276
- const fullName = fetchedHealthWorker?.entry[0]?.resource?.name[0]?.text?.trim();
277
-
278
- const nameParts = fullName.replace(/\s+/g, ' ').split(' ');
279
-
280
- const hasTitle = nameParts[0].endsWith('.');
281
- const nameWithoutTitle = hasTitle ? nameParts.slice(1) : nameParts;
282
-
283
- const givenName = nameWithoutTitle[0] || '';
284
- const familyName = nameWithoutTitle.length > 1 ? nameWithoutTitle[nameWithoutTitle.length - 1] : '';
285
- const middleName = nameWithoutTitle.length > 2 ? nameWithoutTitle.slice(1, -1).join(' ') : '';
286
- const healthWorkerPassPortNumber = fetchedHealthWorker?.link
287
- ?.find(
288
- (link: { relation: string; url: string }) =>
289
- link.relation === 'self' && link.url.includes('identifierType=Passport'),
290
- )
291
- ?.url.split('identifierNumber=')[1]
292
- ?.split('&')[0];
293
-
294
- setValue('givenName', givenName);
295
- setValue('middleName', middleName);
296
- setValue('familyName', familyName);
297
- setValue(
298
- 'nationalId',
299
- fetchedHealthWorker?.entry[0]?.resource?.identifier?.find((id) =>
300
- id.type?.coding?.some((code) => code.code === 'national-id'),
301
- )?.value,
302
- );
303
- setValue(
304
- 'providerLicense',
305
- fetchedHealthWorker?.entry[0]?.resource?.identifier?.find((id) =>
306
- id.type?.coding?.some((code) => code.code === 'license-number'),
307
- )?.value,
308
- );
309
- setValue(
310
- 'registrationNumber',
311
- fetchedHealthWorker?.entry[0]?.resource?.identifier?.find((id) =>
312
- id.type?.coding?.some((code) => code.code === 'board-registration-number'),
313
- )?.value,
314
- );
315
- setValue(
316
- 'phoneNumber',
317
- fetchedHealthWorker?.entry[0]?.resource?.telecom?.find((contact) => contact.system === 'phone')?.value ||
318
- '',
319
- );
320
- setValue(
321
- 'qualification',
322
- fetchedHealthWorker?.entry[0]?.resource?.qualification?.[0]?.code?.coding?.[0]?.display ||
323
- fetchedHealthWorker?.entry[0]?.resource?.extension?.find(
324
- (ext) => ext.url === 'https://ts.kenya-hie.health/Codesystem/specialty',
325
- )?.valueCodeableConcept?.coding?.[0]?.display ||
326
- '',
327
- );
328
- setValue(
329
- 'email',
330
- fetchedHealthWorker?.entry[0]?.resource?.telecom?.find((contact) => contact.system === 'email')?.value ||
331
- '',
332
- );
333
- setValue(
334
- 'licenseExpiryDate',
335
- parseDate(
336
- fetchedHealthWorker?.entry[0]?.resource?.identifier?.find((id) =>
337
- id.type?.coding?.some((code) => code.code === 'license-number'),
338
- )?.period?.end || t('unknown', 'Unknown'),
339
- ),
340
- );
341
- setValue('passportNumber', healthWorkerPassPortNumber);
342
- setValue('providerUniqueIdentifier', fetchedHealthWorker?.entry[0]?.resource?.id);
306
+ setPractitionerValues(fetchedHealthWorker);
343
307
  setHealthWorker(fetchedHealthWorker);
344
308
  },
345
309
  });
@@ -333,3 +333,53 @@ export interface UserResponse {
333
333
  description: string;
334
334
  }>;
335
335
  }
336
+
337
+ export interface CustomHIEPractitionerResponse {
338
+ message: {
339
+ membership: {
340
+ id: string;
341
+ status: string;
342
+ salutation: string;
343
+ full_name: string;
344
+ gender: string;
345
+ first_name: string;
346
+ middle_name: string;
347
+ last_name: string;
348
+ registration_id: string;
349
+ external_reference_id: string;
350
+ licensing_body: string;
351
+ specialty: string;
352
+ is_active: number;
353
+ is_withdrawn: number;
354
+ withdrawal_reason: string;
355
+ withdrawal_date: string;
356
+ };
357
+ licenses: Array<{
358
+ id: string;
359
+ external_reference_id: string;
360
+ license_type: string;
361
+ license_start: string;
362
+ license_end: string;
363
+ }>;
364
+ professional_details: {
365
+ professional_cadre: string;
366
+ practice_type: string;
367
+ specialty: string;
368
+ subspecialty: string;
369
+ discipline_name: string;
370
+ educational_qualifications: string;
371
+ };
372
+ contacts: {
373
+ phone: string;
374
+ email: string;
375
+ postal_address: string;
376
+ };
377
+ identifiers: {
378
+ identification_type: string;
379
+ identification_number: string;
380
+ client_registry_id: string;
381
+ student_id: string;
382
+ };
383
+ };
384
+ fhirFormat: boolean;
385
+ }