@openmrs/esm-billing-app 1.0.1-pre.95 → 1.0.2-pre.56
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/.eslintignore +0 -1
- package/.eslintrc +33 -24
- package/.husky/pre-commit +1 -1
- package/.turbo.json +1 -1
- package/.tx/config +11 -0
- package/README.md +111 -1
- package/dist/1119.js +1 -0
- package/dist/1197.js +1 -0
- package/dist/1362.js +1 -0
- package/dist/1362.js.map +1 -0
- package/dist/2146.js +1 -0
- package/dist/2690.js +1 -0
- package/dist/3029.js +2 -0
- package/dist/3029.js.LICENSE.txt +7 -0
- package/dist/3029.js.map +1 -0
- package/dist/3099.js +1 -0
- package/dist/3511.js +1 -0
- package/dist/3511.js.map +1 -0
- package/dist/3584.js +1 -0
- package/dist/4055.js +1 -0
- package/dist/4132.js +1 -0
- package/dist/4225.js +1 -0
- package/dist/4225.js.map +1 -0
- package/dist/4300.js +1 -0
- package/dist/4335.js +1 -0
- package/dist/4618.js +1 -0
- package/dist/4652.js +1 -0
- package/dist/4817.js +2 -0
- package/dist/4817.js.LICENSE.txt +77 -0
- package/dist/4817.js.map +1 -0
- package/dist/4944.js +1 -0
- package/dist/4993.js +1 -0
- package/dist/4993.js.map +1 -0
- package/dist/5173.js +1 -0
- package/dist/5241.js +1 -0
- package/dist/5442.js +1 -0
- package/dist/5661.js +1 -0
- package/dist/6022.js +1 -0
- package/dist/6468.js +1 -0
- package/dist/6540.js +2 -0
- package/dist/6540.js.map +1 -0
- package/dist/6606.js +2 -0
- package/dist/{591.js.LICENSE.txt → 6606.js.LICENSE.txt} +2 -2
- package/dist/6606.js.map +1 -0
- package/dist/6679.js +1 -0
- package/dist/6840.js +1 -0
- package/dist/6859.js +1 -0
- package/dist/6941.js +1 -0
- package/dist/6941.js.map +1 -0
- package/dist/7097.js +1 -0
- package/dist/7159.js +1 -0
- package/dist/723.js +1 -0
- package/dist/7255.js +1 -0
- package/dist/7255.js.map +1 -0
- package/dist/7617.js +1 -0
- package/dist/763.js +1 -0
- package/dist/763.js.map +1 -0
- package/dist/8163.js +1 -0
- package/dist/8349.js +1 -0
- package/dist/8618.js +1 -0
- package/dist/890.js +1 -0
- package/dist/9055.js +1 -0
- package/dist/9055.js.map +1 -0
- package/dist/9214.js +1 -0
- package/dist/9538.js +1 -0
- package/dist/{935.js → 961.js} +2 -2
- package/dist/{935.js.map → 961.js.map} +1 -1
- package/dist/986.js +1 -0
- package/dist/9879.js +1 -0
- package/dist/9895.js +1 -0
- package/dist/9900.js +1 -0
- package/dist/9913.js +1 -0
- package/dist/main.js +1 -1
- package/dist/main.js.LICENSE.txt +31 -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 +844 -165
- package/dist/openmrs-esm-billing-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/jest.config.js +4 -1
- package/package.json +19 -21
- package/src/bill-history/bill-history.component.tsx +19 -7
- package/src/bill-history/bill-history.scss +24 -9
- package/src/bill-history/bill-history.test.tsx +58 -16
- package/src/bill-item-actions/bill-item-actions.scss +26 -0
- package/src/bill-item-actions/edit-bill-item.component.tsx +221 -0
- package/src/bill-item-actions/edit-bill-item.test.tsx +137 -0
- package/src/billable-services/bill-waiver/bill-selection.component.tsx +1 -1
- package/src/billable-services/bill-waiver/bill-waiver-form.component.tsx +2 -2
- package/src/billable-services/bill-waiver/bill-waiver-form.scss +4 -4
- package/src/billable-services/bill-waiver/bill-waiver.component.tsx +4 -4
- package/src/billable-services/bill-waiver/patient-bills.component.tsx +1 -1
- package/src/billable-services/billable-service.resource.ts +19 -6
- package/src/billable-services/billable-services-home.component.tsx +19 -3
- package/src/billable-services/billable-services-menu-item/item.component.tsx +17 -0
- package/src/billable-services/billable-services-menu-item/item.scss +14 -0
- package/src/billable-services/billable-services.component.tsx +48 -9
- package/src/billable-services/billable-services.scss +10 -9
- package/src/billable-services/billable-services.test.tsx +172 -8
- package/src/billable-services/cash-point/cash-point-configuration.component.tsx +276 -0
- package/src/billable-services/cash-point/cash-point-configuration.scss +23 -0
- package/src/billable-services/create-edit/add-billable-service.component.tsx +126 -47
- package/src/billable-services/create-edit/add-billable-service.scss +14 -8
- package/src/billable-services/create-edit/add-billable-service.test.tsx +12 -10
- package/src/billable-services/dashboard/dashboard.scss +3 -3
- package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +280 -0
- package/src/billable-services/payyment-modes/payment-modes-config.scss +23 -0
- package/src/billing-dashboard/billing-dashboard.component.tsx +17 -4
- package/src/billing-dashboard/billing-dashboard.scss +3 -3
- package/src/billing-form/billing-form.component.tsx +31 -25
- package/src/billing-form/billing-form.scss +9 -10
- package/src/billing-form/visit-attributes/visit-attributes-form.component.tsx +38 -14
- package/src/billing-header/billing-header.component.tsx +21 -5
- package/src/billing-header/billing-header.scss +1 -1
- package/src/billing.resource.ts +24 -8
- package/src/bills-table/bills-table.component.tsx +46 -36
- package/src/bills-table/bills-table.scss +6 -6
- package/src/bills-table/bills-table.test.tsx +108 -68
- package/src/config-schema.ts +36 -1
- package/src/constants.ts +2 -0
- package/src/dashboard.meta.ts +2 -1
- package/src/helpers/functions.ts +0 -2
- package/src/hooks/selectedDateContext.ts +10 -0
- package/src/index.ts +22 -27
- package/src/invoice/invoice-table.component.tsx +95 -56
- package/src/invoice/invoice-table.scss +7 -8
- package/src/invoice/invoice-table.test.tsx +151 -0
- package/src/invoice/invoice.component.tsx +7 -9
- package/src/invoice/invoice.scss +2 -2
- package/src/invoice/invoice.test.tsx +199 -169
- package/src/invoice/payments/payment-form/payment-form.component.tsx +84 -55
- package/src/invoice/payments/payment-form/payment-form.test.tsx +174 -0
- package/src/invoice/payments/payment-history/payment-history.component.tsx +9 -7
- package/src/invoice/payments/payment-history/payment-history.test.tsx +160 -0
- package/src/invoice/payments/payments.component.test.tsx +121 -0
- package/src/invoice/payments/payments.component.tsx +57 -48
- package/src/invoice/payments/utils.ts +17 -13
- package/src/invoice/printable-invoice/print-receipt.component.tsx +23 -8
- package/src/invoice/printable-invoice/print-receipt.test.tsx +50 -0
- package/src/metrics-cards/card.component.tsx +4 -2
- package/src/metrics-cards/metrics-cards.test.tsx +1 -1
- package/src/modal/require-payment-modal.component.tsx +2 -2
- package/src/modal/require-payment-modal.test.tsx +66 -0
- package/src/modal/require-payment.scss +2 -1
- package/src/routes.json +40 -8
- package/src/types/index.ts +15 -0
- package/{i18next-parser.config.js → tools/i18next-parser.config.js} +19 -19
- package/tools/update-openmrs-deps.mjs +42 -0
- package/translations/am.json +53 -0
- package/translations/ar.json +170 -0
- package/translations/ar_SY.json +170 -0
- package/translations/bn.json +170 -0
- package/translations/de.json +170 -0
- package/translations/en.json +53 -0
- package/translations/es.json +53 -0
- package/translations/es_MX.json +170 -0
- package/translations/fr.json +53 -0
- package/translations/he.json +53 -0
- package/translations/hi.json +170 -0
- package/translations/hi_IN.json +170 -0
- package/translations/id.json +170 -0
- package/translations/it.json +170 -0
- package/translations/km.json +53 -0
- package/translations/ku.json +170 -0
- package/translations/ky.json +170 -0
- package/translations/lg.json +170 -0
- package/translations/ne.json +170 -0
- package/translations/pl.json +170 -0
- package/translations/pt.json +170 -0
- package/translations/pt_BR.json +170 -0
- package/translations/qu.json +170 -0
- package/translations/ro_RO.json +170 -0
- package/translations/ru_RU.json +170 -0
- package/translations/si.json +170 -0
- package/translations/sw.json +170 -0
- package/translations/sw_KE.json +170 -0
- package/translations/tr.json +170 -0
- package/translations/tr_TR.json +170 -0
- package/translations/uk.json +170 -0
- package/translations/uz.json +170 -0
- package/translations/uz@Latn.json +170 -0
- package/translations/uz_UZ.json +170 -0
- package/translations/vi.json +170 -0
- package/translations/zh.json +170 -0
- package/translations/zh_CN.json +170 -0
- package/tsconfig.json +10 -8
- package/webpack.config.js +1 -1
- package/dist/146.js +0 -1
- package/dist/146.js.map +0 -1
- package/dist/294.js +0 -2
- package/dist/294.js.map +0 -1
- package/dist/319.js +0 -1
- package/dist/384.js +0 -1
- package/dist/384.js.map +0 -1
- package/dist/421.js +0 -1
- package/dist/421.js.map +0 -1
- package/dist/533.js +0 -1
- package/dist/533.js.map +0 -1
- package/dist/574.js +0 -1
- package/dist/591.js +0 -2
- package/dist/591.js.map +0 -1
- package/dist/614.js +0 -2
- package/dist/614.js.LICENSE.txt +0 -37
- package/dist/614.js.map +0 -1
- package/dist/753.js +0 -1
- package/dist/753.js.map +0 -1
- package/dist/757.js +0 -1
- package/dist/770.js +0 -1
- package/dist/770.js.map +0 -1
- package/dist/783.js +0 -1
- package/dist/783.js.map +0 -1
- package/dist/788.js +0 -1
- package/dist/800.js +0 -2
- package/dist/800.js.LICENSE.txt +0 -3
- package/dist/800.js.map +0 -1
- package/dist/807.js +0 -1
- package/dist/833.js +0 -1
- package/dist/992.js +0 -1
- package/dist/992.js.map +0 -1
- package/src/root.scss +0 -30
- /package/dist/{294.js.LICENSE.txt → 6540.js.LICENSE.txt} +0 -0
- /package/dist/{935.js.LICENSE.txt → 961.js.LICENSE.txt} +0 -0
- /package/{src → tools}/setup-tests.ts +0 -0
- /package/{test-helpers.tsx → tools/test-helpers.tsx} +0 -0
|
@@ -2,67 +2,142 @@ import React from 'react';
|
|
|
2
2
|
import { screen, render } from '@testing-library/react';
|
|
3
3
|
import userEvent from '@testing-library/user-event';
|
|
4
4
|
import { useReactToPrint } from 'react-to-print';
|
|
5
|
-
import {
|
|
6
|
-
import { mockPayments, mockBill } from '../../__mocks__/bills.mock';
|
|
5
|
+
import { mockBill } from '../../__mocks__/bills.mock';
|
|
7
6
|
import { useBill, processBillPayment } from '../billing.resource';
|
|
8
7
|
import { usePaymentModes } from './payments/payment.resource';
|
|
9
8
|
import Invoice from './invoice.component';
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
// Mock convertToCurrency
|
|
11
|
+
jest.mock('../helpers/functions', () => ({
|
|
12
|
+
convertToCurrency: jest.fn((amount) => `USD ${amount}`),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
// Mock i18next
|
|
16
|
+
jest.mock('react-i18next', () => ({
|
|
17
|
+
useTranslation: () => ({
|
|
18
|
+
t: (key: string) => key,
|
|
19
|
+
}),
|
|
20
|
+
}));
|
|
15
21
|
|
|
22
|
+
// Set window.i18next
|
|
23
|
+
window.i18next = {
|
|
24
|
+
language: 'en',
|
|
25
|
+
} as any;
|
|
26
|
+
|
|
27
|
+
// Mock InvoiceTable component
|
|
28
|
+
jest.mock('./invoice-table.component', () =>
|
|
29
|
+
jest.fn(({ bill }) => <div data-testid="mock-invoice-table">Invoice Table Mock</div>),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// Mock payments component
|
|
33
|
+
jest.mock('./payments/payments.component', () =>
|
|
34
|
+
jest.fn(({ bill, mutate, selectedLineItems }) => (
|
|
35
|
+
<div data-testid="mock-payments">
|
|
36
|
+
<h2>Payments</h2>
|
|
37
|
+
<button>Add payment option</button>
|
|
38
|
+
</div>
|
|
39
|
+
)),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// Mock PrintReceipt component
|
|
43
|
+
jest.mock('./printable-invoice/print-receipt.component', () =>
|
|
44
|
+
jest.fn(({ billId }) => <div data-testid="mock-print-receipt">Print Receipt Mock</div>),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Mock PrintableInvoice component
|
|
48
|
+
jest.mock('./printable-invoice/printable-invoice.component', () =>
|
|
49
|
+
jest.fn(({ bill, patient }) => <div data-testid="mock-printable-invoice">Printable Invoice Mock</div>),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Mock payment resource
|
|
16
53
|
jest.mock('./payments/payment.resource', () => ({
|
|
17
54
|
usePaymentModes: jest.fn(),
|
|
18
55
|
updateBillVisitAttribute: jest.fn(),
|
|
19
56
|
}));
|
|
20
57
|
|
|
58
|
+
// Mock billing resource
|
|
21
59
|
jest.mock('../billing.resource', () => ({
|
|
22
60
|
useBill: jest.fn(),
|
|
23
61
|
processBillPayment: jest.fn(),
|
|
24
|
-
useDefaultFacility: jest.fn().mockReturnValue({
|
|
62
|
+
useDefaultFacility: jest.fn().mockReturnValue({
|
|
63
|
+
uuid: '54065383-b4d4-42d2-af4d-d250a1fd2590',
|
|
64
|
+
display: 'MTRH',
|
|
65
|
+
}),
|
|
25
66
|
}));
|
|
26
67
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
});
|
|
68
|
+
// Mock react-router-dom
|
|
69
|
+
jest.mock('react-router-dom', () => ({
|
|
70
|
+
useParams: jest.fn().mockReturnValue({
|
|
71
|
+
patientUuid: 'patientUuid',
|
|
72
|
+
billUuid: 'billUuid',
|
|
73
|
+
}),
|
|
74
|
+
}));
|
|
35
75
|
|
|
36
|
-
|
|
37
|
-
|
|
76
|
+
// Mock react-to-print
|
|
77
|
+
jest.mock('react-to-print', () => ({
|
|
78
|
+
useReactToPrint: jest.fn(),
|
|
79
|
+
}));
|
|
38
80
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
+
}));
|
|
44
107
|
|
|
45
|
-
|
|
46
|
-
|
|
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
|
+
}));
|
|
47
112
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
113
|
+
describe('Invoice', () => {
|
|
114
|
+
const mockedBill = useBill as jest.Mock;
|
|
115
|
+
const mockedProcessBillPayment = processBillPayment as jest.Mock;
|
|
116
|
+
const mockedUsePaymentModes = usePaymentModes as jest.Mock;
|
|
117
|
+
const mockedUseReactToPrint = useReactToPrint as jest.Mock;
|
|
118
|
+
|
|
119
|
+
const defaultBillData = {
|
|
120
|
+
...mockBill,
|
|
121
|
+
uuid: 'test-uuid',
|
|
122
|
+
status: 'PENDING',
|
|
123
|
+
totalAmount: 1000,
|
|
124
|
+
tenderedAmount: 0,
|
|
125
|
+
receiptNumber: 'RCPT-001',
|
|
126
|
+
dateCreated: '2024-01-01',
|
|
127
|
+
lineItems: [
|
|
128
|
+
{
|
|
129
|
+
uuid: 'item-1',
|
|
130
|
+
item: 'Test Service',
|
|
131
|
+
quantity: 1,
|
|
132
|
+
price: 1000,
|
|
133
|
+
paymentStatus: 'PENDING',
|
|
54
134
|
},
|
|
55
|
-
|
|
56
|
-
isLoading: false,
|
|
57
|
-
error: null,
|
|
58
|
-
}),
|
|
135
|
+
],
|
|
59
136
|
};
|
|
60
|
-
});
|
|
61
137
|
|
|
62
|
-
xdescribe('Invoice', () => {
|
|
63
138
|
beforeEach(() => {
|
|
64
139
|
mockedBill.mockReturnValue({
|
|
65
|
-
bill:
|
|
140
|
+
bill: defaultBillData,
|
|
66
141
|
isLoading: false,
|
|
67
142
|
error: null,
|
|
68
143
|
isValidating: false,
|
|
@@ -71,172 +146,127 @@ xdescribe('Invoice', () => {
|
|
|
71
146
|
|
|
72
147
|
mockedUsePaymentModes.mockReturnValue({
|
|
73
148
|
paymentModes: [
|
|
74
|
-
{ uuid: 'uuid', name: 'Cash', description: 'Cash Method', retired: false },
|
|
75
|
-
{ uuid: '
|
|
149
|
+
{ uuid: 'cash-uuid', name: 'Cash', description: 'Cash Method', retired: false },
|
|
150
|
+
{ uuid: 'mpesa-uuid', name: 'MPESA', description: 'MPESA Method', retired: false },
|
|
76
151
|
],
|
|
77
152
|
isLoading: false,
|
|
78
153
|
error: null,
|
|
79
154
|
mutate: jest.fn(),
|
|
80
155
|
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
afterEach(() => jest.clearAllMocks());
|
|
84
156
|
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const expectedHeaders = [
|
|
91
|
-
/Total amount/i,
|
|
92
|
-
/Amount tendered/i,
|
|
93
|
-
/Date and time/i,
|
|
94
|
-
/Invoice status/i,
|
|
95
|
-
/Invoice number/i,
|
|
96
|
-
];
|
|
97
|
-
|
|
98
|
-
expectedHeaders.forEach((header) => {
|
|
99
|
-
expect(screen.getByRole('heading', { name: header })).toBeInTheDocument();
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
const printButton = screen.getByRole('button', { name: /Print bill/i });
|
|
103
|
-
expect(printButton).toBeInTheDocument();
|
|
157
|
+
// Setup print handler mock
|
|
158
|
+
const printHandler = jest.fn();
|
|
159
|
+
mockedUseReactToPrint.mockReturnValue(printHandler);
|
|
160
|
+
});
|
|
104
161
|
|
|
105
|
-
|
|
106
|
-
|
|
162
|
+
afterEach(() => {
|
|
163
|
+
jest.clearAllMocks();
|
|
164
|
+
});
|
|
107
165
|
|
|
108
|
-
|
|
109
|
-
|
|
166
|
+
it('should render error state correctly', () => {
|
|
167
|
+
mockedBill.mockReturnValue({
|
|
168
|
+
bill: null,
|
|
169
|
+
isLoading: false,
|
|
170
|
+
error: new Error('Test error'),
|
|
171
|
+
isValidating: false,
|
|
172
|
+
mutate: jest.fn(),
|
|
110
173
|
});
|
|
111
174
|
|
|
112
|
-
|
|
113
|
-
expect(screen.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const searchInput = screen.getByRole('searchbox');
|
|
117
|
-
expect(searchInput).toBeInTheDocument();
|
|
118
|
-
await user.type(searchInput, 'Hemoglobin');
|
|
119
|
-
expect(screen.getByText('Hemoglobin')).toBeInTheDocument();
|
|
120
|
-
|
|
121
|
-
await user.type(searchInput, 'Some random text');
|
|
122
|
-
expect(screen.queryByText('Hemoglobin')).not.toBeInTheDocument();
|
|
123
|
-
expect(screen.getByText(/No matching items to display/i)).toBeInTheDocument();
|
|
124
|
-
await user.clear(searchInput);
|
|
125
|
-
|
|
126
|
-
const row = mockBill.lineItems[0].item + ' ' + mockBill.receiptNumber + ' ' + mockBill.status.toUpperCase();
|
|
127
|
-
|
|
128
|
-
expect(screen.getByRole('row', { name: new RegExp(row, 'i') })).toBeInTheDocument();
|
|
129
|
-
|
|
130
|
-
// should be able to handle payments
|
|
131
|
-
const paymentSection = await screen.findByRole('heading', { name: /Payments/i });
|
|
132
|
-
expect(paymentSection).toBeInTheDocument();
|
|
133
|
-
|
|
134
|
-
const addPaymentOptionButton = await screen.findByRole('button', { name: /Add payment option/i });
|
|
135
|
-
expect(addPaymentOptionButton).toBeInTheDocument();
|
|
136
|
-
await user.click(addPaymentOptionButton);
|
|
137
|
-
const paymentModeInput = screen.getByRole('combobox', { name: /Payment method/i });
|
|
138
|
-
expect(paymentModeInput).toBeInTheDocument();
|
|
139
|
-
await user.click(paymentModeInput);
|
|
140
|
-
|
|
141
|
-
// select cash payment mode
|
|
142
|
-
const cashPaymentMode = await screen.findByText('Cash');
|
|
143
|
-
expect(cashPaymentMode).toBeInTheDocument();
|
|
144
|
-
await user.click(cashPaymentMode);
|
|
145
|
-
|
|
146
|
-
// enter payment amount
|
|
147
|
-
const paymentAmountInput = screen.getByPlaceholderText('Enter amount');
|
|
148
|
-
expect(paymentAmountInput).toBeInTheDocument();
|
|
149
|
-
await user.type(paymentAmountInput, '100');
|
|
175
|
+
render(<Invoice />);
|
|
176
|
+
expect(screen.getByTestId('error-state')).toBeInTheDocument();
|
|
177
|
+
expect(screen.getByText(/Test error/i)).toBeInTheDocument();
|
|
178
|
+
});
|
|
150
179
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
expect(paymentReferenceNumberInput).toBeInTheDocument();
|
|
154
|
-
await user.type(paymentReferenceNumberInput, '123456');
|
|
180
|
+
it('should render invoice details correctly', () => {
|
|
181
|
+
render(<Invoice />);
|
|
155
182
|
|
|
156
|
-
|
|
183
|
+
// Check invoice details
|
|
184
|
+
expect(screen.getByText(/Total Amount/i)).toBeInTheDocument();
|
|
185
|
+
expect(screen.getByText(/Amount Tendered/i)).toBeInTheDocument();
|
|
186
|
+
expect(screen.getByText(/Invoice Number/i)).toBeInTheDocument();
|
|
187
|
+
expect(screen.getByText(/Date And Time/i)).toBeInTheDocument();
|
|
188
|
+
expect(screen.getByText(/Invoice Status/i)).toBeInTheDocument();
|
|
157
189
|
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
await user.click(processPaymentButton);
|
|
190
|
+
// Check mock components
|
|
191
|
+
expect(screen.getByTestId('mock-invoice-table')).toBeInTheDocument();
|
|
192
|
+
expect(screen.getByTestId('mock-payments')).toBeInTheDocument();
|
|
193
|
+
});
|
|
163
194
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
{
|
|
167
|
-
|
|
168
|
-
cashier: 'fe00dd43-4c39-4ce9-9832-bc3620c80c6c',
|
|
169
|
-
patient: 'b2fcf02b-7ee3-4d16-a48f-576be2b103aa',
|
|
170
|
-
payments: [{ amount: 100, amountTendered: 100, attributes: [], instanceType: 'uuid' }],
|
|
195
|
+
it('should show print receipt button for paid bills', () => {
|
|
196
|
+
mockedBill.mockReturnValue({
|
|
197
|
+
bill: {
|
|
198
|
+
...defaultBillData,
|
|
171
199
|
status: 'PAID',
|
|
200
|
+
tenderedAmount: 1000,
|
|
172
201
|
},
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
kind: 'success',
|
|
178
|
-
subtitle: 'Bill payment processing has been successful',
|
|
179
|
-
timeoutInMs: 3000,
|
|
180
|
-
title: 'Bill payment',
|
|
202
|
+
isLoading: false,
|
|
203
|
+
error: null,
|
|
204
|
+
isValidating: false,
|
|
205
|
+
mutate: jest.fn(),
|
|
181
206
|
});
|
|
182
|
-
});
|
|
183
207
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
renderInvoice();
|
|
188
|
-
|
|
189
|
-
const printButton = screen.getByRole('button', { name: /Print bill/i });
|
|
190
|
-
expect(printButton).toBeInTheDocument();
|
|
191
|
-
await user.click(printButton);
|
|
192
|
-
expect(mockedUseReactToPrint).toHaveBeenCalledTimes(1);
|
|
193
|
-
expect(mockedUseReactToPrint).toHaveBeenCalledWith(
|
|
194
|
-
expect.objectContaining({
|
|
195
|
-
documentTitle: 'Invoice 0035-6 - John Doe',
|
|
196
|
-
}),
|
|
197
|
-
);
|
|
208
|
+
render(<Invoice />);
|
|
209
|
+
expect(screen.getByTestId('mock-print-receipt')).toBeInTheDocument();
|
|
198
210
|
});
|
|
199
211
|
|
|
200
|
-
|
|
212
|
+
it('should handle bill payment processing', async () => {
|
|
201
213
|
const user = userEvent.setup();
|
|
214
|
+
const mockMutate = jest.fn();
|
|
215
|
+
|
|
202
216
|
mockedBill.mockReturnValue({
|
|
203
|
-
bill:
|
|
204
|
-
...mockBill,
|
|
205
|
-
status: 'PAID',
|
|
206
|
-
payments: mockPayments,
|
|
207
|
-
tenderedAmount: 100,
|
|
208
|
-
},
|
|
217
|
+
bill: defaultBillData,
|
|
209
218
|
isLoading: false,
|
|
210
219
|
error: null,
|
|
211
220
|
isValidating: false,
|
|
212
|
-
mutate:
|
|
221
|
+
mutate: mockMutate,
|
|
213
222
|
});
|
|
214
223
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
224
|
+
mockedProcessBillPayment.mockResolvedValue({});
|
|
225
|
+
|
|
226
|
+
render(<Invoice />);
|
|
227
|
+
|
|
228
|
+
// Add payment flow would go here
|
|
229
|
+
// Note: Detailed payment interaction testing should be in the Payments component tests
|
|
230
|
+
|
|
231
|
+
expect(screen.getByText(/Payments/i)).toBeInTheDocument();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should update line items when bill data changes', () => {
|
|
235
|
+
const { rerender } = render(<Invoice />);
|
|
236
|
+
|
|
237
|
+
// Update bill with new line items
|
|
238
|
+
const updatedBill = {
|
|
239
|
+
...defaultBillData,
|
|
240
|
+
lineItems: [
|
|
241
|
+
...defaultBillData.lineItems,
|
|
242
|
+
{
|
|
243
|
+
uuid: 'item-2',
|
|
244
|
+
item: 'New Service',
|
|
245
|
+
quantity: 1,
|
|
246
|
+
price: 500,
|
|
247
|
+
paymentStatus: 'PENDING',
|
|
248
|
+
},
|
|
219
249
|
],
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
mockedBill.mockReturnValue({
|
|
253
|
+
bill: updatedBill,
|
|
220
254
|
isLoading: false,
|
|
221
255
|
error: null,
|
|
256
|
+
isValidating: false,
|
|
222
257
|
mutate: jest.fn(),
|
|
223
258
|
});
|
|
224
259
|
|
|
225
|
-
|
|
226
|
-
const paymentHistorySection = screen.getByRole('heading', { name: /Payments/i });
|
|
227
|
-
expect(paymentHistorySection).toBeInTheDocument();
|
|
260
|
+
rerender(<Invoice />);
|
|
228
261
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
});
|
|
262
|
+
// The mock invoice table should receive updated props
|
|
263
|
+
expect(screen.getByTestId('mock-invoice-table')).toBeInTheDocument();
|
|
264
|
+
});
|
|
233
265
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
expect(
|
|
266
|
+
it('should show patient information correctly', () => {
|
|
267
|
+
render(<Invoice />);
|
|
268
|
+
expect(screen.getByTestId('extension-slot')).toBeInTheDocument();
|
|
237
269
|
});
|
|
238
|
-
});
|
|
239
270
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
271
|
+
// Add more test cases as needed for specific features or edge cases
|
|
272
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback } from 'react';
|
|
1
|
+
import React, { useCallback, useState, useEffect } from 'react';
|
|
2
2
|
import { Controller, useFieldArray, useFormContext } from 'react-hook-form';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { TrashCan, Add } from '@carbon/react/icons';
|
|
@@ -8,11 +8,21 @@ import { type PaymentFormValue } from '../payments.component';
|
|
|
8
8
|
import { usePaymentModes } from '../payment.resource';
|
|
9
9
|
import styles from './payment-form.scss';
|
|
10
10
|
|
|
11
|
-
type PaymentFormProps = {
|
|
11
|
+
type PaymentFormProps = {
|
|
12
|
+
disablePayment: boolean;
|
|
13
|
+
clientBalance: number;
|
|
14
|
+
isSingleLineItemSelected: boolean;
|
|
15
|
+
isSingleLineItem: boolean;
|
|
16
|
+
};
|
|
12
17
|
|
|
13
18
|
const DEFAULT_PAYMENT = { method: '', amount: 0, referenceCode: '' };
|
|
14
19
|
|
|
15
|
-
const PaymentForm: React.FC<PaymentFormProps> = ({
|
|
20
|
+
const PaymentForm: React.FC<PaymentFormProps> = ({
|
|
21
|
+
disablePayment,
|
|
22
|
+
clientBalance,
|
|
23
|
+
isSingleLineItemSelected,
|
|
24
|
+
isSingleLineItem,
|
|
25
|
+
}) => {
|
|
16
26
|
const { t } = useTranslation();
|
|
17
27
|
const {
|
|
18
28
|
control,
|
|
@@ -20,12 +30,25 @@ const PaymentForm: React.FC<PaymentFormProps> = ({ disablePayment, amountDue })
|
|
|
20
30
|
} = useFormContext<PaymentFormValue>();
|
|
21
31
|
const { paymentModes, isLoading, error } = usePaymentModes();
|
|
22
32
|
const { fields, remove, append } = useFieldArray({ name: 'payment', control: control });
|
|
33
|
+
const [isFormVisible, setIsFormVisible] = useState(isSingleLineItem);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (isSingleLineItem) {
|
|
37
|
+
setIsFormVisible(true);
|
|
38
|
+
if (fields.length === 0) {
|
|
39
|
+
append(DEFAULT_PAYMENT);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}, [isSingleLineItem, append, fields.length]);
|
|
23
43
|
|
|
24
|
-
const handleAppendPaymentMode = useCallback(() =>
|
|
44
|
+
const handleAppendPaymentMode = useCallback(() => {
|
|
45
|
+
setIsFormVisible(true);
|
|
46
|
+
append(DEFAULT_PAYMENT);
|
|
47
|
+
}, [append]);
|
|
25
48
|
const handleRemovePaymentMode = useCallback((index) => remove(index), [remove]);
|
|
26
49
|
|
|
27
50
|
if (isLoading) {
|
|
28
|
-
return <NumberInputSkeleton />;
|
|
51
|
+
return <NumberInputSkeleton data-testid="number-input-skeleton" />;
|
|
29
52
|
}
|
|
30
53
|
|
|
31
54
|
if (error) {
|
|
@@ -38,59 +61,65 @@ const PaymentForm: React.FC<PaymentFormProps> = ({ disablePayment, amountDue })
|
|
|
38
61
|
|
|
39
62
|
return (
|
|
40
63
|
<div className={styles.container}>
|
|
41
|
-
{
|
|
42
|
-
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
64
|
+
{isFormVisible &&
|
|
65
|
+
fields.map((field, index) => (
|
|
66
|
+
<div key={field.id} className={styles.paymentMethodContainer}>
|
|
67
|
+
<Controller
|
|
68
|
+
control={control}
|
|
69
|
+
name={`payment.${index}.method`}
|
|
70
|
+
render={({ field }) => (
|
|
71
|
+
<Dropdown
|
|
72
|
+
id="paymentMethod"
|
|
73
|
+
onChange={({ selectedItem }) => field.onChange(selectedItem?.uuid)}
|
|
74
|
+
titleText={t('paymentMethod', 'Payment method')}
|
|
75
|
+
label={t('selectPaymentMethod', 'Select payment method')}
|
|
76
|
+
items={paymentModes}
|
|
77
|
+
itemToString={(item) => (item ? item.name : '')}
|
|
78
|
+
invalid={!!errors?.payment?.[index]?.method}
|
|
79
|
+
invalidText={errors?.payment?.[index]?.method?.message}
|
|
80
|
+
/>
|
|
81
|
+
)}
|
|
82
|
+
/>
|
|
83
|
+
<Controller
|
|
84
|
+
control={control}
|
|
85
|
+
name={`payment.${index}.amount`}
|
|
86
|
+
render={({ field }) => (
|
|
87
|
+
<NumberInput
|
|
88
|
+
id="paymentAmount"
|
|
89
|
+
{...field}
|
|
90
|
+
onChange={(e) => field.onChange(Number(e.target.value))}
|
|
91
|
+
invalid={!!errors?.payment?.[index]?.amount}
|
|
92
|
+
invalidText={errors?.payment?.[index]?.amount?.message}
|
|
93
|
+
label={t('amount', 'Amount')}
|
|
94
|
+
placeholder={t('enterAmount', 'Enter amount')}
|
|
95
|
+
/>
|
|
96
|
+
)}
|
|
97
|
+
/>
|
|
98
|
+
<Controller
|
|
99
|
+
name={`payment.${index}.referenceCode`}
|
|
100
|
+
control={control}
|
|
101
|
+
render={({ field }) => (
|
|
102
|
+
<TextInput
|
|
103
|
+
id="paymentReferenceCode"
|
|
104
|
+
{...field}
|
|
105
|
+
labelText={t('referenceNumber', 'Reference number')}
|
|
106
|
+
placeholder={t('enterReferenceNumber', 'Enter ref. number')}
|
|
107
|
+
type="text"
|
|
108
|
+
/>
|
|
109
|
+
)}
|
|
110
|
+
/>
|
|
111
|
+
<div className={styles.removeButtonContainer}>
|
|
112
|
+
<TrashCan
|
|
113
|
+
onClick={() => handleRemovePaymentMode(index)}
|
|
114
|
+
className={styles.removeButton}
|
|
115
|
+
size={20}
|
|
116
|
+
data-testid="trash-can-icon"
|
|
84
117
|
/>
|
|
85
|
-
|
|
86
|
-
/>
|
|
87
|
-
<div className={styles.removeButtonContainer}>
|
|
88
|
-
<TrashCan onClick={handleRemovePaymentMode} className={styles.removeButton} size={20} />
|
|
118
|
+
</div>
|
|
89
119
|
</div>
|
|
90
|
-
|
|
91
|
-
))}
|
|
120
|
+
))}
|
|
92
121
|
<Button
|
|
93
|
-
disabled={disablePayment}
|
|
122
|
+
disabled={disablePayment || (!isSingleLineItem && !isSingleLineItemSelected)}
|
|
94
123
|
size="md"
|
|
95
124
|
onClick={handleAppendPaymentMode}
|
|
96
125
|
className={styles.paymentButtons}
|