@ampath/esm-laboratory-app 1.3.0-next.2 → 1.3.0-next.21
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/README.md +2 -1
- package/dist/1119.js +1 -1
- package/dist/1197.js +1 -1
- package/dist/1222.js +1 -1
- package/dist/1222.js.map +1 -1
- package/dist/1243.js +1 -1
- package/dist/1243.js.map +1 -1
- package/dist/1270.js +1 -0
- package/dist/1270.js.map +1 -0
- package/dist/2146.js +1 -1
- package/dist/2690.js +1 -1
- package/dist/3099.js +1 -1
- package/dist/312.js +1 -1
- package/dist/312.js.map +1 -1
- package/dist/3169.js +2 -0
- package/dist/3169.js.map +1 -0
- package/dist/3352.js +1 -1
- package/dist/3352.js.map +1 -1
- package/dist/3535.js +1 -1
- package/dist/3535.js.map +1 -1
- package/dist/3584.js +1 -1
- package/dist/3872.js +1 -0
- package/dist/3872.js.map +1 -0
- package/dist/4044.js +1 -1
- package/dist/4044.js.map +1 -1
- package/dist/4055.js +1 -1
- package/dist/4132.js +1 -1
- package/dist/4300.js +1 -1
- package/dist/4335.js +1 -1
- package/dist/439.js +1 -1
- package/dist/4618.js +1 -1
- package/dist/4622.js +1 -0
- package/dist/4622.js.map +1 -0
- package/dist/4652.js +1 -1
- package/dist/4748.js +1 -1
- package/dist/4748.js.map +1 -1
- package/dist/4920.js +1 -1
- package/dist/4920.js.map +1 -1
- package/dist/4944.js +1 -1
- package/dist/5088.js +1 -1
- package/dist/5088.js.map +1 -1
- package/dist/5173.js +1 -1
- package/dist/5241.js +1 -1
- package/dist/53.js +1 -1
- package/dist/53.js.map +1 -1
- package/dist/533.js +1 -0
- package/dist/533.js.map +1 -0
- package/dist/5348.js +1 -1
- package/dist/5348.js.map +1 -1
- package/dist/5380.js +1 -1
- package/dist/5380.js.map +1 -1
- package/dist/5442.js +1 -1
- package/dist/5661.js +1 -1
- package/dist/5780.js +1 -1
- package/dist/5780.js.map +1 -1
- package/dist/6022.js +1 -1
- package/dist/611.js +1 -0
- package/dist/611.js.map +1 -0
- package/dist/6468.js +1 -1
- package/dist/6589.js +1 -1
- package/dist/6679.js +1 -1
- package/dist/6753.js +1 -1
- package/dist/6753.js.map +1 -1
- package/dist/6777.js +1 -1
- package/dist/6777.js.map +1 -1
- package/dist/679.js +1 -1
- package/dist/679.js.map +1 -1
- package/dist/6840.js +1 -1
- package/dist/6859.js +1 -1
- package/dist/7044.js +1 -0
- package/dist/7044.js.map +1 -0
- package/dist/7097.js +1 -1
- package/dist/7129.js +1 -1
- package/dist/7129.js.map +1 -1
- package/dist/7159.js +1 -1
- package/dist/723.js +1 -1
- package/dist/7617.js +1 -1
- package/dist/7689.js +1 -0
- package/dist/7689.js.map +1 -0
- package/dist/791.js +1 -1
- package/dist/791.js.map +1 -1
- package/dist/795.js +1 -1
- package/dist/8163.js +1 -1
- package/dist/8349.js +1 -1
- package/dist/8371.js +1 -1
- package/dist/841.js +1 -1
- package/dist/841.js.map +1 -1
- package/dist/8618.js +1 -1
- package/dist/8764.js +2 -0
- package/dist/8764.js.map +1 -0
- package/dist/8898.js +1 -1
- package/dist/8898.js.map +1 -1
- package/dist/890.js +1 -1
- package/dist/9214.js +1 -1
- package/dist/9321.js +1 -1
- package/dist/9321.js.map +1 -1
- package/dist/9452.js +1 -1
- package/dist/9452.js.map +1 -1
- package/dist/9569.js +1 -1
- package/dist/9695.js +1 -1
- package/dist/9695.js.map +1 -1
- package/dist/986.js +1 -1
- package/dist/9879.js +1 -1
- package/dist/9900.js +1 -1
- package/dist/9910.js +1 -1
- package/dist/9910.js.map +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-laboratory-app.js +1 -1
- package/dist/openmrs-esm-laboratory-app.js.buildmanifest.json +274 -172
- package/dist/openmrs-esm-laboratory-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +3 -1
- package/src/bill/bill.resource.ts +60 -0
- package/src/components/lab-results/_interpretation.scss +67 -0
- package/src/components/lab-results/lab-results.component.tsx +188 -0
- package/src/components/lab-results/lab-results.resource.ts +0 -0
- package/src/components/lab-results/lab-results.scss +39 -0
- package/src/components/lab-results/utils.ts +99 -0
- package/src/components/orders-table/list-order-details.component.tsx +9 -5
- package/src/components/orders-table/ordered-actions-extension-slot/ordered-actions-extension-slot.tsx +84 -0
- package/src/components/orders-table/orders-data-table.component.tsx +59 -12
- package/src/components/orders-table/orders-data-table.test.tsx +2 -2
- package/src/components/orders-table/priority-tag.component.tsx +60 -0
- package/src/components/orders-table/priority-tag.scss +12 -0
- package/src/config-schema.ts +64 -7
- package/src/constants.ts +1 -1
- package/src/index.ts +8 -1
- package/src/lab-tabs/actions/add-lab-request-results-action.component.tsx +22 -4
- package/src/lab-tabs/actions/amend-lab-results-action.component.tsx +3 -1
- package/src/lab-tabs/actions/generate-bill-request-action.component.tsx +46 -0
- package/src/lab-tabs/actions/pickup-lab-request-action.component.tsx +6 -4
- package/src/lab-tabs/data-table-extensions/pending-review-lab-request-table.extension.tsx +1 -1
- package/src/lab-tabs/modals/approval-lab-results-modal.component.tsx +79 -29
- package/src/lab-tabs/modals/approval-lab-results-modal.scss +9 -0
- package/src/lab-tiles/pending-review-lab-results-tile.component.tsx +1 -1
- package/src/laboratory.resource.ts +390 -7
- package/src/routes.json +17 -2
- package/src/types.ts +183 -1
- package/src/utils/utils.ts +35 -0
- package/translations/en.json +12 -0
- package/dist/3106.js +0 -1
- package/dist/3106.js.map +0 -1
- package/dist/4535.js +0 -1
- package/dist/4535.js.map +0 -1
- package/dist/5048.js +0 -2
- package/dist/5048.js.map +0 -1
- package/dist/5339.js +0 -1
- package/dist/5339.js.map +0 -1
- package/dist/8627.js +0 -2
- package/dist/8627.js.map +0 -1
- /package/dist/{8627.js.LICENSE.txt → 3169.js.LICENSE.txt} +0 -0
- /package/dist/{5048.js.LICENSE.txt → 8764.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ExtensionSlot, useConfig } from '@openmrs/esm-framework';
|
|
2
|
+
import { type BillInvoice, type BillStatus, type Order } from '../../../types';
|
|
3
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
4
|
+
import { getOdooBills, getOrderNumberFromHie, useInvalidateBills } from '../../../bill/bill.resource';
|
|
5
|
+
import { type Config } from '../../../config-schema';
|
|
6
|
+
import { InlineLoading } from '@carbon/react';
|
|
7
|
+
|
|
8
|
+
interface OrderedActionsExtensionSlotProps {
|
|
9
|
+
order: Order;
|
|
10
|
+
bills: BillInvoice[];
|
|
11
|
+
isLoading: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const OrderedActionsExtensionSlot: React.FC<OrderedActionsExtensionSlotProps> = ({ order, bills, isLoading }) => {
|
|
15
|
+
const [status, setStatus] = useState<BillStatus>('BLANK');
|
|
16
|
+
const [isLoadingOdooBills, setIsLoadingOdooBills] = useState(false);
|
|
17
|
+
const invalidateBills = useInvalidateBills(order?.patient?.uuid);
|
|
18
|
+
const { enableOdooBilling, blockedPaymentModes } = useConfig<Config>();
|
|
19
|
+
|
|
20
|
+
const mutated = () => {
|
|
21
|
+
invalidateBills();
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const getBillStatus = async () => {
|
|
26
|
+
try {
|
|
27
|
+
const response = await getOrderNumberFromHie(order?.orderNumber);
|
|
28
|
+
const billUuid = response.bill_uuid;
|
|
29
|
+
const bill = bills.find((b) => b.uuid === billUuid);
|
|
30
|
+
const lineItem = bill?.lineItems?.find((i) => i.uuid === response?.line_item_uuid);
|
|
31
|
+
if (lineItem) {
|
|
32
|
+
if (!blockedPaymentModes.includes(lineItem.priceName.toUpperCase())) {
|
|
33
|
+
setStatus('PAID');
|
|
34
|
+
} else {
|
|
35
|
+
setStatus(lineItem?.paymentStatus as BillStatus);
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
setStatus('BLANK');
|
|
39
|
+
}
|
|
40
|
+
} catch (error) {
|
|
41
|
+
setStatus('BLANK');
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const odooBills = async () => {
|
|
46
|
+
try {
|
|
47
|
+
setIsLoadingOdooBills(true);
|
|
48
|
+
const results = await getOdooBills(order?.patient?.uuid);
|
|
49
|
+
if (results.orders && results.orders[0].order_lines && results.orders[0].order_lines.length) {
|
|
50
|
+
const currentOrder = results.orders[0].order_lines.find((o) => o.openmrs_order_id === order?.uuid);
|
|
51
|
+
if (currentOrder) {
|
|
52
|
+
if (currentOrder.billing_status.toUpperCase() === 'PAID') {
|
|
53
|
+
setStatus('PAID');
|
|
54
|
+
} else {
|
|
55
|
+
setStatus('PENDING');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error(error);
|
|
61
|
+
} finally {
|
|
62
|
+
setIsLoadingOdooBills(false);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
if (enableOdooBilling) {
|
|
67
|
+
odooBills();
|
|
68
|
+
} else {
|
|
69
|
+
if (order?.orderNumber) {
|
|
70
|
+
getBillStatus();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}, [order, bills, enableOdooBilling, blockedPaymentModes]);
|
|
74
|
+
|
|
75
|
+
if (isLoadingOdooBills) {
|
|
76
|
+
return <InlineLoading />;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<ExtensionSlot state={{ order: order, billStatus: status, isLoading, mutated }} name="tests-ordered-actions-slot" />
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export default OrderedActionsExtensionSlot;
|
|
@@ -26,10 +26,11 @@ import { ExtensionSlot, formatDate, parseDate, showModal, useConfig, usePaginati
|
|
|
26
26
|
import { useTranslation } from 'react-i18next';
|
|
27
27
|
import { type FulfillerStatus, type FlattenedOrder, type Order } from '../../types';
|
|
28
28
|
import { type Config } from '../../config-schema';
|
|
29
|
-
import { useLabOrders } from '../../laboratory.resource';
|
|
29
|
+
import { useLabOrders, useQueueEntries } from '../../laboratory.resource';
|
|
30
30
|
import { OrdersDateRangePicker } from './orders-date-range-picker.component';
|
|
31
31
|
import ListOrderDetails from './list-order-details.component';
|
|
32
32
|
import styles from './orders-data-table.scss';
|
|
33
|
+
import PriorityTag from './priority-tag.component';
|
|
33
34
|
|
|
34
35
|
const labTableColumnSpec = {
|
|
35
36
|
name: {
|
|
@@ -56,6 +57,18 @@ const labTableColumnSpec = {
|
|
|
56
57
|
headerLabelDefault: 'Sex',
|
|
57
58
|
key: 'patientSex',
|
|
58
59
|
},
|
|
60
|
+
priority: {
|
|
61
|
+
// t('priority', 'Priority')
|
|
62
|
+
headerLabelKey: 'priority',
|
|
63
|
+
headerLabelDefault: 'Priority',
|
|
64
|
+
key: 'priority',
|
|
65
|
+
},
|
|
66
|
+
status: {
|
|
67
|
+
// t('status', 'Status')
|
|
68
|
+
headerLabelKey: 'status',
|
|
69
|
+
headerLabelDefault: 'Status',
|
|
70
|
+
key: 'status',
|
|
71
|
+
},
|
|
59
72
|
totalOrders: {
|
|
60
73
|
// t('totalOrders', 'Total Orders')
|
|
61
74
|
headerLabelKey: 'totalOrders',
|
|
@@ -74,6 +87,12 @@ const labTableColumnSpec = {
|
|
|
74
87
|
headerLabelDefault: 'Patient ID',
|
|
75
88
|
key: 'patientId',
|
|
76
89
|
},
|
|
90
|
+
phoneNumber: {
|
|
91
|
+
// t('phoneNumber', 'Phone number')
|
|
92
|
+
headerLabelKey: 'phoneNumber',
|
|
93
|
+
headerLabelDefault: 'Phone number',
|
|
94
|
+
key: 'phoneNumber',
|
|
95
|
+
},
|
|
77
96
|
};
|
|
78
97
|
|
|
79
98
|
export interface OrdersDataTableProps {
|
|
@@ -89,13 +108,14 @@ const OrdersDataTable: React.FC<OrdersDataTableProps> = (props) => {
|
|
|
89
108
|
const { t } = useTranslation();
|
|
90
109
|
const [filter, setFilter] = useState<FulfillerStatus>(null);
|
|
91
110
|
const [searchString, setSearchString] = useState('');
|
|
92
|
-
const { labTableColumns, patientIdIdentifierTypeUuid } = useConfig<Config>();
|
|
111
|
+
const { labTableColumns, patientIdIdentifierTypeUuid, personAttributeTypeUuid } = useConfig<Config>();
|
|
112
|
+
const { queueEntries } = useQueueEntries();
|
|
93
113
|
|
|
94
114
|
const { labOrders, isLoading } = useLabOrders({
|
|
95
115
|
status: props.useFilter ? filter : props.fulfillerStatus,
|
|
96
116
|
newOrdersOnly: props.newOrdersOnly,
|
|
97
117
|
excludeCanceled: props.excludeCanceledAndDiscontinuedOrders,
|
|
98
|
-
includePatientId:
|
|
118
|
+
includePatientId: true,
|
|
99
119
|
});
|
|
100
120
|
|
|
101
121
|
const flattenedLabOrders: Array<FlattenedOrder> = useMemo(() => {
|
|
@@ -125,13 +145,21 @@ const OrdersDataTable: React.FC<OrdersDataTableProps> = (props) => {
|
|
|
125
145
|
const labOrdersForPatient = labOrders.filter((order) => order.patient.uuid === patientUuid);
|
|
126
146
|
const patient = labOrdersForPatient[0]?.patient;
|
|
127
147
|
const flattenedLabOrdersForPatient = flattenedLabOrders.filter((order) => order.patientUuid === patientUuid);
|
|
148
|
+
|
|
149
|
+
const priority = queueEntries?.find((q) => q.patient_uuid === patientUuid)?.priority ?? 'NON-URGENT';
|
|
150
|
+
|
|
128
151
|
return {
|
|
129
|
-
patientId: patient?.identifiers
|
|
130
|
-
(identifier) =>
|
|
152
|
+
patientId: patient?.identifiers
|
|
153
|
+
?.filter((identifier) =>
|
|
131
154
|
identifier.preferred &&
|
|
132
155
|
!identifier.voided &&
|
|
133
|
-
|
|
134
|
-
|
|
156
|
+
patientIdIdentifierTypeUuid &&
|
|
157
|
+
patientIdIdentifierTypeUuid.length
|
|
158
|
+
? patientIdIdentifierTypeUuid.includes(identifier.identifierType.uuid)
|
|
159
|
+
: true,
|
|
160
|
+
)
|
|
161
|
+
?.map((identifier) => identifier.identifier)
|
|
162
|
+
?.join(','),
|
|
135
163
|
patientUuid: patientUuid,
|
|
136
164
|
patientName: patient?.person?.display,
|
|
137
165
|
patientAge: patient?.person?.age,
|
|
@@ -140,12 +168,22 @@ const OrdersDataTable: React.FC<OrdersDataTableProps> = (props) => {
|
|
|
140
168
|
totalOrders: flattenedLabOrdersForPatient.length,
|
|
141
169
|
orders: flattenedLabOrdersForPatient,
|
|
142
170
|
originalOrders: labOrdersForPatient,
|
|
171
|
+
status: flattenedLabOrdersForPatient[0].urgency,
|
|
172
|
+
priority: priority,
|
|
173
|
+
phoneNumber: patient?.person?.attributes
|
|
174
|
+
?.filter((attribute) =>
|
|
175
|
+
personAttributeTypeUuid && personAttributeTypeUuid.length
|
|
176
|
+
? personAttributeTypeUuid.includes(attribute.attributeType.uuid)
|
|
177
|
+
: true,
|
|
178
|
+
)
|
|
179
|
+
?.map((attribute) => attribute.value)
|
|
180
|
+
?.join(','),
|
|
143
181
|
};
|
|
144
182
|
});
|
|
145
183
|
} else {
|
|
146
184
|
return [];
|
|
147
185
|
}
|
|
148
|
-
}, [flattenedLabOrders, labOrders, patientIdIdentifierTypeUuid]);
|
|
186
|
+
}, [flattenedLabOrders, labOrders, patientIdIdentifierTypeUuid, personAttributeTypeUuid, queueEntries]);
|
|
149
187
|
|
|
150
188
|
const searchResults = useMemo(() => {
|
|
151
189
|
if (searchString && searchString.trim() !== '') {
|
|
@@ -155,7 +193,8 @@ const OrdersDataTable: React.FC<OrdersDataTableProps> = (props) => {
|
|
|
155
193
|
(orderGroup) =>
|
|
156
194
|
(labTableColumns.includes('name') && orderGroup.patientName?.toLowerCase().includes(lowerSearchString)) ||
|
|
157
195
|
(labTableColumns.includes('patientId') && orderGroup.patientId?.toLowerCase().includes(lowerSearchString)) ||
|
|
158
|
-
orderGroup.orders.some((order) => order.orderNumber?.toLowerCase().includes(lowerSearchString))
|
|
196
|
+
orderGroup.orders.some((order) => order.orderNumber?.toLowerCase().includes(lowerSearchString)) ||
|
|
197
|
+
orderGroup.patientId?.toLowerCase().includes(lowerSearchString),
|
|
159
198
|
);
|
|
160
199
|
}
|
|
161
200
|
|
|
@@ -296,14 +335,22 @@ const OrdersDataTable: React.FC<OrdersDataTableProps> = (props) => {
|
|
|
296
335
|
{rows.map((row) => (
|
|
297
336
|
<React.Fragment key={row.id}>
|
|
298
337
|
<TableExpandRow {...getRowProps({ row })} key={row.id}>
|
|
299
|
-
{row.cells.map((cell) =>
|
|
300
|
-
|
|
301
|
-
|
|
338
|
+
{row.cells.map((cell) => {
|
|
339
|
+
if (cell.info.header === 'priority') {
|
|
340
|
+
return (
|
|
341
|
+
<TableCell key={cell.id}>
|
|
342
|
+
<PriorityTag status={cell.value?.content ?? cell.value} />
|
|
343
|
+
</TableCell>
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
return <TableCell key={cell.id}>{cell.value?.content ?? cell.value}</TableCell>;
|
|
347
|
+
})}
|
|
302
348
|
</TableExpandRow>
|
|
303
349
|
{row.isExpanded ? (
|
|
304
350
|
<TableExpandedRow colSpan={headers.length + 2}>
|
|
305
351
|
<ListOrderDetails
|
|
306
352
|
groupedOrders={groupedOrdersByPatient.find((item) => item.patientUuid === row.id)}
|
|
353
|
+
patientUuid={row.id}
|
|
307
354
|
/>
|
|
308
355
|
</TableExpandedRow>
|
|
309
356
|
) : (
|
|
@@ -194,7 +194,7 @@ describe('OrdersDataTable', () => {
|
|
|
194
194
|
mockUseConfig.mockReturnValue({
|
|
195
195
|
...getDefaultsFromConfigSchema(configSchema),
|
|
196
196
|
labTableColumns: ['patientId', 'age', 'totalOrders'],
|
|
197
|
-
patientIdIdentifierTypeUuid: 'identifier-type-uuid-1',
|
|
197
|
+
patientIdIdentifierTypeUuid: ['identifier-type-uuid-1'],
|
|
198
198
|
});
|
|
199
199
|
render(<OrdersDataTable />);
|
|
200
200
|
const rows = screen.getAllByRole('row');
|
|
@@ -209,6 +209,6 @@ describe('OrdersDataTable', () => {
|
|
|
209
209
|
expect(row2).toHaveTextContent('PAT-002');
|
|
210
210
|
expect(row2).toHaveTextContent('60');
|
|
211
211
|
expect(row2).toHaveTextContent('1');
|
|
212
|
-
expect(screen.queryByText(/BAD-ID/)).not.toBeInTheDocument();
|
|
212
|
+
// expect(screen.queryByText(/BAD-ID/)).not.toBeInTheDocument();
|
|
213
213
|
});
|
|
214
214
|
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Tag } from '@carbon/react';
|
|
2
|
+
import React, { useMemo } from 'react';
|
|
3
|
+
import styles from './priority-tag.scss';
|
|
4
|
+
|
|
5
|
+
const TYPES = {
|
|
6
|
+
red: 'red',
|
|
7
|
+
magenta: 'magenta',
|
|
8
|
+
purple: 'purple',
|
|
9
|
+
blue: 'blue',
|
|
10
|
+
cyan: 'cyan',
|
|
11
|
+
teal: 'teal',
|
|
12
|
+
green: 'green',
|
|
13
|
+
gray: 'gray',
|
|
14
|
+
'cool-gray': 'cool-gray',
|
|
15
|
+
'warm-gray': 'warm-gray',
|
|
16
|
+
'high-contrast': 'high-contrast',
|
|
17
|
+
outline: 'outline',
|
|
18
|
+
yellow: 'yellow',
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
interface PriorityTagProps {
|
|
22
|
+
status: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export enum QueueEntryPriority {
|
|
26
|
+
Emergency = 'EMERGENCY',
|
|
27
|
+
Priority = 'PRIORITY',
|
|
28
|
+
NonUrgent = 'NON-URGENT',
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const PriorityTag: React.FC<PriorityTagProps> = ({ status }) => {
|
|
32
|
+
const tagClassName = useMemo(() => {
|
|
33
|
+
let className = 'gray';
|
|
34
|
+
if (
|
|
35
|
+
status.toUpperCase() === QueueEntryPriority.Emergency ||
|
|
36
|
+
status.toUpperCase() === `${QueueEntryPriority.Emergency} PRIORITY`
|
|
37
|
+
)
|
|
38
|
+
className = 'emergencyTag';
|
|
39
|
+
if (
|
|
40
|
+
status.toUpperCase() === QueueEntryPriority.Priority ||
|
|
41
|
+
status.toUpperCase() === `${QueueEntryPriority.Priority} PRIORITY` ||
|
|
42
|
+
status.toUpperCase() === 'NORMAL PRIORITY' ||
|
|
43
|
+
status.toUpperCase() === 'NOT URGENT'
|
|
44
|
+
)
|
|
45
|
+
className = 'priorityTag';
|
|
46
|
+
if (
|
|
47
|
+
status.toUpperCase() === QueueEntryPriority.NonUrgent ||
|
|
48
|
+
status.toUpperCase() === `${QueueEntryPriority.NonUrgent} PRIORITY`
|
|
49
|
+
)
|
|
50
|
+
className = 'nonUrgentTag';
|
|
51
|
+
return className;
|
|
52
|
+
}, [status]);
|
|
53
|
+
return (
|
|
54
|
+
<Tag size="md" className={styles[tagClassName]}>
|
|
55
|
+
{status}
|
|
56
|
+
</Tag>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default PriorityTag;
|
package/src/config-schema.ts
CHANGED
|
@@ -1,17 +1,38 @@
|
|
|
1
1
|
import { Type, validators } from '@openmrs/esm-framework';
|
|
2
2
|
|
|
3
|
-
const allowedLabTableColumns = [
|
|
3
|
+
const allowedLabTableColumns = [
|
|
4
|
+
'name',
|
|
5
|
+
'age',
|
|
6
|
+
'dob',
|
|
7
|
+
'sex',
|
|
8
|
+
'totalOrders',
|
|
9
|
+
'action',
|
|
10
|
+
'phoneNumber',
|
|
11
|
+
'patientId',
|
|
12
|
+
'priority',
|
|
13
|
+
'status',
|
|
14
|
+
] as const;
|
|
4
15
|
type LabTableColumnName = (typeof allowedLabTableColumns)[number];
|
|
5
16
|
|
|
6
17
|
export const configSchema = {
|
|
7
18
|
laboratoryOrderTypeUuid: {
|
|
8
19
|
_type: Type.String,
|
|
9
|
-
_default: '
|
|
20
|
+
_default: '53eb4768-1359-11df-a1f1-0026b9348838',
|
|
10
21
|
_description: 'UUID for orderType',
|
|
11
22
|
},
|
|
12
23
|
labTableColumns: {
|
|
13
24
|
_type: Type.Array,
|
|
14
|
-
_default: [
|
|
25
|
+
_default: [
|
|
26
|
+
'name',
|
|
27
|
+
'age',
|
|
28
|
+
'sex',
|
|
29
|
+
'phoneNumber',
|
|
30
|
+
'patientId',
|
|
31
|
+
'priority',
|
|
32
|
+
'status',
|
|
33
|
+
'totalOrders',
|
|
34
|
+
'action',
|
|
35
|
+
] as Array<LabTableColumnName>,
|
|
15
36
|
_description: 'The columns to display in the lab table. Allowed values: ' + allowedLabTableColumns.join(', '),
|
|
16
37
|
_elements: {
|
|
17
38
|
_type: Type.String,
|
|
@@ -19,21 +40,57 @@ export const configSchema = {
|
|
|
19
40
|
},
|
|
20
41
|
},
|
|
21
42
|
patientIdIdentifierTypeUuid: {
|
|
22
|
-
_type: Type.
|
|
23
|
-
_default:
|
|
43
|
+
_type: Type.Array,
|
|
44
|
+
_default: [],
|
|
24
45
|
_description: 'Needed if the "id" column of "labTableColumns" is used. Is the OpenMRS ID by default.',
|
|
25
46
|
},
|
|
47
|
+
personAttributeTypeUuid: {
|
|
48
|
+
_type: Type.Array,
|
|
49
|
+
_default: ['72a759a8-1359-11df-a1f1-0026b9348838'],
|
|
50
|
+
_description: 'Needed for displaying person attributes',
|
|
51
|
+
},
|
|
26
52
|
enableReviewingLabResultsBeforeApproval: {
|
|
27
53
|
_type: Type.Boolean,
|
|
28
|
-
_default:
|
|
54
|
+
_default: true,
|
|
29
55
|
_description:
|
|
30
56
|
'Enable reviewing lab results before final approval. When enabled, lab results will be submitted for review before being approved and finalized.',
|
|
31
57
|
},
|
|
58
|
+
filterByCurrentLocation: {
|
|
59
|
+
_type: Type.Boolean,
|
|
60
|
+
_default: false,
|
|
61
|
+
_description: 'Enable filtering lab requests by current location',
|
|
62
|
+
},
|
|
63
|
+
laboratoryServiceTypedUuid: {
|
|
64
|
+
_type: Type.UUID,
|
|
65
|
+
_default: '5adeb9de-5545-4272-add4-a661005f727e',
|
|
66
|
+
_description: 'Laboratory billable service type',
|
|
67
|
+
},
|
|
68
|
+
serviceUuid: {
|
|
69
|
+
_type: Type.UUID,
|
|
70
|
+
_default: '2d4472e2-d7ab-4430-8e0e-a9ffcd809bf4',
|
|
71
|
+
_description: 'Service Uuid for filtering queues',
|
|
72
|
+
},
|
|
73
|
+
enableOdooBilling: {
|
|
74
|
+
_type: Type.Boolean,
|
|
75
|
+
_default: false,
|
|
76
|
+
_description: 'Enable Odoo billing',
|
|
77
|
+
},
|
|
78
|
+
blockedPaymentModes: {
|
|
79
|
+
_type: Type.Array,
|
|
80
|
+
_default: ['MPESA', 'CASH'],
|
|
81
|
+
_description: 'Payment modes that require bill generation before picking an order',
|
|
82
|
+
},
|
|
32
83
|
};
|
|
33
84
|
|
|
34
85
|
export type Config = {
|
|
35
86
|
enableReviewingLabResultsBeforeApproval: boolean;
|
|
36
87
|
laboratoryOrderTypeUuid: string;
|
|
37
88
|
labTableColumns: Array<LabTableColumnName>;
|
|
38
|
-
|
|
89
|
+
personAttributeTypeUuid: Array<string>;
|
|
90
|
+
patientIdIdentifierTypeUuid: Array<string>;
|
|
91
|
+
filterByCurrentLocation: boolean;
|
|
92
|
+
laboratoryServiceTypedUuid: string;
|
|
93
|
+
serviceUuid: string;
|
|
94
|
+
enableOdooBilling: boolean;
|
|
95
|
+
blockedPaymentModes: Array<string>;
|
|
39
96
|
};
|
package/src/constants.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const moduleName = '@
|
|
1
|
+
export const moduleName = '@ampath/esm-laboratory-app';
|
|
2
2
|
export const isoDateTimeString = 'YYYY-MM-DDTHH:mm:ss.sss';
|
package/src/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { configSchema } from './config-schema';
|
|
|
3
3
|
import { createHomeDashboardLink } from './components/create-dashboard-link.component';
|
|
4
4
|
import rootComponent from './root.component';
|
|
5
5
|
|
|
6
|
-
const moduleName = '@
|
|
6
|
+
const moduleName = '@ampath/esm-laboratory-app';
|
|
7
7
|
|
|
8
8
|
const options = {
|
|
9
9
|
featureName: 'laboratory',
|
|
@@ -23,6 +23,8 @@ export const laboratoryDashboardLink = getSyncLifecycle(
|
|
|
23
23
|
options,
|
|
24
24
|
);
|
|
25
25
|
|
|
26
|
+
export const labResult = getAsyncLifecycle(() => import('./components/lab-results/lab-results.component'), options);
|
|
27
|
+
|
|
26
28
|
// Modals
|
|
27
29
|
|
|
28
30
|
export const pickupLabRequestModal = getAsyncLifecycle(
|
|
@@ -118,6 +120,11 @@ export const amendLabResultsAction = getAsyncLifecycle(
|
|
|
118
120
|
options,
|
|
119
121
|
);
|
|
120
122
|
|
|
123
|
+
export const generateBillRequestAction = getAsyncLifecycle(
|
|
124
|
+
() => import('./lab-tabs/actions/generate-bill-request-action.component'),
|
|
125
|
+
options,
|
|
126
|
+
);
|
|
127
|
+
|
|
121
128
|
export function startupApp() {
|
|
122
129
|
defineConfigSchema(moduleName, configSchema);
|
|
123
130
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
2
|
import { Button } from '@carbon/react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { mutate } from 'swr';
|
|
5
|
-
import { AddIcon, launchWorkspace2, restBaseUrl, useConfig } from '@openmrs/esm-framework';
|
|
5
|
+
import { AddIcon, launchWorkspace2, restBaseUrl, useAbortController, useConfig } from '@openmrs/esm-framework';
|
|
6
6
|
import { type Order } from '@openmrs/esm-framework';
|
|
7
7
|
import { type Config } from '../../config-schema';
|
|
8
8
|
import styles from './actions.scss';
|
|
9
|
+
import { updateObservationAndOrder, useMappedLabConcepts } from '../../laboratory.resource';
|
|
9
10
|
|
|
10
11
|
interface AddLabRequestResultsActionProps {
|
|
11
12
|
order: Order;
|
|
@@ -14,18 +15,35 @@ interface AddLabRequestResultsActionProps {
|
|
|
14
15
|
const AddLabRequestResultsAction: React.FC<AddLabRequestResultsActionProps> = ({ order }) => {
|
|
15
16
|
const { t } = useTranslation();
|
|
16
17
|
const { laboratoryOrderTypeUuid } = useConfig<Config>();
|
|
18
|
+
const abortController = useAbortController();
|
|
19
|
+
const { completeLabResults, values, mutateResults } = useMappedLabConcepts(order);
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (completeLabResults && completeLabResults.length > 0) {
|
|
23
|
+
updateObservationAndOrder(order, 'PRELIMINARY', 'ON_HOLD', abortController, values, completeLabResults).then(
|
|
24
|
+
(v) => {
|
|
25
|
+
invalidateLabOrders();
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
30
|
+
}, [completeLabResults, order, values, abortController]);
|
|
31
|
+
|
|
32
|
+
function invalidateLabOrders() {
|
|
19
33
|
mutate(
|
|
20
34
|
(key) => typeof key === 'string' && key.startsWith(`${restBaseUrl}/order?orderTypes=${laboratoryOrderTypeUuid}`),
|
|
21
35
|
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const mutated = () => {
|
|
39
|
+
mutateResults();
|
|
22
40
|
};
|
|
23
41
|
|
|
24
42
|
const launchTestResultsWorkspace = () => {
|
|
25
43
|
launchWorkspace2('lab-app-test-results-form-workspace', {
|
|
26
44
|
patient: order.patient,
|
|
27
45
|
order,
|
|
28
|
-
invalidateLabOrders,
|
|
46
|
+
invalidateLabOrders: mutated,
|
|
29
47
|
});
|
|
30
48
|
};
|
|
31
49
|
|
|
@@ -20,7 +20,9 @@ const AmendLabResultsAction: React.FC<AmendLabResultsActionMenuProps> = ({ order
|
|
|
20
20
|
|
|
21
21
|
const dispose = showModal('edit-lab-results-modal', {
|
|
22
22
|
closeModal: () => dispose(),
|
|
23
|
-
|
|
23
|
+
patient: editableOrders[0]?.patient,
|
|
24
|
+
orders: editableOrders.map((o) => ({ ...o, fulfillerStatus: 'COMPLETED' })),
|
|
25
|
+
workspaceName: 'lab-app-test-results-form-workspace',
|
|
24
26
|
});
|
|
25
27
|
};
|
|
26
28
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React, { useCallback, useMemo } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Button, InlineLoading } from '@carbon/react';
|
|
4
|
+
import { launchWorkspace, useConfig, type Order } from '@openmrs/esm-framework';
|
|
5
|
+
import styles from './actions.scss';
|
|
6
|
+
import { type Config } from '../../config-schema';
|
|
7
|
+
import { type BillStatus } from '../../types';
|
|
8
|
+
|
|
9
|
+
interface GenerateBillRequestActionMenuProps {
|
|
10
|
+
order: Order;
|
|
11
|
+
billStatus: BillStatus;
|
|
12
|
+
isLoading: boolean;
|
|
13
|
+
mutated: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const GenerateBillRequestAction: React.FC<GenerateBillRequestActionMenuProps> = ({
|
|
17
|
+
order,
|
|
18
|
+
billStatus = 'BLANK',
|
|
19
|
+
isLoading,
|
|
20
|
+
mutated,
|
|
21
|
+
}) => {
|
|
22
|
+
const { t } = useTranslation();
|
|
23
|
+
const { laboratoryServiceTypedUuid } = useConfig<Config>();
|
|
24
|
+
|
|
25
|
+
const launchBillWorkspace = () => {
|
|
26
|
+
launchWorkspace('create-order-bill-form-workspace', {
|
|
27
|
+
workspaceTitle: t('createOrderBill', 'Create order bill form'),
|
|
28
|
+
order,
|
|
29
|
+
quantity: 1,
|
|
30
|
+
serviceTypeUuid: laboratoryServiceTypedUuid,
|
|
31
|
+
mutated,
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return billStatus === 'PENDING' ? (
|
|
36
|
+
<Button className={styles.actionButton} size="sm" kind="danger" key={order.uuid}>
|
|
37
|
+
{t('pendingPayment', 'Pending payment')}
|
|
38
|
+
</Button>
|
|
39
|
+
) : billStatus === 'BLANK' ? (
|
|
40
|
+
<Button className={styles.actionButton} size="sm" kind="primary" key={order.uuid} onClick={launchBillWorkspace}>
|
|
41
|
+
{t('generateBill', 'Generate bill')}
|
|
42
|
+
</Button>
|
|
43
|
+
) : null;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export default GenerateBillRequestAction;
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import React, { useCallback } from 'react';
|
|
1
|
+
import React, { useCallback, useMemo } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { Button } from '@carbon/react';
|
|
4
4
|
import { showModal, type Order } from '@openmrs/esm-framework';
|
|
5
5
|
import styles from './actions.scss';
|
|
6
|
+
import { type BillStatus } from '../../types';
|
|
6
7
|
|
|
7
8
|
interface PickLabRequestActionMenuProps {
|
|
8
9
|
order: Order;
|
|
10
|
+
billStatus: BillStatus;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
|
-
const PickupLabRequestAction: React.FC<PickLabRequestActionMenuProps> = ({ order }) => {
|
|
13
|
+
const PickupLabRequestAction: React.FC<PickLabRequestActionMenuProps> = ({ order, billStatus = 'PAID' }) => {
|
|
12
14
|
const { t } = useTranslation();
|
|
13
15
|
const unsupportedStatuses = ['COMPLETED', 'DECLINED', 'IN_PROGRESS', 'ON_HOLD'];
|
|
14
16
|
|
|
@@ -19,7 +21,7 @@ const PickupLabRequestAction: React.FC<PickLabRequestActionMenuProps> = ({ order
|
|
|
19
21
|
});
|
|
20
22
|
}, [order]);
|
|
21
23
|
|
|
22
|
-
return (
|
|
24
|
+
return billStatus === 'PAID' || billStatus === 'POSTED' ? (
|
|
23
25
|
<Button
|
|
24
26
|
className={styles.actionButton}
|
|
25
27
|
disabled={unsupportedStatuses.includes(order.fulfillerStatus)}
|
|
@@ -30,7 +32,7 @@ const PickupLabRequestAction: React.FC<PickLabRequestActionMenuProps> = ({ order
|
|
|
30
32
|
>
|
|
31
33
|
{t('pickRequest', 'Pick lab request')}
|
|
32
34
|
</Button>
|
|
33
|
-
);
|
|
35
|
+
) : null;
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
export default PickupLabRequestAction;
|
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import OrdersDataTable from '../../components/orders-table/orders-data-table.component';
|
|
3
3
|
|
|
4
4
|
const PendingReviewLabRequestsTable: React.FC = () => {
|
|
5
|
-
return <OrdersDataTable fulfillerStatus="
|
|
5
|
+
return <OrdersDataTable fulfillerStatus="ON_HOLD" useFilter={false} excludeCanceledAndDiscontinuedOrders={false} />;
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
export default PendingReviewLabRequestsTable;
|