@openmrs/esm-billing-app 1.0.2-pre.76 → 1.0.2-pre.761
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 +55 -9
- package/__mocks__/bills.mock.ts +12 -0
- package/__mocks__/react-i18next.js +6 -5
- package/dist/1119.js +1 -1
- package/dist/1146.js +1 -2
- package/dist/1146.js.map +1 -1
- package/dist/1197.js +1 -1
- package/dist/1856.js +1 -0
- package/dist/1856.js.map +1 -0
- package/dist/2146.js +1 -1
- package/dist/2177.js +2 -0
- package/dist/2177.js.LICENSE.txt +9 -0
- package/dist/2177.js.map +1 -0
- package/dist/2524.js +1 -0
- package/dist/2524.js.map +1 -0
- package/dist/2690.js +1 -1
- package/dist/3041.js +1 -0
- package/dist/3041.js.map +1 -0
- package/dist/3099.js +1 -1
- package/dist/3584.js +1 -1
- package/dist/4055.js +1 -1
- package/dist/4132.js +1 -1
- package/dist/4225.js +1 -0
- package/dist/4225.js.map +1 -0
- package/dist/4300.js +1 -1
- package/dist/4335.js +1 -1
- package/dist/4618.js +1 -1
- package/dist/4652.js +1 -1
- package/dist/4724.js +1 -0
- package/dist/4724.js.map +1 -0
- package/dist/4739.js +1 -1
- package/dist/4739.js.map +1 -1
- package/dist/4944.js +1 -1
- package/dist/5173.js +1 -1
- package/dist/5241.js +1 -1
- package/dist/5422.js +1 -0
- package/dist/5422.js.map +1 -0
- package/dist/5442.js +1 -1
- package/dist/5661.js +1 -1
- package/dist/6022.js +1 -1
- package/dist/6468.js +1 -1
- package/dist/6540.js +1 -1
- package/dist/6540.js.map +1 -1
- package/dist/6606.js +1 -0
- package/dist/6606.js.map +1 -0
- package/dist/6679.js +1 -1
- package/dist/6840.js +1 -1
- package/dist/6859.js +1 -1
- package/dist/7097.js +1 -1
- package/dist/7159.js +1 -1
- package/dist/723.js +1 -1
- package/dist/7452.js +2 -0
- package/dist/7452.js.map +1 -0
- package/dist/7617.js +1 -1
- package/dist/795.js +1 -1
- package/dist/8163.js +1 -1
- package/dist/8349.js +1 -1
- package/dist/8618.js +1 -1
- package/dist/890.js +1 -1
- package/dist/8930.js +2 -0
- package/dist/{6525.js.LICENSE.txt → 8930.js.LICENSE.txt} +16 -4
- package/dist/8930.js.map +1 -0
- package/dist/9214.js +1 -1
- package/dist/942.js +1 -0
- package/dist/942.js.map +1 -0
- package/dist/9538.js +1 -1
- package/dist/9569.js +1 -1
- package/dist/961.js +1 -1
- package/dist/961.js.map +1 -1
- package/dist/986.js +1 -1
- package/dist/9879.js +1 -1
- package/dist/9895.js +1 -1
- package/dist/9900.js +1 -1
- package/dist/9913.js +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-billing-app.js +1 -1
- package/dist/openmrs-esm-billing-app.js.buildmanifest.json +368 -262
- package/dist/openmrs-esm-billing-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/e2e/README.md +19 -18
- package/e2e/specs/sample-test.spec.ts +0 -1
- package/package.json +10 -10
- package/src/bill-history/bill-history.component.tsx +17 -25
- package/src/bill-history/bill-history.scss +4 -94
- package/src/bill-history/bill-history.test.tsx +37 -78
- package/src/bill-item-actions/bill-item-actions.scss +0 -4
- package/src/bill-item-actions/{edit-bill-item.component.tsx → edit-bill-item.modal.tsx} +57 -56
- package/src/bill-item-actions/edit-bill-item.test.tsx +22 -25
- package/src/billable-services/bill-waiver/bill-selection.component.tsx +2 -2
- package/src/billable-services/bill-waiver/patient-bills.component.tsx +3 -3
- package/src/billable-services/billable-service.resource.ts +17 -9
- package/src/billable-services/billable-services-home.component.tsx +1 -1
- package/src/billable-services/billable-services.component.tsx +142 -145
- package/src/billable-services/billable-services.scss +3 -0
- package/src/billable-services/billable-services.test.tsx +2 -45
- package/src/billable-services/cash-point/add-cash-point.modal.tsx +168 -0
- package/src/billable-services/cash-point/cash-point-configuration.component.tsx +18 -192
- package/src/billable-services/cash-point/cash-point-configuration.scss +1 -5
- package/src/billable-services/create-edit/add-billable-service.component.tsx +345 -298
- package/src/billable-services/create-edit/add-billable-service.scss +5 -6
- package/src/billable-services/create-edit/add-billable-service.test.tsx +37 -36
- package/src/billable-services/create-edit/edit-billable-service.modal.tsx +51 -0
- package/src/billable-services/payment-modes/add-payment-mode.modal.tsx +121 -0
- package/src/billable-services/payment-modes/delete-payment-mode.modal.tsx +72 -0
- package/src/billable-services/payment-modes/payment-modes-config.component.tsx +125 -0
- package/src/billable-services/{payyment-modes → payment-modes}/payment-modes-config.scss +5 -4
- package/src/billing-form/billing-checkin-form.component.tsx +2 -3
- package/src/billing-form/billing-checkin-form.test.tsx +97 -24
- package/src/billing-form/billing-form.component.tsx +214 -269
- package/src/billing-form/billing-form.scss +143 -0
- package/src/billing.resource.ts +16 -19
- package/src/bills-table/bills-table.test.tsx +97 -53
- package/src/config-schema.ts +52 -18
- package/src/dashboard.meta.ts +4 -2
- package/src/helpers/functions.ts +5 -4
- package/src/index.ts +17 -6
- package/src/invoice/invoice-table.component.tsx +24 -54
- package/src/invoice/invoice-table.scss +1 -5
- package/src/invoice/invoice-table.test.tsx +21 -47
- package/src/invoice/invoice.component.tsx +36 -29
- package/src/invoice/invoice.scss +7 -4
- package/src/invoice/invoice.test.tsx +22 -48
- package/src/invoice/payments/payment-form/payment-form.component.tsx +29 -29
- package/src/invoice/payments/payment-form/payment-form.scss +5 -6
- package/src/invoice/payments/payment-form/payment-form.test.tsx +215 -67
- package/src/invoice/payments/payment-history/payment-history.component.tsx +6 -4
- package/src/invoice/payments/payment-history/payment-history.test.tsx +9 -14
- package/src/invoice/payments/payments.component.tsx +53 -65
- package/src/invoice/payments/payments.test.tsx +282 -0
- package/src/invoice/payments/utils.ts +5 -23
- package/src/invoice/printable-invoice/print-receipt.component.tsx +3 -2
- package/src/invoice/printable-invoice/print-receipt.test.tsx +14 -25
- package/src/invoice/printable-invoice/printable-footer.component.tsx +2 -2
- package/src/invoice/printable-invoice/printable-footer.test.tsx +4 -13
- package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +12 -11
- package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +16 -14
- package/src/invoice/printable-invoice/printable-invoice.component.tsx +19 -33
- package/src/metrics-cards/metrics-cards.test.tsx +18 -5
- package/src/modal/require-payment-modal.test.tsx +27 -22
- package/src/modal/{require-payment-modal.component.tsx → require-payment.modal.tsx} +17 -18
- package/src/routes.json +22 -2
- package/src/types/index.ts +26 -17
- package/translations/am.json +60 -18
- package/translations/ar.json +60 -18
- package/translations/ar_SY.json +60 -18
- package/translations/bn.json +65 -23
- package/translations/de.json +60 -18
- package/translations/en.json +60 -18
- package/translations/en_US.json +60 -18
- package/translations/es.json +60 -18
- package/translations/es_MX.json +60 -18
- package/translations/fr.json +73 -31
- package/translations/he.json +60 -18
- package/translations/hi.json +60 -18
- package/translations/hi_IN.json +60 -18
- package/translations/id.json +61 -19
- package/translations/it.json +96 -54
- package/translations/ka.json +61 -19
- package/translations/km.json +60 -18
- package/translations/ku.json +60 -18
- package/translations/ky.json +60 -18
- package/translations/lg.json +60 -18
- package/translations/ne.json +60 -18
- package/translations/pl.json +60 -18
- package/translations/pt.json +60 -18
- package/translations/pt_BR.json +60 -18
- package/translations/qu.json +60 -18
- package/translations/ro_RO.json +206 -164
- package/translations/ru_RU.json +60 -18
- package/translations/si.json +60 -18
- package/translations/sw.json +60 -18
- package/translations/sw_KE.json +60 -18
- package/translations/tr.json +60 -18
- package/translations/tr_TR.json +60 -18
- package/translations/uk.json +60 -18
- package/translations/uz.json +60 -18
- package/translations/uz@Latn.json +60 -18
- package/translations/uz_UZ.json +60 -18
- package/translations/vi.json +60 -18
- package/translations/zh.json +60 -18
- package/translations/zh_CN.json +117 -75
- package/dist/1146.js.LICENSE.txt +0 -21
- package/dist/2352.js +0 -1
- package/dist/2352.js.map +0 -1
- package/dist/246.js +0 -1
- package/dist/246.js.map +0 -1
- package/dist/6525.js +0 -2
- package/dist/6525.js.map +0 -1
- package/dist/8556.js +0 -2
- package/dist/8556.js.map +0 -1
- package/dist/8638.js +0 -1
- package/dist/8638.js.map +0 -1
- package/dist/9968.js +0 -1
- package/dist/9968.js.map +0 -1
- package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +0 -280
- package/src/invoice/payments/payments.component.test.tsx +0 -121
- /package/dist/{8556.js.LICENSE.txt → 7452.js.LICENSE.txt} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useMemo, useState,
|
|
1
|
+
import React, { useMemo, useState, useCallback } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import fuzzy from 'fuzzy';
|
|
4
4
|
import {
|
|
@@ -13,42 +13,38 @@ import {
|
|
|
13
13
|
TableHead,
|
|
14
14
|
TableHeader,
|
|
15
15
|
TableRow,
|
|
16
|
-
TableSelectRow,
|
|
17
16
|
TableToolbarSearch,
|
|
18
17
|
Tile,
|
|
19
18
|
type DataTableRow,
|
|
20
19
|
} from '@carbon/react';
|
|
21
20
|
import { Edit } from '@carbon/react/icons';
|
|
22
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
isDesktop,
|
|
23
|
+
showModal,
|
|
24
|
+
useConfig,
|
|
25
|
+
useDebounce,
|
|
26
|
+
useLayoutType,
|
|
27
|
+
getCoreTranslation,
|
|
28
|
+
} from '@openmrs/esm-framework';
|
|
23
29
|
import { type LineItem, type MappedBill } from '../types';
|
|
24
30
|
import { convertToCurrency } from '../helpers';
|
|
31
|
+
import type { BillingConfig } from '../config-schema';
|
|
25
32
|
import styles from './invoice-table.scss';
|
|
26
33
|
|
|
27
34
|
type InvoiceTableProps = {
|
|
28
35
|
bill: MappedBill;
|
|
29
|
-
isSelectable?: boolean;
|
|
30
36
|
isLoadingBill?: boolean;
|
|
31
|
-
onSelectItem?: (selectedLineItems: LineItem[]) => void;
|
|
32
37
|
};
|
|
33
38
|
|
|
34
|
-
const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill,
|
|
39
|
+
const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isLoadingBill }) => {
|
|
35
40
|
const { t } = useTranslation();
|
|
36
|
-
const { defaultCurrency, showEditBillButton } = useConfig();
|
|
41
|
+
const { defaultCurrency, showEditBillButton } = useConfig<BillingConfig>();
|
|
37
42
|
const layout = useLayoutType();
|
|
38
43
|
const lineItems = useMemo(() => bill?.lineItems ?? [], [bill?.lineItems]);
|
|
39
|
-
const paidLineItems = useMemo(() => lineItems?.filter((item) => item.paymentStatus === 'PAID') ?? [], [lineItems]);
|
|
40
44
|
const responsiveSize = isDesktop(layout) ? 'sm' : 'lg';
|
|
41
|
-
|
|
42
|
-
const [selectedLineItems, setSelectedLineItems] = useState(paidLineItems ?? []);
|
|
43
45
|
const [searchTerm, setSearchTerm] = useState('');
|
|
44
46
|
const debouncedSearchTerm = useDebounce(searchTerm);
|
|
45
47
|
|
|
46
|
-
useEffect(() => {
|
|
47
|
-
if (onSelectItem) {
|
|
48
|
-
onSelectItem(selectedLineItems);
|
|
49
|
-
}
|
|
50
|
-
}, [selectedLineItems, onSelectItem]);
|
|
51
|
-
|
|
52
48
|
const filteredLineItems = useMemo(() => {
|
|
53
49
|
if (!debouncedSearchTerm) {
|
|
54
50
|
return lineItems;
|
|
@@ -65,14 +61,14 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
|
|
|
65
61
|
}, [debouncedSearchTerm, lineItems]);
|
|
66
62
|
|
|
67
63
|
const tableHeaders = [
|
|
68
|
-
{ header: 'No', key: 'no', width: 7 }, // Width as a percentage
|
|
69
|
-
{ header: 'Bill item', key: 'billItem', width: 25 },
|
|
70
|
-
{ header: 'Bill code', key: 'billCode', width: 20 },
|
|
71
|
-
{ header: 'Status', key: 'status', width: 25 },
|
|
72
|
-
{ header: 'Quantity', key: 'quantity', width: 15 },
|
|
73
|
-
{ header: 'Price', key: 'price', width: 24 },
|
|
74
|
-
{ header: 'Total', key: 'total', width: 15 },
|
|
75
|
-
{ header:
|
|
64
|
+
{ header: t('number', 'No'), key: 'no', width: 7 }, // Width as a percentage
|
|
65
|
+
{ header: t('billItem', 'Bill item'), key: 'billItem', width: 25 },
|
|
66
|
+
{ header: t('billCode', 'Bill code'), key: 'billCode', width: 20 },
|
|
67
|
+
{ header: t('status', 'Status'), key: 'status', width: 25 },
|
|
68
|
+
{ header: t('quantity', 'Quantity'), key: 'quantity', width: 15 },
|
|
69
|
+
{ header: t('price', 'Price'), key: 'price', width: 24 },
|
|
70
|
+
{ header: t('total', 'Total'), key: 'total', width: 15 },
|
|
71
|
+
{ header: getCoreTranslation('actions'), key: 'actionButton' },
|
|
76
72
|
];
|
|
77
73
|
|
|
78
74
|
const handleSelectBillItem = useCallback(
|
|
@@ -135,23 +131,10 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
|
|
|
135
131
|
);
|
|
136
132
|
}
|
|
137
133
|
|
|
138
|
-
const handleRowSelection = (row: typeof DataTableRow, checked: boolean) => {
|
|
139
|
-
const matchingRow = filteredLineItems.find((item) => item.uuid === row.id);
|
|
140
|
-
let newSelectedLineItems;
|
|
141
|
-
|
|
142
|
-
if (checked) {
|
|
143
|
-
newSelectedLineItems = [...selectedLineItems, matchingRow];
|
|
144
|
-
} else {
|
|
145
|
-
newSelectedLineItems = selectedLineItems.filter((item) => item.uuid !== row.id);
|
|
146
|
-
}
|
|
147
|
-
setSelectedLineItems(newSelectedLineItems);
|
|
148
|
-
onSelectItem(newSelectedLineItems);
|
|
149
|
-
};
|
|
150
|
-
|
|
151
134
|
return (
|
|
152
|
-
|
|
153
|
-
<DataTable headers={tableHeaders}
|
|
154
|
-
{({ rows, headers, getRowProps,
|
|
135
|
+
<>
|
|
136
|
+
<DataTable headers={tableHeaders} rows={tableRows} size={responsiveSize} useZebraStyles>
|
|
137
|
+
{({ rows, headers, getRowProps, getTableProps }) => (
|
|
155
138
|
<TableContainer
|
|
156
139
|
description={
|
|
157
140
|
<span className={styles.tableDescription}>
|
|
@@ -172,7 +155,6 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
|
|
|
172
155
|
className={`${styles.invoiceTable} billingTable`}>
|
|
173
156
|
<TableHead>
|
|
174
157
|
<TableRow>
|
|
175
|
-
{rows.length > 1 && isSelectable ? <TableHeader /> : null}
|
|
176
158
|
{headers.map((header) => (
|
|
177
159
|
<TableHeader key={header.key}>{header.header}</TableHeader>
|
|
178
160
|
))}
|
|
@@ -186,18 +168,6 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
|
|
|
186
168
|
{...getRowProps({
|
|
187
169
|
row,
|
|
188
170
|
})}>
|
|
189
|
-
{rows.length > 1 && isSelectable && (
|
|
190
|
-
<TableSelectRow
|
|
191
|
-
aria-label="Select row"
|
|
192
|
-
{...getSelectionProps({ row })}
|
|
193
|
-
disabled={tableRows[index].status === 'PAID'}
|
|
194
|
-
onChange={(checked: boolean) => handleRowSelection(row, checked)}
|
|
195
|
-
checked={
|
|
196
|
-
tableRows[index].status === 'PAID' ||
|
|
197
|
-
Boolean(selectedLineItems?.find((item) => item?.uuid === row?.id))
|
|
198
|
-
}
|
|
199
|
-
/>
|
|
200
|
-
)}
|
|
201
171
|
{row.cells.map((cell) => (
|
|
202
172
|
<TableCell key={cell.id}>{cell.value}</TableCell>
|
|
203
173
|
))}
|
|
@@ -221,7 +191,7 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
|
|
|
221
191
|
</Layer>
|
|
222
192
|
</div>
|
|
223
193
|
)}
|
|
224
|
-
|
|
194
|
+
</>
|
|
225
195
|
);
|
|
226
196
|
};
|
|
227
197
|
|
|
@@ -1,43 +1,24 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import
|
|
3
|
-
import { render, screen,
|
|
4
|
-
import { showModal } from '@openmrs/esm-framework';
|
|
5
|
-
import InvoiceTable from './invoice-table.component';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { render, screen, act } from '@testing-library/react';
|
|
4
|
+
import { getDefaultsFromConfigSchema, showModal, useConfig } from '@openmrs/esm-framework';
|
|
6
5
|
import { type MappedBill } from '../types';
|
|
6
|
+
import { configSchema, type BillingConfig } from '../config-schema';
|
|
7
|
+
import InvoiceTable from './invoice-table.component';
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
jest.mock('react-i18next', () => ({
|
|
10
|
-
useTranslation: jest.fn(() => ({
|
|
11
|
-
t: jest.fn((key, fallback) => fallback || key),
|
|
12
|
-
i18n: { language: 'en' },
|
|
13
|
-
})),
|
|
14
|
-
}));
|
|
15
|
-
|
|
16
|
-
jest.mock('@openmrs/esm-framework', () => ({
|
|
17
|
-
showModal: jest.fn(),
|
|
18
|
-
useConfig: jest.fn(() => ({
|
|
19
|
-
defaultCurrency: 'USD',
|
|
20
|
-
showEditBillButton: true,
|
|
21
|
-
})),
|
|
22
|
-
useDebounce: jest.fn((value) => value),
|
|
23
|
-
useLayoutType: jest.fn(() => 'desktop'),
|
|
24
|
-
isDesktop: jest.fn(() => true),
|
|
25
|
-
}));
|
|
9
|
+
const mockUseConfig = jest.mocked(useConfig<BillingConfig>);
|
|
26
10
|
|
|
27
11
|
jest.mock('../helpers', () => ({
|
|
28
12
|
convertToCurrency: jest.fn((price) => `USD ${price}`),
|
|
29
13
|
}));
|
|
30
14
|
|
|
31
15
|
describe('InvoiceTable', () => {
|
|
32
|
-
const mockT = jest.fn((key) => key);
|
|
33
|
-
|
|
34
16
|
beforeEach(() => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
jest.useRealTimers();
|
|
17
|
+
mockUseConfig.mockReturnValue({
|
|
18
|
+
...getDefaultsFromConfigSchema(configSchema),
|
|
19
|
+
defaultCurrency: 'USD',
|
|
20
|
+
showEditBillButton: true,
|
|
21
|
+
});
|
|
41
22
|
});
|
|
42
23
|
|
|
43
24
|
const bill: MappedBill = {
|
|
@@ -103,11 +84,12 @@ describe('InvoiceTable', () => {
|
|
|
103
84
|
expect(screen.getByTestId('receipt-number-0')).toHaveTextContent('12345');
|
|
104
85
|
});
|
|
105
86
|
|
|
106
|
-
it('renders the edit button and calls showModal when clicked', () => {
|
|
87
|
+
it('renders the edit button and calls showModal when clicked', async () => {
|
|
88
|
+
const user = userEvent.setup();
|
|
107
89
|
render(<InvoiceTable bill={bill} />);
|
|
108
90
|
|
|
109
91
|
const editButton = screen.getByTestId('edit-button-1');
|
|
110
|
-
|
|
92
|
+
await user.click(editButton);
|
|
111
93
|
expect(showModal).toHaveBeenCalledWith('edit-bill-line-item-dialog', expect.anything());
|
|
112
94
|
});
|
|
113
95
|
|
|
@@ -117,31 +99,23 @@ describe('InvoiceTable', () => {
|
|
|
117
99
|
expect(screen.getByTestId('loader')).toBeInTheDocument();
|
|
118
100
|
});
|
|
119
101
|
|
|
120
|
-
it('filters line items based on the search term', () => {
|
|
102
|
+
it('filters line items based on the search term', async () => {
|
|
103
|
+
const user = userEvent.setup();
|
|
121
104
|
render(<InvoiceTable bill={bill} />);
|
|
122
|
-
const searchInput = screen.getByPlaceholderText(
|
|
105
|
+
const searchInput = screen.getByPlaceholderText(/search this table/i);
|
|
123
106
|
|
|
124
|
-
|
|
107
|
+
await user.type(searchInput, 'Item 2');
|
|
125
108
|
|
|
126
109
|
expect(screen.queryByText('Item 1')).not.toBeInTheDocument();
|
|
127
110
|
expect(screen.getByText('Item 2')).toBeInTheDocument();
|
|
128
111
|
});
|
|
129
112
|
|
|
130
|
-
it('
|
|
131
|
-
const
|
|
132
|
-
render(<InvoiceTable bill={bill} onSelectItem={onSelectItem} />);
|
|
133
|
-
|
|
134
|
-
const checkboxes = screen.getAllByLabelText('Select row');
|
|
135
|
-
fireEvent.click(checkboxes[0]);
|
|
136
|
-
|
|
137
|
-
expect(onSelectItem).toHaveBeenCalledWith([bill.lineItems[0]]);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it('resets isRedirecting to false after timeout', () => {
|
|
113
|
+
it('resets isRedirecting to false after timeout', async () => {
|
|
114
|
+
const user = userEvent.setup();
|
|
141
115
|
render(<InvoiceTable bill={bill} />);
|
|
142
116
|
|
|
143
117
|
const button = screen.getByTestId('edit-button-1');
|
|
144
|
-
|
|
118
|
+
await user.click(button);
|
|
145
119
|
act(() => {
|
|
146
120
|
jest.advanceTimersByTime(1000);
|
|
147
121
|
});
|
|
@@ -4,15 +4,15 @@ import { Printer } from '@carbon/react/icons';
|
|
|
4
4
|
import { useParams } from 'react-router-dom';
|
|
5
5
|
import { useReactToPrint } from 'react-to-print';
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
|
-
import { ExtensionSlot, useConfig, usePatient } from '@openmrs/esm-framework';
|
|
8
|
-
import { ErrorState } from '@openmrs/esm-patient-common-lib';
|
|
9
|
-
import { convertToCurrency } from '../helpers';
|
|
10
|
-
import { type LineItem } from '../types';
|
|
11
|
-
import { useBill } from '../billing.resource';
|
|
7
|
+
import { ExtensionSlot, showSnackbar, useConfig, usePatient } from '@openmrs/esm-framework';
|
|
12
8
|
import InvoiceTable from './invoice-table.component';
|
|
13
9
|
import Payments from './payments/payments.component';
|
|
14
10
|
import PrintReceipt from './printable-invoice/print-receipt.component';
|
|
15
11
|
import PrintableInvoice from './printable-invoice/printable-invoice.component';
|
|
12
|
+
import { ErrorState } from '@openmrs/esm-patient-common-lib';
|
|
13
|
+
import { convertToCurrency } from '../helpers';
|
|
14
|
+
import { useBill, useDefaultFacility } from '../billing.resource';
|
|
15
|
+
import type { BillingConfig } from '../config-schema';
|
|
16
16
|
import styles from './invoice.scss';
|
|
17
17
|
|
|
18
18
|
interface InvoiceDetailsProps {
|
|
@@ -22,25 +22,20 @@ interface InvoiceDetailsProps {
|
|
|
22
22
|
|
|
23
23
|
const Invoice: React.FC = () => {
|
|
24
24
|
const { t } = useTranslation();
|
|
25
|
+
const { data } = useDefaultFacility();
|
|
25
26
|
const { billUuid, patientUuid } = useParams();
|
|
26
27
|
const { patient, isLoading: isLoadingPatient } = usePatient(patientUuid);
|
|
27
28
|
const { bill, isLoading: isLoadingBill, error, mutate } = useBill(billUuid);
|
|
28
29
|
const [isPrinting, setIsPrinting] = useState(false);
|
|
29
|
-
const [selectedLineItems, setSelectedLineItems] = useState<LineItem[]>([]);
|
|
30
30
|
const componentRef = useRef<HTMLDivElement>(null);
|
|
31
31
|
const onBeforeGetContentResolve = useRef<(() => void) | null>(null);
|
|
32
|
-
const { defaultCurrency } = useConfig();
|
|
33
|
-
const handleSelectItem = (lineItems: LineItem[]) => {
|
|
34
|
-
setSelectedLineItems(lineItems);
|
|
35
|
-
};
|
|
32
|
+
const { defaultCurrency } = useConfig<BillingConfig>();
|
|
36
33
|
|
|
37
34
|
const handleAfterPrint = useCallback(() => {
|
|
38
35
|
onBeforeGetContentResolve.current = null;
|
|
39
36
|
setIsPrinting(false);
|
|
40
37
|
}, []);
|
|
41
38
|
|
|
42
|
-
const reactToPrintContent = useCallback(() => componentRef.current, []);
|
|
43
|
-
|
|
44
39
|
const handleOnBeforeGetContent = useCallback(() => {
|
|
45
40
|
return new Promise<void>((resolve) => {
|
|
46
41
|
if (patient && bill) {
|
|
@@ -51,11 +46,17 @@ const Invoice: React.FC = () => {
|
|
|
51
46
|
}, [bill, patient]);
|
|
52
47
|
|
|
53
48
|
const handlePrint = useReactToPrint({
|
|
54
|
-
|
|
49
|
+
contentRef: componentRef,
|
|
55
50
|
documentTitle: `Invoice ${bill?.receiptNumber} - ${patient?.name?.[0]?.given?.join(' ')} ${patient?.name?.[0].family}`,
|
|
56
|
-
|
|
51
|
+
onBeforePrint: handleOnBeforeGetContent,
|
|
57
52
|
onAfterPrint: handleAfterPrint,
|
|
58
|
-
|
|
53
|
+
preserveAfterPrint: false,
|
|
54
|
+
onPrintError: (_, error) =>
|
|
55
|
+
showSnackbar({
|
|
56
|
+
title: t('errorPrintingInvoice', 'Error printing invoice'),
|
|
57
|
+
kind: 'error',
|
|
58
|
+
subtitle: error.message,
|
|
59
|
+
}),
|
|
59
60
|
});
|
|
60
61
|
|
|
61
62
|
useEffect(() => {
|
|
@@ -64,11 +65,14 @@ const Invoice: React.FC = () => {
|
|
|
64
65
|
}
|
|
65
66
|
}, [isPrinting]);
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
// Do not remove this comment. Adds the translation keys for the invoice details
|
|
69
|
+
/**
|
|
70
|
+
* t('totalAmount', 'Total Amount')
|
|
71
|
+
* t('amountTendered', 'Amount Tendered')
|
|
72
|
+
* t('invoiceNumber', 'Invoice Number')
|
|
73
|
+
* t('dateAndTime', 'Date And Time')
|
|
74
|
+
* t('invoiceStatus', 'Invoice Status')
|
|
75
|
+
*/
|
|
72
76
|
const invoiceDetails = {
|
|
73
77
|
'Total Amount': convertToCurrency(bill?.totalAmount, defaultCurrency),
|
|
74
78
|
'Amount Tendered': convertToCurrency(bill?.tenderedAmount, defaultCurrency),
|
|
@@ -77,14 +81,14 @@ const Invoice: React.FC = () => {
|
|
|
77
81
|
'Invoice Status': bill?.status,
|
|
78
82
|
};
|
|
79
83
|
|
|
80
|
-
if (isLoadingPatient
|
|
84
|
+
if (isLoadingPatient || isLoadingBill) {
|
|
81
85
|
return (
|
|
82
86
|
<div className={styles.invoiceContainer}>
|
|
83
87
|
<InlineLoading
|
|
84
88
|
className={styles.loader}
|
|
85
89
|
status="active"
|
|
86
90
|
iconDescription="Loading"
|
|
87
|
-
description=
|
|
91
|
+
description={t('loadingBillInfo', 'Loading bill information...')}
|
|
88
92
|
/>
|
|
89
93
|
</div>
|
|
90
94
|
);
|
|
@@ -109,7 +113,7 @@ const Invoice: React.FC = () => {
|
|
|
109
113
|
</section>
|
|
110
114
|
<div>
|
|
111
115
|
<Button
|
|
112
|
-
disabled={isPrinting}
|
|
116
|
+
disabled={isPrinting || isLoadingPatient || isLoadingBill}
|
|
113
117
|
onClick={handlePrint}
|
|
114
118
|
renderIcon={(props) => <Printer size={24} {...props} />}
|
|
115
119
|
iconDescription="Print bill"
|
|
@@ -120,20 +124,23 @@ const Invoice: React.FC = () => {
|
|
|
120
124
|
</div>
|
|
121
125
|
</div>
|
|
122
126
|
|
|
123
|
-
<InvoiceTable bill={bill} isLoadingBill={isLoadingBill}
|
|
124
|
-
<Payments bill={bill} mutate={mutate}
|
|
127
|
+
<InvoiceTable bill={bill} isLoadingBill={isLoadingBill} />
|
|
128
|
+
<Payments bill={bill} mutate={mutate} />
|
|
125
129
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
{bill && patient && (
|
|
131
|
+
<div className={styles.printContainer}>
|
|
132
|
+
<PrintableInvoice bill={bill} patient={patient} defaultFacility={data} componentRef={componentRef} />
|
|
133
|
+
</div>
|
|
134
|
+
)}
|
|
129
135
|
</div>
|
|
130
136
|
);
|
|
131
137
|
};
|
|
132
138
|
|
|
133
139
|
function InvoiceDetails({ label, value }: InvoiceDetailsProps) {
|
|
140
|
+
const { t } = useTranslation();
|
|
134
141
|
return (
|
|
135
142
|
<div>
|
|
136
|
-
<h1 className={styles.label}>{label}</h1>
|
|
143
|
+
<h1 className={styles.label}>{t(label)}</h1>
|
|
137
144
|
<span className={styles.value}>{value}</span>
|
|
138
145
|
</div>
|
|
139
146
|
);
|
package/src/invoice/invoice.scss
CHANGED
|
@@ -78,16 +78,19 @@
|
|
|
78
78
|
color: colors.$cool-gray-90;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
.printContainer {
|
|
82
|
+
background-color: colors.$white;
|
|
83
|
+
display: none;
|
|
84
|
+
|
|
85
|
+
@media print {
|
|
86
|
+
display: block !important;
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
@media print {
|
|
89
91
|
html,
|
|
90
92
|
body {
|
|
93
|
+
display: block !important;
|
|
91
94
|
background-color: colors.$white !important;
|
|
92
95
|
}
|
|
93
96
|
}
|
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { screen, render } from '@testing-library/react';
|
|
3
2
|
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { screen, render } from '@testing-library/react';
|
|
4
4
|
import { useReactToPrint } from 'react-to-print';
|
|
5
|
-
import {
|
|
5
|
+
import { getDefaultsFromConfigSchema, useConfig, usePatient } from '@openmrs/esm-framework';
|
|
6
|
+
import { configSchema, type BillingConfig } from '../config-schema';
|
|
7
|
+
import { mockBill, mockPatient } from '../../__mocks__/bills.mock';
|
|
6
8
|
import { useBill, processBillPayment } from '../billing.resource';
|
|
7
9
|
import { usePaymentModes } from './payments/payment.resource';
|
|
8
10
|
import Invoice from './invoice.component';
|
|
9
11
|
|
|
12
|
+
const mockUseConfig = jest.mocked(useConfig<BillingConfig>);
|
|
13
|
+
|
|
10
14
|
// Mock convertToCurrency
|
|
11
15
|
jest.mock('../helpers/functions', () => ({
|
|
12
16
|
convertToCurrency: jest.fn((amount) => `USD ${amount}`),
|
|
13
17
|
}));
|
|
14
18
|
|
|
15
|
-
// Mock i18next
|
|
16
|
-
jest.mock('react-i18next', () => ({
|
|
17
|
-
useTranslation: () => ({
|
|
18
|
-
t: (key: string) => key,
|
|
19
|
-
}),
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
19
|
// Set window.i18next
|
|
23
20
|
window.i18next = {
|
|
24
21
|
language: 'en',
|
|
@@ -78,40 +75,9 @@ jest.mock('react-to-print', () => ({
|
|
|
78
75
|
useReactToPrint: jest.fn(),
|
|
79
76
|
}));
|
|
80
77
|
|
|
81
|
-
// Mock OpenMRS framework
|
|
82
|
-
jest.mock('@openmrs/esm-framework', () => ({
|
|
83
|
-
showSnackbar: jest.fn(),
|
|
84
|
-
useLayoutType: jest.fn(() => 'desktop'),
|
|
85
|
-
isDesktop: jest.fn(() => true),
|
|
86
|
-
useConfig: jest.fn(() => ({
|
|
87
|
-
defaultCurrency: 'USD',
|
|
88
|
-
})),
|
|
89
|
-
formatDate: jest.fn((date) => date?.toString() ?? ''),
|
|
90
|
-
ExtensionSlot: jest.fn(({ children }) => <div data-testid="extension-slot">{children}</div>),
|
|
91
|
-
usePatient: jest.fn().mockReturnValue({
|
|
92
|
-
patient: {
|
|
93
|
-
id: 'b2fcf02b-7ee3-4d16-a48f-576be2b103aa',
|
|
94
|
-
name: [{ given: ['John'], family: 'Doe' }],
|
|
95
|
-
},
|
|
96
|
-
patientUuid: 'b2fcf02b-7ee3-4d16-a48f-576be2b103aa',
|
|
97
|
-
isLoading: false,
|
|
98
|
-
error: null,
|
|
99
|
-
}),
|
|
100
|
-
createGlobalStore: jest.fn(),
|
|
101
|
-
getGlobalStore: jest.fn(() => ({
|
|
102
|
-
subscribe: jest.fn(),
|
|
103
|
-
getState: jest.fn(),
|
|
104
|
-
setState: jest.fn(),
|
|
105
|
-
})),
|
|
106
|
-
}));
|
|
107
|
-
|
|
108
|
-
// Mock patient common lib
|
|
109
|
-
jest.mock('@openmrs/esm-patient-common-lib', () => ({
|
|
110
|
-
ErrorState: jest.fn(({ error }) => <div data-testid="error-state">Error: {error?.message || error}</div>),
|
|
111
|
-
}));
|
|
112
|
-
|
|
113
78
|
describe('Invoice', () => {
|
|
114
79
|
const mockedBill = useBill as jest.Mock;
|
|
80
|
+
const mockedPatient = usePatient as jest.Mock;
|
|
115
81
|
const mockedProcessBillPayment = processBillPayment as jest.Mock;
|
|
116
82
|
const mockedUsePaymentModes = usePaymentModes as jest.Mock;
|
|
117
83
|
const mockedUseReactToPrint = useReactToPrint as jest.Mock;
|
|
@@ -144,6 +110,14 @@ describe('Invoice', () => {
|
|
|
144
110
|
mutate: jest.fn(),
|
|
145
111
|
});
|
|
146
112
|
|
|
113
|
+
mockedPatient.mockReturnValue({
|
|
114
|
+
patient: mockPatient,
|
|
115
|
+
isLoading: false,
|
|
116
|
+
error: null,
|
|
117
|
+
isValidating: false,
|
|
118
|
+
mutate: jest.fn(),
|
|
119
|
+
});
|
|
120
|
+
|
|
147
121
|
mockedUsePaymentModes.mockReturnValue({
|
|
148
122
|
paymentModes: [
|
|
149
123
|
{ uuid: 'cash-uuid', name: 'Cash', description: 'Cash Method', retired: false },
|
|
@@ -154,15 +128,13 @@ describe('Invoice', () => {
|
|
|
154
128
|
mutate: jest.fn(),
|
|
155
129
|
});
|
|
156
130
|
|
|
131
|
+
mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(configSchema), defaultCurrency: 'USD' });
|
|
132
|
+
|
|
157
133
|
// Setup print handler mock
|
|
158
134
|
const printHandler = jest.fn();
|
|
159
135
|
mockedUseReactToPrint.mockReturnValue(printHandler);
|
|
160
136
|
});
|
|
161
137
|
|
|
162
|
-
afterEach(() => {
|
|
163
|
-
jest.clearAllMocks();
|
|
164
|
-
});
|
|
165
|
-
|
|
166
138
|
it('should render error state correctly', () => {
|
|
167
139
|
mockedBill.mockReturnValue({
|
|
168
140
|
bill: null,
|
|
@@ -173,8 +145,8 @@ describe('Invoice', () => {
|
|
|
173
145
|
});
|
|
174
146
|
|
|
175
147
|
render(<Invoice />);
|
|
176
|
-
expect(screen.
|
|
177
|
-
expect(screen.getByText(/
|
|
148
|
+
expect(screen.getByText(/Invoice error/i)).toBeInTheDocument();
|
|
149
|
+
expect(screen.getByText(/Error/)).toBeInTheDocument();
|
|
178
150
|
});
|
|
179
151
|
|
|
180
152
|
it('should render invoice details correctly', () => {
|
|
@@ -265,7 +237,9 @@ describe('Invoice', () => {
|
|
|
265
237
|
|
|
266
238
|
it('should show patient information correctly', () => {
|
|
267
239
|
render(<Invoice />);
|
|
268
|
-
|
|
240
|
+
// Check that the invoice details are rendered
|
|
241
|
+
expect(screen.getByText('Invoice Number')).toBeInTheDocument();
|
|
242
|
+
expect(screen.getByText('RCPT-001')).toBeInTheDocument();
|
|
269
243
|
});
|
|
270
244
|
|
|
271
245
|
// Add more test cases as needed for specific features or edge cases
|