@openmrs/esm-dispensing-app 1.9.1 → 1.9.2-pre.1002
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/.eslintrc +8 -0
- package/.turbo/cache/f95995cd573a4ae0-meta.json +1 -0
- package/.turbo/cache/f95995cd573a4ae0.tar.zst +0 -0
- package/.turbo/turbo-build.log +38 -0
- package/README.md +14 -4
- package/dist/1119.js +1 -1
- package/dist/1197.js +1 -1
- package/dist/1856.js +1 -0
- package/dist/1856.js.map +1 -0
- package/dist/2146.js +1 -1
- package/dist/2690.js +1 -1
- package/dist/2898.js +1 -1
- package/dist/2898.js.map +1 -1
- package/dist/3099.js +1 -1
- package/dist/3584.js +1 -1
- package/dist/4055.js +1 -1
- package/dist/4099.js +1 -1
- package/dist/4099.js.map +1 -1
- package/dist/4132.js +1 -1
- package/dist/4300.js +1 -1
- package/dist/4335.js +1 -1
- package/dist/445.js +2 -0
- package/dist/445.js.map +1 -0
- package/dist/4618.js +1 -1
- package/dist/4652.js +1 -1
- package/dist/4944.js +1 -1
- package/dist/4999.js +2 -0
- package/dist/4999.js.map +1 -0
- package/dist/5173.js +1 -1
- package/dist/5241.js +1 -1
- package/dist/5379.js +1 -0
- package/dist/5379.js.map +1 -0
- package/dist/5442.js +1 -1
- package/dist/561.js +1 -0
- package/dist/561.js.map +1 -0
- package/dist/5661.js +1 -1
- package/dist/5897.js +1 -1
- package/dist/5897.js.map +1 -1
- package/dist/6022.js +1 -1
- package/dist/609.js +1 -1
- package/dist/609.js.map +1 -1
- package/dist/6468.js +1 -1
- package/dist/6540.js +1 -1
- package/dist/6540.js.map +1 -1
- package/dist/6679.js +1 -1
- package/dist/67.js +1 -1
- package/dist/67.js.map +1 -1
- package/dist/6840.js +1 -1
- package/dist/6841.js +1 -1
- package/dist/6841.js.map +1 -1
- package/dist/6859.js +1 -1
- package/dist/7097.js +1 -1
- package/dist/7159.js +1 -1
- package/dist/723.js +1 -1
- package/dist/7255.js +1 -0
- package/dist/7255.js.map +1 -0
- package/dist/7617.js +1 -1
- package/dist/795.js +1 -1
- package/dist/8163.js +1 -1
- package/dist/8349.js +1 -1
- package/dist/8600.js +1 -1
- package/dist/8600.js.map +1 -1
- package/dist/8618.js +1 -1
- package/dist/890.js +1 -1
- package/dist/9214.js +1 -1
- package/dist/9538.js +1 -1
- package/dist/9569.js +1 -1
- package/dist/961.js +1 -1
- package/dist/961.js.map +1 -1
- package/dist/963.js +1 -1
- package/dist/963.js.map +1 -1
- package/dist/986.js +1 -1
- package/dist/9879.js +1 -1
- package/dist/9895.js +1 -1
- package/dist/9900.js +1 -1
- package/dist/9913.js +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-dispensing-app.js +1 -1
- package/dist/openmrs-esm-dispensing-app.js.buildmanifest.json +302 -323
- package/dist/openmrs-esm-dispensing-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/e2e/commands/drug-order-operations.ts +2 -3
- package/e2e/commands/encounter-operations.ts +4 -8
- package/e2e/commands/index.ts +3 -3
- package/e2e/commands/patient-operations.ts +1 -1
- package/e2e/commands/provider-operations.ts +2 -5
- package/e2e/commands/visit-operations.ts +1 -1
- package/e2e/fixtures/api.ts +1 -1
- package/e2e/specs/close-prescription.spec.ts +5 -0
- package/e2e/specs/dispense-medication.spec.ts +4 -0
- package/e2e/specs/pause-prescription.spec.ts +5 -0
- package/e2e/support/github/Dockerfile +1 -1
- package/e2e/support/github/run-e2e-docker-env.sh +23 -29
- package/package.json +12 -11
- package/src/components/action-buttons.component.tsx +8 -3
- package/src/components/action-buttons.test.tsx +3 -0
- package/src/components/prescription-actions/close-action-button.component.tsx +3 -1
- package/src/components/prescription-actions/dispense-action-button.component.tsx +3 -1
- package/src/components/prescription-actions/pause-action-button.component.tsx +3 -1
- package/src/dashboard/dispensing-dashboard.component.tsx +3 -0
- package/src/fill-prescription/fill-prescription-button.comonent.tsx +43 -0
- package/src/fill-prescription/fill-prescription-button.scss +8 -0
- package/src/forms/close-dispense-form.workspace.tsx +2 -1
- package/src/forms/dispense-form.workspace.tsx +16 -16
- package/src/forms/forms.scss +15 -0
- package/src/forms/medication-dispense-review.component.tsx +34 -87
- package/src/forms/medication-dispense-review.test.tsx +1 -1
- package/src/forms/pause-dispense-form.workspace.tsx +2 -1
- package/src/history/history-and-comments.component.tsx +2 -0
- package/src/medication-dispense/medication-dispense.resource.tsx +11 -9
- package/src/medication-request/medication-request.resource.test.tsx +7 -3
- package/src/medication-request/medication-request.resource.tsx +47 -44
- 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-details.component.tsx +3 -1
- package/src/prescriptions/prescription-tab-lists.component.tsx +18 -84
- package/src/prescriptions/prescription-tab-panel.component.tsx +45 -148
- package/src/prescriptions/prescriptions-table.component.tsx +164 -0
- package/src/routes.json +2 -2
- package/src/types.ts +10 -1
- package/src/utils.ts +31 -4
- package/tools/setup-tests.ts +7 -0
- package/translations/am.json +3 -0
- package/translations/ar.json +3 -0
- package/translations/ar_SY.json +3 -0
- package/translations/bn.json +3 -0
- package/translations/de.json +3 -0
- package/translations/en.json +3 -0
- package/translations/en_US.json +3 -0
- package/translations/es.json +3 -0
- package/translations/es_MX.json +3 -0
- package/translations/fr.json +3 -0
- package/translations/he.json +3 -0
- package/translations/hi.json +3 -0
- package/translations/hi_IN.json +3 -0
- package/translations/id.json +3 -0
- package/translations/it.json +28 -25
- package/translations/ka.json +3 -0
- package/translations/km.json +3 -0
- package/translations/ku.json +3 -0
- package/translations/ky.json +3 -0
- package/translations/lg.json +3 -0
- package/translations/ne.json +3 -0
- package/translations/pl.json +3 -0
- package/translations/pt.json +3 -0
- package/translations/pt_BR.json +3 -0
- package/translations/qu.json +3 -0
- package/translations/ro_RO.json +3 -0
- package/translations/ru_RU.json +3 -0
- package/translations/si.json +3 -0
- package/translations/sw.json +3 -0
- package/translations/sw_KE.json +3 -0
- package/translations/tr.json +3 -0
- package/translations/tr_TR.json +3 -0
- package/translations/uk.json +3 -0
- package/translations/uz.json +3 -0
- package/translations/uz@Latn.json +3 -0
- package/translations/uz_UZ.json +3 -0
- package/translations/vi.json +3 -0
- package/translations/zh.json +3 -0
- package/translations/zh_CN.json +28 -25
- package/dist/1033.js +0 -1
- package/dist/1033.js.map +0 -1
- package/dist/3414.js +0 -1
- package/dist/3414.js.map +0 -1
- package/dist/6411.js +0 -1
- package/dist/6411.js.map +0 -1
- package/dist/6741.js +0 -2
- package/dist/6741.js.map +0 -1
- package/dist/7268.js +0 -2
- package/dist/7268.js.map +0 -1
- package/dist/7556.js +0 -1
- package/dist/7556.js.map +0 -1
- package/dist/7935.js +0 -1
- package/dist/7935.js.map +0 -1
- package/e2e/commands/medication-request-operation.ts +0 -97
- /package/dist/{7268.js.LICENSE.txt → 445.js.LICENSE.txt} +0 -0
- /package/dist/{6741.js.LICENSE.txt → 4999.js.LICENSE.txt} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect, useMemo, useState } from 'react';
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { Button, Checkbox, Form, FormLabel, InlineLoading } from '@carbon/react';
|
|
4
4
|
import {
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
computeNewFulfillerStatusAfterDispenseEvent,
|
|
21
21
|
getFulfillerStatus,
|
|
22
22
|
getUuidFromReference,
|
|
23
|
+
markEncounterAsStale,
|
|
23
24
|
revalidate,
|
|
24
25
|
} from '../utils';
|
|
25
26
|
import { type PharmacyConfig } from '../config-schema';
|
|
@@ -61,9 +62,6 @@ const DispenseForm: React.FC<DispenseFormProps> = ({
|
|
|
61
62
|
// Keep track of medication dispense payload
|
|
62
63
|
const [medicationDispensePayload, setMedicationDispensePayload] = useState<MedicationDispense>();
|
|
63
64
|
|
|
64
|
-
// whether or not the form is valid and ready to submit
|
|
65
|
-
const [isValid, setIsValid] = useState(false);
|
|
66
|
-
|
|
67
65
|
// to prevent duplicate submits
|
|
68
66
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
69
67
|
|
|
@@ -74,6 +72,7 @@ const DispenseForm: React.FC<DispenseFormProps> = ({
|
|
|
74
72
|
if (!isSubmitting) {
|
|
75
73
|
setIsSubmitting(true);
|
|
76
74
|
const abortController = new AbortController();
|
|
75
|
+
markEncounterAsStale(encounterUuid);
|
|
77
76
|
saveMedicationDispense(medicationDispensePayload, MedicationDispenseStatus.completed, abortController)
|
|
78
77
|
.then((response) => {
|
|
79
78
|
if (response.ok) {
|
|
@@ -173,8 +172,16 @@ const DispenseForm: React.FC<DispenseFormProps> = ({
|
|
|
173
172
|
}
|
|
174
173
|
};
|
|
175
174
|
|
|
176
|
-
const
|
|
177
|
-
|
|
175
|
+
const updateMedicationDispense = useCallback((medicationDispenseUpdate: Partial<MedicationDispense>) => {
|
|
176
|
+
setMedicationDispensePayload((prevState) => ({
|
|
177
|
+
...prevState,
|
|
178
|
+
...medicationDispenseUpdate,
|
|
179
|
+
}));
|
|
180
|
+
}, []);
|
|
181
|
+
|
|
182
|
+
// whether or not the form is valid and ready to submit
|
|
183
|
+
const isValid = useMemo(() => {
|
|
184
|
+
return (
|
|
178
185
|
medicationDispensePayload &&
|
|
179
186
|
medicationDispensePayload.performer &&
|
|
180
187
|
medicationDispensePayload.performer[0]?.actor.reference &&
|
|
@@ -188,19 +195,12 @@ const DispenseForm: React.FC<DispenseFormProps> = ({
|
|
|
188
195
|
(!medicationDispensePayload.substitution.wasSubstituted ||
|
|
189
196
|
(medicationDispensePayload.substitution.reason[0]?.coding[0].code &&
|
|
190
197
|
medicationDispensePayload.substitution.type?.coding[0].code))
|
|
191
|
-
)
|
|
192
|
-
|
|
193
|
-
} else {
|
|
194
|
-
setIsValid(false);
|
|
195
|
-
}
|
|
196
|
-
};
|
|
198
|
+
);
|
|
199
|
+
}, [medicationDispensePayload, quantityRemaining]);
|
|
197
200
|
|
|
198
201
|
// initialize the internal dispense payload with the dispenses passed in as props
|
|
199
202
|
useEffect(() => setMedicationDispensePayload(medicationDispense), [medicationDispense]);
|
|
200
203
|
|
|
201
|
-
// check is valid on any changes
|
|
202
|
-
useEffect(checkIsValid, [medicationDispensePayload, quantityRemaining, inventoryItem]);
|
|
203
|
-
|
|
204
204
|
const isButtonDisabled = (config.enableStockDispense ? !inventoryItem : false) || !isValid || isSubmitting;
|
|
205
205
|
|
|
206
206
|
const bannerState = useMemo(() => {
|
|
@@ -238,7 +238,7 @@ const DispenseForm: React.FC<DispenseFormProps> = ({
|
|
|
238
238
|
<div>
|
|
239
239
|
<MedicationDispenseReview
|
|
240
240
|
medicationDispense={medicationDispensePayload}
|
|
241
|
-
updateMedicationDispense={
|
|
241
|
+
updateMedicationDispense={updateMedicationDispense}
|
|
242
242
|
quantityRemaining={quantityRemaining}
|
|
243
243
|
quantityDispensed={quantityDispensed}
|
|
244
244
|
/>
|
package/src/forms/forms.scss
CHANGED
|
@@ -152,3 +152,18 @@
|
|
|
152
152
|
.reviewContainer {
|
|
153
153
|
margin: 0.8rem 0 0 0 !important;
|
|
154
154
|
}
|
|
155
|
+
|
|
156
|
+
// copied from .sectionHeader of drug-order-form.scss
|
|
157
|
+
.prescriptionFormSectionHeader {
|
|
158
|
+
@include type.type-style('heading-01');
|
|
159
|
+
margin-bottom: layout.$spacing-05;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// copied from .field of drug-order-form.scss
|
|
163
|
+
.prescriptionFormField {
|
|
164
|
+
margin-bottom: layout.$spacing-05;
|
|
165
|
+
|
|
166
|
+
:global(.cds--label) {
|
|
167
|
+
vertical-align: middle;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -7,6 +7,7 @@ import { getConceptCodingUuid, getMedicationReferenceOrCodeableConcept, getOpenM
|
|
|
7
7
|
import { useMedicationCodeableConcept, useMedicationFormulations } from '../medication/medication.resource';
|
|
8
8
|
import { useMedicationRequest, usePrescriptionDetails } from '../medication-request/medication-request.resource';
|
|
9
9
|
import {
|
|
10
|
+
blankSubstitution,
|
|
10
11
|
useOrderConfig,
|
|
11
12
|
useProviders,
|
|
12
13
|
useSubstitutionReasonValueSet,
|
|
@@ -20,7 +21,7 @@ import styles from '../components/medication-dispense-review.scss';
|
|
|
20
21
|
|
|
21
22
|
interface MedicationDispenseReviewProps {
|
|
22
23
|
medicationDispense: MedicationDispense;
|
|
23
|
-
updateMedicationDispense:
|
|
24
|
+
updateMedicationDispense(updates: Partial<MedicationDispense>);
|
|
24
25
|
quantityRemaining: number;
|
|
25
26
|
quantityDispensed: number;
|
|
26
27
|
}
|
|
@@ -35,15 +36,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
|
|
|
35
36
|
const config = useConfig<PharmacyConfig>();
|
|
36
37
|
const session = useSession();
|
|
37
38
|
const [isEditingFormulation, setIsEditingFormulation] = useState(false);
|
|
38
|
-
const [isSubstitution, setIsSubstitution] = useState(false);
|
|
39
|
-
// Dosing Unit eg Tablets
|
|
40
|
-
const [drugDosingUnits, setDrugDosingUnits] = useState([]);
|
|
41
|
-
// Dispensing Unit eg Tablets
|
|
42
|
-
const [drugDispensingUnits, setDrugDispensingUnits] = useState([]);
|
|
43
|
-
// Route eg Oral
|
|
44
|
-
const [drugRoutes, setDrugRoutes] = useState([]);
|
|
45
|
-
// Frequency eg Twice daily
|
|
46
|
-
const [orderFrequencies, setOrderFrequencies] = useState([]);
|
|
47
39
|
// type of substitution question
|
|
48
40
|
const [substitutionTypes, setSubstitutionTypes] = useState([]);
|
|
49
41
|
// reason for substitution question
|
|
@@ -72,41 +64,21 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
|
|
|
72
64
|
// we fetch this just to get the prescription date
|
|
73
65
|
const { prescriptionDate } = usePrescriptionDetails(medicationRequest ? medicationRequest.encounter.reference : null);
|
|
74
66
|
|
|
75
|
-
|
|
67
|
+
const { drugRoutes, drugDosingUnits, drugDispensingUnits, orderFrequencies } = useMemo(() => {
|
|
76
68
|
if (orderConfigObject) {
|
|
77
|
-
//
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
orderConfigObject.
|
|
81
|
-
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
orderConfigObject.drugDosingUnits.forEach(
|
|
89
|
-
(x) => availableDosingUnits.includes(x.uuid) || otherDosingUnits.push({ id: x.uuid, text: x.display }),
|
|
90
|
-
);
|
|
91
|
-
setDrugDosingUnits([...drugDosingUnits, ...otherDosingUnits]);
|
|
92
|
-
|
|
93
|
-
// sync dispensing unit options with what's defined in the order config
|
|
94
|
-
const availableDispensingUnits = drugDispensingUnits.map((x) => x.id);
|
|
95
|
-
const otherDispensingUnits = [];
|
|
96
|
-
orderConfigObject.drugDispensingUnits.forEach(
|
|
97
|
-
(x) => availableDispensingUnits.includes(x.uuid) || otherDispensingUnits.push({ id: x.uuid, text: x.display }),
|
|
98
|
-
);
|
|
99
|
-
setDrugDispensingUnits([...drugDispensingUnits, ...otherDispensingUnits]);
|
|
100
|
-
|
|
101
|
-
// sync order frequency options with order config
|
|
102
|
-
const availableFrequencies = orderFrequencies.map((x) => x.id);
|
|
103
|
-
const otherFrequencyOptions = [];
|
|
104
|
-
orderConfigObject.orderFrequencies.forEach(
|
|
105
|
-
(x) => availableFrequencies.includes(x.uuid) || otherFrequencyOptions.push({ id: x.uuid, text: x.display }),
|
|
106
|
-
);
|
|
107
|
-
setOrderFrequencies([...orderFrequencies, ...otherFrequencyOptions]);
|
|
69
|
+
// Dosing Unit eg Tablets
|
|
70
|
+
const drugRoutes = orderConfigObject.drugRoutes.map((x) => ({ id: x.uuid, text: x.display }));
|
|
71
|
+
// Dispensing Unit eg Tablets
|
|
72
|
+
const drugDosingUnits = orderConfigObject.drugDosingUnits.map((x) => ({ id: x.uuid, text: x.display }));
|
|
73
|
+
// Route eg Oral
|
|
74
|
+
const drugDispensingUnits = orderConfigObject.drugDispensingUnits.map((x) => ({ id: x.uuid, text: x.display }));
|
|
75
|
+
// Frequency eg Twice daily
|
|
76
|
+
const orderFrequencies = orderConfigObject.orderFrequencies.map((x) => ({ id: x.uuid, text: x.display }));
|
|
77
|
+
return { drugRoutes, drugDosingUnits, drugDispensingUnits, orderFrequencies };
|
|
78
|
+
} else {
|
|
79
|
+
return { drugRoutes: [], drugDosingUnits: [], drugDispensingUnits: [], orderFrequencies: [] };
|
|
108
80
|
}
|
|
109
|
-
}, [
|
|
81
|
+
}, [orderConfigObject]);
|
|
110
82
|
|
|
111
83
|
useEffect(() => {
|
|
112
84
|
const substitutionTypeOptions = [];
|
|
@@ -164,36 +136,25 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
|
|
|
164
136
|
);
|
|
165
137
|
|
|
166
138
|
// check to see if the current dispense would be a substitution, and update accordingly
|
|
139
|
+
const isSubstitution =
|
|
140
|
+
medicationRequest?.medicationReference?.reference &&
|
|
141
|
+
medicationDispense?.medicationReference?.reference &&
|
|
142
|
+
medicationRequest.medicationReference.reference != medicationDispense.medicationReference.reference;
|
|
143
|
+
|
|
144
|
+
const { substitution } = medicationDispense;
|
|
167
145
|
useEffect(() => {
|
|
168
|
-
if (
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
updateMedicationDispense({
|
|
175
|
-
...medicationDispense,
|
|
176
|
-
substitution: {
|
|
177
|
-
...medicationDispense.substitution,
|
|
178
|
-
wasSubstituted: true,
|
|
179
|
-
},
|
|
180
|
-
});
|
|
146
|
+
if (isSubstitution) {
|
|
147
|
+
// make sure that the value substitution.wasSubstituted exists and is truthy
|
|
148
|
+
if (!substitution.wasSubstituted) {
|
|
149
|
+
const newSubstitution = { ...substitution, wasSubstituted: true };
|
|
150
|
+
updateMedicationDispense({ substitution: newSubstitution });
|
|
151
|
+
}
|
|
181
152
|
} else {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
substitution: {
|
|
186
|
-
wasSubstituted: false,
|
|
187
|
-
reason: [
|
|
188
|
-
{
|
|
189
|
-
coding: [{ code: null }],
|
|
190
|
-
},
|
|
191
|
-
],
|
|
192
|
-
type: { coding: [{ code: null }] },
|
|
193
|
-
},
|
|
194
|
-
});
|
|
153
|
+
if (substitution != blankSubstitution) {
|
|
154
|
+
updateMedicationDispense({ substitution: blankSubstitution });
|
|
155
|
+
}
|
|
195
156
|
}
|
|
196
|
-
}, [
|
|
157
|
+
}, [isSubstitution, substitution, updateMedicationDispense]);
|
|
197
158
|
|
|
198
159
|
useEffect(() => {
|
|
199
160
|
setUserCanModify(session?.user && userHasAccess(PRIVILEGE_CREATE_DISPENSE_MODIFY_DETAILS, session.user));
|
|
@@ -211,11 +172,9 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
|
|
|
211
172
|
}
|
|
212
173
|
: undefined;
|
|
213
174
|
}, [medicationDispense?.performer, providers, session?.currentProvider?.uuid, session?.user?.person?.display]);
|
|
214
|
-
|
|
215
175
|
useEffect(() => {
|
|
216
176
|
if (initialDispenser?.uuid) {
|
|
217
177
|
updateMedicationDispense({
|
|
218
|
-
...medicationDispense,
|
|
219
178
|
performer: [
|
|
220
179
|
{
|
|
221
180
|
actor: {
|
|
@@ -225,7 +184,7 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
|
|
|
225
184
|
],
|
|
226
185
|
});
|
|
227
186
|
}
|
|
228
|
-
}, [initialDispenser,
|
|
187
|
+
}, [initialDispenser, updateMedicationDispense]);
|
|
229
188
|
|
|
230
189
|
return (
|
|
231
190
|
<div className={styles.medicationDispenseReviewContainer}>
|
|
@@ -251,7 +210,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
|
|
|
251
210
|
onChange={({ selectedItem }) => {
|
|
252
211
|
const typedItem = selectedItem as Medication;
|
|
253
212
|
updateMedicationDispense({
|
|
254
|
-
...medicationDispense,
|
|
255
213
|
medicationCodeableConcept: undefined,
|
|
256
214
|
medicationReference: {
|
|
257
215
|
reference: 'Medication/' + typedItem?.id,
|
|
@@ -279,7 +237,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
|
|
|
279
237
|
}}
|
|
280
238
|
onChange={({ selectedItem }) => {
|
|
281
239
|
updateMedicationDispense({
|
|
282
|
-
...medicationDispense,
|
|
283
240
|
substitution: {
|
|
284
241
|
...medicationDispense.substitution,
|
|
285
242
|
type: {
|
|
@@ -312,7 +269,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
|
|
|
312
269
|
}}
|
|
313
270
|
onChange={({ selectedItem }) => {
|
|
314
271
|
updateMedicationDispense({
|
|
315
|
-
...medicationDispense,
|
|
316
272
|
substitution: {
|
|
317
273
|
...medicationDispense.substitution,
|
|
318
274
|
reason: [
|
|
@@ -362,10 +318,9 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
|
|
|
362
318
|
max={config.dispenseBehavior.restrictTotalQuantityDispensed ? quantityRemaining : undefined}
|
|
363
319
|
onChange={(event, state) => {
|
|
364
320
|
updateMedicationDispense({
|
|
365
|
-
...medicationDispense,
|
|
366
321
|
quantity: {
|
|
367
322
|
...medicationDispense.quantity,
|
|
368
|
-
value: state.value ? parseFloat(state.value.toString()) :
|
|
323
|
+
value: state.value ? parseFloat(state.value.toString()) : 0,
|
|
369
324
|
},
|
|
370
325
|
});
|
|
371
326
|
}}
|
|
@@ -383,7 +338,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
|
|
|
383
338
|
}}
|
|
384
339
|
onChange={({ selectedItem }) => {
|
|
385
340
|
updateMedicationDispense({
|
|
386
|
-
...medicationDispense,
|
|
387
341
|
// note that we specifically recreate doseQuantity to overwrite any unit or system properties that may have been set
|
|
388
342
|
quantity: {
|
|
389
343
|
value: medicationDispense.quantity.value,
|
|
@@ -408,7 +362,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
|
|
|
408
362
|
value={medicationDispense.dosageInstruction[0].doseAndRate[0].doseQuantity.value}
|
|
409
363
|
onChange={(event, state) => {
|
|
410
364
|
updateMedicationDispense({
|
|
411
|
-
...medicationDispense,
|
|
412
365
|
dosageInstruction: [
|
|
413
366
|
{
|
|
414
367
|
...medicationDispense.dosageInstruction[0],
|
|
@@ -417,7 +370,7 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
|
|
|
417
370
|
...medicationDispense.dosageInstruction[0].doseAndRate[0],
|
|
418
371
|
doseQuantity: {
|
|
419
372
|
...medicationDispense.dosageInstruction[0].doseAndRate[0].doseQuantity,
|
|
420
|
-
value: state.value ? parseFloat(state.value.toString()) :
|
|
373
|
+
value: state.value ? parseFloat(state.value.toString()) : 0,
|
|
421
374
|
},
|
|
422
375
|
},
|
|
423
376
|
],
|
|
@@ -440,7 +393,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
|
|
|
440
393
|
}}
|
|
441
394
|
onChange={({ selectedItem }) => {
|
|
442
395
|
updateMedicationDispense({
|
|
443
|
-
...medicationDispense,
|
|
444
396
|
dosageInstruction: [
|
|
445
397
|
{
|
|
446
398
|
...medicationDispense.dosageInstruction[0],
|
|
@@ -474,7 +426,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
|
|
|
474
426
|
itemToString={(item) => item?.text}
|
|
475
427
|
onChange={({ selectedItem }) => {
|
|
476
428
|
updateMedicationDispense({
|
|
477
|
-
...medicationDispense,
|
|
478
429
|
dosageInstruction: [
|
|
479
430
|
{
|
|
480
431
|
...medicationDispense.dosageInstruction[0],
|
|
@@ -507,7 +458,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
|
|
|
507
458
|
itemToString={(item) => item?.text}
|
|
508
459
|
onChange={({ selectedItem }) => {
|
|
509
460
|
updateMedicationDispense({
|
|
510
|
-
...medicationDispense,
|
|
511
461
|
dosageInstruction: [
|
|
512
462
|
{
|
|
513
463
|
...medicationDispense.dosageInstruction[0],
|
|
@@ -535,7 +485,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
|
|
|
535
485
|
maxLength={65535}
|
|
536
486
|
onChange={(e) => {
|
|
537
487
|
updateMedicationDispense({
|
|
538
|
-
...medicationDispense,
|
|
539
488
|
dosageInstruction: [
|
|
540
489
|
{
|
|
541
490
|
...medicationDispense.dosageInstruction[0],
|
|
@@ -555,7 +504,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
|
|
|
555
504
|
const currentDate = medicationDispense.whenHandedOver ? dayjs(medicationDispense.whenHandedOver) : null;
|
|
556
505
|
const selectedDate = dayjs(input);
|
|
557
506
|
updateMedicationDispense({
|
|
558
|
-
...medicationDispense,
|
|
559
507
|
whenHandedOver: currentDate?.isSame(selectedDate, 'day')
|
|
560
508
|
? currentDate.toISOString()
|
|
561
509
|
: selectedDate.toISOString(), // to preserve any time component, only update if the day actually changes
|
|
@@ -570,7 +518,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
|
|
|
570
518
|
initialSelectedItem={initialDispenser}
|
|
571
519
|
onChange={({ selectedItem }) => {
|
|
572
520
|
updateMedicationDispense({
|
|
573
|
-
...medicationDispense,
|
|
574
521
|
performer: [
|
|
575
522
|
{
|
|
576
523
|
actor: {
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from '@openmrs/esm-framework';
|
|
14
14
|
import { saveMedicationDispense, useReasonForPauseValueSet } from '../medication-dispense/medication-dispense.resource';
|
|
15
15
|
import { updateMedicationRequestFulfillerStatus } from '../medication-request/medication-request.resource';
|
|
16
|
-
import { getUuidFromReference, revalidate } from '../utils';
|
|
16
|
+
import { getUuidFromReference, markEncounterAsStale, revalidate } from '../utils';
|
|
17
17
|
import { type MedicationDispense, MedicationDispenseStatus, MedicationRequestFulfillerStatus } from '../types';
|
|
18
18
|
import { type PharmacyConfig } from '../config-schema';
|
|
19
19
|
import styles from './forms.scss';
|
|
@@ -71,6 +71,7 @@ const PauseDispenseForm: React.FC<PauseDispenseFormProps> = ({
|
|
|
71
71
|
if (!isSubmitting) {
|
|
72
72
|
setIsSubmitting(true);
|
|
73
73
|
const abortController = new AbortController();
|
|
74
|
+
markEncounterAsStale(encounterUuid);
|
|
74
75
|
saveMedicationDispense(medicationDispensePayload, MedicationDispenseStatus.on_hold, abortController)
|
|
75
76
|
.then((response) => {
|
|
76
77
|
// only update request status when added a new dispense event, not updating
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
revalidate,
|
|
35
35
|
sortMedicationDispensesByWhenHandedOver,
|
|
36
36
|
computeTotalQuantityDispensed,
|
|
37
|
+
markEncounterAsStale,
|
|
37
38
|
} from '../utils';
|
|
38
39
|
import { type PharmacyConfig } from '../config-schema';
|
|
39
40
|
|
|
@@ -222,6 +223,7 @@ const HistoryAndComments: React.FC<{
|
|
|
222
223
|
config.dispenseBehavior.restrictTotalQuantityDispensed,
|
|
223
224
|
);
|
|
224
225
|
|
|
226
|
+
markEncounterAsStale(encounterUuid);
|
|
225
227
|
deleteMedicationDispense(medicationDispense.id)
|
|
226
228
|
.then(() => {
|
|
227
229
|
showSnackbar({
|
|
@@ -166,16 +166,18 @@ export function initiateMedicationDispenseBody(
|
|
|
166
166
|
],
|
|
167
167
|
},
|
|
168
168
|
],
|
|
169
|
-
substitution:
|
|
170
|
-
wasSubstituted: false,
|
|
171
|
-
reason: [
|
|
172
|
-
{
|
|
173
|
-
coding: [{ code: null }],
|
|
174
|
-
},
|
|
175
|
-
],
|
|
176
|
-
type: { coding: [{ code: null }] },
|
|
177
|
-
},
|
|
169
|
+
substitution: blankSubstitution,
|
|
178
170
|
};
|
|
179
171
|
}
|
|
180
172
|
return medicationDispense;
|
|
181
173
|
}
|
|
174
|
+
|
|
175
|
+
export const blankSubstitution = {
|
|
176
|
+
wasSubstituted: false,
|
|
177
|
+
reason: [
|
|
178
|
+
{
|
|
179
|
+
coding: [{ code: null }],
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
type: { coding: [{ code: null }] },
|
|
183
|
+
};
|
|
@@ -13,12 +13,16 @@ import { JSON_MERGE_PATH_MIME_TYPE, OPENMRS_FHIR_EXT_REQUEST_FULFILLER_STATUS }
|
|
|
13
13
|
|
|
14
14
|
jest.mocked(openmrsFetch);
|
|
15
15
|
jest.mock('swr');
|
|
16
|
+
jest.mock('react', () => ({
|
|
17
|
+
...jest.requireActual('react'),
|
|
18
|
+
useMemo: jest.fn((fn) => fn()),
|
|
19
|
+
}));
|
|
16
20
|
|
|
17
21
|
describe('Medication Request Resource Test', () => {
|
|
18
22
|
test('usePrescriptionsTable should call active endpoint and proper date based on expiration period if status parameter is active', () => {
|
|
19
23
|
// @ts-ignore
|
|
20
24
|
useSWR.mockImplementation(() => ({ data: { data: 'mockedReturnData' } }));
|
|
21
|
-
usePrescriptionsTable(5, 5, 'bob', null, 'ACTIVE', 10, 10000);
|
|
25
|
+
usePrescriptionsTable(true, 5, 5, 'bob', null, 'ACTIVE', 10, 10000);
|
|
22
26
|
expect(useSWR).toHaveBeenCalledWith(
|
|
23
27
|
`/ws/fhir2/R4/Encounter?_query=encountersWithMedicationRequests&_getpagesoffset=5&_count=5&date=ge${dayjs()
|
|
24
28
|
.startOf('day')
|
|
@@ -32,7 +36,7 @@ describe('Medication Request Resource Test', () => {
|
|
|
32
36
|
test('usePrescriptionsTable should call all endpoint if status parameter is not active', () => {
|
|
33
37
|
// @ts-ignore
|
|
34
38
|
useSWR.mockImplementation(() => ({ data: { data: 'mockedReturnData' } }));
|
|
35
|
-
usePrescriptionsTable(5, 5, 'bob', null, null, 10, 10000);
|
|
39
|
+
usePrescriptionsTable(true, 5, 5, 'bob', null, null, 10, 10000);
|
|
36
40
|
expect(useSWR).toHaveBeenCalledWith(
|
|
37
41
|
`/ws/fhir2/R4/Encounter?_query=encountersWithMedicationRequests&_getpagesoffset=5&_count=5&patientSearchTerm=bob`,
|
|
38
42
|
openmrsFetch,
|
|
@@ -587,7 +591,7 @@ describe('Medication Request Resource Test', () => {
|
|
|
587
591
|
|
|
588
592
|
// @ts-ignore
|
|
589
593
|
useSWR.mockImplementation(() => ({ data: { data: queryResultsBundle } }));
|
|
590
|
-
const { prescriptionsTableRows, totalOrders } = usePrescriptionsTable(2, 0, 'bob', 'ACTIVE', null, 90, 10000);
|
|
594
|
+
const { prescriptionsTableRows, totalOrders } = usePrescriptionsTable(true, 2, 0, 'bob', 'ACTIVE', null, 90, 10000);
|
|
591
595
|
expect(totalOrders).toBe(26);
|
|
592
596
|
expect(prescriptionsTableRows.length).toBe(2);
|
|
593
597
|
expect(prescriptionsTableRows[0].id).toBe('7aee7123-9e50-4f72-a636-895d77a63e98');
|
|
@@ -23,8 +23,10 @@ import {
|
|
|
23
23
|
} from '../utils';
|
|
24
24
|
import dayjs from 'dayjs';
|
|
25
25
|
import { JSON_MERGE_PATH_MIME_TYPE, OPENMRS_FHIR_EXT_REQUEST_FULFILLER_STATUS } from '../constants';
|
|
26
|
+
import { useMemo } from 'react';
|
|
26
27
|
|
|
27
28
|
export function usePrescriptionsTable(
|
|
29
|
+
loadData: boolean,
|
|
28
30
|
pageSize: number = 10,
|
|
29
31
|
pageOffset: number = 0,
|
|
30
32
|
patientSearchTerm: string = '',
|
|
@@ -34,15 +36,17 @@ export function usePrescriptionsTable(
|
|
|
34
36
|
refreshInterval: number,
|
|
35
37
|
) {
|
|
36
38
|
const { data, error } = useSWR<{ data: EncounterResponse }, Error>(
|
|
37
|
-
|
|
38
|
-
?
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
loadData
|
|
40
|
+
? status === 'ACTIVE'
|
|
41
|
+
? getPrescriptionTableActiveMedicationRequestsEndpoint(
|
|
42
|
+
pageOffset,
|
|
43
|
+
pageSize,
|
|
44
|
+
dayjs(new Date()).startOf('day').subtract(medicationRequestExpirationPeriodInDays, 'day').toISOString(),
|
|
45
|
+
patientSearchTerm,
|
|
46
|
+
location,
|
|
47
|
+
)
|
|
48
|
+
: getPrescriptionTableAllMedicationRequestsEndpoint(pageOffset, pageSize, patientSearchTerm, location)
|
|
49
|
+
: null,
|
|
46
50
|
openmrsFetch,
|
|
47
51
|
{ refreshInterval: refreshInterval },
|
|
48
52
|
);
|
|
@@ -124,54 +128,53 @@ function buildPrescriptionsTableRow(
|
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
export function usePrescriptionDetails(encounterUuid: string, refreshInterval = null) {
|
|
127
|
-
const
|
|
128
|
-
let prescriptionDate: Date;
|
|
129
|
-
let isLoading = true;
|
|
130
|
-
|
|
131
|
-
const { data, error } = useSWR<{ data: MedicationRequestResponse }, Error>(
|
|
131
|
+
const { data, ...rest } = useSWR<{ data: MedicationRequestResponse }, Error>(
|
|
132
132
|
getPrescriptionDetailsEndpoint(encounterUuid),
|
|
133
133
|
openmrsFetch,
|
|
134
134
|
{ refreshInterval: refreshInterval },
|
|
135
135
|
);
|
|
136
136
|
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
.map((entry) => entry.resource as Encounter);
|
|
143
|
-
|
|
144
|
-
if (encounter) {
|
|
145
|
-
// by definition of the request (search by encounter) there should be one and only one encounter
|
|
146
|
-
prescriptionDate = parseDate(encounter[0]?.period.start);
|
|
137
|
+
const { medicationRequestBundles, prescriptionDate } = useMemo(() => {
|
|
138
|
+
const medicationRequestBundles: Array<MedicationRequestBundle> = [];
|
|
139
|
+
let prescriptionDate: Date;
|
|
140
|
+
if (data) {
|
|
141
|
+
const results = data?.data.entry;
|
|
147
142
|
|
|
148
|
-
const
|
|
149
|
-
?.filter((entry) => entry?.resource?.resourceType == '
|
|
150
|
-
.map((entry) => entry.resource as
|
|
151
|
-
|
|
152
|
-
const medicationDispenses = results
|
|
153
|
-
?.filter((entry) => entry?.resource?.resourceType == 'MedicationDispense')
|
|
154
|
-
.map((entry) => entry.resource as MedicationDispense)
|
|
155
|
-
.sort(sortMedicationDispensesByWhenHandedOver);
|
|
143
|
+
const encounter = results
|
|
144
|
+
?.filter((entry) => entry?.resource?.resourceType == 'Encounter')
|
|
145
|
+
.map((entry) => entry.resource as Encounter);
|
|
156
146
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
|
|
147
|
+
if (encounter) {
|
|
148
|
+
// by definition of the request (search by encounter) there should be one and only one encounter
|
|
149
|
+
prescriptionDate = parseDate(encounter[0]?.period.start);
|
|
150
|
+
|
|
151
|
+
const medicationRequests = results
|
|
152
|
+
?.filter((entry) => entry?.resource?.resourceType == 'MedicationRequest')
|
|
153
|
+
.map((entry) => entry.resource as MedicationRequest);
|
|
154
|
+
|
|
155
|
+
const medicationDispenses = results
|
|
156
|
+
?.filter((entry) => entry?.resource?.resourceType == 'MedicationDispense')
|
|
157
|
+
.map((entry) => entry.resource as MedicationDispense)
|
|
158
|
+
.sort(sortMedicationDispensesByWhenHandedOver);
|
|
159
|
+
|
|
160
|
+
medicationRequests.every((medicationRequest) =>
|
|
161
|
+
medicationRequestBundles.push({
|
|
162
|
+
request: medicationRequest,
|
|
163
|
+
dispenses: getAssociatedMedicationDispenses(medicationRequest, medicationDispenses).sort(
|
|
164
|
+
sortMedicationDispensesByWhenHandedOver,
|
|
165
|
+
),
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
168
|
+
}
|
|
165
169
|
}
|
|
166
|
-
}
|
|
167
170
|
|
|
168
|
-
|
|
171
|
+
return { medicationRequestBundles, prescriptionDate };
|
|
172
|
+
}, [data]);
|
|
169
173
|
|
|
170
174
|
return {
|
|
171
175
|
medicationRequestBundles,
|
|
172
176
|
prescriptionDate,
|
|
173
|
-
|
|
174
|
-
isLoading,
|
|
177
|
+
...rest,
|
|
175
178
|
};
|
|
176
179
|
}
|
|
177
180
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Button, Search, TabPanel } from '@carbon/react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { PatientSearchPictogram } from '@openmrs/esm-framework';
|
|
5
|
+
import PrescriptionsTable from './prescriptions-table.component';
|
|
6
|
+
import styles from './patient-search-tab-panel.scss';
|
|
7
|
+
|
|
8
|
+
const PatientSearchTabPanel: React.FC = () => {
|
|
9
|
+
const { t } = useTranslation();
|
|
10
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
11
|
+
const [submittedSearchTerm, setSubmittedSearchTerm] = useState('');
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<TabPanel>
|
|
15
|
+
<div className={styles.searchTabPanel}>
|
|
16
|
+
<form
|
|
17
|
+
className={styles.searchBar}
|
|
18
|
+
onSubmit={(e) => {
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
setSubmittedSearchTerm(searchTerm);
|
|
21
|
+
}}>
|
|
22
|
+
<Search
|
|
23
|
+
closeButtonLabelText={t('clearSearchInput', 'Clear search input')}
|
|
24
|
+
defaultValue={searchTerm}
|
|
25
|
+
placeholder={t('searchForPatient', 'Search for a patient by name or identifier number')}
|
|
26
|
+
labelText={t('searchForPatient', 'Search for a patient by name or identifier number')}
|
|
27
|
+
onChange={(e) => {
|
|
28
|
+
setSearchTerm(e.target.value);
|
|
29
|
+
}}
|
|
30
|
+
onClear={() => setSubmittedSearchTerm('')}
|
|
31
|
+
size="lg"
|
|
32
|
+
/>
|
|
33
|
+
<Button kind="secondary" type="submit">
|
|
34
|
+
{t('search', 'Search')}
|
|
35
|
+
</Button>
|
|
36
|
+
</form>
|
|
37
|
+
{submittedSearchTerm ? (
|
|
38
|
+
<PrescriptionsTable
|
|
39
|
+
loadData={true}
|
|
40
|
+
status={'ACTIVE'}
|
|
41
|
+
debouncedSearchTerm={submittedSearchTerm}
|
|
42
|
+
location={''}
|
|
43
|
+
/>
|
|
44
|
+
) : (
|
|
45
|
+
<div className={styles.searchForPatientPlaceholder}>
|
|
46
|
+
<div>
|
|
47
|
+
<PatientSearchPictogram />
|
|
48
|
+
<h5>{t('searchForPatientHeader', 'Search for a patient')}</h5>
|
|
49
|
+
<div>{t('searchForPatient', 'Search for a patient by name or identifier number')}</div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
)}
|
|
53
|
+
</div>
|
|
54
|
+
</TabPanel>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export default PatientSearchTabPanel;
|