@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.
Files changed (154) hide show
  1. package/README.md +2 -1
  2. package/dist/1119.js +1 -1
  3. package/dist/1197.js +1 -1
  4. package/dist/1222.js +1 -1
  5. package/dist/1222.js.map +1 -1
  6. package/dist/1243.js +1 -1
  7. package/dist/1243.js.map +1 -1
  8. package/dist/1270.js +1 -0
  9. package/dist/1270.js.map +1 -0
  10. package/dist/2146.js +1 -1
  11. package/dist/2690.js +1 -1
  12. package/dist/3099.js +1 -1
  13. package/dist/312.js +1 -1
  14. package/dist/312.js.map +1 -1
  15. package/dist/3169.js +2 -0
  16. package/dist/3169.js.map +1 -0
  17. package/dist/3352.js +1 -1
  18. package/dist/3352.js.map +1 -1
  19. package/dist/3535.js +1 -1
  20. package/dist/3535.js.map +1 -1
  21. package/dist/3584.js +1 -1
  22. package/dist/3872.js +1 -0
  23. package/dist/3872.js.map +1 -0
  24. package/dist/4044.js +1 -1
  25. package/dist/4044.js.map +1 -1
  26. package/dist/4055.js +1 -1
  27. package/dist/4132.js +1 -1
  28. package/dist/4300.js +1 -1
  29. package/dist/4335.js +1 -1
  30. package/dist/439.js +1 -1
  31. package/dist/4618.js +1 -1
  32. package/dist/4622.js +1 -0
  33. package/dist/4622.js.map +1 -0
  34. package/dist/4652.js +1 -1
  35. package/dist/4748.js +1 -1
  36. package/dist/4748.js.map +1 -1
  37. package/dist/4920.js +1 -1
  38. package/dist/4920.js.map +1 -1
  39. package/dist/4944.js +1 -1
  40. package/dist/5088.js +1 -1
  41. package/dist/5088.js.map +1 -1
  42. package/dist/5173.js +1 -1
  43. package/dist/5241.js +1 -1
  44. package/dist/53.js +1 -1
  45. package/dist/53.js.map +1 -1
  46. package/dist/533.js +1 -0
  47. package/dist/533.js.map +1 -0
  48. package/dist/5348.js +1 -1
  49. package/dist/5348.js.map +1 -1
  50. package/dist/5380.js +1 -1
  51. package/dist/5380.js.map +1 -1
  52. package/dist/5442.js +1 -1
  53. package/dist/5661.js +1 -1
  54. package/dist/5780.js +1 -1
  55. package/dist/5780.js.map +1 -1
  56. package/dist/6022.js +1 -1
  57. package/dist/611.js +1 -0
  58. package/dist/611.js.map +1 -0
  59. package/dist/6468.js +1 -1
  60. package/dist/6589.js +1 -1
  61. package/dist/6679.js +1 -1
  62. package/dist/6753.js +1 -1
  63. package/dist/6753.js.map +1 -1
  64. package/dist/6777.js +1 -1
  65. package/dist/6777.js.map +1 -1
  66. package/dist/679.js +1 -1
  67. package/dist/679.js.map +1 -1
  68. package/dist/6840.js +1 -1
  69. package/dist/6859.js +1 -1
  70. package/dist/7044.js +1 -0
  71. package/dist/7044.js.map +1 -0
  72. package/dist/7097.js +1 -1
  73. package/dist/7129.js +1 -1
  74. package/dist/7129.js.map +1 -1
  75. package/dist/7159.js +1 -1
  76. package/dist/723.js +1 -1
  77. package/dist/7617.js +1 -1
  78. package/dist/7689.js +1 -0
  79. package/dist/7689.js.map +1 -0
  80. package/dist/791.js +1 -1
  81. package/dist/791.js.map +1 -1
  82. package/dist/795.js +1 -1
  83. package/dist/8163.js +1 -1
  84. package/dist/8349.js +1 -1
  85. package/dist/8371.js +1 -1
  86. package/dist/841.js +1 -1
  87. package/dist/841.js.map +1 -1
  88. package/dist/8618.js +1 -1
  89. package/dist/8764.js +2 -0
  90. package/dist/8764.js.map +1 -0
  91. package/dist/8898.js +1 -1
  92. package/dist/8898.js.map +1 -1
  93. package/dist/890.js +1 -1
  94. package/dist/9214.js +1 -1
  95. package/dist/9321.js +1 -1
  96. package/dist/9321.js.map +1 -1
  97. package/dist/9452.js +1 -1
  98. package/dist/9452.js.map +1 -1
  99. package/dist/9569.js +1 -1
  100. package/dist/9695.js +1 -1
  101. package/dist/9695.js.map +1 -1
  102. package/dist/986.js +1 -1
  103. package/dist/9879.js +1 -1
  104. package/dist/9900.js +1 -1
  105. package/dist/9910.js +1 -1
  106. package/dist/9910.js.map +1 -1
  107. package/dist/9913.js +1 -1
  108. package/dist/main.js +1 -1
  109. package/dist/main.js.map +1 -1
  110. package/dist/openmrs-esm-laboratory-app.js +1 -1
  111. package/dist/openmrs-esm-laboratory-app.js.buildmanifest.json +274 -172
  112. package/dist/openmrs-esm-laboratory-app.js.map +1 -1
  113. package/dist/routes.json +1 -1
  114. package/package.json +3 -1
  115. package/src/bill/bill.resource.ts +60 -0
  116. package/src/components/lab-results/_interpretation.scss +67 -0
  117. package/src/components/lab-results/lab-results.component.tsx +188 -0
  118. package/src/components/lab-results/lab-results.resource.ts +0 -0
  119. package/src/components/lab-results/lab-results.scss +39 -0
  120. package/src/components/lab-results/utils.ts +99 -0
  121. package/src/components/orders-table/list-order-details.component.tsx +9 -5
  122. package/src/components/orders-table/ordered-actions-extension-slot/ordered-actions-extension-slot.tsx +84 -0
  123. package/src/components/orders-table/orders-data-table.component.tsx +59 -12
  124. package/src/components/orders-table/orders-data-table.test.tsx +2 -2
  125. package/src/components/orders-table/priority-tag.component.tsx +60 -0
  126. package/src/components/orders-table/priority-tag.scss +12 -0
  127. package/src/config-schema.ts +64 -7
  128. package/src/constants.ts +1 -1
  129. package/src/index.ts +8 -1
  130. package/src/lab-tabs/actions/add-lab-request-results-action.component.tsx +22 -4
  131. package/src/lab-tabs/actions/amend-lab-results-action.component.tsx +3 -1
  132. package/src/lab-tabs/actions/generate-bill-request-action.component.tsx +46 -0
  133. package/src/lab-tabs/actions/pickup-lab-request-action.component.tsx +6 -4
  134. package/src/lab-tabs/data-table-extensions/pending-review-lab-request-table.extension.tsx +1 -1
  135. package/src/lab-tabs/modals/approval-lab-results-modal.component.tsx +79 -29
  136. package/src/lab-tabs/modals/approval-lab-results-modal.scss +9 -0
  137. package/src/lab-tiles/pending-review-lab-results-tile.component.tsx +1 -1
  138. package/src/laboratory.resource.ts +390 -7
  139. package/src/routes.json +17 -2
  140. package/src/types.ts +183 -1
  141. package/src/utils/utils.ts +35 -0
  142. package/translations/en.json +12 -0
  143. package/dist/3106.js +0 -1
  144. package/dist/3106.js.map +0 -1
  145. package/dist/4535.js +0 -1
  146. package/dist/4535.js.map +0 -1
  147. package/dist/5048.js +0 -2
  148. package/dist/5048.js.map +0 -1
  149. package/dist/5339.js +0 -1
  150. package/dist/5339.js.map +0 -1
  151. package/dist/8627.js +0 -2
  152. package/dist/8627.js.map +0 -1
  153. /package/dist/{8627.js.LICENSE.txt → 3169.js.LICENSE.txt} +0 -0
  154. /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: labTableColumns.includes('patientId'),
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?.find(
130
- (identifier) =>
152
+ patientId: patient?.identifiers
153
+ ?.filter((identifier) =>
131
154
  identifier.preferred &&
132
155
  !identifier.voided &&
133
- identifier.identifierType.uuid === patientIdIdentifierTypeUuid,
134
- )?.identifier,
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
- <TableCell key={cell.id}>{cell.value?.content ?? cell.value}</TableCell>
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;
@@ -0,0 +1,12 @@
1
+ .nonUrgentTag {
2
+ background-color: rgb(167, 240, 186);
3
+ color: rgb(14, 96, 39);
4
+ }
5
+ .emergencyTag {
6
+ background-color: rgb(255, 215, 217);
7
+ color: rgb(162, 25, 31);
8
+ }
9
+ .priorityTag {
10
+ background-color: rgb(248, 237, 98);
11
+ color: brown;
12
+ }
@@ -1,17 +1,38 @@
1
1
  import { Type, validators } from '@openmrs/esm-framework';
2
2
 
3
- const allowedLabTableColumns = ['name', 'age', 'dob', 'sex', 'totalOrders', 'action', 'patientId'] as const;
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: '52a447d3-a64a-11e3-9aeb-50e549534c5e',
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: ['name', 'age', 'sex', 'totalOrders', 'action'] as Array<LabTableColumnName>,
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.UUID,
23
- _default: '05a29f94-c0ed-11e2-94be-8c13b969e334',
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: false,
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
- patientIdIdentifierTypeUuid: string;
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 = '@openmrs/esm-laboratory-app';
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 = '@openmrs/esm-laboratory-app';
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
- const invalidateLabOrders = () => {
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
- orders: editableOrders,
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="DRAFT" useFilter={false} excludeCanceledAndDiscontinuedOrders={false} />;
5
+ return <OrdersDataTable fulfillerStatus="ON_HOLD" useFilter={false} excludeCanceledAndDiscontinuedOrders={false} />;
6
6
  };
7
7
 
8
8
  export default PendingReviewLabRequestsTable;