@openmrs/esm-billing-app 1.0.2-pre.76 → 1.0.2-pre.764
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 +54 -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} +100 -78
- package/src/bill-item-actions/edit-bill-item.test.tsx +116 -31
- 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 +216 -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 -24
- 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 +34 -68
- package/src/invoice/invoice-table.scss +1 -5
- package/src/invoice/invoice-table.test.tsx +20 -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 +31 -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 +70 -21
- 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
|
|
41
|
+
const { defaultCurrency } = 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(
|
|
@@ -100,24 +96,20 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
|
|
|
100
96
|
total: item.price * item.quantity,
|
|
101
97
|
actionButton: (
|
|
102
98
|
<span>
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
/>
|
|
113
|
-
) : (
|
|
114
|
-
'--'
|
|
115
|
-
)}
|
|
99
|
+
<Button
|
|
100
|
+
data-testid={`edit-button-${item.uuid}`}
|
|
101
|
+
renderIcon={Edit}
|
|
102
|
+
hasIconOnly
|
|
103
|
+
kind="ghost"
|
|
104
|
+
iconDescription={t('editThisBillItem', 'Edit this bill item')}
|
|
105
|
+
tooltipPosition="left"
|
|
106
|
+
onClick={() => handleSelectBillItem(item)}
|
|
107
|
+
/>
|
|
116
108
|
</span>
|
|
117
109
|
),
|
|
118
110
|
};
|
|
119
111
|
}) ?? [],
|
|
120
|
-
[filteredLineItems, bill?.receiptNumber, defaultCurrency,
|
|
112
|
+
[filteredLineItems, bill?.receiptNumber, defaultCurrency, t, handleSelectBillItem],
|
|
121
113
|
);
|
|
122
114
|
|
|
123
115
|
if (isLoadingBill) {
|
|
@@ -135,23 +127,10 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
|
|
|
135
127
|
);
|
|
136
128
|
}
|
|
137
129
|
|
|
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
130
|
return (
|
|
152
|
-
|
|
153
|
-
<DataTable headers={tableHeaders}
|
|
154
|
-
{({ rows, headers, getRowProps,
|
|
131
|
+
<>
|
|
132
|
+
<DataTable headers={tableHeaders} rows={tableRows} size={responsiveSize} useZebraStyles>
|
|
133
|
+
{({ rows, headers, getRowProps, getTableProps }) => (
|
|
155
134
|
<TableContainer
|
|
156
135
|
description={
|
|
157
136
|
<span className={styles.tableDescription}>
|
|
@@ -172,7 +151,6 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
|
|
|
172
151
|
className={`${styles.invoiceTable} billingTable`}>
|
|
173
152
|
<TableHead>
|
|
174
153
|
<TableRow>
|
|
175
|
-
{rows.length > 1 && isSelectable ? <TableHeader /> : null}
|
|
176
154
|
{headers.map((header) => (
|
|
177
155
|
<TableHeader key={header.key}>{header.header}</TableHeader>
|
|
178
156
|
))}
|
|
@@ -186,18 +164,6 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
|
|
|
186
164
|
{...getRowProps({
|
|
187
165
|
row,
|
|
188
166
|
})}>
|
|
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
167
|
{row.cells.map((cell) => (
|
|
202
168
|
<TableCell key={cell.id}>{cell.value}</TableCell>
|
|
203
169
|
))}
|
|
@@ -221,7 +187,7 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
|
|
|
221
187
|
</Layer>
|
|
222
188
|
</div>
|
|
223
189
|
)}
|
|
224
|
-
|
|
190
|
+
</>
|
|
225
191
|
);
|
|
226
192
|
};
|
|
227
193
|
|
|
@@ -1,43 +1,23 @@
|
|
|
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
|
-
afterEach(() => {
|
|
40
|
-
jest.useRealTimers();
|
|
17
|
+
mockUseConfig.mockReturnValue({
|
|
18
|
+
...getDefaultsFromConfigSchema(configSchema),
|
|
19
|
+
defaultCurrency: 'USD',
|
|
20
|
+
});
|
|
41
21
|
});
|
|
42
22
|
|
|
43
23
|
const bill: MappedBill = {
|
|
@@ -103,11 +83,12 @@ describe('InvoiceTable', () => {
|
|
|
103
83
|
expect(screen.getByTestId('receipt-number-0')).toHaveTextContent('12345');
|
|
104
84
|
});
|
|
105
85
|
|
|
106
|
-
it('renders the edit button and calls showModal when clicked', () => {
|
|
86
|
+
it('renders the edit button and calls showModal when clicked', async () => {
|
|
87
|
+
const user = userEvent.setup();
|
|
107
88
|
render(<InvoiceTable bill={bill} />);
|
|
108
89
|
|
|
109
90
|
const editButton = screen.getByTestId('edit-button-1');
|
|
110
|
-
|
|
91
|
+
await user.click(editButton);
|
|
111
92
|
expect(showModal).toHaveBeenCalledWith('edit-bill-line-item-dialog', expect.anything());
|
|
112
93
|
});
|
|
113
94
|
|
|
@@ -117,31 +98,23 @@ describe('InvoiceTable', () => {
|
|
|
117
98
|
expect(screen.getByTestId('loader')).toBeInTheDocument();
|
|
118
99
|
});
|
|
119
100
|
|
|
120
|
-
it('filters line items based on the search term', () => {
|
|
101
|
+
it('filters line items based on the search term', async () => {
|
|
102
|
+
const user = userEvent.setup();
|
|
121
103
|
render(<InvoiceTable bill={bill} />);
|
|
122
|
-
const searchInput = screen.getByPlaceholderText(
|
|
104
|
+
const searchInput = screen.getByPlaceholderText(/search this table/i);
|
|
123
105
|
|
|
124
|
-
|
|
106
|
+
await user.type(searchInput, 'Item 2');
|
|
125
107
|
|
|
126
108
|
expect(screen.queryByText('Item 1')).not.toBeInTheDocument();
|
|
127
109
|
expect(screen.getByText('Item 2')).toBeInTheDocument();
|
|
128
110
|
});
|
|
129
111
|
|
|
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', () => {
|
|
112
|
+
it('resets isRedirecting to false after timeout', async () => {
|
|
113
|
+
const user = userEvent.setup();
|
|
141
114
|
render(<InvoiceTable bill={bill} />);
|
|
142
115
|
|
|
143
116
|
const button = screen.getByTestId('edit-button-1');
|
|
144
|
-
|
|
117
|
+
await user.click(button);
|
|
145
118
|
act(() => {
|
|
146
119
|
jest.advanceTimersByTime(1000);
|
|
147
120
|
});
|
|
@@ -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
|