@openmrs/esm-billing-app 1.0.2-pre.90 → 1.0.2-pre.905
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/.eslintrc +16 -2
- 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/1537.js +1 -0
- package/dist/1537.js.map +1 -0
- package/dist/1856.js +1 -0
- package/dist/1856.js.map +1 -0
- package/dist/2146.js +1 -1
- package/dist/2524.js +1 -0
- package/dist/2524.js.map +1 -0
- package/dist/2690.js +1 -1
- package/dist/3099.js +1 -1
- package/dist/3584.js +1 -1
- package/dist/3717.js +2 -0
- package/dist/3717.js.map +1 -0
- package/dist/4055.js +1 -1
- package/dist/4132.js +1 -1
- 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/4692.js +1 -0
- package/dist/4692.js.map +1 -0
- 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/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/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/7255.js +1 -1
- package/dist/7255.js.map +1 -1
- 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/8708.js +2 -0
- package/dist/{6557.js.LICENSE.txt → 8708.js.LICENSE.txt} +22 -0
- package/dist/8708.js.map +1 -0
- package/dist/890.js +1 -1
- package/dist/9214.js +1 -1
- 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 +282 -296
- package/dist/openmrs-esm-billing-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/e2e/README.md +19 -18
- package/e2e/core/test.ts +1 -1
- package/e2e/fixtures/api.ts +1 -1
- package/e2e/specs/sample-test.spec.ts +0 -1
- package/e2e/support/github/Dockerfile +1 -1
- package/package.json +18 -15
- package/src/bill-history/bill-history.component.tsx +20 -28
- 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 +21 -5
- package/src/bill-item-actions/edit-bill-item.modal.tsx +225 -0
- package/src/bill-item-actions/edit-bill-item.test.tsx +214 -40
- package/src/billable-services/bill-waiver/bill-selection.component.tsx +5 -5
- package/src/billable-services/bill-waiver/bill-waiver-form.component.tsx +28 -32
- package/src/billable-services/bill-waiver/patient-bills.component.tsx +7 -7
- package/src/billable-services/bill-waiver/utils.ts +13 -3
- package/src/billable-services/{create-edit/add-billable-service.scss → billable-service-form/billable-service-form.scss} +32 -64
- package/src/billable-services/billable-service-form/billable-service-form.test.tsx +893 -0
- package/src/billable-services/billable-service-form/billable-service-form.workspace.tsx +504 -0
- package/src/billable-services/billable-service.resource.ts +42 -26
- package/src/billable-services/billable-services-home.component.tsx +13 -42
- package/src/billable-services/billable-services-left-panel-link.component.tsx +48 -0
- package/src/billable-services/billable-services-left-panel-menu.component.tsx +46 -0
- package/src/billable-services/billable-services-menu-item/item.component.tsx +5 -4
- package/src/billable-services/billable-services.component.tsx +156 -152
- package/src/billable-services/billable-services.scss +29 -0
- package/src/billable-services/billable-services.test.tsx +6 -49
- package/src/billable-services/cash-point/add-cash-point.modal.tsx +170 -0
- package/src/billable-services/cash-point/cash-point-configuration.component.tsx +19 -193
- package/src/billable-services/cash-point/cash-point-configuration.scss +1 -5
- package/src/billable-services/dashboard/dashboard.component.tsx +0 -2
- package/src/billable-services/payment-modes/add-payment-mode.modal.tsx +121 -0
- package/src/billable-services/payment-modes/delete-payment-mode.modal.tsx +74 -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/billable-services-admin-card-link.component.test.tsx +2 -2
- package/src/billable-services-admin-card-link.component.tsx +1 -1
- package/src/billing-dashboard/billing-dashboard.scss +1 -1
- package/src/billing-form/billing-checkin-form.component.tsx +21 -17
- package/src/billing-form/billing-checkin-form.test.tsx +99 -26
- package/src/billing-form/billing-form.component.tsx +222 -292
- package/src/billing-form/billing-form.scss +143 -0
- package/src/billing-form/visit-attributes/visit-attributes-form.component.tsx +1 -1
- package/src/billing.resource.ts +69 -74
- package/src/bills-table/bills-table.component.tsx +3 -3
- package/src/bills-table/bills-table.test.tsx +98 -54
- 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 +71 -9
- package/src/invoice/invoice-table.component.tsx +36 -70
- package/src/invoice/invoice-table.scss +8 -5
- package/src/invoice/invoice-table.test.tsx +273 -62
- package/src/invoice/invoice.component.tsx +39 -32
- package/src/invoice/invoice.scss +11 -4
- package/src/invoice/invoice.test.tsx +324 -120
- package/src/invoice/payments/invoice-breakdown/invoice-breakdown.scss +9 -9
- package/src/invoice/payments/payment-form/payment-form.component.tsx +43 -34
- package/src/invoice/payments/payment-form/payment-form.scss +5 -6
- package/src/invoice/payments/payment-form/payment-form.test.tsx +216 -66
- 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 +55 -67
- package/src/invoice/payments/payments.scss +4 -3
- package/src/invoice/payments/payments.test.tsx +282 -0
- package/src/invoice/payments/utils.ts +15 -27
- 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 +20 -34
- package/src/left-panel-link.test.tsx +1 -4
- package/src/metrics-cards/metrics-cards.component.tsx +16 -6
- package/src/metrics-cards/metrics-cards.scss +4 -0
- 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} +18 -19
- package/src/routes.json +44 -20
- package/src/types/index.ts +81 -23
- package/translations/am.json +132 -75
- package/translations/ar.json +133 -76
- package/translations/ar_SY.json +133 -76
- package/translations/bn.json +135 -78
- package/translations/de.json +133 -76
- package/translations/en.json +133 -78
- package/translations/en_US.json +133 -76
- package/translations/es.json +132 -75
- package/translations/es_MX.json +133 -76
- package/translations/fr.json +138 -81
- package/translations/he.json +132 -75
- package/translations/hi.json +133 -76
- package/translations/hi_IN.json +133 -76
- package/translations/id.json +133 -76
- package/translations/it.json +159 -102
- package/translations/ka.json +133 -76
- package/translations/km.json +132 -75
- package/translations/ku.json +133 -76
- package/translations/ky.json +133 -76
- package/translations/lg.json +133 -76
- package/translations/ne.json +133 -76
- package/translations/pl.json +133 -76
- package/translations/pt.json +133 -76
- package/translations/pt_BR.json +133 -76
- package/translations/qu.json +133 -76
- package/translations/ro_RO.json +222 -165
- package/translations/ru_RU.json +133 -76
- package/translations/si.json +133 -76
- package/translations/sw.json +133 -76
- package/translations/sw_KE.json +133 -76
- package/translations/tr.json +133 -76
- package/translations/tr_TR.json +133 -76
- package/translations/uk.json +133 -76
- package/translations/uz.json +133 -76
- package/translations/uz@Latn.json +133 -76
- package/translations/uz_UZ.json +133 -76
- package/translations/vi.json +133 -76
- package/translations/zh.json +133 -76
- package/translations/zh_CN.json +164 -107
- 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/4689.js +0 -2
- package/dist/4689.js.map +0 -1
- package/dist/6557.js +0 -2
- package/dist/6557.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/bill-item-actions/edit-bill-item.component.tsx +0 -221
- package/src/billable-services/create-edit/add-billable-service.component.tsx +0 -401
- package/src/billable-services/create-edit/add-billable-service.test.tsx +0 -154
- package/src/billable-services/dashboard/service-metrics.component.tsx +0 -41
- 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/{4689.js.LICENSE.txt → 3717.js.LICENSE.txt} +0 -0
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import userEvent from '@testing-library/user-event';
|
|
3
|
-
import { render, screen } from '@testing-library/react';
|
|
4
|
-
import { type FetchResponse, navigate } from '@openmrs/esm-framework';
|
|
5
|
-
import {
|
|
6
|
-
useBillableServices,
|
|
7
|
-
usePaymentModes,
|
|
8
|
-
useServiceTypes,
|
|
9
|
-
createBillableSerice,
|
|
10
|
-
} from '../billable-service.resource';
|
|
11
|
-
import AddBillableService from './add-billable-service.component';
|
|
12
|
-
|
|
13
|
-
const mockUseBillableServices = useBillableServices as jest.MockedFunction<typeof useBillableServices>;
|
|
14
|
-
const mockUsePaymentModes = usePaymentModes as jest.MockedFunction<typeof usePaymentModes>;
|
|
15
|
-
const mockUseServiceTypes = useServiceTypes as jest.MockedFunction<typeof useServiceTypes>;
|
|
16
|
-
const mockCreateBillableSerice = createBillableSerice as jest.MockedFunction<typeof createBillableSerice>;
|
|
17
|
-
const mockNavigate = navigate as jest.MockedFunction<typeof navigate>;
|
|
18
|
-
|
|
19
|
-
jest.mock('../billable-service.resource', () => ({
|
|
20
|
-
useBillableServices: jest.fn(),
|
|
21
|
-
usePaymentModes: jest.fn(),
|
|
22
|
-
useServiceTypes: jest.fn(),
|
|
23
|
-
createBillableSerice: jest.fn(),
|
|
24
|
-
}));
|
|
25
|
-
|
|
26
|
-
const mockPaymentModes = [
|
|
27
|
-
{ uuid: '63eff7a4-6f82-43c4-a333-dbcc58fe9f74', name: 'Cash', description: 'Cash Payment', retired: false },
|
|
28
|
-
{
|
|
29
|
-
uuid: 'beac329b-f1dc-4a33-9e7c-d95821a137a6',
|
|
30
|
-
name: 'Insurance',
|
|
31
|
-
description: 'Insurance method of payment',
|
|
32
|
-
retired: false,
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
uuid: '28989582-e8c3-46b0-96d0-c249cb06d5c6',
|
|
36
|
-
name: 'MPESA',
|
|
37
|
-
description: 'Mobile money method of payment',
|
|
38
|
-
retired: false,
|
|
39
|
-
},
|
|
40
|
-
];
|
|
41
|
-
|
|
42
|
-
const mockServiceTypes = [
|
|
43
|
-
{ uuid: 'c9604249-db0a-4d03-b074-fc6bc2fa13e6', display: 'Lab service' },
|
|
44
|
-
{ uuid: 'b75e466f-a6f5-4d5e-849a-84424d3c85cd', display: 'Pharmacy service' },
|
|
45
|
-
{ uuid: 'ce914b2d-44f6-4b6c-933f-c57a3938e35b', display: 'Peer educator service' },
|
|
46
|
-
{ uuid: 'c23d3224-2218-4007-8f22-e1f3d5a8e58a', display: 'Nutrition service' },
|
|
47
|
-
{ uuid: '65487ff4-63b3-452a-8985-6a1f4a0cc08d', display: 'TB service' },
|
|
48
|
-
{ uuid: '9db142d5-5cc4-4c05-9f83-06ed294caa67', display: 'Family planning service' },
|
|
49
|
-
{ uuid: 'a487a743-62ce-4f93-a66b-c5154ee8987d', display: 'Adherence counselling service' },
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
xdescribe('AddBillableService', () => {
|
|
53
|
-
beforeEach(() => {
|
|
54
|
-
jest.resetAllMocks();
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('should render billable services form and generate correct payload', async () => {
|
|
58
|
-
const user = userEvent.setup();
|
|
59
|
-
const mockOnClose = jest.fn();
|
|
60
|
-
mockUseBillableServices.mockReturnValue({
|
|
61
|
-
billableServices: [],
|
|
62
|
-
isLoading: false,
|
|
63
|
-
error: null,
|
|
64
|
-
mutate: jest.fn(),
|
|
65
|
-
isValidating: false,
|
|
66
|
-
});
|
|
67
|
-
mockUsePaymentModes.mockReturnValue({ paymentModes: mockPaymentModes, error: null, isLoading: false });
|
|
68
|
-
mockUseServiceTypes.mockReturnValue({ serviceTypes: mockServiceTypes, error: false, isLoading: false });
|
|
69
|
-
|
|
70
|
-
render(<AddBillableService onClose={mockOnClose} />);
|
|
71
|
-
|
|
72
|
-
const formTitle = screen.getByRole('heading', { name: /Add Billable Services/i });
|
|
73
|
-
expect(formTitle).toBeInTheDocument();
|
|
74
|
-
|
|
75
|
-
const serviceNameTextInp = screen.getByRole('textbox', { name: /Service Name/i });
|
|
76
|
-
expect(serviceNameTextInp).toBeInTheDocument();
|
|
77
|
-
|
|
78
|
-
const serviceShortNameTextInp = screen.getByRole('textbox', { name: /Short Name/i });
|
|
79
|
-
expect(serviceShortNameTextInp).toBeInTheDocument();
|
|
80
|
-
|
|
81
|
-
await user.type(serviceNameTextInp, 'Test Service Name');
|
|
82
|
-
await user.type(serviceShortNameTextInp, 'Test Short Name');
|
|
83
|
-
|
|
84
|
-
expect(serviceNameTextInp).toHaveValue('Test Service Name');
|
|
85
|
-
expect(serviceShortNameTextInp).toHaveValue('Test Short Name');
|
|
86
|
-
|
|
87
|
-
const serviceTypeComboBox = screen.getByRole('combobox', { name: /Service Type/i });
|
|
88
|
-
expect(serviceTypeComboBox).toBeInTheDocument();
|
|
89
|
-
await user.click(serviceTypeComboBox);
|
|
90
|
-
const serviceTypeOptions = screen.getByRole('option', { name: /Lab service/i });
|
|
91
|
-
expect(serviceTypeOptions).toBeInTheDocument();
|
|
92
|
-
await user.click(serviceTypeOptions);
|
|
93
|
-
|
|
94
|
-
const addPaymentMethodBtn = screen.getByRole('button', { name: /Add payment option/i });
|
|
95
|
-
expect(addPaymentMethodBtn).toBeInTheDocument();
|
|
96
|
-
|
|
97
|
-
await user.click(addPaymentMethodBtn);
|
|
98
|
-
|
|
99
|
-
const paymentMethodComboBox = screen.getByRole('combobox', { name: /Payment Mode/i });
|
|
100
|
-
expect(paymentMethodComboBox).toBeInTheDocument();
|
|
101
|
-
await user.click(paymentMethodComboBox);
|
|
102
|
-
const paymentMethodOptions = screen.getByRole('option', { name: /Cash/i });
|
|
103
|
-
expect(paymentMethodOptions).toBeInTheDocument();
|
|
104
|
-
await user.click(paymentMethodOptions);
|
|
105
|
-
|
|
106
|
-
const priceTextInp = screen.getByRole('textbox', { name: /Price/i });
|
|
107
|
-
expect(priceTextInp).toBeInTheDocument();
|
|
108
|
-
await user.type(priceTextInp, '1000');
|
|
109
|
-
|
|
110
|
-
mockCreateBillableSerice.mockReturnValue(Promise.resolve({} as FetchResponse<any>));
|
|
111
|
-
const saveBtn = screen.getByRole('button', { name: /Save/i });
|
|
112
|
-
expect(saveBtn).toBeInTheDocument();
|
|
113
|
-
await user.click(saveBtn);
|
|
114
|
-
|
|
115
|
-
expect(mockCreateBillableSerice).toHaveBeenCalledTimes(1);
|
|
116
|
-
expect(mockCreateBillableSerice).toHaveBeenCalledWith({
|
|
117
|
-
name: 'Test Service Name',
|
|
118
|
-
shortName: 'Test Short Name',
|
|
119
|
-
serviceType: undefined,
|
|
120
|
-
servicePrices: [
|
|
121
|
-
{
|
|
122
|
-
paymentMode: '63eff7a4-6f82-43c4-a333-dbcc58fe9f74',
|
|
123
|
-
price: '01000',
|
|
124
|
-
name: 'Cash',
|
|
125
|
-
},
|
|
126
|
-
],
|
|
127
|
-
serviceStatus: 'ENABLED',
|
|
128
|
-
});
|
|
129
|
-
expect(mockNavigate).toHaveBeenCalledTimes(1);
|
|
130
|
-
expect(mockNavigate).toHaveBeenCalledWith({ to: '/openmrs/spa/billable-services' });
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test("should navigate back to billable services dashboard when 'Cancel' button is clicked", async () => {
|
|
134
|
-
const user = userEvent.setup();
|
|
135
|
-
const mockOnClose = jest.fn();
|
|
136
|
-
mockUseBillableServices.mockReturnValue({
|
|
137
|
-
billableServices: [],
|
|
138
|
-
isLoading: false,
|
|
139
|
-
error: null,
|
|
140
|
-
mutate: jest.fn(),
|
|
141
|
-
isValidating: false,
|
|
142
|
-
});
|
|
143
|
-
mockUsePaymentModes.mockReturnValue({ paymentModes: mockPaymentModes, error: null, isLoading: false });
|
|
144
|
-
mockUseServiceTypes.mockReturnValue({ serviceTypes: mockServiceTypes, error: false, isLoading: false });
|
|
145
|
-
|
|
146
|
-
render(<AddBillableService onClose={mockOnClose} />);
|
|
147
|
-
|
|
148
|
-
const cancelBtn = screen.getByRole('button', { name: /Cancel/i });
|
|
149
|
-
expect(cancelBtn).toBeInTheDocument();
|
|
150
|
-
await user.click(cancelBtn);
|
|
151
|
-
|
|
152
|
-
expect(mockOnClose).toHaveBeenCalledTimes(1);
|
|
153
|
-
});
|
|
154
|
-
});
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import React, { useMemo } from 'react';
|
|
2
|
-
import { InlineLoading } from '@carbon/react';
|
|
3
|
-
import { useTranslation } from 'react-i18next';
|
|
4
|
-
import { ErrorState } from '@openmrs/esm-patient-common-lib';
|
|
5
|
-
import { useBillableServices } from '../billable-service.resource';
|
|
6
|
-
import Card from '../../metrics-cards/card.component';
|
|
7
|
-
import styles from '../../metrics-cards/metrics-cards.scss';
|
|
8
|
-
import { ExtensionSlot } from '@openmrs/esm-framework';
|
|
9
|
-
|
|
10
|
-
export default function ServiceMetrics() {
|
|
11
|
-
const { t } = useTranslation();
|
|
12
|
-
const { isLoading, error } = useBillableServices();
|
|
13
|
-
|
|
14
|
-
const cards = useMemo(
|
|
15
|
-
() => [
|
|
16
|
-
{ title: 'Cash Revenue', count: '--' },
|
|
17
|
-
{ title: 'Insurance Revenue', count: '--' },
|
|
18
|
-
{ title: 'Pending Claims', count: '--' },
|
|
19
|
-
],
|
|
20
|
-
[],
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
if (isLoading) {
|
|
24
|
-
return (
|
|
25
|
-
<section className={styles.container}>
|
|
26
|
-
<InlineLoading status="active" iconDescription="Loading" description="Loading service metrics..." />
|
|
27
|
-
</section>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (error) {
|
|
32
|
-
return <ErrorState headerTitle={t('serviceMetrics', 'Service Metrics')} error={error} />;
|
|
33
|
-
}
|
|
34
|
-
return (
|
|
35
|
-
<section className={styles.container}>
|
|
36
|
-
{cards.map((card) => (
|
|
37
|
-
<Card key={card.title} title={card.title} count={card.count} />
|
|
38
|
-
))}
|
|
39
|
-
</section>
|
|
40
|
-
);
|
|
41
|
-
}
|
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
Button,
|
|
4
|
-
DataTable,
|
|
5
|
-
TableContainer,
|
|
6
|
-
Table,
|
|
7
|
-
TableHead,
|
|
8
|
-
TableRow,
|
|
9
|
-
TableHeader,
|
|
10
|
-
TableBody,
|
|
11
|
-
TableCell,
|
|
12
|
-
Modal,
|
|
13
|
-
TextInput,
|
|
14
|
-
OverflowMenu,
|
|
15
|
-
OverflowMenuItem,
|
|
16
|
-
} from '@carbon/react';
|
|
17
|
-
import { Add } from '@carbon/react/icons';
|
|
18
|
-
import { useTranslation } from 'react-i18next';
|
|
19
|
-
import { useForm, Controller } from 'react-hook-form';
|
|
20
|
-
import { z } from 'zod';
|
|
21
|
-
import { zodResolver } from '@hookform/resolvers/zod';
|
|
22
|
-
import { showSnackbar, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
23
|
-
import { CardHeader } from '@openmrs/esm-patient-common-lib';
|
|
24
|
-
import styles from './payment-modes-config.scss';
|
|
25
|
-
|
|
26
|
-
// Validation schema
|
|
27
|
-
const paymentModeSchema = z.object({
|
|
28
|
-
name: z.string().min(1, 'Payment Mode Name is required'),
|
|
29
|
-
description: z.string().optional(),
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
type PaymentModeFormValues = z.infer<typeof paymentModeSchema>;
|
|
33
|
-
|
|
34
|
-
const PaymentModesConfig: React.FC = () => {
|
|
35
|
-
const { t } = useTranslation();
|
|
36
|
-
const [paymentModes, setPaymentModes] = useState([]);
|
|
37
|
-
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
38
|
-
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
|
39
|
-
const [selectedPaymentMode, setSelectedPaymentMode] = useState(null);
|
|
40
|
-
|
|
41
|
-
const {
|
|
42
|
-
control,
|
|
43
|
-
handleSubmit,
|
|
44
|
-
reset,
|
|
45
|
-
formState: { errors, isSubmitting },
|
|
46
|
-
} = useForm<PaymentModeFormValues>({
|
|
47
|
-
resolver: zodResolver(paymentModeSchema),
|
|
48
|
-
defaultValues: {
|
|
49
|
-
name: '',
|
|
50
|
-
description: '',
|
|
51
|
-
},
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
const fetchPaymentModes = useCallback(async () => {
|
|
55
|
-
try {
|
|
56
|
-
const response = await openmrsFetch(`${restBaseUrl}/billing/paymentMode?v=full`);
|
|
57
|
-
setPaymentModes(response.data.results || []);
|
|
58
|
-
} catch (err) {
|
|
59
|
-
showSnackbar({
|
|
60
|
-
title: t('error', 'Error'),
|
|
61
|
-
subtitle: t('errorFetchingPaymentModes', 'An error occurred while fetching payment modes.'),
|
|
62
|
-
kind: 'error',
|
|
63
|
-
isLowContrast: false,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
}, [t]);
|
|
67
|
-
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
fetchPaymentModes();
|
|
70
|
-
}, [fetchPaymentModes]);
|
|
71
|
-
|
|
72
|
-
const onSubmit = async (data: PaymentModeFormValues) => {
|
|
73
|
-
// Check for duplicate payment mode name
|
|
74
|
-
const isDuplicate = paymentModes.some((mode) => mode.name.toLowerCase() === data.name.toLowerCase());
|
|
75
|
-
|
|
76
|
-
if (isDuplicate) {
|
|
77
|
-
showSnackbar({
|
|
78
|
-
title: t('error', 'Error'),
|
|
79
|
-
subtitle: t(
|
|
80
|
-
'duplicatePaymentModeError',
|
|
81
|
-
'A payment mode with the same name already exists. Please create another payment mode',
|
|
82
|
-
),
|
|
83
|
-
kind: 'error',
|
|
84
|
-
isLowContrast: false,
|
|
85
|
-
});
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
const response = await openmrsFetch(`${restBaseUrl}/billing/paymentMode`, {
|
|
91
|
-
method: 'POST',
|
|
92
|
-
headers: {
|
|
93
|
-
'Content-Type': 'application/json',
|
|
94
|
-
},
|
|
95
|
-
body: JSON.stringify({
|
|
96
|
-
name: data.name,
|
|
97
|
-
description: data.description,
|
|
98
|
-
}),
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
if (response.ok) {
|
|
102
|
-
showSnackbar({
|
|
103
|
-
title: t('success', 'Success'),
|
|
104
|
-
subtitle: t('paymentModeSaved', 'Payment mode was successfully saved.'),
|
|
105
|
-
kind: 'success',
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
setIsModalOpen(false);
|
|
109
|
-
reset({ name: '', description: '' });
|
|
110
|
-
fetchPaymentModes();
|
|
111
|
-
} else {
|
|
112
|
-
const errorData = response.data || {};
|
|
113
|
-
showSnackbar({
|
|
114
|
-
title: t('error', 'Error'),
|
|
115
|
-
subtitle:
|
|
116
|
-
errorData.message || t('errorSavingPaymentMode', 'An error occurred while saving the payment mode.'),
|
|
117
|
-
kind: 'error',
|
|
118
|
-
isLowContrast: false,
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
} catch (err) {
|
|
122
|
-
showSnackbar({
|
|
123
|
-
title: t('error', 'Error'),
|
|
124
|
-
subtitle: t('errorSavingPaymentMode', 'An error occurred while saving the payment mode.'),
|
|
125
|
-
kind: 'error',
|
|
126
|
-
isLowContrast: false,
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
const handleDelete = async () => {
|
|
132
|
-
if (!selectedPaymentMode) return;
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
await openmrsFetch(`${restBaseUrl}/billing/paymentMode/${selectedPaymentMode.uuid}`, {
|
|
136
|
-
method: 'DELETE',
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
showSnackbar({
|
|
140
|
-
title: t('success', 'Success'),
|
|
141
|
-
subtitle: t('paymentModeDeleted', 'Payment mode was successfully deleted.'),
|
|
142
|
-
kind: 'success',
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
setIsDeleteModalOpen(false);
|
|
146
|
-
setSelectedPaymentMode(null);
|
|
147
|
-
fetchPaymentModes();
|
|
148
|
-
} catch (err) {
|
|
149
|
-
showSnackbar({
|
|
150
|
-
title: t('error', 'Error'),
|
|
151
|
-
subtitle: t('errorDeletingPaymentMode', 'An error occurred while deleting the payment mode.'),
|
|
152
|
-
kind: 'error',
|
|
153
|
-
isLowContrast: false,
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
const rowData = paymentModes.map((mode) => ({
|
|
159
|
-
id: mode.uuid,
|
|
160
|
-
name: mode.name,
|
|
161
|
-
description: mode.description || '--',
|
|
162
|
-
}));
|
|
163
|
-
|
|
164
|
-
const headerData = [
|
|
165
|
-
{ key: 'name', header: t('name', 'Name') },
|
|
166
|
-
{ key: 'description', header: t('description', 'Description') },
|
|
167
|
-
{ key: 'actions', header: t('actions', 'Actions') },
|
|
168
|
-
];
|
|
169
|
-
|
|
170
|
-
return (
|
|
171
|
-
<div className={styles.container}>
|
|
172
|
-
<div className={styles.card}>
|
|
173
|
-
<CardHeader title={t('paymentModeHistory', 'Payment Mode History')}>
|
|
174
|
-
<Button renderIcon={Add} onClick={() => setIsModalOpen(true)} kind="ghost">
|
|
175
|
-
{t('addPaymentMode', 'Add New Payment Mode')}
|
|
176
|
-
</Button>
|
|
177
|
-
</CardHeader>
|
|
178
|
-
<div className={styles.historyContainer}>
|
|
179
|
-
<DataTable rows={rowData} headers={headerData} isSortable size="lg">
|
|
180
|
-
{({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
|
|
181
|
-
<TableContainer>
|
|
182
|
-
<Table className={styles.table} {...getTableProps()}>
|
|
183
|
-
<TableHead>
|
|
184
|
-
<TableRow>
|
|
185
|
-
{headers.map((header) => (
|
|
186
|
-
<TableHeader key={header.key} {...getHeaderProps({ header })}>
|
|
187
|
-
{header.header}
|
|
188
|
-
</TableHeader>
|
|
189
|
-
))}
|
|
190
|
-
</TableRow>
|
|
191
|
-
</TableHead>
|
|
192
|
-
<TableBody>
|
|
193
|
-
{rows.map((row) => (
|
|
194
|
-
<TableRow key={row.id} {...getRowProps({ row })}>
|
|
195
|
-
{row.cells.map((cell) =>
|
|
196
|
-
cell.info.header !== 'actions' ? (
|
|
197
|
-
<TableCell key={cell.id}>{cell.value}</TableCell>
|
|
198
|
-
) : (
|
|
199
|
-
<TableCell key={cell.id}>
|
|
200
|
-
<OverflowMenu>
|
|
201
|
-
<OverflowMenuItem
|
|
202
|
-
itemText={t('delete', 'Delete')}
|
|
203
|
-
onClick={() => {
|
|
204
|
-
const selected = paymentModes.find((p) => p.uuid === row.id);
|
|
205
|
-
setSelectedPaymentMode(selected);
|
|
206
|
-
setIsDeleteModalOpen(true);
|
|
207
|
-
}}
|
|
208
|
-
/>
|
|
209
|
-
</OverflowMenu>
|
|
210
|
-
</TableCell>
|
|
211
|
-
),
|
|
212
|
-
)}
|
|
213
|
-
</TableRow>
|
|
214
|
-
))}
|
|
215
|
-
</TableBody>
|
|
216
|
-
</Table>
|
|
217
|
-
</TableContainer>
|
|
218
|
-
)}
|
|
219
|
-
</DataTable>
|
|
220
|
-
</div>
|
|
221
|
-
</div>
|
|
222
|
-
|
|
223
|
-
{/* Modal for Adding New Payment Mode */}
|
|
224
|
-
<Modal
|
|
225
|
-
open={isModalOpen}
|
|
226
|
-
modalHeading={t('addPaymentMode', 'Add Payment Mode')}
|
|
227
|
-
onRequestClose={() => setIsModalOpen(false)}
|
|
228
|
-
onRequestSubmit={handleSubmit(onSubmit)}
|
|
229
|
-
primaryButtonText={t('save', 'Save')}
|
|
230
|
-
secondaryButtonText={t('cancel', 'Cancel')}
|
|
231
|
-
isPrimaryButtonDisabled={isSubmitting}>
|
|
232
|
-
<form>
|
|
233
|
-
<Controller
|
|
234
|
-
name="name"
|
|
235
|
-
control={control}
|
|
236
|
-
render={({ field }) => (
|
|
237
|
-
<TextInput
|
|
238
|
-
id="payment-mode-name"
|
|
239
|
-
labelText={t('paymentModeName', 'Payment Mode Name')}
|
|
240
|
-
placeholder={t('paymentModeNamePlaceholder', 'e.g., Cash, Credit Card')}
|
|
241
|
-
invalid={!!errors.name}
|
|
242
|
-
invalidText={errors.name?.message}
|
|
243
|
-
{...field}
|
|
244
|
-
/>
|
|
245
|
-
)}
|
|
246
|
-
/>
|
|
247
|
-
<Controller
|
|
248
|
-
name="description"
|
|
249
|
-
control={control}
|
|
250
|
-
render={({ field }) => (
|
|
251
|
-
<TextInput
|
|
252
|
-
id="payment-mode-description"
|
|
253
|
-
labelText={t('description', 'Description')}
|
|
254
|
-
placeholder={t('descriptionPlaceholder', 'e.g., Used for all cash transactions')}
|
|
255
|
-
invalid={!!errors.description}
|
|
256
|
-
invalidText={errors.description?.message}
|
|
257
|
-
{...field}
|
|
258
|
-
/>
|
|
259
|
-
)}
|
|
260
|
-
/>
|
|
261
|
-
</form>
|
|
262
|
-
</Modal>
|
|
263
|
-
|
|
264
|
-
{/* Modal for Deleting Payment Mode */}
|
|
265
|
-
<Modal
|
|
266
|
-
open={isDeleteModalOpen}
|
|
267
|
-
modalHeading={t('deletePaymentMode', 'Delete Payment Mode')}
|
|
268
|
-
onRequestClose={() => setIsDeleteModalOpen(false)}
|
|
269
|
-
onRequestSubmit={handleDelete}
|
|
270
|
-
primaryButtonText={t('delete', 'Delete')}
|
|
271
|
-
secondaryButtonText={t('cancel', 'Cancel')}
|
|
272
|
-
primaryButtonDanger
|
|
273
|
-
danger>
|
|
274
|
-
<p>{t('confirmDeleteMessage', 'Are you sure you want to delete this payment mode? Proceed cautiously.')}</p>
|
|
275
|
-
</Modal>
|
|
276
|
-
</div>
|
|
277
|
-
);
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
export default PaymentModesConfig;
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import userEvent from '@testing-library/user-event';
|
|
3
|
-
import { render, screen } from '@testing-library/react';
|
|
4
|
-
import { useVisit, useConfig, navigate } from '@openmrs/esm-framework';
|
|
5
|
-
import { useBillableServices } from '../../billable-services/billable-service.resource';
|
|
6
|
-
import { type MappedBill, type LineItem } from '../../types';
|
|
7
|
-
import Payments from './payments.component';
|
|
8
|
-
|
|
9
|
-
// Add this mock for currency formatting
|
|
10
|
-
const mockFormatToParts = jest.fn().mockReturnValue([{ type: 'integer', value: '1000' }]);
|
|
11
|
-
const mockFormat = jest.fn().mockReturnValue('$1000.00');
|
|
12
|
-
global.Intl.NumberFormat = jest.fn().mockImplementation(() => ({
|
|
13
|
-
formatToParts: mockFormatToParts,
|
|
14
|
-
format: mockFormat,
|
|
15
|
-
})) as any;
|
|
16
|
-
global.Intl.NumberFormat.supportedLocalesOf = jest.fn().mockReturnValue(['en-US']);
|
|
17
|
-
|
|
18
|
-
jest.mock('../../billing.resource', () => ({
|
|
19
|
-
processBillPayment: jest.fn(),
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
|
-
jest.mock('../../billable-services/billable-service.resource', () => ({
|
|
23
|
-
useBillableServices: jest.fn(),
|
|
24
|
-
}));
|
|
25
|
-
|
|
26
|
-
describe('Payments', () => {
|
|
27
|
-
const mockBill: MappedBill = {
|
|
28
|
-
uuid: 'bill-uuid',
|
|
29
|
-
id: 1,
|
|
30
|
-
patientUuid: 'patient-uuid',
|
|
31
|
-
patientName: 'John Doe',
|
|
32
|
-
cashPointUuid: 'cash-point-uuid',
|
|
33
|
-
cashPointName: 'Main Cash Point',
|
|
34
|
-
cashPointLocation: 'Main Hospital',
|
|
35
|
-
cashier: {
|
|
36
|
-
uuid: 'provider-1',
|
|
37
|
-
display: 'Jane Doe',
|
|
38
|
-
links: [
|
|
39
|
-
{
|
|
40
|
-
rel: 'self',
|
|
41
|
-
uri: 'http://example.com/provider/1',
|
|
42
|
-
resourceAlias: 'Jane Doe',
|
|
43
|
-
},
|
|
44
|
-
],
|
|
45
|
-
},
|
|
46
|
-
payments: [
|
|
47
|
-
{
|
|
48
|
-
uuid: 'payment-1',
|
|
49
|
-
dateCreated: new Date('2023-09-01T12:00:00Z').getTime(),
|
|
50
|
-
amountTendered: 100,
|
|
51
|
-
amount: 80,
|
|
52
|
-
instanceType: {
|
|
53
|
-
uuid: 'instance-1',
|
|
54
|
-
name: 'Credit Card',
|
|
55
|
-
description: 'Credit Card payment',
|
|
56
|
-
retired: false,
|
|
57
|
-
},
|
|
58
|
-
attributes: [],
|
|
59
|
-
voided: false,
|
|
60
|
-
resourceVersion: '1.0',
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
uuid: 'payment-2',
|
|
64
|
-
dateCreated: new Date('2023-09-05T14:00:00Z').getTime(),
|
|
65
|
-
amountTendered: 200,
|
|
66
|
-
amount: 180,
|
|
67
|
-
instanceType: {
|
|
68
|
-
uuid: 'instance-2',
|
|
69
|
-
name: 'Cash',
|
|
70
|
-
description: 'Cash payment',
|
|
71
|
-
retired: false,
|
|
72
|
-
},
|
|
73
|
-
attributes: [],
|
|
74
|
-
voided: false,
|
|
75
|
-
resourceVersion: '1.0',
|
|
76
|
-
},
|
|
77
|
-
],
|
|
78
|
-
receiptNumber: '12345',
|
|
79
|
-
status: 'PAID',
|
|
80
|
-
identifier: 'invoice-123',
|
|
81
|
-
dateCreated: '2023-09-01T12:00:00Z',
|
|
82
|
-
lineItems: [],
|
|
83
|
-
billingService: 'Billing Service',
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const mockMutate = jest.fn();
|
|
87
|
-
const mockSelectedLineItems: LineItem[] = [];
|
|
88
|
-
|
|
89
|
-
beforeEach(() => {
|
|
90
|
-
jest.clearAllMocks();
|
|
91
|
-
(useVisit as jest.Mock).mockReturnValue({ currentVisit: null });
|
|
92
|
-
(useConfig as jest.Mock).mockReturnValue({ defaultCurrency: 'USD' });
|
|
93
|
-
(useBillableServices as jest.Mock).mockReturnValue({ billableServices: [], isLoading: false });
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('renders payment form and history', () => {
|
|
97
|
-
render(<Payments bill={mockBill} mutate={mockMutate} selectedLineItems={mockSelectedLineItems} />);
|
|
98
|
-
expect(screen.getByText('Payments')).toBeInTheDocument();
|
|
99
|
-
expect(screen.getByText('Total Amount:')).toBeInTheDocument();
|
|
100
|
-
expect(screen.getByText('Total Tendered:')).toBeInTheDocument();
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('calculates and displays correct amounts', () => {
|
|
104
|
-
render(<Payments bill={mockBill} mutate={mockMutate} selectedLineItems={mockSelectedLineItems} />);
|
|
105
|
-
const amountElements = screen.getAllByText('$1000.00');
|
|
106
|
-
expect(amountElements[amountElements.length - 3]).toBeInTheDocument();
|
|
107
|
-
expect(amountElements[amountElements.length - 2]).toBeInTheDocument();
|
|
108
|
-
expect(amountElements[amountElements.length - 1]).toBeInTheDocument();
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('disables Process Payment button when form is invalid', () => {
|
|
112
|
-
render(<Payments bill={mockBill} mutate={mockMutate} selectedLineItems={mockSelectedLineItems} />);
|
|
113
|
-
expect(screen.getByText('Process Payment')).toBeDisabled();
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('navigates to billing dashboard when Discard is clicked', async () => {
|
|
117
|
-
render(<Payments bill={mockBill} mutate={mockMutate} selectedLineItems={mockSelectedLineItems} />);
|
|
118
|
-
await userEvent.click(screen.getByText('Discard'));
|
|
119
|
-
expect(navigate).toHaveBeenCalled();
|
|
120
|
-
});
|
|
121
|
-
});
|
|
File without changes
|