@openmrs/esm-billing-app 1.0.2-pre.761 → 1.0.2-pre.767
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 +0 -1
- package/dist/4300.js +1 -1
- package/dist/4739.js +1 -1
- package/dist/4739.js.map +1 -1
- package/dist/942.js +1 -1
- package/dist/942.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-billing-app.js.buildmanifest.json +12 -12
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/bill-item-actions/edit-bill-item.modal.tsx +49 -28
- package/src/bill-item-actions/edit-bill-item.test.tsx +95 -7
- package/src/billing-form/billing-form.component.tsx +2 -0
- package/src/config-schema.ts +0 -6
- package/src/invoice/invoice-table.component.tsx +12 -16
- package/src/invoice/invoice-table.test.tsx +267 -30
- package/src/invoice/invoice.test.tsx +315 -85
- package/src/invoice/payments/payment-form/payment-form.component.tsx +2 -0
- package/translations/en.json +11 -4
|
@@ -1,68 +1,51 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
|
-
import { screen,
|
|
3
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
4
4
|
import { useReactToPrint } from 'react-to-print';
|
|
5
5
|
import { getDefaultsFromConfigSchema, useConfig, usePatient } from '@openmrs/esm-framework';
|
|
6
6
|
import { configSchema, type BillingConfig } from '../config-schema';
|
|
7
7
|
import { mockBill, mockPatient } from '../../__mocks__/bills.mock';
|
|
8
|
-
import { useBill
|
|
8
|
+
import { useBill } from '../billing.resource';
|
|
9
9
|
import { usePaymentModes } from './payments/payment.resource';
|
|
10
10
|
import Invoice from './invoice.component';
|
|
11
11
|
|
|
12
12
|
const mockUseConfig = jest.mocked(useConfig<BillingConfig>);
|
|
13
|
+
const mockUseBill = jest.mocked(useBill);
|
|
14
|
+
const mockUsePatient = jest.mocked(usePatient);
|
|
15
|
+
const mockUsePaymentModes = jest.mocked(usePaymentModes);
|
|
16
|
+
const mockUseReactToPrint = jest.mocked(useReactToPrint);
|
|
13
17
|
|
|
14
|
-
// Mock convertToCurrency
|
|
15
18
|
jest.mock('../helpers/functions', () => ({
|
|
16
19
|
convertToCurrency: jest.fn((amount) => `USD ${amount}`),
|
|
17
20
|
}));
|
|
18
21
|
|
|
19
|
-
// Set window.i18next
|
|
20
22
|
window.i18next = {
|
|
21
23
|
language: 'en',
|
|
22
24
|
} as any;
|
|
23
25
|
|
|
24
|
-
// Mock InvoiceTable component
|
|
25
|
-
jest.mock('./invoice-table.component', () =>
|
|
26
|
-
jest.fn(({ bill }) => <div data-testid="mock-invoice-table">Invoice Table Mock</div>),
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
// Mock payments component
|
|
30
|
-
jest.mock('./payments/payments.component', () =>
|
|
31
|
-
jest.fn(({ bill, mutate, selectedLineItems }) => (
|
|
32
|
-
<div data-testid="mock-payments">
|
|
33
|
-
<h2>Payments</h2>
|
|
34
|
-
<button>Add payment option</button>
|
|
35
|
-
</div>
|
|
36
|
-
)),
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
// Mock PrintReceipt component
|
|
40
26
|
jest.mock('./printable-invoice/print-receipt.component', () =>
|
|
41
|
-
jest.fn((
|
|
27
|
+
jest.fn(() => <div data-testid="mock-print-receipt">Print Receipt Mock</div>),
|
|
42
28
|
);
|
|
43
29
|
|
|
44
|
-
// Mock PrintableInvoice component
|
|
45
30
|
jest.mock('./printable-invoice/printable-invoice.component', () =>
|
|
46
|
-
jest.fn((
|
|
31
|
+
jest.fn(() => <div data-testid="mock-printable-invoice">Printable Invoice Mock</div>),
|
|
47
32
|
);
|
|
48
33
|
|
|
49
|
-
// Mock payment resource
|
|
50
34
|
jest.mock('./payments/payment.resource', () => ({
|
|
51
35
|
usePaymentModes: jest.fn(),
|
|
52
36
|
updateBillVisitAttribute: jest.fn(),
|
|
53
37
|
}));
|
|
54
38
|
|
|
55
|
-
// Mock billing resource
|
|
56
39
|
jest.mock('../billing.resource', () => ({
|
|
57
40
|
useBill: jest.fn(),
|
|
58
|
-
processBillPayment: jest.fn(),
|
|
59
41
|
useDefaultFacility: jest.fn().mockReturnValue({
|
|
60
|
-
|
|
61
|
-
|
|
42
|
+
data: {
|
|
43
|
+
uuid: '54065383-b4d4-42d2-af4d-d250a1fd2590',
|
|
44
|
+
display: 'MTRH',
|
|
45
|
+
},
|
|
62
46
|
}),
|
|
63
47
|
}));
|
|
64
48
|
|
|
65
|
-
// Mock react-router-dom
|
|
66
49
|
jest.mock('react-router-dom', () => ({
|
|
67
50
|
useParams: jest.fn().mockReturnValue({
|
|
68
51
|
patientUuid: 'patientUuid',
|
|
@@ -70,18 +53,11 @@ jest.mock('react-router-dom', () => ({
|
|
|
70
53
|
}),
|
|
71
54
|
}));
|
|
72
55
|
|
|
73
|
-
// Mock react-to-print
|
|
74
56
|
jest.mock('react-to-print', () => ({
|
|
75
57
|
useReactToPrint: jest.fn(),
|
|
76
58
|
}));
|
|
77
59
|
|
|
78
60
|
describe('Invoice', () => {
|
|
79
|
-
const mockedBill = useBill as jest.Mock;
|
|
80
|
-
const mockedPatient = usePatient as jest.Mock;
|
|
81
|
-
const mockedProcessBillPayment = processBillPayment as jest.Mock;
|
|
82
|
-
const mockedUsePaymentModes = usePaymentModes as jest.Mock;
|
|
83
|
-
const mockedUseReactToPrint = useReactToPrint as jest.Mock;
|
|
84
|
-
|
|
85
61
|
const defaultBillData = {
|
|
86
62
|
...mockBill,
|
|
87
63
|
uuid: 'test-uuid',
|
|
@@ -97,12 +73,20 @@ describe('Invoice', () => {
|
|
|
97
73
|
quantity: 1,
|
|
98
74
|
price: 1000,
|
|
99
75
|
paymentStatus: 'PENDING',
|
|
76
|
+
billableService: 'Test Service',
|
|
77
|
+
display: '',
|
|
78
|
+
voided: false,
|
|
79
|
+
voidReason: '',
|
|
80
|
+
priceName: '',
|
|
81
|
+
priceUuid: '',
|
|
82
|
+
lineItemOrder: 0,
|
|
83
|
+
resourceVersion: '',
|
|
100
84
|
},
|
|
101
85
|
],
|
|
102
86
|
};
|
|
103
87
|
|
|
104
88
|
beforeEach(() => {
|
|
105
|
-
|
|
89
|
+
mockUseBill.mockReturnValue({
|
|
106
90
|
bill: defaultBillData,
|
|
107
91
|
isLoading: false,
|
|
108
92
|
error: null,
|
|
@@ -110,15 +94,14 @@ describe('Invoice', () => {
|
|
|
110
94
|
mutate: jest.fn(),
|
|
111
95
|
});
|
|
112
96
|
|
|
113
|
-
|
|
114
|
-
patient: mockPatient,
|
|
97
|
+
mockUsePatient.mockReturnValue({
|
|
98
|
+
patient: mockPatient as any,
|
|
115
99
|
isLoading: false,
|
|
116
100
|
error: null,
|
|
117
|
-
|
|
118
|
-
mutate: jest.fn(),
|
|
101
|
+
patientUuid: 'patientUuid',
|
|
119
102
|
});
|
|
120
103
|
|
|
121
|
-
|
|
104
|
+
mockUsePaymentModes.mockReturnValue({
|
|
122
105
|
paymentModes: [
|
|
123
106
|
{ uuid: 'cash-uuid', name: 'Cash', description: 'Cash Method', retired: false },
|
|
124
107
|
{ uuid: 'mpesa-uuid', name: 'MPESA', description: 'MPESA Method', retired: false },
|
|
@@ -130,42 +113,75 @@ describe('Invoice', () => {
|
|
|
130
113
|
|
|
131
114
|
mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(configSchema), defaultCurrency: 'USD' });
|
|
132
115
|
|
|
133
|
-
// Setup print handler mock
|
|
134
116
|
const printHandler = jest.fn();
|
|
135
|
-
|
|
117
|
+
mockUseReactToPrint.mockReturnValue(printHandler);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should render loading state when bill is loading', () => {
|
|
121
|
+
mockUseBill.mockReturnValue({
|
|
122
|
+
bill: null,
|
|
123
|
+
isLoading: true,
|
|
124
|
+
error: null,
|
|
125
|
+
isValidating: false,
|
|
126
|
+
mutate: jest.fn(),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
render(<Invoice />);
|
|
130
|
+
expect(screen.getByText(/loading bill information/i)).toBeInTheDocument();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should render loading state when patient is loading', () => {
|
|
134
|
+
mockUsePatient.mockReturnValue({
|
|
135
|
+
patient: null as any,
|
|
136
|
+
isLoading: true,
|
|
137
|
+
error: null,
|
|
138
|
+
patientUuid: 'patientUuid',
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
render(<Invoice />);
|
|
142
|
+
expect(screen.getByText(/loading bill information/i)).toBeInTheDocument();
|
|
136
143
|
});
|
|
137
144
|
|
|
138
|
-
it('should render error state
|
|
139
|
-
|
|
145
|
+
it('should render error state when bill fails to load', () => {
|
|
146
|
+
mockUseBill.mockReturnValue({
|
|
140
147
|
bill: null,
|
|
141
148
|
isLoading: false,
|
|
142
|
-
error: new Error('
|
|
149
|
+
error: new Error('Failed to load bill'),
|
|
143
150
|
isValidating: false,
|
|
144
151
|
mutate: jest.fn(),
|
|
145
152
|
});
|
|
146
153
|
|
|
147
154
|
render(<Invoice />);
|
|
148
|
-
expect(screen.getByText(/
|
|
149
|
-
expect(screen.getByText(/Error/)).toBeInTheDocument();
|
|
155
|
+
expect(screen.getByText(/invoice error/i)).toBeInTheDocument();
|
|
150
156
|
});
|
|
151
157
|
|
|
152
158
|
it('should render invoice details correctly', () => {
|
|
153
159
|
render(<Invoice />);
|
|
154
160
|
|
|
155
|
-
|
|
156
|
-
expect(screen.
|
|
157
|
-
expect(screen.getByText(/
|
|
158
|
-
expect(screen.getByText(/
|
|
159
|
-
expect(screen.getByText(/
|
|
160
|
-
expect(screen.
|
|
161
|
+
expect(screen.getAllByText(/total amount/i).length).toBeGreaterThan(0);
|
|
162
|
+
expect(screen.getAllByText(/amount tendered/i).length).toBeGreaterThan(0);
|
|
163
|
+
expect(screen.getByText(/invoice number/i)).toBeInTheDocument();
|
|
164
|
+
expect(screen.getByText(/date and time/i)).toBeInTheDocument();
|
|
165
|
+
expect(screen.getByText(/invoice status/i)).toBeInTheDocument();
|
|
166
|
+
expect(screen.getAllByText('RCPT-001').length).toBeGreaterThan(0);
|
|
167
|
+
expect(screen.getAllByText('PENDING').length).toBeGreaterThan(0);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should render invoice table with line items', () => {
|
|
171
|
+
render(<Invoice />);
|
|
161
172
|
|
|
162
|
-
|
|
163
|
-
expect(screen.
|
|
164
|
-
|
|
173
|
+
expect(screen.getByText(/line items/i)).toBeInTheDocument();
|
|
174
|
+
expect(screen.getByText('Test Service')).toBeInTheDocument();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should render payments section', () => {
|
|
178
|
+
render(<Invoice />);
|
|
179
|
+
|
|
180
|
+
expect(screen.getByText(/payments/i)).toBeInTheDocument();
|
|
165
181
|
});
|
|
166
182
|
|
|
167
183
|
it('should show print receipt button for paid bills', () => {
|
|
168
|
-
|
|
184
|
+
mockUseBill.mockReturnValue({
|
|
169
185
|
bill: {
|
|
170
186
|
...defaultBillData,
|
|
171
187
|
status: 'PAID',
|
|
@@ -181,66 +197,280 @@ describe('Invoice', () => {
|
|
|
181
197
|
expect(screen.getByTestId('mock-print-receipt')).toBeInTheDocument();
|
|
182
198
|
});
|
|
183
199
|
|
|
184
|
-
it('should
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
200
|
+
it('should show print receipt button for bills with tendered amount', () => {
|
|
201
|
+
mockUseBill.mockReturnValue({
|
|
202
|
+
bill: {
|
|
203
|
+
...defaultBillData,
|
|
204
|
+
status: 'PENDING',
|
|
205
|
+
tenderedAmount: 500,
|
|
206
|
+
},
|
|
190
207
|
isLoading: false,
|
|
191
208
|
error: null,
|
|
192
209
|
isValidating: false,
|
|
193
|
-
mutate:
|
|
210
|
+
mutate: jest.fn(),
|
|
194
211
|
});
|
|
195
212
|
|
|
196
|
-
|
|
213
|
+
render(<Invoice />);
|
|
214
|
+
expect(screen.getByTestId('mock-print-receipt')).toBeInTheDocument();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should not show print receipt button for unpaid bills', () => {
|
|
218
|
+
render(<Invoice />);
|
|
219
|
+
expect(screen.queryByTestId('mock-print-receipt')).not.toBeInTheDocument();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should handle print button click', async () => {
|
|
223
|
+
const handlePrintMock = jest.fn();
|
|
224
|
+
const user = userEvent.setup();
|
|
225
|
+
mockUseReactToPrint.mockReturnValue(handlePrintMock);
|
|
197
226
|
|
|
198
227
|
render(<Invoice />);
|
|
199
228
|
|
|
200
|
-
|
|
201
|
-
|
|
229
|
+
const printButton = screen.getByRole('button', { name: /print bill/i });
|
|
230
|
+
await user.click(printButton);
|
|
202
231
|
|
|
203
|
-
|
|
232
|
+
await waitFor(() => {
|
|
233
|
+
expect(handlePrintMock).toHaveBeenCalled();
|
|
234
|
+
});
|
|
204
235
|
});
|
|
205
236
|
|
|
206
|
-
it('should
|
|
207
|
-
|
|
237
|
+
it('should disable print button while printing', () => {
|
|
238
|
+
render(<Invoice />);
|
|
208
239
|
|
|
209
|
-
|
|
210
|
-
|
|
240
|
+
const printButton = screen.getByRole('button', { name: /print bill/i });
|
|
241
|
+
expect(printButton).not.toBeDisabled();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('should render patient header when patient data is available', () => {
|
|
245
|
+
render(<Invoice />);
|
|
246
|
+
|
|
247
|
+
// Patient header is rendered via ExtensionSlot
|
|
248
|
+
expect(screen.getByText(/line items/i)).toBeInTheDocument();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should search and filter line items in the table', async () => {
|
|
252
|
+
const billWithMultipleItems = {
|
|
211
253
|
...defaultBillData,
|
|
212
254
|
lineItems: [
|
|
213
|
-
|
|
255
|
+
{
|
|
256
|
+
uuid: 'item-1',
|
|
257
|
+
item: 'Lab Test',
|
|
258
|
+
quantity: 1,
|
|
259
|
+
price: 500,
|
|
260
|
+
paymentStatus: 'PENDING',
|
|
261
|
+
billableService: 'Lab Test',
|
|
262
|
+
display: '',
|
|
263
|
+
voided: false,
|
|
264
|
+
voidReason: '',
|
|
265
|
+
priceName: '',
|
|
266
|
+
priceUuid: '',
|
|
267
|
+
lineItemOrder: 0,
|
|
268
|
+
resourceVersion: '',
|
|
269
|
+
},
|
|
214
270
|
{
|
|
215
271
|
uuid: 'item-2',
|
|
216
|
-
item: '
|
|
272
|
+
item: 'X-Ray',
|
|
217
273
|
quantity: 1,
|
|
218
274
|
price: 500,
|
|
219
275
|
paymentStatus: 'PENDING',
|
|
276
|
+
billableService: 'X-Ray',
|
|
277
|
+
display: '',
|
|
278
|
+
voided: false,
|
|
279
|
+
voidReason: '',
|
|
280
|
+
priceName: '',
|
|
281
|
+
priceUuid: '',
|
|
282
|
+
lineItemOrder: 1,
|
|
283
|
+
resourceVersion: '',
|
|
220
284
|
},
|
|
221
285
|
],
|
|
222
286
|
};
|
|
223
287
|
|
|
224
|
-
|
|
225
|
-
bill:
|
|
288
|
+
mockUseBill.mockReturnValue({
|
|
289
|
+
bill: billWithMultipleItems,
|
|
226
290
|
isLoading: false,
|
|
227
291
|
error: null,
|
|
228
292
|
isValidating: false,
|
|
229
293
|
mutate: jest.fn(),
|
|
230
294
|
});
|
|
231
295
|
|
|
296
|
+
const user = userEvent.setup();
|
|
297
|
+
render(<Invoice />);
|
|
298
|
+
|
|
299
|
+
expect(screen.getByText('Lab Test')).toBeInTheDocument();
|
|
300
|
+
expect(screen.getByText('X-Ray')).toBeInTheDocument();
|
|
301
|
+
|
|
302
|
+
const searchInput = screen.getByPlaceholderText(/search this table/i);
|
|
303
|
+
await user.type(searchInput, 'Lab Test');
|
|
304
|
+
|
|
305
|
+
await waitFor(() => {
|
|
306
|
+
expect(screen.getByText('Lab Test')).toBeInTheDocument();
|
|
307
|
+
expect(screen.queryByText('X-Ray')).not.toBeInTheDocument();
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('should handle bill data updates via mutate', () => {
|
|
312
|
+
const mockMutate = jest.fn();
|
|
313
|
+
mockUseBill.mockReturnValue({
|
|
314
|
+
bill: defaultBillData,
|
|
315
|
+
isLoading: false,
|
|
316
|
+
error: null,
|
|
317
|
+
isValidating: false,
|
|
318
|
+
mutate: mockMutate,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
const { rerender } = render(<Invoice />);
|
|
322
|
+
|
|
323
|
+
const updatedBill = {
|
|
324
|
+
...defaultBillData,
|
|
325
|
+
status: 'PAID',
|
|
326
|
+
tenderedAmount: 1000,
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
mockUseBill.mockReturnValue({
|
|
330
|
+
bill: updatedBill,
|
|
331
|
+
isLoading: false,
|
|
332
|
+
error: null,
|
|
333
|
+
isValidating: false,
|
|
334
|
+
mutate: mockMutate,
|
|
335
|
+
});
|
|
336
|
+
|
|
232
337
|
rerender(<Invoice />);
|
|
233
338
|
|
|
234
|
-
|
|
235
|
-
|
|
339
|
+
expect(screen.getByText('PAID')).toBeInTheDocument();
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('should display correct currency formatting', () => {
|
|
343
|
+
render(<Invoice />);
|
|
344
|
+
|
|
345
|
+
// convertToCurrency is mocked to return "USD ${amount}"
|
|
346
|
+
expect(screen.getAllByText('USD 1000').length).toBeGreaterThan(0);
|
|
347
|
+
expect(screen.getAllByText('USD 0').length).toBeGreaterThan(0);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('should disable print button when isPrinting state is true', () => {
|
|
351
|
+
// Mock isPrinting state by checking the button's disabled state when loading
|
|
352
|
+
mockUseBill.mockReturnValue({
|
|
353
|
+
bill: defaultBillData,
|
|
354
|
+
isLoading: true,
|
|
355
|
+
error: null,
|
|
356
|
+
isValidating: false,
|
|
357
|
+
mutate: jest.fn(),
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
render(<Invoice />);
|
|
361
|
+
// When bill is loading, component shows loading state, not the button
|
|
362
|
+
expect(screen.getByText(/loading bill information/i)).toBeInTheDocument();
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('should not render PrintableInvoice when bill is missing', () => {
|
|
366
|
+
mockUseBill.mockReturnValue({
|
|
367
|
+
bill: null,
|
|
368
|
+
isLoading: false,
|
|
369
|
+
error: null,
|
|
370
|
+
isValidating: false,
|
|
371
|
+
mutate: jest.fn(),
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
mockUsePatient.mockReturnValue({
|
|
375
|
+
patient: mockPatient as any,
|
|
376
|
+
isLoading: false,
|
|
377
|
+
error: null,
|
|
378
|
+
patientUuid: 'patientUuid',
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
render(<Invoice />);
|
|
382
|
+
|
|
383
|
+
// PrintableInvoice should not be rendered when bill is null
|
|
384
|
+
// Since it's in a hidden div, we can't easily assert its absence
|
|
385
|
+
// but we can verify the main content doesn't have the print container
|
|
386
|
+
expect(screen.queryByTestId('mock-printable-invoice')).not.toBeInTheDocument();
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('should not render PrintableInvoice when patient is missing', () => {
|
|
390
|
+
mockUsePatient.mockReturnValue({
|
|
391
|
+
patient: null as any,
|
|
392
|
+
isLoading: false,
|
|
393
|
+
error: null,
|
|
394
|
+
patientUuid: 'patientUuid',
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
render(<Invoice />);
|
|
398
|
+
|
|
399
|
+
// PrintableInvoice requires both bill and patient
|
|
400
|
+
expect(screen.queryByTestId('mock-printable-invoice')).not.toBeInTheDocument();
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('should render PrintableInvoice when both bill and patient exist', () => {
|
|
404
|
+
render(<Invoice />);
|
|
405
|
+
|
|
406
|
+
// PrintableInvoice should be rendered with both bill and patient
|
|
407
|
+
expect(screen.getByTestId('mock-printable-invoice')).toBeInTheDocument();
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it('should pass correct props to InvoiceTable', () => {
|
|
411
|
+
render(<Invoice />);
|
|
412
|
+
|
|
413
|
+
// Verify InvoiceTable is rendered with line items
|
|
414
|
+
expect(screen.getByText('Test Service')).toBeInTheDocument();
|
|
415
|
+
expect(screen.getByText(/line items/i)).toBeInTheDocument();
|
|
236
416
|
});
|
|
237
417
|
|
|
238
|
-
it('should
|
|
418
|
+
it('should pass mutate function to Payments component', () => {
|
|
419
|
+
const mockMutate = jest.fn();
|
|
420
|
+
mockUseBill.mockReturnValue({
|
|
421
|
+
bill: defaultBillData,
|
|
422
|
+
isLoading: false,
|
|
423
|
+
error: null,
|
|
424
|
+
isValidating: false,
|
|
425
|
+
mutate: mockMutate,
|
|
426
|
+
});
|
|
427
|
+
|
|
239
428
|
render(<Invoice />);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
expect(screen.getByText(
|
|
429
|
+
|
|
430
|
+
// Payments component should be rendered
|
|
431
|
+
expect(screen.getByText(/payments/i)).toBeInTheDocument();
|
|
243
432
|
});
|
|
244
433
|
|
|
245
|
-
|
|
434
|
+
it('should show print receipt for bills with partial payment', () => {
|
|
435
|
+
mockUseBill.mockReturnValue({
|
|
436
|
+
bill: {
|
|
437
|
+
...defaultBillData,
|
|
438
|
+
status: 'PENDING',
|
|
439
|
+
totalAmount: 1000,
|
|
440
|
+
tenderedAmount: 500, // Partial payment
|
|
441
|
+
},
|
|
442
|
+
isLoading: false,
|
|
443
|
+
error: null,
|
|
444
|
+
isValidating: false,
|
|
445
|
+
mutate: jest.fn(),
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
render(<Invoice />);
|
|
449
|
+
expect(screen.getByTestId('mock-print-receipt')).toBeInTheDocument();
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it('should render ExtensionSlot when patient and patientUuid exist', () => {
|
|
453
|
+
render(<Invoice />);
|
|
454
|
+
|
|
455
|
+
// The component renders, which includes the ExtensionSlot
|
|
456
|
+
// We can verify this indirectly by checking the main content is present
|
|
457
|
+
expect(screen.getByText(/invoice number/i)).toBeInTheDocument();
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('should not show print receipt for bills with zero tendered amount', () => {
|
|
461
|
+
mockUseBill.mockReturnValue({
|
|
462
|
+
bill: {
|
|
463
|
+
...defaultBillData,
|
|
464
|
+
status: 'PENDING',
|
|
465
|
+
tenderedAmount: 0,
|
|
466
|
+
},
|
|
467
|
+
isLoading: false,
|
|
468
|
+
error: null,
|
|
469
|
+
isValidating: false,
|
|
470
|
+
mutate: jest.fn(),
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
render(<Invoice />);
|
|
474
|
+
expect(screen.queryByTestId('mock-print-receipt')).not.toBeInTheDocument();
|
|
475
|
+
});
|
|
246
476
|
});
|
|
@@ -80,6 +80,8 @@ const PaymentForm: React.FC<PaymentFormProps> = ({ disablePayment, isSingleLineI
|
|
|
80
80
|
render={({ field }) => (
|
|
81
81
|
<NumberInput
|
|
82
82
|
allowEmpty
|
|
83
|
+
disableWheel
|
|
84
|
+
hideSteppers
|
|
83
85
|
id="paymentAmount"
|
|
84
86
|
invalid={!!errors?.payment?.[index]?.amount}
|
|
85
87
|
invalidText={errors?.payment?.[index]?.amount?.message}
|
package/translations/en.json
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
{
|
|
2
|
-
"action": "Action",
|
|
3
2
|
"add": "Add",
|
|
4
3
|
"addBill": "Add bill item(s)",
|
|
5
4
|
"addBillableServices": "Add Billable Services",
|
|
@@ -11,7 +10,6 @@
|
|
|
11
10
|
"addPaymentMethod": "Add payment method",
|
|
12
11
|
"addPaymentMode": "Add Payment Mode",
|
|
13
12
|
"addPaymentOption": "Add payment option",
|
|
14
|
-
"addPaymentOptions": "Add payment option",
|
|
15
13
|
"amount": "Amount",
|
|
16
14
|
"amountDue": "Amount Due",
|
|
17
15
|
"amountMustBePositive": "Amount must be greater than 0",
|
|
@@ -75,7 +73,7 @@
|
|
|
75
73
|
"discountAmount": "Discount Amount",
|
|
76
74
|
"editBillableService": "Edit billable service",
|
|
77
75
|
"editBillableServices": "Edit Billable Services",
|
|
78
|
-
"editBillLineItem": "Edit bill line item
|
|
76
|
+
"editBillLineItem": "Edit bill line item",
|
|
79
77
|
"editThisBillItem": "Edit this bill item",
|
|
80
78
|
"enterAmount": "Enter amount",
|
|
81
79
|
"enterConcept": "Associated concept",
|
|
@@ -112,6 +110,10 @@
|
|
|
112
110
|
"itemsToBeBilled": "Items to be billed",
|
|
113
111
|
"launchBillForm": "Launch bill form",
|
|
114
112
|
"lineItems": "Line items",
|
|
113
|
+
"lineItemUpdated": "Line item updated",
|
|
114
|
+
"lineItemUpdateErrorDefault": "Unable to update the bill line item. Please try again.",
|
|
115
|
+
"lineItemUpdateFailed": "Failed to update line item",
|
|
116
|
+
"lineItemUpdateSuccess": "The bill line item has been updated successfully",
|
|
115
117
|
"loading": "Loading data...",
|
|
116
118
|
"loadingBillInfo": "Loading bill information...",
|
|
117
119
|
"loadingBillingServices": "Loading billing services...",
|
|
@@ -157,12 +159,18 @@
|
|
|
157
159
|
"price": "Unit Price",
|
|
158
160
|
"priceIsRequired": "Price is required",
|
|
159
161
|
"priceMustBeGreaterThanZero": "Price must be greater than 0",
|
|
162
|
+
"priceMustBeNumber": "Price must be a valid number",
|
|
163
|
+
"priceMustBePositive": "Price must be greater than 0",
|
|
160
164
|
"priceMustBeValidPositiveNumber": "Price must be a valid positive number",
|
|
161
165
|
"prices": "Prices",
|
|
162
166
|
"printBill": "Print bill",
|
|
163
167
|
"printReceipt": "Print receipt",
|
|
164
168
|
"processPayment": "Process Payment",
|
|
165
169
|
"quantity": "Quantity",
|
|
170
|
+
"quantityCannotExceed100": "Quantity cannot exceed 100",
|
|
171
|
+
"quantityMustBeAtLeastOne": "Quantity must be at least 1",
|
|
172
|
+
"quantityMustBeInteger": "Quantity must be a whole number",
|
|
173
|
+
"quantityMustBeNumber": "Quantity must be a valid number",
|
|
166
174
|
"quantityRequired": "Quantity is required",
|
|
167
175
|
"referenceNumber": "Reference number",
|
|
168
176
|
"remove": "Remove",
|
|
@@ -182,7 +190,6 @@
|
|
|
182
190
|
"selectPaymentMethodRequired": "Please select a payment method for all items",
|
|
183
191
|
"selectPaymentMode": "Select payment mode",
|
|
184
192
|
"selectServiceType": "Select service type",
|
|
185
|
-
"sellingAmount": "Enter selling price",
|
|
186
193
|
"sellingPrice": "Selling Price",
|
|
187
194
|
"serviceMetrics": "Service Metrics",
|
|
188
195
|
"serviceName": "Service Name",
|