@openmrs/esm-billing-app 1.0.2-pre.820 → 1.0.2-pre.824
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/1856.js +1 -1
- package/dist/3717.js +2 -0
- package/dist/3717.js.map +1 -0
- package/dist/4300.js +1 -1
- package/dist/4344.js +1 -1
- package/dist/4344.js.map +1 -1
- package/dist/4724.js +1 -1
- package/dist/4724.js.map +1 -1
- package/dist/4739.js +1 -1
- package/dist/4739.js.map +1 -1
- package/dist/6295.js +2 -0
- package/dist/6295.js.map +1 -0
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-billing-app.js +1 -1
- package/dist/openmrs-esm-billing-app.js.buildmanifest.json +70 -70
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/bill-item-actions/bill-item-actions.scss +21 -1
- package/src/bill-item-actions/edit-bill-item.modal.tsx +19 -37
- package/src/bill-item-actions/edit-bill-item.test.tsx +105 -16
- package/src/billable-services/bill-waiver/bill-selection.component.tsx +2 -2
- package/src/billable-services/bill-waiver/bill-waiver-form.component.tsx +26 -29
- package/src/billing-form/billing-checkin-form.component.tsx +18 -13
- package/src/billing-form/billing-form.component.tsx +20 -37
- package/src/billing.resource.ts +23 -12
- package/src/invoice/invoice-table.component.tsx +1 -1
- package/src/invoice/invoice-table.test.tsx +1 -1
- package/src/routes.json +4 -6
- package/translations/en.json +5 -3
- package/dist/7452.js +0 -2
- package/dist/7452.js.map +0 -1
- package/dist/8930.js +0 -2
- package/dist/8930.js.map +0 -1
- /package/dist/{7452.js.LICENSE.txt → 3717.js.LICENSE.txt} +0 -0
- /package/dist/{8930.js.LICENSE.txt → 6295.js.LICENSE.txt} +0 -0
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
3
|
import { render, screen, waitFor } from '@testing-library/react';
|
|
4
|
-
import { type FetchResponse, showSnackbar } from '@openmrs/esm-framework';
|
|
4
|
+
import { type FetchResponse, getDefaultsFromConfigSchema, showSnackbar, useConfig } from '@openmrs/esm-framework';
|
|
5
|
+
import { configSchema, type BillingConfig } from '../config-schema';
|
|
5
6
|
import { type MappedBill } from '../types';
|
|
6
7
|
import { updateBillItems } from '../billing.resource';
|
|
7
8
|
import EditBillLineItemModal from './edit-bill-item.modal';
|
|
8
9
|
|
|
9
10
|
const mockUpdateBillItems = jest.mocked(updateBillItems);
|
|
10
11
|
const mockShowSnackbar = jest.mocked(showSnackbar);
|
|
11
|
-
|
|
12
|
-
jest.mock('../billing.resource', () => ({
|
|
13
|
-
updateBillItems: jest.fn(() => Promise.resolve({})),
|
|
14
|
-
}));
|
|
12
|
+
const mockUseConfig = jest.mocked(useConfig<BillingConfig>);
|
|
15
13
|
|
|
16
14
|
const mockBillableServices = [
|
|
17
15
|
{ name: 'X-Ray Service', uuid: 'xray-uuid-123' },
|
|
@@ -19,6 +17,10 @@ const mockBillableServices = [
|
|
|
19
17
|
{ name: 'Consultation Service', uuid: 'consult-uuid-789' },
|
|
20
18
|
];
|
|
21
19
|
|
|
20
|
+
jest.mock('../billing.resource', () => ({
|
|
21
|
+
updateBillItems: jest.fn().mockResolvedValue({}),
|
|
22
|
+
}));
|
|
23
|
+
|
|
22
24
|
jest.mock('../billable-services/billable-service.resource', () => ({
|
|
23
25
|
useBillableServices: jest.fn(() => ({
|
|
24
26
|
billableServices: mockBillableServices,
|
|
@@ -79,34 +81,44 @@ const mockItem = {
|
|
|
79
81
|
resourceVersion: '1.0',
|
|
80
82
|
};
|
|
81
83
|
|
|
82
|
-
describe('
|
|
83
|
-
|
|
84
|
+
describe('EditBillItem', () => {
|
|
85
|
+
beforeEach(() => {
|
|
86
|
+
mockUseConfig.mockReturnValue({
|
|
87
|
+
...getDefaultsFromConfigSchema(configSchema),
|
|
88
|
+
defaultCurrency: 'USD',
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const mockCloseModal = jest.fn();
|
|
84
93
|
|
|
85
94
|
test('renders the form with correct fields and default values', () => {
|
|
86
|
-
render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={
|
|
95
|
+
render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={mockCloseModal} />);
|
|
87
96
|
|
|
88
97
|
expect(screen.getByText('Edit bill line item')).toBeInTheDocument();
|
|
89
|
-
expect(screen.getByText(
|
|
98
|
+
expect(screen.getByText(/John Doe/)).toBeInTheDocument();
|
|
99
|
+
expect(screen.getByText(/Main Cashpoint/)).toBeInTheDocument();
|
|
100
|
+
expect(screen.getByText(/123456/)).toBeInTheDocument();
|
|
90
101
|
expect(screen.getByRole('spinbutton', { name: /Quantity/ })).toHaveValue(2);
|
|
91
102
|
expect(screen.getByLabelText(/Unit Price/)).toHaveValue('100');
|
|
92
|
-
expect(screen.getByText(/Total/)).toHaveTextContent(
|
|
103
|
+
expect(screen.getByText(/Total/)).toHaveTextContent(/200/);
|
|
93
104
|
});
|
|
94
105
|
|
|
95
106
|
test('updates total when quantity is changed', async () => {
|
|
96
107
|
const user = userEvent.setup();
|
|
97
|
-
render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={
|
|
108
|
+
render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={mockCloseModal} />);
|
|
98
109
|
|
|
99
110
|
const quantityInput = screen.getByRole('spinbutton', { name: /Quantity/ });
|
|
111
|
+
await user.clear(quantityInput);
|
|
100
112
|
await user.type(quantityInput, '3');
|
|
101
113
|
|
|
102
|
-
expect(screen.getByText(/Total/)).toHaveTextContent(
|
|
114
|
+
expect(screen.getByText(/Total/)).toHaveTextContent(/300/);
|
|
103
115
|
});
|
|
104
116
|
|
|
105
117
|
test('submits the form and shows a success notification', async () => {
|
|
106
118
|
const user = userEvent.setup();
|
|
107
119
|
mockUpdateBillItems.mockResolvedValueOnce({} as FetchResponse<any>);
|
|
108
120
|
|
|
109
|
-
render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={
|
|
121
|
+
render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={mockCloseModal} />);
|
|
110
122
|
|
|
111
123
|
await user.click(screen.getByText(/Save/));
|
|
112
124
|
|
|
@@ -117,7 +129,7 @@ describe('ChangeStatus component', () => {
|
|
|
117
129
|
subtitle: 'The bill line item has been updated successfully',
|
|
118
130
|
kind: 'success',
|
|
119
131
|
});
|
|
120
|
-
expect(
|
|
132
|
+
expect(mockCloseModal).toHaveBeenCalled();
|
|
121
133
|
});
|
|
122
134
|
});
|
|
123
135
|
|
|
@@ -125,7 +137,7 @@ describe('ChangeStatus component', () => {
|
|
|
125
137
|
const user = userEvent.setup();
|
|
126
138
|
mockUpdateBillItems.mockRejectedValueOnce({ message: 'Error occurred' });
|
|
127
139
|
|
|
128
|
-
render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={
|
|
140
|
+
render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={mockCloseModal} />);
|
|
129
141
|
|
|
130
142
|
await user.click(screen.getByText(/Save/));
|
|
131
143
|
|
|
@@ -197,7 +209,7 @@ describe('ChangeStatus component', () => {
|
|
|
197
209
|
// Editing the Lab Test item (item-2)
|
|
198
210
|
const itemToEdit = billWithMultipleItems.lineItems[1];
|
|
199
211
|
|
|
200
|
-
render(<EditBillLineItemModal bill={billWithMultipleItems} item={itemToEdit} closeModal={
|
|
212
|
+
render(<EditBillLineItemModal bill={billWithMultipleItems} item={itemToEdit} closeModal={mockCloseModal} />);
|
|
201
213
|
|
|
202
214
|
await user.click(screen.getByText(/Save/));
|
|
203
215
|
|
|
@@ -219,4 +231,81 @@ describe('ChangeStatus component', () => {
|
|
|
219
231
|
expect(labItem?.billableService).toBe('lab-uuid-456');
|
|
220
232
|
});
|
|
221
233
|
});
|
|
234
|
+
|
|
235
|
+
test('shows validation error for quantity less than 1', async () => {
|
|
236
|
+
const user = userEvent.setup();
|
|
237
|
+
render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={mockCloseModal} />);
|
|
238
|
+
|
|
239
|
+
const quantityInput = screen.getByRole('spinbutton', { name: /Quantity/ });
|
|
240
|
+
await user.clear(quantityInput);
|
|
241
|
+
await user.type(quantityInput, '0');
|
|
242
|
+
|
|
243
|
+
// Try to submit
|
|
244
|
+
await user.click(screen.getByText(/Save/));
|
|
245
|
+
|
|
246
|
+
// Should show validation error
|
|
247
|
+
await waitFor(() => {
|
|
248
|
+
expect(screen.getByText(/Quantity must be at least 1/)).toBeInTheDocument();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Should NOT call the update function
|
|
252
|
+
expect(mockUpdateBillItems).not.toHaveBeenCalled();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test('shows validation error for quantity greater than 100', async () => {
|
|
256
|
+
const user = userEvent.setup();
|
|
257
|
+
render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={mockCloseModal} />);
|
|
258
|
+
|
|
259
|
+
const quantityInput = screen.getByRole('spinbutton', { name: /Quantity/ });
|
|
260
|
+
await user.clear(quantityInput);
|
|
261
|
+
await user.type(quantityInput, '101');
|
|
262
|
+
|
|
263
|
+
await user.click(screen.getByText(/Save/));
|
|
264
|
+
|
|
265
|
+
await waitFor(() => {
|
|
266
|
+
expect(screen.getByText(/Quantity cannot exceed 100/)).toBeInTheDocument();
|
|
267
|
+
});
|
|
268
|
+
expect(mockUpdateBillItems).not.toHaveBeenCalled();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test('shows validation error for non-integer quantity', async () => {
|
|
272
|
+
const user = userEvent.setup();
|
|
273
|
+
render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={mockCloseModal} />);
|
|
274
|
+
|
|
275
|
+
const quantityInput = screen.getByRole('spinbutton', { name: /Quantity/ });
|
|
276
|
+
await user.clear(quantityInput);
|
|
277
|
+
await user.type(quantityInput, '2.5');
|
|
278
|
+
|
|
279
|
+
await user.click(screen.getByText(/Save/));
|
|
280
|
+
|
|
281
|
+
await waitFor(() => {
|
|
282
|
+
expect(screen.getByText(/Quantity must be a whole number/)).toBeInTheDocument();
|
|
283
|
+
});
|
|
284
|
+
expect(mockUpdateBillItems).not.toHaveBeenCalled();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test('clears validation error when valid quantity is entered', async () => {
|
|
288
|
+
const user = userEvent.setup();
|
|
289
|
+
render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={mockCloseModal} />);
|
|
290
|
+
|
|
291
|
+
const quantityInput = screen.getByRole('spinbutton', { name: /Quantity/ });
|
|
292
|
+
|
|
293
|
+
// Enter invalid value
|
|
294
|
+
await user.clear(quantityInput);
|
|
295
|
+
await user.type(quantityInput, '0');
|
|
296
|
+
await user.click(screen.getByText(/Save/));
|
|
297
|
+
|
|
298
|
+
await waitFor(() => {
|
|
299
|
+
expect(screen.getByText(/Quantity must be at least 1/)).toBeInTheDocument();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Fix it
|
|
303
|
+
await user.clear(quantityInput);
|
|
304
|
+
await user.type(quantityInput, '5');
|
|
305
|
+
|
|
306
|
+
// Error should disappear
|
|
307
|
+
await waitFor(() => {
|
|
308
|
+
expect(screen.queryByText(/Quantity must be at least 1/)).not.toBeInTheDocument();
|
|
309
|
+
});
|
|
310
|
+
});
|
|
222
311
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Checkbox,
|
|
4
4
|
Layer,
|
|
@@ -20,7 +20,7 @@ const PatientBillsSelections: React.FC<{ bills: MappedBill; setPatientUuid: (pat
|
|
|
20
20
|
setPatientUuid,
|
|
21
21
|
}) => {
|
|
22
22
|
const { t } = useTranslation();
|
|
23
|
-
const [selectedBills, setSelectedBills] =
|
|
23
|
+
const [selectedBills, setSelectedBills] = useState<Array<LineItem>>([]);
|
|
24
24
|
const { defaultCurrency } = useConfig();
|
|
25
25
|
|
|
26
26
|
const checkBoxLabel = (lineItem) => {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
2
|
import { Form, Stack, FormGroup, Layer, Button, NumberInput } from '@carbon/react';
|
|
3
3
|
import { TaskAdd } from '@carbon/react/icons';
|
|
4
4
|
import { mutate } from 'swr';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
import { showSnackbar, useConfig } from '@openmrs/esm-framework';
|
|
7
7
|
import { createBillWaiverPayload } from './utils';
|
|
8
|
-
import { convertToCurrency } from '../../helpers';
|
|
8
|
+
import { calculateTotalAmount, convertToCurrency } from '../../helpers';
|
|
9
9
|
import { processBillPayment } from '../../billing.resource';
|
|
10
10
|
import { useBillableItems } from '../../billing-form/billing-form.resource';
|
|
11
11
|
import type { LineItem, MappedBill } from '../../types';
|
|
@@ -20,16 +20,16 @@ type BillWaiverFormProps = {
|
|
|
20
20
|
|
|
21
21
|
const BillWaiverForm: React.FC<BillWaiverFormProps> = ({ bill, lineItems, setPatientUuid }) => {
|
|
22
22
|
const { t } = useTranslation();
|
|
23
|
-
const [waiverAmount, setWaiverAmount] =
|
|
24
|
-
const { lineItems: billableLineItems
|
|
25
|
-
const totalAmount = lineItems
|
|
23
|
+
const [waiverAmount, setWaiverAmount] = useState(0);
|
|
24
|
+
const { lineItems: billableLineItems } = useBillableItems();
|
|
25
|
+
const totalAmount = calculateTotalAmount(lineItems);
|
|
26
26
|
const { defaultCurrency } = useConfig();
|
|
27
27
|
|
|
28
28
|
if (lineItems?.length === 0) {
|
|
29
29
|
return null;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const handleProcessPayment = () => {
|
|
32
|
+
const handleProcessPayment = async () => {
|
|
33
33
|
const waiverEndPointPayload = createBillWaiverPayload(
|
|
34
34
|
bill,
|
|
35
35
|
waiverAmount,
|
|
@@ -38,29 +38,26 @@ const BillWaiverForm: React.FC<BillWaiverFormProps> = ({ bill, lineItems, setPat
|
|
|
38
38
|
billableLineItems,
|
|
39
39
|
);
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
()
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
});
|
|
62
|
-
},
|
|
63
|
-
);
|
|
41
|
+
try {
|
|
42
|
+
await processBillPayment(waiverEndPointPayload, bill.uuid);
|
|
43
|
+
showSnackbar({
|
|
44
|
+
title: t('billWaiver', 'Bill waiver'),
|
|
45
|
+
subtitle: t('billWaiverSuccess', 'Bill waiver successful'),
|
|
46
|
+
kind: 'success',
|
|
47
|
+
isLowContrast: true,
|
|
48
|
+
});
|
|
49
|
+
setPatientUuid('');
|
|
50
|
+
mutate((key) => typeof key === 'string' && key.startsWith(`${apiBasePath}bill?v=full`), undefined, {
|
|
51
|
+
revalidate: true,
|
|
52
|
+
});
|
|
53
|
+
} catch (error) {
|
|
54
|
+
showSnackbar({
|
|
55
|
+
title: t('billWaiver', 'Bill waiver'),
|
|
56
|
+
subtitle: t('billWaiverError', 'Bill waiver failed {{error}}', { error: error?.message }),
|
|
57
|
+
kind: 'error',
|
|
58
|
+
isLowContrast: true,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
64
61
|
};
|
|
65
62
|
|
|
66
63
|
return (
|
|
@@ -21,20 +21,25 @@ const BillingCheckInForm: React.FC<BillingCheckInFormProps> = ({ patientUuid, se
|
|
|
21
21
|
const [paymentMethod, setPaymentMethod] = useState<any>();
|
|
22
22
|
let lineList = [];
|
|
23
23
|
|
|
24
|
-
const handleCreateExtraVisitInfo = useCallback(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
},
|
|
29
|
-
(error) => {
|
|
24
|
+
const handleCreateExtraVisitInfo = useCallback(
|
|
25
|
+
async (createBillPayload) => {
|
|
26
|
+
try {
|
|
27
|
+
await createPatientBill(createBillPayload);
|
|
30
28
|
showSnackbar({
|
|
31
|
-
title: 'Patient
|
|
32
|
-
subtitle: '
|
|
29
|
+
title: t('patientBill', 'Patient bill'),
|
|
30
|
+
subtitle: t('billCreatedSuccessfully', 'Bill created successfully'),
|
|
31
|
+
kind: 'success',
|
|
32
|
+
});
|
|
33
|
+
} catch (error) {
|
|
34
|
+
showSnackbar({
|
|
35
|
+
title: t('billCreationError', 'Bill creation error'),
|
|
36
|
+
subtitle: t('errorCreatingBill', 'An error occurred while creating the bill'),
|
|
33
37
|
kind: 'error',
|
|
34
38
|
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
[t],
|
|
42
|
+
);
|
|
38
43
|
|
|
39
44
|
const handleBillingService = ({ selectedItem }) => {
|
|
40
45
|
const cashPointUuid = cashPoints?.[0]?.uuid ?? '';
|
|
@@ -74,7 +79,7 @@ const BillingCheckInForm: React.FC<BillingCheckInFormProps> = ({ patientUuid, se
|
|
|
74
79
|
<InlineLoading
|
|
75
80
|
status="active"
|
|
76
81
|
iconDescription={getCoreTranslation('loading')}
|
|
77
|
-
description={t('loadingBillingServices', 'Loading billing services
|
|
82
|
+
description={`${t('loadingBillingServices', 'Loading billing services')}...`}
|
|
78
83
|
/>
|
|
79
84
|
);
|
|
80
85
|
}
|
|
@@ -110,7 +115,7 @@ const BillingCheckInForm: React.FC<BillingCheckInFormProps> = ({ patientUuid, se
|
|
|
110
115
|
<div className={styles.sectionTitle}>{t('billing', 'Billing')}</div>
|
|
111
116
|
<div className={styles.sectionField}></div>
|
|
112
117
|
<Dropdown
|
|
113
|
-
label={t('selectBillableService', 'Select a billable service
|
|
118
|
+
label={t('selectBillableService', 'Select a billable service')}
|
|
114
119
|
onChange={handleBillingService}
|
|
115
120
|
id="billable-items"
|
|
116
121
|
items={lineList}
|
|
@@ -1,21 +1,7 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { mutate } from 'swr';
|
|
4
|
-
import {
|
|
5
|
-
Button,
|
|
6
|
-
ButtonSet,
|
|
7
|
-
ComboBox,
|
|
8
|
-
Form,
|
|
9
|
-
NumberInput,
|
|
10
|
-
InlineLoading,
|
|
11
|
-
InlineNotification,
|
|
12
|
-
Table,
|
|
13
|
-
TableHead,
|
|
14
|
-
TableBody,
|
|
15
|
-
TableRow,
|
|
16
|
-
TableHeader,
|
|
17
|
-
TableCell,
|
|
18
|
-
} from '@carbon/react';
|
|
4
|
+
import { Button, ButtonSet, ComboBox, Form, NumberInput, InlineLoading, InlineNotification } from '@carbon/react';
|
|
19
5
|
import { TrashCan } from '@carbon/react/icons';
|
|
20
6
|
import { useConfig, useLayoutType, showSnackbar, getCoreTranslation } from '@openmrs/esm-framework';
|
|
21
7
|
import { processBillItems, useBillableServices } from '../billing.resource';
|
|
@@ -119,7 +105,7 @@ const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }
|
|
|
119
105
|
return true;
|
|
120
106
|
};
|
|
121
107
|
|
|
122
|
-
const postBillItems = () => {
|
|
108
|
+
const postBillItems = async () => {
|
|
123
109
|
if (!validateSelectedItems()) {
|
|
124
110
|
return;
|
|
125
111
|
}
|
|
@@ -147,27 +133,24 @@ const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }
|
|
|
147
133
|
});
|
|
148
134
|
|
|
149
135
|
const url = `${apiBasePath}bill`;
|
|
150
|
-
|
|
151
|
-
()
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
});
|
|
169
|
-
},
|
|
170
|
-
);
|
|
136
|
+
try {
|
|
137
|
+
await processBillItems(bill);
|
|
138
|
+
closeWorkspace();
|
|
139
|
+
mutate((key) => typeof key === 'string' && key.startsWith(url), undefined, { revalidate: true });
|
|
140
|
+
showSnackbar({
|
|
141
|
+
title: t('saveBill', 'Save Bill'),
|
|
142
|
+
subtitle: t('billProcessingSuccess', 'Bill processing has been successful'),
|
|
143
|
+
kind: 'success',
|
|
144
|
+
});
|
|
145
|
+
} catch (error) {
|
|
146
|
+
showSnackbar({
|
|
147
|
+
title: t('billProcessingError', 'Bill processing error'),
|
|
148
|
+
kind: 'error',
|
|
149
|
+
subtitle: error?.message,
|
|
150
|
+
});
|
|
151
|
+
} finally {
|
|
152
|
+
setIsSubmitting(false);
|
|
153
|
+
}
|
|
171
154
|
};
|
|
172
155
|
|
|
173
156
|
return (
|
package/src/billing.resource.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import isEmpty from 'lodash-es/isEmpty';
|
|
2
|
-
import sortBy from 'lodash-es/sortBy';
|
|
3
1
|
import useSWR from 'swr';
|
|
2
|
+
import sortBy from 'lodash-es/sortBy';
|
|
4
3
|
import {
|
|
5
4
|
formatDate,
|
|
6
5
|
parseDate,
|
|
@@ -21,9 +20,11 @@ import type {
|
|
|
21
20
|
} from './types';
|
|
22
21
|
|
|
23
22
|
const mapBillProperties = (bill: PatientInvoice): MappedBill => {
|
|
23
|
+
const activeLineItems = bill?.lineItems?.filter((item) => !item.voided) || [];
|
|
24
|
+
|
|
24
25
|
const status =
|
|
25
|
-
|
|
26
|
-
?
|
|
26
|
+
activeLineItems.length > 1
|
|
27
|
+
? activeLineItems.some((item) => item.paymentStatus === 'PENDING')
|
|
27
28
|
? 'PENDING'
|
|
28
29
|
: 'PAID'
|
|
29
30
|
: bill.status;
|
|
@@ -41,28 +42,38 @@ const mapBillProperties = (bill: PatientInvoice): MappedBill => {
|
|
|
41
42
|
cashPointName: bill?.cashPoint?.name,
|
|
42
43
|
cashPointLocation: bill?.cashPoint?.location?.display,
|
|
43
44
|
dateCreated: bill?.dateCreated ? formatDate(parseDate(bill.dateCreated), { mode: 'wide' }) : '--',
|
|
44
|
-
lineItems:
|
|
45
|
-
billingService:
|
|
45
|
+
lineItems: activeLineItems,
|
|
46
|
+
billingService: activeLineItems.map((lineItem) => lineItem?.item || lineItem?.billableService || '--').join(' '),
|
|
46
47
|
payments: bill.payments,
|
|
47
48
|
display: bill?.display,
|
|
48
|
-
totalAmount:
|
|
49
|
+
totalAmount: activeLineItems?.map((item) => item.price * item.quantity).reduce((prev, curr) => prev + curr, 0),
|
|
49
50
|
tenderedAmount: bill?.payments?.map((item) => item.amountTendered).reduce((prev, curr) => prev + curr, 0),
|
|
50
51
|
};
|
|
51
52
|
};
|
|
52
53
|
|
|
53
54
|
export const useBills = (patientUuid: string = '', billStatus: string = '') => {
|
|
54
|
-
|
|
55
|
+
// Build URL with status parameter if provided
|
|
56
|
+
const buildUrl = () => {
|
|
57
|
+
let url = `${apiBasePath}bill?v=full`;
|
|
55
58
|
|
|
56
|
-
|
|
59
|
+
if (patientUuid) {
|
|
60
|
+
url += `&patientUuid=${patientUuid}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (billStatus) {
|
|
64
|
+
url += `&status=${billStatus}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return url;
|
|
68
|
+
};
|
|
57
69
|
|
|
58
70
|
const { data, error, isLoading, isValidating, mutate } = useSWR<{ data: { results: Array<PatientInvoice> } }>(
|
|
59
|
-
|
|
71
|
+
buildUrl(),
|
|
60
72
|
openmrsFetch,
|
|
61
73
|
);
|
|
62
74
|
|
|
63
75
|
const sortedBills = sortBy(data?.data?.results ?? [], ['dateCreated']).reverse();
|
|
64
|
-
const
|
|
65
|
-
const mappedResults = filteredBills?.map((bill) => mapBillProperties(bill));
|
|
76
|
+
const mappedResults = sortedBills?.map((bill) => mapBillProperties(bill));
|
|
66
77
|
|
|
67
78
|
return {
|
|
68
79
|
bills: mappedResults,
|
|
@@ -73,7 +73,7 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isLoadingBill }) => {
|
|
|
73
73
|
|
|
74
74
|
const handleSelectBillItem = useCallback(
|
|
75
75
|
(row: LineItem) => {
|
|
76
|
-
const dispose = showModal('edit-bill-line-item-
|
|
76
|
+
const dispose = showModal('edit-bill-line-item-modal', {
|
|
77
77
|
bill,
|
|
78
78
|
item: row,
|
|
79
79
|
closeModal: () => dispose(),
|
|
@@ -168,7 +168,7 @@ describe('InvoiceTable', () => {
|
|
|
168
168
|
|
|
169
169
|
expect(mockShowModal).toHaveBeenCalledTimes(1);
|
|
170
170
|
expect(mockShowModal).toHaveBeenCalledWith(
|
|
171
|
-
'edit-bill-line-item-
|
|
171
|
+
'edit-bill-line-item-modal',
|
|
172
172
|
expect.objectContaining({
|
|
173
173
|
bill: defaultBill,
|
|
174
174
|
item: expect.objectContaining({ uuid: '1' }),
|
package/src/routes.json
CHANGED
|
@@ -78,12 +78,6 @@
|
|
|
78
78
|
"name": "billing-home-tiles-ext",
|
|
79
79
|
"slot": "billing-home-tiles-slot",
|
|
80
80
|
"component": "serviceMetrics"
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
"name": "edit-bill-line-item-dialog",
|
|
84
|
-
"component": "editBillLineItemModal",
|
|
85
|
-
"online": true,
|
|
86
|
-
"offline": true
|
|
87
81
|
}
|
|
88
82
|
],
|
|
89
83
|
"modals": [
|
|
@@ -103,6 +97,10 @@
|
|
|
103
97
|
"name": "edit-bill-item-modal",
|
|
104
98
|
"component": "editBillLineItemModal"
|
|
105
99
|
},
|
|
100
|
+
{
|
|
101
|
+
"name": "edit-bill-line-item-modal",
|
|
102
|
+
"component": "editBillLineItemModal"
|
|
103
|
+
},
|
|
106
104
|
{
|
|
107
105
|
"name": "edit-billable-service-modal",
|
|
108
106
|
"component": "editBillableServiceModal"
|
package/translations/en.json
CHANGED
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
"billableServices__lower": "billable services",
|
|
25
25
|
"billAmount": "Bill amount",
|
|
26
26
|
"billCode": "Bill code",
|
|
27
|
+
"billCreatedSuccessfully": "Bill created successfully",
|
|
28
|
+
"billCreationError": "Bill creation error",
|
|
27
29
|
"billedItems": "Billed Items",
|
|
28
30
|
"billedTo": "Billed to",
|
|
29
31
|
"billErrorService": "Bill service error",
|
|
@@ -81,7 +83,7 @@
|
|
|
81
83
|
"enterSellingPrice": "Enter selling price",
|
|
82
84
|
"enterServiceName": "Enter service name",
|
|
83
85
|
"enterServiceShortName": "Enter service short name",
|
|
84
|
-
"
|
|
86
|
+
"errorCreatingBill": "An error occurred while creating the bill",
|
|
85
87
|
"errorDeletingPaymentMode": "An error occurred while deleting the payment mode.",
|
|
86
88
|
"errorFetchingCashPoints": "An error occurred while fetching cash points.",
|
|
87
89
|
"errorFetchingLocations": "An error occurred while fetching locations.",
|
|
@@ -132,6 +134,7 @@
|
|
|
132
134
|
"noResultsFor": "No results for",
|
|
133
135
|
"number": "No",
|
|
134
136
|
"ok": "OK",
|
|
137
|
+
"patientBill": "Patient bill",
|
|
135
138
|
"patientBillingAlert": "Patient Billing Alert",
|
|
136
139
|
"patientBills": "Patient bill",
|
|
137
140
|
"patientBillsDescription": "List of patient bills",
|
|
@@ -152,7 +155,6 @@
|
|
|
152
155
|
"paymentOptionRequired": "At least one payment option is required",
|
|
153
156
|
"paymentProcessedSuccessfully": "Payment processed successfully",
|
|
154
157
|
"payments": "Payments",
|
|
155
|
-
"pleaseRequiredFields": "Please fill all required fields",
|
|
156
158
|
"policyNumber": "Policy number",
|
|
157
159
|
"postWaiver": "Post waiver",
|
|
158
160
|
"previousPage": "Previous page",
|
|
@@ -181,7 +183,7 @@
|
|
|
181
183
|
"searching": "Searching",
|
|
182
184
|
"searchItems": "Search items and services",
|
|
183
185
|
"searchThisTable": "Search this table",
|
|
184
|
-
"selectBillableService": "Select a billable service
|
|
186
|
+
"selectBillableService": "Select a billable service",
|
|
185
187
|
"selectedItems": "Selected Items",
|
|
186
188
|
"selectLocation": "Select Location",
|
|
187
189
|
"selectPatientCategory": "Select patient category",
|