@ampath/esm-dispensing-app 1.10.0-next.1
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/.editorconfig +12 -0
- package/.eslintignore +2 -0
- package/.eslintrc +80 -0
- package/.husky/pre-commit +7 -0
- package/.husky/pre-push +6 -0
- package/.prettierignore +12 -0
- package/.tx/config +11 -0
- package/.yarn/versions/1c40b9b6.yml +0 -0
- package/.yarn/versions/ff162597.yml +0 -0
- package/LICENSE +401 -0
- package/README.md +124 -0
- package/__mocks__/react-i18next.js +51 -0
- package/dist/1043.js +1 -0
- package/dist/1043.js.map +1 -0
- package/dist/1119.js +1 -0
- package/dist/1197.js +1 -0
- package/dist/2146.js +1 -0
- package/dist/2177.js +2 -0
- package/dist/2177.js.LICENSE.txt +9 -0
- package/dist/2177.js.map +1 -0
- package/dist/2690.js +1 -0
- package/dist/2890.js +2 -0
- package/dist/2890.js.LICENSE.txt +14 -0
- package/dist/2890.js.map +1 -0
- package/dist/2898.js +1 -0
- package/dist/2898.js.map +1 -0
- package/dist/3041.js +1 -0
- package/dist/3041.js.map +1 -0
- package/dist/3099.js +1 -0
- package/dist/3184.js +2 -0
- package/dist/3184.js.LICENSE.txt +14 -0
- package/dist/3184.js.map +1 -0
- package/dist/3568.js +1 -0
- package/dist/3568.js.map +1 -0
- package/dist/3584.js +1 -0
- package/dist/4055.js +1 -0
- package/dist/4099.js +1 -0
- package/dist/4099.js.map +1 -0
- package/dist/4132.js +1 -0
- package/dist/4225.js +1 -0
- package/dist/4225.js.map +1 -0
- package/dist/4300.js +1 -0
- package/dist/4335.js +1 -0
- package/dist/4353.js +1 -0
- package/dist/4353.js.map +1 -0
- package/dist/439.js +1 -0
- package/dist/4618.js +1 -0
- package/dist/4652.js +1 -0
- package/dist/4944.js +1 -0
- package/dist/5173.js +1 -0
- package/dist/5241.js +1 -0
- package/dist/5422.js +1 -0
- package/dist/5422.js.map +1 -0
- package/dist/5442.js +1 -0
- package/dist/5661.js +1 -0
- package/dist/5897.js +1 -0
- package/dist/5897.js.map +1 -0
- package/dist/6022.js +1 -0
- package/dist/609.js +1 -0
- package/dist/609.js.map +1 -0
- package/dist/6468.js +1 -0
- package/dist/6540.js +2 -0
- package/dist/6540.js.LICENSE.txt +9 -0
- package/dist/6540.js.map +1 -0
- package/dist/6589.js +1 -0
- package/dist/6606.js +1 -0
- package/dist/6606.js.map +1 -0
- package/dist/6679.js +1 -0
- package/dist/6825.js +1 -0
- package/dist/6825.js.map +1 -0
- package/dist/6840.js +1 -0
- package/dist/6841.js +1 -0
- package/dist/6841.js.map +1 -0
- package/dist/6859.js +1 -0
- package/dist/7097.js +1 -0
- package/dist/7159.js +1 -0
- package/dist/723.js +1 -0
- package/dist/7240.js +1 -0
- package/dist/7240.js.map +1 -0
- package/dist/7255.js +1 -0
- package/dist/7255.js.map +1 -0
- package/dist/7617.js +1 -0
- package/dist/795.js +1 -0
- package/dist/8163.js +1 -0
- package/dist/8349.js +1 -0
- package/dist/8371.js +1 -0
- package/dist/8569.js +2 -0
- package/dist/8569.js.LICENSE.txt +41 -0
- package/dist/8569.js.map +1 -0
- package/dist/8600.js +1 -0
- package/dist/8600.js.map +1 -0
- package/dist/8618.js +1 -0
- package/dist/8885.js +1 -0
- package/dist/8885.js.map +1 -0
- package/dist/890.js +1 -0
- package/dist/9214.js +1 -0
- package/dist/9538.js +1 -0
- package/dist/9569.js +1 -0
- package/dist/961.js +2 -0
- package/dist/961.js.LICENSE.txt +19 -0
- package/dist/961.js.map +1 -0
- package/dist/963.js +1 -0
- package/dist/963.js.map +1 -0
- package/dist/986.js +1 -0
- package/dist/9879.js +1 -0
- package/dist/9895.js +1 -0
- package/dist/9900.js +1 -0
- package/dist/9913.js +1 -0
- package/dist/main.js +2 -0
- package/dist/main.js.LICENSE.txt +51 -0
- package/dist/main.js.map +1 -0
- package/dist/openmrs-esm-dispensing-app.js +1 -0
- package/dist/openmrs-esm-dispensing-app.js.buildmanifest.json +1645 -0
- package/dist/openmrs-esm-dispensing-app.js.map +1 -0
- package/dist/routes.json +1 -0
- package/e2e/README.md +119 -0
- package/e2e/commands/drug-order-operations.ts +43 -0
- package/e2e/commands/encounter-operations.ts +60 -0
- package/e2e/commands/index.ts +5 -0
- package/e2e/commands/patient-operations.ts +109 -0
- package/e2e/commands/provider-operations.ts +9 -0
- package/e2e/commands/types/index.ts +157 -0
- package/e2e/commands/visit-operations.ts +38 -0
- package/e2e/core/global-setup.ts +32 -0
- package/e2e/core/index.ts +1 -0
- package/e2e/core/test.ts +31 -0
- package/e2e/fixtures/api.ts +27 -0
- package/e2e/fixtures/index.ts +1 -0
- package/e2e/pages/dispensing-page.ts +9 -0
- package/e2e/pages/index.ts +1 -0
- package/e2e/specs/active-prescriptions.spec.ts +72 -0
- package/e2e/specs/close-prescription.spec.ts +71 -0
- package/e2e/specs/dispense-medication.spec.ts +71 -0
- package/e2e/specs/pause-prescription.spec.ts +72 -0
- package/e2e/support/github/Dockerfile +34 -0
- package/e2e/support/github/docker-compose.yml +24 -0
- package/e2e/support/github/run-e2e-docker-env.sh +42 -0
- package/e2e/types/index.ts +157 -0
- package/example.env +7 -0
- package/jest.config.js +24 -0
- package/package.json +110 -0
- package/playwright.config.ts +36 -0
- package/prettier.config.js +8 -0
- package/src/components/action-buttons.component.tsx +87 -0
- package/src/components/action-buttons.scss +16 -0
- package/src/components/action-buttons.test.tsx +217 -0
- package/src/components/medication-card.component.tsx +37 -0
- package/src/components/medication-card.scss +20 -0
- package/src/components/medication-card.test.tsx +36 -0
- package/src/components/medication-dispense-review.scss +108 -0
- package/src/components/medication-event.component.tsx +96 -0
- package/src/components/medication-event.scss +44 -0
- package/src/components/medication-event.test.tsx +212 -0
- package/src/components/prescription-actions/close-action-button.component.tsx +50 -0
- package/src/components/prescription-actions/dispense-action-button.component.tsx +57 -0
- package/src/components/prescription-actions/pause-action-button.component.tsx +49 -0
- package/src/conditions/conditions.component.tsx +118 -0
- package/src/conditions/conditions.resource.ts +100 -0
- package/src/conditions/conditions.scss +26 -0
- package/src/conditions/conditions.test.tsx +200 -0
- package/src/config-schema.ts +192 -0
- package/src/constants.ts +22 -0
- package/src/dashboard/dispensing-dashboard-link.component.tsx +36 -0
- package/src/dashboard/dispensing-dashboard.component.tsx +36 -0
- package/src/declarations.d.ts +2 -0
- package/src/diagnoses/diagnoses.component.tsx +111 -0
- package/src/diagnoses/diagnoses.resource.ts +30 -0
- package/src/diagnoses/diagnoses.scss +31 -0
- package/src/dispensing-link.component.tsx +9 -0
- package/src/dispensing-tiles/dispensing-tile.component.tsx +42 -0
- package/src/dispensing-tiles/dispensing-tile.scss +43 -0
- package/src/dispensing-tiles/dispensing-tiles.component.tsx +39 -0
- package/src/dispensing-tiles/dispensing-tiles.resource.tsx +30 -0
- package/src/dispensing-tiles/dispensing-tiles.scss +11 -0
- package/src/dispensing.component.tsx +31 -0
- package/src/dispensing.scss +5 -0
- package/src/dispensing.test.tsx +9 -0
- package/src/fill-prescription/fill-prescription-button.component.tsx +103 -0
- package/src/fill-prescription/fill-prescription-button.scss +8 -0
- package/src/fill-prescription/on-prescription-filled.modal.tsx +140 -0
- package/src/fill-prescription/on-prescription-filled.scss +7 -0
- package/src/forms/close-dispense-form.workspace.tsx +194 -0
- package/src/forms/dispense-form.workspace.test.tsx +334 -0
- package/src/forms/dispense-form.workspace.tsx +324 -0
- package/src/forms/forms.scss +152 -0
- package/src/forms/medication-dispense-review.component.tsx +649 -0
- package/src/forms/medication-dispense-review.test.tsx +158 -0
- package/src/forms/pause-dispense-form.workspace.tsx +196 -0
- package/src/forms/stock-dispense/stock-dispense.component.tsx +126 -0
- package/src/forms/stock-dispense/stock.resource.tsx +67 -0
- package/src/history/delete-confirm.modal.tsx +35 -0
- package/src/history/history-and-comments.component.tsx +338 -0
- package/src/history/history-and-comments.scss +54 -0
- package/src/index.ts +57 -0
- package/src/location/location.resource.test.tsx +108 -0
- package/src/location/location.resource.tsx +32 -0
- package/src/medication/medication.resource.test.tsx +156 -0
- package/src/medication/medication.resource.tsx +45 -0
- package/src/medication-dispense/medication-dispense.resource.test.tsx +243 -0
- package/src/medication-dispense/medication-dispense.resource.tsx +178 -0
- package/src/medication-request/medication-request.resource.test.tsx +1333 -0
- package/src/medication-request/medication-request.resource.tsx +257 -0
- package/src/patient/patient-info-cell.component.tsx +26 -0
- package/src/patient/patient.resources.ts +14 -0
- package/src/pharmacy-header/pharmacy-header.component.tsx +35 -0
- package/src/pharmacy-header/pharmacy-header.scss +55 -0
- package/src/pharmacy-header/pharmacy-illustration.component.tsx +30 -0
- package/src/prescriptions/patient-search-tab-panel.component.tsx +58 -0
- package/src/prescriptions/patient-search-tab-panel.scss +26 -0
- package/src/prescriptions/prescription-actions.component.tsx +24 -0
- package/src/prescriptions/prescription-actions.scss +14 -0
- package/src/prescriptions/prescription-details.component.tsx +152 -0
- package/src/prescriptions/prescription-details.scss +87 -0
- package/src/prescriptions/prescription-details.test.tsx +267 -0
- package/src/prescriptions/prescription-expanded.component.tsx +58 -0
- package/src/prescriptions/prescription-expanded.scss +56 -0
- package/src/prescriptions/prescription-tab-lists.component.tsx +70 -0
- package/src/prescriptions/prescription-tab-panel.component.tsx +83 -0
- package/src/prescriptions/prescriptions-table.component.tsx +189 -0
- package/src/prescriptions/prescriptions.scss +152 -0
- package/src/print-prescription/prescription-print-action.component.tsx +30 -0
- package/src/print-prescription/prescription-print-preview.modal.tsx +92 -0
- package/src/print-prescription/prescription-printout.component.tsx +154 -0
- package/src/print-prescription/print-prescription.scss +75 -0
- package/src/print-prescription/printable-prescriptions.component.tsx +57 -0
- package/src/routes.json +137 -0
- package/src/types.ts +530 -0
- package/src/utils.test.ts +2947 -0
- package/src/utils.ts +637 -0
- package/tools/i18next-parser.config.js +89 -0
- package/tools/setup-tests.ts +8 -0
- package/tools/update-openmrs-deps.mjs +42 -0
- package/translations/am.json +133 -0
- package/translations/ar.json +133 -0
- package/translations/ar_SY.json +133 -0
- package/translations/bn.json +133 -0
- package/translations/cs.json +133 -0
- package/translations/de.json +133 -0
- package/translations/en.json +133 -0
- package/translations/en_US.json +133 -0
- package/translations/es.json +133 -0
- package/translations/es_MX.json +133 -0
- package/translations/fr.json +133 -0
- package/translations/he.json +133 -0
- package/translations/hi.json +133 -0
- package/translations/hi_IN.json +133 -0
- package/translations/id.json +133 -0
- package/translations/it.json +133 -0
- package/translations/ka.json +133 -0
- package/translations/km.json +133 -0
- package/translations/ku.json +133 -0
- package/translations/ky.json +133 -0
- package/translations/lg.json +133 -0
- package/translations/ne.json +133 -0
- package/translations/pl.json +133 -0
- package/translations/pt.json +133 -0
- package/translations/pt_BR.json +133 -0
- package/translations/qu.json +133 -0
- package/translations/ro_RO.json +133 -0
- package/translations/ru_RU.json +133 -0
- package/translations/si.json +133 -0
- package/translations/sq.json +133 -0
- package/translations/sw.json +133 -0
- package/translations/sw_KE.json +133 -0
- package/translations/tr.json +133 -0
- package/translations/tr_TR.json +133 -0
- package/translations/uk.json +133 -0
- package/translations/uz.json +133 -0
- package/translations/uz@Latn.json +133 -0
- package/translations/uz_UZ.json +133 -0
- package/translations/vi.json +133 -0
- package/translations/zh.json +133 -0
- package/translations/zh_CN.json +133 -0
- package/translations/zh_TW.json +133 -0
- package/tsconfig.json +23 -0
- package/turbo.json +41 -0
- package/webpack.config.js +1 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import { useConfig, useSession } from '@openmrs/esm-framework';
|
|
4
|
+
import { type MedicationDispense, MedicationDispenseStatus } from '../types';
|
|
5
|
+
import MedicationDispenseReview from './medication-dispense-review.component';
|
|
6
|
+
import { useProviders } from '../medication-dispense/medication-dispense.resource';
|
|
7
|
+
|
|
8
|
+
jest.mock('../medication-dispense/medication-dispense.resource', () => ({
|
|
9
|
+
...jest.requireActual('../medication-dispense/medication-dispense.resource'),
|
|
10
|
+
useProviders: jest.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
const mockUseConfig = jest.mocked(useConfig);
|
|
14
|
+
const mockUseProviders = jest.mocked(useProviders);
|
|
15
|
+
const mockUseSession = jest.mocked(useSession);
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
mockUseConfig.mockReturnValue({
|
|
19
|
+
dispenseBehavior: {
|
|
20
|
+
allowModifyingPrescription: false,
|
|
21
|
+
restrictTotalQuantityDispensed: false,
|
|
22
|
+
},
|
|
23
|
+
valueSets: {
|
|
24
|
+
substitutionType: { uuid: '123' },
|
|
25
|
+
substitutionReason: { uuid: 'abc' },
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
mockUseProviders.mockReturnValue([]);
|
|
29
|
+
mockUseSession.mockReturnValue({
|
|
30
|
+
currentProvider: {
|
|
31
|
+
uuid: 'user-uuid-123',
|
|
32
|
+
identifier: 'user-identifier-123',
|
|
33
|
+
},
|
|
34
|
+
} as any);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('Medication Dispense Review Component tests', () => {
|
|
38
|
+
test('component should render medication dispense review', () => {
|
|
39
|
+
const medicationDispense: MedicationDispense = {
|
|
40
|
+
resourceType: 'MedicationDispense',
|
|
41
|
+
id: 'ff2fa24c-b0d4-457c-bbdf-7d6512b8b746',
|
|
42
|
+
type: {
|
|
43
|
+
coding: [
|
|
44
|
+
{
|
|
45
|
+
code: '123',
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
meta: {
|
|
50
|
+
lastUpdated: '2023-01-10T10:52:36.000-05:00',
|
|
51
|
+
},
|
|
52
|
+
status: MedicationDispenseStatus.completed,
|
|
53
|
+
medicationReference: {
|
|
54
|
+
reference: 'Medication/78f96684-dfbe-11e9-8a34-2a2ae2dbcce4',
|
|
55
|
+
type: 'Medication',
|
|
56
|
+
display: 'Lamivudine (3TC), Oral solution, 10mg/mL, 240mL bottle',
|
|
57
|
+
},
|
|
58
|
+
subject: {
|
|
59
|
+
reference: 'Patient/b36dbb26-8309-4d51-80ed-07bbb63ab928',
|
|
60
|
+
type: 'Patient',
|
|
61
|
+
display: 'Test, Fiona (ZL EMR ID: Y480K4)',
|
|
62
|
+
},
|
|
63
|
+
performer: [
|
|
64
|
+
{
|
|
65
|
+
actor: {
|
|
66
|
+
reference: 'Practitioner/6558dfef-bfe1-488e-b048-ed286aded924',
|
|
67
|
+
type: 'Practitioner',
|
|
68
|
+
identifier: {
|
|
69
|
+
value: 'LADAL',
|
|
70
|
+
},
|
|
71
|
+
display: 'Dylan, Bob (Identifier: LADAL)',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
location: {
|
|
76
|
+
reference: 'Location/47521fa1-ac7f-4fdb-b167-a86fe223a3d9',
|
|
77
|
+
type: 'Location',
|
|
78
|
+
display: 'Klinik MNT',
|
|
79
|
+
},
|
|
80
|
+
authorizingPrescription: [
|
|
81
|
+
{
|
|
82
|
+
reference: 'MedicationRequest/9e416e5f-a435-433c-99d3-8b2fd1e8a421',
|
|
83
|
+
type: 'MedicationRequest',
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
quantity: {
|
|
87
|
+
value: 30.0,
|
|
88
|
+
unit: 'Tablet',
|
|
89
|
+
system: 'http://snomed.info/sct',
|
|
90
|
+
code: '385055001',
|
|
91
|
+
},
|
|
92
|
+
whenPrepared: '2023-01-10T10:52:22-05:00',
|
|
93
|
+
whenHandedOver: '2023-01-10T10:52:22-05:00',
|
|
94
|
+
dosageInstruction: [
|
|
95
|
+
{
|
|
96
|
+
timing: {
|
|
97
|
+
code: {
|
|
98
|
+
coding: [
|
|
99
|
+
{
|
|
100
|
+
code: '023bee9c-2dbb-483c-8401-46f0ccf6b333',
|
|
101
|
+
display: 'ON (night)',
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
text: 'ON (night)',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
asNeededBoolean: false,
|
|
108
|
+
route: {
|
|
109
|
+
coding: [
|
|
110
|
+
{
|
|
111
|
+
code: '46aaaca8-1f21-410a-aac9-67bfcc1fd577',
|
|
112
|
+
display: 'Oral',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
system: 'https://openconceptlab.org/orgs/CIEL/sources/CIEL',
|
|
116
|
+
code: '160240',
|
|
117
|
+
display: 'Oral',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
system: 'http://snomed.info/sct',
|
|
121
|
+
code: '26643006',
|
|
122
|
+
display: 'Oral',
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
text: 'Oral',
|
|
126
|
+
},
|
|
127
|
+
doseAndRate: [
|
|
128
|
+
{
|
|
129
|
+
doseQuantity: {
|
|
130
|
+
value: 1.0,
|
|
131
|
+
unit: 'Tablet',
|
|
132
|
+
system: 'http://snomed.info/sct',
|
|
133
|
+
code: '385055001',
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
substitution: {
|
|
140
|
+
wasSubstituted: false,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const mockUpdate = jest.fn();
|
|
145
|
+
render(
|
|
146
|
+
<MedicationDispenseReview
|
|
147
|
+
medicationDispense={medicationDispense}
|
|
148
|
+
updateMedicationDispense={mockUpdate}
|
|
149
|
+
isFreeTextDosage={false}
|
|
150
|
+
setIsFreeTextDosage={() => {}}
|
|
151
|
+
quantityRemaining={30}
|
|
152
|
+
quantityDispensed={30}
|
|
153
|
+
/>,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// TODO test expected views and various interactions
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Button, ComboBox, Form, InlineLoading } from '@carbon/react';
|
|
4
|
+
import {
|
|
5
|
+
ExtensionSlot,
|
|
6
|
+
getCoreTranslation,
|
|
7
|
+
ResponsiveWrapper,
|
|
8
|
+
showSnackbar,
|
|
9
|
+
useConfig,
|
|
10
|
+
usePatient,
|
|
11
|
+
Workspace2,
|
|
12
|
+
type Workspace2DefinitionProps,
|
|
13
|
+
} from '@openmrs/esm-framework';
|
|
14
|
+
import { saveMedicationDispense, useReasonForPauseValueSet } from '../medication-dispense/medication-dispense.resource';
|
|
15
|
+
import { updateMedicationRequestFulfillerStatus } from '../medication-request/medication-request.resource';
|
|
16
|
+
import { getUuidFromReference, markEncounterAsStale, revalidate } from '../utils';
|
|
17
|
+
import { type MedicationDispense, MedicationDispenseStatus, MedicationRequestFulfillerStatus } from '../types';
|
|
18
|
+
import { type PharmacyConfig } from '../config-schema';
|
|
19
|
+
import styles from './forms.scss';
|
|
20
|
+
|
|
21
|
+
type PauseDispenseFormProps = {
|
|
22
|
+
medicationDispense: MedicationDispense;
|
|
23
|
+
mode: 'enter' | 'edit';
|
|
24
|
+
patientUuid?: string;
|
|
25
|
+
encounterUuid: string;
|
|
26
|
+
customWorkspaceTitle?: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const PauseDispenseForm: React.FC<Workspace2DefinitionProps<PauseDispenseFormProps, {}, {}>> = ({
|
|
30
|
+
workspaceProps: { medicationDispense, mode, patientUuid, encounterUuid, customWorkspaceTitle },
|
|
31
|
+
closeWorkspace,
|
|
32
|
+
}) => {
|
|
33
|
+
const { t } = useTranslation();
|
|
34
|
+
const config = useConfig<PharmacyConfig>();
|
|
35
|
+
const { patient, isLoading } = usePatient(patientUuid);
|
|
36
|
+
|
|
37
|
+
// Keep track of medication dispense payload
|
|
38
|
+
const [medicationDispensePayload, setMedicationDispensePayload] = useState<MedicationDispense>();
|
|
39
|
+
|
|
40
|
+
// whether or not the form is valid and ready to submit
|
|
41
|
+
const [isValid, setIsValid] = useState(false);
|
|
42
|
+
|
|
43
|
+
// to prevent duplicate submits
|
|
44
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
45
|
+
const [reasonsForPause, setReasonsForPause] = useState([]);
|
|
46
|
+
const { reasonForPauseValueSet } = useReasonForPauseValueSet(config.valueSets.reasonForPause.uuid);
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const reasonForPauseOptions = [];
|
|
50
|
+
|
|
51
|
+
if (reasonForPauseValueSet?.compose?.include) {
|
|
52
|
+
const uuidValueSet = reasonForPauseValueSet.compose.include.find((include) => !include.system);
|
|
53
|
+
if (uuidValueSet) {
|
|
54
|
+
uuidValueSet.concept?.forEach((concept) =>
|
|
55
|
+
reasonForPauseOptions.push({
|
|
56
|
+
id: concept.code,
|
|
57
|
+
text: concept.display,
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
reasonForPauseOptions.sort((a, b) => a.text.localeCompare(b.text));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
setReasonsForPause(reasonForPauseOptions);
|
|
64
|
+
}, [reasonForPauseValueSet]);
|
|
65
|
+
|
|
66
|
+
const handleSubmit = () => {
|
|
67
|
+
if (!isSubmitting) {
|
|
68
|
+
setIsSubmitting(true);
|
|
69
|
+
const abortController = new AbortController();
|
|
70
|
+
markEncounterAsStale(encounterUuid);
|
|
71
|
+
saveMedicationDispense(medicationDispensePayload, MedicationDispenseStatus.on_hold, abortController)
|
|
72
|
+
.then((response) => {
|
|
73
|
+
// only update request status when added a new dispense event, not updating
|
|
74
|
+
if (response.ok && !medicationDispense.id) {
|
|
75
|
+
return updateMedicationRequestFulfillerStatus(
|
|
76
|
+
getUuidFromReference(
|
|
77
|
+
medicationDispensePayload.authorizingPrescription[0].reference, // assumes authorizing prescription exist
|
|
78
|
+
),
|
|
79
|
+
MedicationRequestFulfillerStatus.on_hold,
|
|
80
|
+
);
|
|
81
|
+
} else {
|
|
82
|
+
return response;
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
.then((response) => {
|
|
86
|
+
if (response.ok) {
|
|
87
|
+
revalidate(encounterUuid);
|
|
88
|
+
showSnackbar({
|
|
89
|
+
kind: 'success',
|
|
90
|
+
title: t(
|
|
91
|
+
mode === 'enter' ? 'medicationDispensePaused' : 'medicationDispenseUpdated',
|
|
92
|
+
mode === 'enter' ? 'Medication dispense paused.' : 'Dispense record successfully updated.',
|
|
93
|
+
),
|
|
94
|
+
});
|
|
95
|
+
closeWorkspace({ discardUnsavedChanges: true });
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
.catch((error) => {
|
|
99
|
+
showSnackbar({
|
|
100
|
+
title: t(
|
|
101
|
+
mode === 'enter' ? 'medicationDispensePauseError' : 'medicationDispenseUpdatedError',
|
|
102
|
+
mode === 'enter' ? 'Error pausing medication dispense.' : 'Error updating dispense record',
|
|
103
|
+
),
|
|
104
|
+
kind: 'error',
|
|
105
|
+
subtitle: error?.message,
|
|
106
|
+
});
|
|
107
|
+
setIsSubmitting(false);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const checkIsValid = () => {
|
|
113
|
+
if (medicationDispensePayload && medicationDispensePayload.statusReasonCodeableConcept?.coding[0].code) {
|
|
114
|
+
setIsValid(true);
|
|
115
|
+
} else {
|
|
116
|
+
setIsValid(false);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// initialize the internal dispense payload with the dispenses passed in as props
|
|
121
|
+
useEffect(() => setMedicationDispensePayload(medicationDispense), [medicationDispense]);
|
|
122
|
+
|
|
123
|
+
// check is valid on any changes
|
|
124
|
+
useEffect(checkIsValid, [medicationDispensePayload]);
|
|
125
|
+
|
|
126
|
+
const bannerState = useMemo(() => {
|
|
127
|
+
if (patient) {
|
|
128
|
+
return {
|
|
129
|
+
patient,
|
|
130
|
+
patientUuid,
|
|
131
|
+
hideActionsOverflow: true,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}, [patient, patientUuid]);
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<Workspace2 title={customWorkspaceTitle ?? t('pausePrescription', 'Pause prescription')}>
|
|
138
|
+
<Form className={styles.formWrapper}>
|
|
139
|
+
<div>
|
|
140
|
+
{isLoading && (
|
|
141
|
+
<InlineLoading
|
|
142
|
+
className={styles.bannerLoading}
|
|
143
|
+
iconDescription="Loading"
|
|
144
|
+
description="Loading banner"
|
|
145
|
+
status="active"
|
|
146
|
+
/>
|
|
147
|
+
)}
|
|
148
|
+
{patient && <ExtensionSlot name="patient-header-slot" state={bannerState} />}
|
|
149
|
+
<section className={styles.formGroup}>
|
|
150
|
+
<ResponsiveWrapper>
|
|
151
|
+
<ComboBox
|
|
152
|
+
id="reasonForPause"
|
|
153
|
+
items={reasonsForPause}
|
|
154
|
+
titleText={t('reasonForPause', 'Reason for pause')}
|
|
155
|
+
itemToString={(item) => item?.text}
|
|
156
|
+
initialSelectedItem={{
|
|
157
|
+
id: medicationDispense.statusReasonCodeableConcept?.coding[0]?.code,
|
|
158
|
+
text: medicationDispense.statusReasonCodeableConcept?.text,
|
|
159
|
+
}}
|
|
160
|
+
onChange={({ selectedItem }) => {
|
|
161
|
+
setMedicationDispensePayload({
|
|
162
|
+
...medicationDispensePayload,
|
|
163
|
+
statusReasonCodeableConcept: {
|
|
164
|
+
coding: [
|
|
165
|
+
{
|
|
166
|
+
code: selectedItem?.id,
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
}}
|
|
172
|
+
/>
|
|
173
|
+
</ResponsiveWrapper>
|
|
174
|
+
</section>
|
|
175
|
+
</div>
|
|
176
|
+
<section className={styles.buttonGroup}>
|
|
177
|
+
<Button
|
|
178
|
+
disabled={isSubmitting}
|
|
179
|
+
onClick={() => {
|
|
180
|
+
closeWorkspace();
|
|
181
|
+
}}
|
|
182
|
+
kind="secondary">
|
|
183
|
+
{getCoreTranslation('cancel', 'Cancel')}
|
|
184
|
+
</Button>
|
|
185
|
+
<Button disabled={!isValid || isSubmitting} onClick={handleSubmit}>
|
|
186
|
+
{/* t('pause', 'Pause')
|
|
187
|
+
t('saveChanges', 'Save changes') */}
|
|
188
|
+
{t(mode === 'enter' ? 'pause' : 'saveChanges', mode === 'enter' ? 'Pause' : 'Save changes')}
|
|
189
|
+
</Button>
|
|
190
|
+
</section>
|
|
191
|
+
</Form>
|
|
192
|
+
</Workspace2>
|
|
193
|
+
);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export default PauseDispenseForm;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { ComboBox, InlineLoading, InlineNotification, Layer } from '@carbon/react';
|
|
4
|
+
import { formatDate, useConfig } from '@openmrs/esm-framework';
|
|
5
|
+
import { type MedicationDispense, type InventoryItem } from '../../types';
|
|
6
|
+
import { type PharmacyConfig } from '../../config-schema';
|
|
7
|
+
import { useDispenseStock } from './stock.resource';
|
|
8
|
+
|
|
9
|
+
type StockDispenseProps = {
|
|
10
|
+
medicationDispense: MedicationDispense;
|
|
11
|
+
updateInventoryItem: (inventoryItem: InventoryItem) => void;
|
|
12
|
+
inventoryItem: InventoryItem;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const StockDispense: React.FC<StockDispenseProps> = ({ medicationDispense, updateInventoryItem }) => {
|
|
16
|
+
const { t } = useTranslation();
|
|
17
|
+
const config = useConfig<PharmacyConfig>();
|
|
18
|
+
|
|
19
|
+
const drugUuid = medicationDispense?.medicationReference?.reference?.split('/')[1];
|
|
20
|
+
const { inventoryItems, error, isLoading } = useDispenseStock(drugUuid);
|
|
21
|
+
const validInventoryItems = inventoryItems
|
|
22
|
+
.filter((item) => isValidBatch(medicationDispense, item))
|
|
23
|
+
.sort((a, b) => new Date(a.expiration).getTime() - new Date(b.expiration).getTime());
|
|
24
|
+
|
|
25
|
+
function parseDate(dateString) {
|
|
26
|
+
return new Date(dateString);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//check whether the drug will expire before the medication period ends
|
|
30
|
+
function isValidBatch(medicationToDispense, inventoryItem) {
|
|
31
|
+
if (typeof config !== 'undefined' && !config.validateBatch) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
if (medicationToDispense?.dosageInstruction && medicationToDispense?.dosageInstruction.length > 0) {
|
|
35
|
+
return medicationToDispense.dosageInstruction.some((instruction) => {
|
|
36
|
+
if (
|
|
37
|
+
instruction.timing?.repeat?.duration &&
|
|
38
|
+
instruction.timing?.repeat?.durationUnit &&
|
|
39
|
+
inventoryItem.quantity > 0
|
|
40
|
+
) {
|
|
41
|
+
const durationUnit = instruction.timing.repeat.durationUnit;
|
|
42
|
+
const durationValue = instruction.timing.repeat.duration;
|
|
43
|
+
const lastMedicationDate = new Date();
|
|
44
|
+
|
|
45
|
+
switch (durationUnit) {
|
|
46
|
+
case 's':
|
|
47
|
+
lastMedicationDate.setSeconds(lastMedicationDate.getSeconds() + durationValue);
|
|
48
|
+
break;
|
|
49
|
+
case 'min':
|
|
50
|
+
lastMedicationDate.setMinutes(lastMedicationDate.getMinutes() + durationValue);
|
|
51
|
+
break;
|
|
52
|
+
case 'h':
|
|
53
|
+
lastMedicationDate.setHours(lastMedicationDate.getHours() + durationValue);
|
|
54
|
+
break;
|
|
55
|
+
case 'd':
|
|
56
|
+
lastMedicationDate.setDate(lastMedicationDate.getDate() + durationValue);
|
|
57
|
+
break;
|
|
58
|
+
case 'wk':
|
|
59
|
+
lastMedicationDate.setDate(lastMedicationDate.getDate() + durationValue * 7);
|
|
60
|
+
break;
|
|
61
|
+
case 'mo':
|
|
62
|
+
lastMedicationDate.setMonth(lastMedicationDate.getMonth() + durationValue);
|
|
63
|
+
break;
|
|
64
|
+
case 'y':
|
|
65
|
+
lastMedicationDate.setFullYear(lastMedicationDate.getFullYear() + durationValue);
|
|
66
|
+
break;
|
|
67
|
+
default:
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const expiryDate = parseDate(inventoryItem.expiration);
|
|
72
|
+
return expiryDate > lastMedicationDate;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const toStockDispense = (inventoryItems) => {
|
|
81
|
+
return t(
|
|
82
|
+
'stockDispenseDetails',
|
|
83
|
+
'Batch: {{batchNumber}} - Quantity: {{quantity}} ({{quantityUoM}}) - Expiry: {{expiration}}',
|
|
84
|
+
{
|
|
85
|
+
batchNumber: inventoryItems.batchNumber,
|
|
86
|
+
quantity: Math.floor(inventoryItems.quantity),
|
|
87
|
+
quantityUoM: inventoryItems.quantityUoM,
|
|
88
|
+
expiration: formatDate(new Date(inventoryItems.expiration)),
|
|
89
|
+
},
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
if (error) {
|
|
94
|
+
return (
|
|
95
|
+
<InlineNotification
|
|
96
|
+
aria-label="closes notification"
|
|
97
|
+
kind="error"
|
|
98
|
+
lowContrast={true}
|
|
99
|
+
statusIconDescription="notification"
|
|
100
|
+
subtitle={t('errorLoadingInventoryItems', 'Error fetching inventory items')}
|
|
101
|
+
title={t('error', 'Error')}
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (isLoading) {
|
|
107
|
+
return <InlineLoading description={t('loadingInventoryItems', 'Loading inventory items...')} />;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<Layer>
|
|
112
|
+
<ComboBox
|
|
113
|
+
id="stockDispense"
|
|
114
|
+
items={validInventoryItems}
|
|
115
|
+
onChange={({ selectedItem }) => {
|
|
116
|
+
updateInventoryItem(selectedItem);
|
|
117
|
+
}}
|
|
118
|
+
itemToString={(item) => (item ? toStockDispense(item) : '')}
|
|
119
|
+
titleText={t('stockDispense', 'Stock Dispense')}
|
|
120
|
+
placeholder={t('selectStockDispense', 'Select stock to dispense from')}
|
|
121
|
+
/>
|
|
122
|
+
</Layer>
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export default StockDispense;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import useSWR from 'swr';
|
|
2
|
+
import { openmrsFetch, useSession } from '@openmrs/esm-framework';
|
|
3
|
+
import { type StockDispenseRequest, type InventoryItem, type MedicationDispense } from '../../types';
|
|
4
|
+
import { getUuidFromReference } from '../../utils';
|
|
5
|
+
|
|
6
|
+
//TODO: Add configuration to retrieve the stock dispense endpoint
|
|
7
|
+
// For stock dispense to work, stock management module should be installed and configured
|
|
8
|
+
/**
|
|
9
|
+
* Fetches the inventory items for a given drug UUID.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} drugUuid - The UUID of the drug.
|
|
12
|
+
* @returns {Array} - The inventory items.
|
|
13
|
+
*/
|
|
14
|
+
export const useDispenseStock = (drugUuid: string) => {
|
|
15
|
+
const session = useSession();
|
|
16
|
+
const url = `/ws/rest/v1/stockmanagement/stockiteminventory?v=default&totalCount=true&drugUuid=${drugUuid}&includeBatchNo=true&groupBy=LocationStockItemBatchNo&dispenseLocationUuid=${session?.sessionLocation?.uuid}&includeStrength=1&includeConceptRefIds=1&emptyBatch=1&emptyBatchLocationUuid=${session?.sessionLocation?.uuid}&dispenseAtLocation=1`;
|
|
17
|
+
const { data, error, isLoading } = useSWR<{ data: { results: Array<InventoryItem> } }>(url, openmrsFetch);
|
|
18
|
+
return { inventoryItems: data?.data?.results ?? [], error, isLoading };
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Sends a POST request to the inventory dispense endpoint with the provided stock dispense request.
|
|
23
|
+
*
|
|
24
|
+
* @param {AbortController} abortController - The AbortController used to cancel the request.
|
|
25
|
+
* @returns {Promise<Response>} - A Promise that resolves to the response of the POST request.
|
|
26
|
+
*/
|
|
27
|
+
export async function sendStockDispenseRequest(
|
|
28
|
+
stockDispenseRequest,
|
|
29
|
+
abortController: AbortController,
|
|
30
|
+
): Promise<Response> {
|
|
31
|
+
const url = '/ws/rest/v1/stockmanagement/dispenserequest';
|
|
32
|
+
return await openmrsFetch(url, {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
signal: abortController.signal,
|
|
35
|
+
headers: {
|
|
36
|
+
'Content-Type': 'application/json',
|
|
37
|
+
},
|
|
38
|
+
body: JSON.stringify({ dispenseItems: [stockDispenseRequest] }),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Creates a stock dispense request payload.
|
|
44
|
+
*
|
|
45
|
+
* @param inventoryItem - The inventory item to dispense.
|
|
46
|
+
* @param patientUuid - The UUID of the patient.
|
|
47
|
+
* @param encounterUuid - The UUID of the encounter.
|
|
48
|
+
* @param medicationDispensePayload - The medication dispense payload.
|
|
49
|
+
* @returns The stock dispense request payload.
|
|
50
|
+
*/
|
|
51
|
+
export const createStockDispenseRequestPayload = (
|
|
52
|
+
inventoryItem: InventoryItem,
|
|
53
|
+
patientUuid: string,
|
|
54
|
+
encounterUuid: string,
|
|
55
|
+
medicationDispensePayload: MedicationDispense,
|
|
56
|
+
): StockDispenseRequest => {
|
|
57
|
+
return {
|
|
58
|
+
dispenseLocation: inventoryItem.locationUuid,
|
|
59
|
+
patient: patientUuid,
|
|
60
|
+
order: getUuidFromReference(medicationDispensePayload.authorizingPrescription[0].reference),
|
|
61
|
+
encounter: encounterUuid,
|
|
62
|
+
stockItem: inventoryItem?.stockItemUuid,
|
|
63
|
+
stockBatch: inventoryItem.stockBatchUuid,
|
|
64
|
+
stockItemPackagingUOM: inventoryItem.quantityUoMUuid,
|
|
65
|
+
quantity: medicationDispensePayload.quantity.value,
|
|
66
|
+
};
|
|
67
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Button, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
|
|
5
|
+
const DeleteConfirmModal = ({
|
|
6
|
+
title,
|
|
7
|
+
onClose,
|
|
8
|
+
onDelete,
|
|
9
|
+
|
|
10
|
+
message,
|
|
11
|
+
}: {
|
|
12
|
+
isOpen: boolean;
|
|
13
|
+
onClose: () => void;
|
|
14
|
+
onDelete: () => void;
|
|
15
|
+
title: string;
|
|
16
|
+
message: string;
|
|
17
|
+
}) => {
|
|
18
|
+
const { t } = useTranslation();
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<React.Fragment>
|
|
22
|
+
<ModalHeader closeModal={onClose} title={title}></ModalHeader>
|
|
23
|
+
<ModalBody>{message}</ModalBody>
|
|
24
|
+
<ModalFooter>
|
|
25
|
+
<Button kind="secondary" onClick={onClose}>
|
|
26
|
+
{t('cancel', 'Cancel')}
|
|
27
|
+
</Button>
|
|
28
|
+
<Button kind="danger" onClick={onDelete}>
|
|
29
|
+
{t('delete', 'Delete')}
|
|
30
|
+
</Button>
|
|
31
|
+
</ModalFooter>
|
|
32
|
+
</React.Fragment>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
export default DeleteConfirmModal;
|