@openmrs/esm-billing-app 1.0.2-pre.669 → 1.0.2-pre.673
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/4300.js +1 -1
- package/dist/4739.js +1 -1
- package/dist/4739.js.map +1 -1
- package/dist/8638.js +1 -1
- package/dist/8638.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-billing-app.js.buildmanifest.json +13 -13
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/billing-form/billing-form.component.tsx +109 -261
- package/src/billing-form/billing-form.scss +3 -0
- package/src/billing.resource.ts +5 -12
- package/src/types/index.ts +13 -12
- package/translations/en.json +0 -6
|
@@ -330,7 +330,7 @@
|
|
|
330
330
|
"auxiliaryFiles": [
|
|
331
331
|
"2352.js.map"
|
|
332
332
|
],
|
|
333
|
-
"hash": "
|
|
333
|
+
"hash": "808a51d03d0ca1dd",
|
|
334
334
|
"childrenByOrder": {}
|
|
335
335
|
},
|
|
336
336
|
{
|
|
@@ -571,9 +571,9 @@
|
|
|
571
571
|
"initial": false,
|
|
572
572
|
"entry": false,
|
|
573
573
|
"recorded": false,
|
|
574
|
-
"size":
|
|
574
|
+
"size": 7097,
|
|
575
575
|
"sizes": {
|
|
576
|
-
"javascript":
|
|
576
|
+
"javascript": 7097
|
|
577
577
|
},
|
|
578
578
|
"names": [],
|
|
579
579
|
"idHints": [],
|
|
@@ -585,7 +585,7 @@
|
|
|
585
585
|
"4300.js"
|
|
586
586
|
],
|
|
587
587
|
"auxiliaryFiles": [],
|
|
588
|
-
"hash": "
|
|
588
|
+
"hash": "88f7c7e8c98a95c1",
|
|
589
589
|
"childrenByOrder": {}
|
|
590
590
|
},
|
|
591
591
|
{
|
|
@@ -659,9 +659,9 @@
|
|
|
659
659
|
"initial": false,
|
|
660
660
|
"entry": false,
|
|
661
661
|
"recorded": false,
|
|
662
|
-
"size":
|
|
662
|
+
"size": 24140,
|
|
663
663
|
"sizes": {
|
|
664
|
-
"javascript":
|
|
664
|
+
"javascript": 24140
|
|
665
665
|
},
|
|
666
666
|
"names": [],
|
|
667
667
|
"idHints": [],
|
|
@@ -675,7 +675,7 @@
|
|
|
675
675
|
"auxiliaryFiles": [
|
|
676
676
|
"4739.js.map"
|
|
677
677
|
],
|
|
678
|
-
"hash": "
|
|
678
|
+
"hash": "b0822fd2d079ef17",
|
|
679
679
|
"childrenByOrder": {}
|
|
680
680
|
},
|
|
681
681
|
{
|
|
@@ -1236,9 +1236,9 @@
|
|
|
1236
1236
|
"initial": false,
|
|
1237
1237
|
"entry": false,
|
|
1238
1238
|
"recorded": false,
|
|
1239
|
-
"size":
|
|
1239
|
+
"size": 1090617,
|
|
1240
1240
|
"sizes": {
|
|
1241
|
-
"javascript":
|
|
1241
|
+
"javascript": 1090575,
|
|
1242
1242
|
"consume-shared": 42
|
|
1243
1243
|
},
|
|
1244
1244
|
"names": [],
|
|
@@ -1252,7 +1252,7 @@
|
|
|
1252
1252
|
"auxiliaryFiles": [
|
|
1253
1253
|
"8638.js.map"
|
|
1254
1254
|
],
|
|
1255
|
-
"hash": "
|
|
1255
|
+
"hash": "fc1d72a2976f2aa3",
|
|
1256
1256
|
"childrenByOrder": {}
|
|
1257
1257
|
},
|
|
1258
1258
|
{
|
|
@@ -1260,10 +1260,10 @@
|
|
|
1260
1260
|
"initial": true,
|
|
1261
1261
|
"entry": true,
|
|
1262
1262
|
"recorded": false,
|
|
1263
|
-
"size":
|
|
1263
|
+
"size": 5111188,
|
|
1264
1264
|
"sizes": {
|
|
1265
1265
|
"consume-shared": 210,
|
|
1266
|
-
"javascript":
|
|
1266
|
+
"javascript": 5088536,
|
|
1267
1267
|
"share-init": 336,
|
|
1268
1268
|
"runtime": 22106
|
|
1269
1269
|
},
|
|
@@ -1280,7 +1280,7 @@
|
|
|
1280
1280
|
"auxiliaryFiles": [
|
|
1281
1281
|
"main.js.map"
|
|
1282
1282
|
],
|
|
1283
|
-
"hash": "
|
|
1283
|
+
"hash": "27f78dd3200a3e70",
|
|
1284
1284
|
"childrenByOrder": {}
|
|
1285
1285
|
},
|
|
1286
1286
|
{
|
package/dist/routes.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":">=2.24.0","fhir2":">=1.2"},"pages":[{"component":"billableServicesHome","route":"billable-services"}],"extensions":[{"component":"billingDashboardLink","name":"billing-dashboard-link","slot":"homepage-dashboard-slot","meta":{"name":"billing","title":"billing","slot":"billing-dashboard-slot"},"featureFlag":"billing"},{"component":"root","name":"billing-dashboard-root","slot":"billing-dashboard-slot"},{"name":"billing-patient-summary","component":"billingPatientSummary","slot":"patient-chart-billing-dashboard-slot","order":10,"meta":{"columnSpan":4}},{"name":"billing-summary-dashboard-link","component":"billingSummaryDashboardLink","slot":"patient-chart-dashboard-slot","order":11,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-billing-dashboard-slot","path":"Billing history"},"featureFlag":"billing"},{"name":"billable-services-app-menu-item","component":"billableServicesAppMenuItem","slot":"app-menu-item-slot","meta":{"name":"Billable Services"}},{"name":"billing-checkin-form","slot":"extra-visit-attribute-slot","component":"billingCheckInForm","featureFlag":"billing"},{"slot":"system-admin-page-card-link-slot","component":"billableServicesCardLink","name":"billable-services-admin-card-link"},{"name":"patient-banner-billing-tags","component":"visitAttributeTags","slot":"patient-banner-tags-slot","order":2},{"name":"billing-home-tiles-ext","slot":"billing-home-tiles-slot","component":"serviceMetrics"},{"name":"edit-bill-line-item-dialog","component":"editBillLineItemDialog","online":true,"offline":true}],"modals":[{"name":"require-billing-modal","component":"requirePaymentModal"}],"workspaces":[{"name":"billing-form-workspace","title":"billingForm","component":"billingFormWorkspace","type":"form"}],"featureFlags":[{"flagName":"billing","label":"Billing module","description":"This feature introduces navigation links on the patient chart and home page to allow accessing the billing module features"}],"version":"1.0.2-pre.
|
|
1
|
+
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":">=2.24.0","fhir2":">=1.2"},"pages":[{"component":"billableServicesHome","route":"billable-services"}],"extensions":[{"component":"billingDashboardLink","name":"billing-dashboard-link","slot":"homepage-dashboard-slot","meta":{"name":"billing","title":"billing","slot":"billing-dashboard-slot"},"featureFlag":"billing"},{"component":"root","name":"billing-dashboard-root","slot":"billing-dashboard-slot"},{"name":"billing-patient-summary","component":"billingPatientSummary","slot":"patient-chart-billing-dashboard-slot","order":10,"meta":{"columnSpan":4}},{"name":"billing-summary-dashboard-link","component":"billingSummaryDashboardLink","slot":"patient-chart-dashboard-slot","order":11,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-billing-dashboard-slot","path":"Billing history"},"featureFlag":"billing"},{"name":"billable-services-app-menu-item","component":"billableServicesAppMenuItem","slot":"app-menu-item-slot","meta":{"name":"Billable Services"}},{"name":"billing-checkin-form","slot":"extra-visit-attribute-slot","component":"billingCheckInForm","featureFlag":"billing"},{"slot":"system-admin-page-card-link-slot","component":"billableServicesCardLink","name":"billable-services-admin-card-link"},{"name":"patient-banner-billing-tags","component":"visitAttributeTags","slot":"patient-banner-tags-slot","order":2},{"name":"billing-home-tiles-ext","slot":"billing-home-tiles-slot","component":"serviceMetrics"},{"name":"edit-bill-line-item-dialog","component":"editBillLineItemDialog","online":true,"offline":true}],"modals":[{"name":"require-billing-modal","component":"requirePaymentModal"}],"workspaces":[{"name":"billing-form-workspace","title":"billingForm","component":"billingFormWorkspace","type":"form"}],"featureFlags":[{"flagName":"billing","label":"Billing module","description":"This feature introduces navigation links on the patient chart and home page to allow accessing the billing module features"}],"version":"1.0.2-pre.673"}
|
package/package.json
CHANGED
|
@@ -1,31 +1,28 @@
|
|
|
1
|
-
import React, { useState
|
|
2
|
-
import
|
|
3
|
-
import
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { mutate } from 'swr';
|
|
4
4
|
import {
|
|
5
5
|
Button,
|
|
6
6
|
ButtonSet,
|
|
7
|
+
ComboBox,
|
|
7
8
|
Form,
|
|
9
|
+
NumberInput,
|
|
8
10
|
InlineLoading,
|
|
9
|
-
|
|
10
|
-
RadioButtonGroup,
|
|
11
|
-
Search,
|
|
12
|
-
Stack,
|
|
11
|
+
InlineNotification,
|
|
13
12
|
Table,
|
|
14
|
-
TableBody,
|
|
15
|
-
TableCell,
|
|
16
13
|
TableHead,
|
|
17
|
-
|
|
14
|
+
TableBody,
|
|
18
15
|
TableRow,
|
|
16
|
+
TableHeader,
|
|
17
|
+
TableCell,
|
|
19
18
|
} from '@carbon/react';
|
|
20
19
|
import { TrashCan } from '@carbon/react/icons';
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
20
|
+
import { useConfig, useLayoutType, showSnackbar } from '@openmrs/esm-framework';
|
|
21
|
+
import { processBillItems, useBillableServices } from '../billing.resource';
|
|
22
|
+
import { calculateTotalAmount, convertToCurrency } from '../helpers/functions';
|
|
23
|
+
import type { BillingConfig } from '../config-schema';
|
|
24
|
+
import type { BillableItem, LineItem } from '../types';
|
|
25
25
|
import { apiBasePath } from '../constants';
|
|
26
|
-
import { convertToCurrency } from '../helpers';
|
|
27
|
-
import { type BillabeItem } from '../types';
|
|
28
|
-
import { useFetchSearchResults, processBillItems } from '../billing.resource';
|
|
29
26
|
import styles from './billing-form.scss';
|
|
30
27
|
|
|
31
28
|
type BillingFormProps = {
|
|
@@ -34,150 +31,47 @@ type BillingFormProps = {
|
|
|
34
31
|
};
|
|
35
32
|
|
|
36
33
|
const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }) => {
|
|
37
|
-
const { t } = useTranslation();
|
|
38
|
-
const { defaultCurrency, postBilledItems } = useConfig();
|
|
39
34
|
const isTablet = useLayoutType() === 'tablet';
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
const [searchOptions, setSearchOptions] = useState([]);
|
|
43
|
-
const [billItems, setBillItems] = useState([]);
|
|
44
|
-
const [category, setCategory] = useState('');
|
|
45
|
-
const [saveDisabled, setSaveDisabled] = useState<boolean>(false);
|
|
35
|
+
const { t } = useTranslation();
|
|
36
|
+
const { defaultCurrency, postBilledItems } = useConfig<BillingConfig>();
|
|
46
37
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
47
|
-
const [
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
Qnty: z.number().min(1, t('quantityGreaterThanZero', 'Quantity must be at least one for all items.')), // zod logic
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const calculateTotal = (event, itemName) => {
|
|
64
|
-
const quantity = parseInt(event.target.value);
|
|
65
|
-
let isValid = true;
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
billItemSchema.parse({ Qnty: quantity });
|
|
69
|
-
} catch (error) {
|
|
70
|
-
isValid = false;
|
|
71
|
-
const parsedErrorMessage = JSON.parse(error.message);
|
|
72
|
-
showToast({
|
|
73
|
-
title: t('saveBill', 'Save Bill'),
|
|
74
|
-
kind: 'error',
|
|
75
|
-
description: parsedErrorMessage[0].message,
|
|
76
|
-
});
|
|
38
|
+
const [selectedItems, setSelectedItems] = useState<LineItem[]>([]);
|
|
39
|
+
const { data, error, isLoading } = useBillableServices();
|
|
40
|
+
|
|
41
|
+
const selectBillableItem = (item: BillableItem) => {
|
|
42
|
+
if (!item) return;
|
|
43
|
+
const existingItem = selectedItems.find((selectedItem) => selectedItem.uuid === item.uuid);
|
|
44
|
+
|
|
45
|
+
if (existingItem) {
|
|
46
|
+
const updatedItem = { ...existingItem, quantity: existingItem.quantity + 1 };
|
|
47
|
+
setSelectedItems(
|
|
48
|
+
[...selectedItems].map((selectedItem) => (selectedItem.uuid === item.uuid ? updatedItem : selectedItem)),
|
|
49
|
+
);
|
|
50
|
+
return;
|
|
77
51
|
}
|
|
78
52
|
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
setBillItems(updatedItems);
|
|
91
|
-
|
|
92
|
-
const updatedGrandTotal = updatedItems.reduce((acc, item) => acc + item.Total, 0);
|
|
93
|
-
setGrandTotal(updatedGrandTotal);
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const calculateTotalAfterAddBillItem = (items) => {
|
|
97
|
-
const sum = items.reduce((acc, item) => acc + item.Price * item.Qnty, 0);
|
|
98
|
-
setGrandTotal(sum);
|
|
53
|
+
const mappedItem: LineItem = {
|
|
54
|
+
uuid: item.uuid,
|
|
55
|
+
display: item.name,
|
|
56
|
+
quantity: 1,
|
|
57
|
+
price: item.servicePrices?.length > 0 ? parseFloat(item.servicePrices?.[0]?.price) : 0,
|
|
58
|
+
billableService: item.uuid,
|
|
59
|
+
paymentStatus: 'PENDING',
|
|
60
|
+
lineItemOrder: 0,
|
|
61
|
+
};
|
|
62
|
+
setSelectedItems([...selectedItems, mappedItem]);
|
|
99
63
|
};
|
|
100
64
|
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
let updatedItems = [];
|
|
105
|
-
if (existingItemIndex >= 0) {
|
|
106
|
-
updatedItems = billItems.map((item, index) => {
|
|
107
|
-
if (index === existingItemIndex) {
|
|
108
|
-
const updatedQuantity = item.Qnty + 1;
|
|
109
|
-
return { ...item, Qnty: updatedQuantity, Total: updatedQuantity * item.Price };
|
|
110
|
-
}
|
|
111
|
-
return item;
|
|
112
|
-
});
|
|
113
|
-
} else {
|
|
114
|
-
const newItem = {
|
|
115
|
-
uuid: itemid,
|
|
116
|
-
Item: itemname,
|
|
117
|
-
Qnty: 1,
|
|
118
|
-
Price: itemPrice,
|
|
119
|
-
Total: itemPrice,
|
|
120
|
-
category: itemcategory,
|
|
121
|
-
};
|
|
122
|
-
updatedItems = [...billItems, newItem];
|
|
123
|
-
setAddedItems([...addedItems, newItem]);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
setBillItems(updatedItems);
|
|
127
|
-
calculateTotalAfterAddBillItem(updatedItems);
|
|
128
|
-
(document.getElementById('searchField') as HTMLInputElement).value = '';
|
|
65
|
+
const updateQuantity = (uuid: string, quantity: number) => {
|
|
66
|
+
const updatedItems = [...selectedItems].map((item) => (item.uuid === uuid ? { ...item, quantity } : item));
|
|
67
|
+
setSelectedItems(updatedItems);
|
|
129
68
|
};
|
|
130
69
|
|
|
131
|
-
const
|
|
132
|
-
const updatedItems =
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
// Update the list of added items
|
|
136
|
-
setAddedItems(addedItems.filter((item) => item.uuid !== uuid));
|
|
137
|
-
|
|
138
|
-
const updatedGrandTotal = updatedItems.reduce((acc, item) => acc + item.Total, 0);
|
|
139
|
-
setGrandTotal(updatedGrandTotal);
|
|
70
|
+
const removeSelectedBillableItem = (uuid: string) => {
|
|
71
|
+
const updatedItems = [...selectedItems].filter((item) => item.uuid !== uuid);
|
|
72
|
+
setSelectedItems(updatedItems);
|
|
140
73
|
};
|
|
141
74
|
|
|
142
|
-
const { data, error, isLoading, isValidating } = useFetchSearchResults(debouncedSearchTerm, category);
|
|
143
|
-
|
|
144
|
-
const handleSearchTermChange = (e: React.ChangeEvent<HTMLInputElement>) => setSearchTerm(e.target.value);
|
|
145
|
-
|
|
146
|
-
const filterItems = useMemo(() => {
|
|
147
|
-
if (!debouncedSearchTerm || isLoading || error) {
|
|
148
|
-
return [];
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const res = data as { results: BillabeItem[] };
|
|
152
|
-
const existingItemUuids = new Set(billItems.map((item) => item.uuid));
|
|
153
|
-
|
|
154
|
-
const preprocessedData = res?.results
|
|
155
|
-
?.map((item) => {
|
|
156
|
-
return {
|
|
157
|
-
uuid: item.uuid || '',
|
|
158
|
-
Item: item.commonName ? item.commonName : item.name,
|
|
159
|
-
Qnty: 1,
|
|
160
|
-
Price: item.commonName ? 10 : item.servicePrices[0]?.price,
|
|
161
|
-
Total: item.commonName ? 10 : item.servicePrices[0]?.price,
|
|
162
|
-
category: item.commonName ? 'StockItem' : 'Service',
|
|
163
|
-
};
|
|
164
|
-
})
|
|
165
|
-
.filter((item) => !existingItemUuids.has(item.uuid));
|
|
166
|
-
|
|
167
|
-
return debouncedSearchTerm
|
|
168
|
-
? fuzzy
|
|
169
|
-
.filter(debouncedSearchTerm, preprocessedData, {
|
|
170
|
-
extract: (o) => `${o.Item}`,
|
|
171
|
-
})
|
|
172
|
-
.sort((r1, r2) => r1.score - r2.score)
|
|
173
|
-
.map((result) => result.original)
|
|
174
|
-
: searchOptions;
|
|
175
|
-
}, [debouncedSearchTerm, isLoading, error, data, billItems, searchOptions]);
|
|
176
|
-
|
|
177
|
-
useEffect(() => {
|
|
178
|
-
setSearchOptions(filterItems);
|
|
179
|
-
}, [filterItems]);
|
|
180
|
-
|
|
181
75
|
const postBillItems = () => {
|
|
182
76
|
setIsSubmitting(true);
|
|
183
77
|
const bill = {
|
|
@@ -189,23 +83,16 @@ const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }
|
|
|
189
83
|
status: 'PENDING',
|
|
190
84
|
};
|
|
191
85
|
|
|
192
|
-
|
|
193
|
-
const lineItem:
|
|
194
|
-
quantity:
|
|
195
|
-
price: item.
|
|
196
|
-
priceName: 'Default',
|
|
197
|
-
priceUuid: postBilledItems.priceUuid,
|
|
86
|
+
selectedItems.forEach((item) => {
|
|
87
|
+
const lineItem: LineItem = {
|
|
88
|
+
quantity: item.quantity,
|
|
89
|
+
price: item.price,
|
|
198
90
|
lineItemOrder: 0,
|
|
199
91
|
paymentStatus: 'PENDING',
|
|
92
|
+
billableService: item.uuid,
|
|
200
93
|
};
|
|
201
94
|
|
|
202
|
-
|
|
203
|
-
lineItem.item = item.uuid;
|
|
204
|
-
} else {
|
|
205
|
-
lineItem.billableService = item.uuid;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
bill?.lineItems.push(lineItem);
|
|
95
|
+
bill.lineItems.push(lineItem);
|
|
209
96
|
});
|
|
210
97
|
|
|
211
98
|
const url = `${apiBasePath}bill`;
|
|
@@ -233,57 +120,28 @@ const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }
|
|
|
233
120
|
);
|
|
234
121
|
};
|
|
235
122
|
|
|
236
|
-
const handleClearSearchTerm = () => {
|
|
237
|
-
setSearchOptions([]);
|
|
238
|
-
};
|
|
239
|
-
|
|
240
123
|
return (
|
|
241
124
|
<Form className={styles.form}>
|
|
242
125
|
<div className={styles.grid}>
|
|
243
|
-
|
|
244
|
-
<
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
<RadioButton labelText={t('service', 'Service')} value="Service" id="service" />
|
|
252
|
-
</RadioButtonGroup>
|
|
253
|
-
</Stack>
|
|
254
|
-
<Stack>
|
|
255
|
-
<Search
|
|
256
|
-
size="lg"
|
|
257
|
-
id="searchField"
|
|
258
|
-
disabled={disableSearch}
|
|
259
|
-
closeButtonLabelText={t('clearSearchInput', 'Clear search input')}
|
|
260
|
-
className={styles.mt2}
|
|
261
|
-
placeholder={t('searchItems', 'Search items and services')}
|
|
262
|
-
labelText={t('searchItems', 'Search items and services')}
|
|
263
|
-
onKeyUp={handleSearchTermChange}
|
|
264
|
-
onClear={handleClearSearchTerm}
|
|
126
|
+
{isLoading ? (
|
|
127
|
+
<InlineLoading description={t('loading', 'Loading') + '...'} />
|
|
128
|
+
) : error ? (
|
|
129
|
+
<InlineNotification
|
|
130
|
+
kind="error"
|
|
131
|
+
lowContrast
|
|
132
|
+
title={t('billErrorService', 'Bill service error')}
|
|
133
|
+
subtitle={t('errorLoadingBillServices', 'Error loading bill services')}
|
|
265
134
|
/>
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
{
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
{row.Item} Qnty.{row.Qnty} {defaultCurrency}.{row.Price}
|
|
277
|
-
</Button>
|
|
278
|
-
</li>
|
|
279
|
-
))}
|
|
280
|
-
|
|
281
|
-
{searchOptions?.length === 0 && !isLoading && !!debouncedSearchTerm && (
|
|
282
|
-
<p>{t('noResultsFound', 'No results found')}</p>
|
|
283
|
-
)}
|
|
284
|
-
</ul>
|
|
285
|
-
</Stack>
|
|
286
|
-
<Stack>
|
|
135
|
+
) : (
|
|
136
|
+
<ComboBox
|
|
137
|
+
id="searchItems"
|
|
138
|
+
onChange={({ selectedItem: item }: { selectedItem: BillableItem }) => selectBillableItem(item)}
|
|
139
|
+
itemToString={(item: BillableItem) => item?.name || ''}
|
|
140
|
+
items={data ?? []}
|
|
141
|
+
titleText={t('searchItems', 'Search items and services')}
|
|
142
|
+
/>
|
|
143
|
+
)}
|
|
144
|
+
{selectedItems && selectedItems.length > 0 && (
|
|
287
145
|
<Table aria-label="sample table" className={styles.mt2}>
|
|
288
146
|
<TableHead>
|
|
289
147
|
<TableRow>
|
|
@@ -295,63 +153,53 @@ const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }
|
|
|
295
153
|
</TableRow>
|
|
296
154
|
</TableHead>
|
|
297
155
|
<TableBody>
|
|
298
|
-
{
|
|
299
|
-
|
|
300
|
-
<
|
|
301
|
-
|
|
302
|
-
<
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
<
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
<TrashCan onClick={() => removeItemFromBill(row.uuid)} className={styles.removeButton} />
|
|
322
|
-
</TableCell>
|
|
323
|
-
</TableRow>
|
|
324
|
-
))
|
|
325
|
-
) : (
|
|
326
|
-
<p>{t('loading', 'Loading...')}</p>
|
|
327
|
-
)}
|
|
156
|
+
{selectedItems.map((row) => (
|
|
157
|
+
<TableRow>
|
|
158
|
+
<TableCell>{row.display}</TableCell>
|
|
159
|
+
<TableCell>
|
|
160
|
+
<NumberInput
|
|
161
|
+
id={row.uuid}
|
|
162
|
+
min={1}
|
|
163
|
+
value={row.quantity}
|
|
164
|
+
onChange={(_, { value }) => {
|
|
165
|
+
const number = parseFloat(String(value));
|
|
166
|
+
updateQuantity(row.uuid, isNaN(number) ? 1 : number);
|
|
167
|
+
}}
|
|
168
|
+
/>
|
|
169
|
+
</TableCell>
|
|
170
|
+
<TableCell id={row.uuid + 'Price'}>{row.price}</TableCell>
|
|
171
|
+
<TableCell id={row.uuid + 'Total'} className="totalValue">
|
|
172
|
+
{row.price * row.quantity}
|
|
173
|
+
</TableCell>
|
|
174
|
+
<TableCell>
|
|
175
|
+
<TrashCan className={styles.removeButton} onClick={() => removeSelectedBillableItem(row.uuid)} />
|
|
176
|
+
</TableCell>
|
|
177
|
+
</TableRow>
|
|
178
|
+
))}
|
|
328
179
|
<TableRow>
|
|
329
180
|
<TableCell colSpan={3}></TableCell>
|
|
330
181
|
<TableCell style={{ fontWeight: 'bold' }}>{t('grandTotal', 'Grand total')}:</TableCell>
|
|
331
|
-
<TableCell id="GrandTotalSum">
|
|
182
|
+
<TableCell id="GrandTotalSum">
|
|
183
|
+
{convertToCurrency(calculateTotalAmount(selectedItems), defaultCurrency)}
|
|
184
|
+
</TableCell>
|
|
332
185
|
</TableRow>
|
|
333
186
|
</TableBody>
|
|
334
187
|
</Table>
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
<ButtonSet className={isTablet ? styles.tablet : styles.desktop}>
|
|
338
|
-
<Button className={styles.button} kind="secondary" disabled={isSubmitting} onClick={closeWorkspace}>
|
|
339
|
-
{t('discard', 'Discard')}
|
|
340
|
-
</Button>
|
|
341
|
-
<Button
|
|
342
|
-
className={styles.button}
|
|
343
|
-
kind="primary"
|
|
344
|
-
onClick={postBillItems}
|
|
345
|
-
disabled={isSubmitting || saveDisabled}
|
|
346
|
-
type="submit">
|
|
347
|
-
{isSubmitting ? (
|
|
348
|
-
<InlineLoading description={t('saving', 'Saving') + '...'} />
|
|
349
|
-
) : (
|
|
350
|
-
t('saveAndClose', 'Save and close')
|
|
351
|
-
)}
|
|
352
|
-
</Button>
|
|
353
|
-
</ButtonSet>
|
|
188
|
+
)}
|
|
354
189
|
</div>
|
|
190
|
+
|
|
191
|
+
<ButtonSet className={isTablet ? styles.tablet : styles.desktop}>
|
|
192
|
+
<Button className={styles.button} kind="secondary" disabled={isSubmitting || selectedItems.length === 0} onClick={closeWorkspace}>
|
|
193
|
+
{t('discard', 'Discard')}
|
|
194
|
+
</Button>
|
|
195
|
+
<Button className={styles.button} kind="primary" onClick={postBillItems} disabled={isSubmitting} type="submit">
|
|
196
|
+
{isSubmitting ? (
|
|
197
|
+
<InlineLoading description={t('saving', 'Saving') + '...'} />
|
|
198
|
+
) : (
|
|
199
|
+
t('saveAndClose', 'Save and close')
|
|
200
|
+
)}
|
|
201
|
+
</Button>
|
|
202
|
+
</ButtonSet>
|
|
355
203
|
</Form>
|
|
356
204
|
);
|
|
357
205
|
};
|
package/src/billing.resource.ts
CHANGED
|
@@ -9,11 +9,11 @@ import {
|
|
|
9
9
|
openmrsFetch,
|
|
10
10
|
useSession,
|
|
11
11
|
useVisit,
|
|
12
|
-
restBaseUrl,
|
|
13
12
|
type SessionLocation,
|
|
13
|
+
useOpenmrsFetchAll,
|
|
14
14
|
} from '@openmrs/esm-framework';
|
|
15
15
|
import { apiBasePath, omrsDateFormat } from './constants';
|
|
16
|
-
import type { MappedBill, PatientInvoice } from './types';
|
|
16
|
+
import type { MappedBill, PatientInvoice, BillableItem } from './types';
|
|
17
17
|
import SelectedDateContext from './hooks/selectedDateContext';
|
|
18
18
|
|
|
19
19
|
export const useBills = (patientUuid: string = '', billStatus: string = '') => {
|
|
@@ -134,16 +134,9 @@ export const usePatientPaymentInfo = (patientUuid: string) => {
|
|
|
134
134
|
return paymentInformation;
|
|
135
135
|
};
|
|
136
136
|
|
|
137
|
-
export function
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
url = `${restBaseUrl}/stockmanagement/stockitem?v=default&limit=10&q=${searchVal}`;
|
|
141
|
-
} else {
|
|
142
|
-
url = `${apiBasePath}billableService?v=custom:(uuid,name,shortName,serviceStatus,serviceType:(display),servicePrices:(uuid,name,price,paymentMode))`;
|
|
143
|
-
}
|
|
144
|
-
const { data, error, isLoading, isValidating } = useSWR(searchVal ? url : null, openmrsFetch, {});
|
|
145
|
-
|
|
146
|
-
return { data: data?.data, error, isLoading: isLoading, isValidating };
|
|
137
|
+
export function useBillableServices() {
|
|
138
|
+
const url = `${apiBasePath}billableService?v=custom:(uuid,name,shortName,serviceStatus,serviceType:(display),servicePrices:(uuid,name,price,paymentMode))`;
|
|
139
|
+
return useOpenmrsFetchAll<BillableItem>(url);
|
|
147
140
|
}
|
|
148
141
|
|
|
149
142
|
export const processBillItems = (payload) => {
|
package/src/types/index.ts
CHANGED
|
@@ -52,19 +52,19 @@ interface Provider {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
export interface LineItem {
|
|
55
|
-
uuid
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
item: string;
|
|
60
|
-
billableService: string;
|
|
55
|
+
uuid?: string;
|
|
56
|
+
item?: string;
|
|
57
|
+
paymentStatus: string;
|
|
58
|
+
billableService?: string;
|
|
61
59
|
quantity: number;
|
|
62
60
|
price: number;
|
|
63
|
-
priceName
|
|
64
|
-
priceUuid
|
|
65
|
-
lineItemOrder
|
|
66
|
-
resourceVersion
|
|
67
|
-
|
|
61
|
+
priceName?: string;
|
|
62
|
+
priceUuid?: string;
|
|
63
|
+
lineItemOrder?: number;
|
|
64
|
+
resourceVersion?: string;
|
|
65
|
+
display?: string;
|
|
66
|
+
voided?: boolean;
|
|
67
|
+
voidReason?: string | null;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
interface PatientLink {
|
|
@@ -168,7 +168,7 @@ export type ServiceConcept = {
|
|
|
168
168
|
display: string;
|
|
169
169
|
};
|
|
170
170
|
|
|
171
|
-
export type
|
|
171
|
+
export type BillableItem = {
|
|
172
172
|
uuid: string;
|
|
173
173
|
id?: number;
|
|
174
174
|
name?: string;
|
|
@@ -177,6 +177,7 @@ export type BillabeItem = {
|
|
|
177
177
|
};
|
|
178
178
|
|
|
179
179
|
export type ServicePrice = {
|
|
180
|
+
name: string;
|
|
180
181
|
price: string;
|
|
181
182
|
uuid: string;
|
|
182
183
|
};
|