@openmrs/esm-billing-app 1.0.2-pre.76 → 1.0.2-pre.761
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -9
- package/__mocks__/bills.mock.ts +12 -0
- package/__mocks__/react-i18next.js +6 -5
- package/dist/1119.js +1 -1
- package/dist/1146.js +1 -2
- package/dist/1146.js.map +1 -1
- package/dist/1197.js +1 -1
- package/dist/1856.js +1 -0
- package/dist/1856.js.map +1 -0
- package/dist/2146.js +1 -1
- package/dist/2177.js +2 -0
- package/dist/2177.js.LICENSE.txt +9 -0
- package/dist/2177.js.map +1 -0
- package/dist/2524.js +1 -0
- package/dist/2524.js.map +1 -0
- package/dist/2690.js +1 -1
- package/dist/3041.js +1 -0
- package/dist/3041.js.map +1 -0
- package/dist/3099.js +1 -1
- package/dist/3584.js +1 -1
- package/dist/4055.js +1 -1
- package/dist/4132.js +1 -1
- package/dist/4225.js +1 -0
- package/dist/4225.js.map +1 -0
- package/dist/4300.js +1 -1
- package/dist/4335.js +1 -1
- package/dist/4618.js +1 -1
- package/dist/4652.js +1 -1
- package/dist/4724.js +1 -0
- package/dist/4724.js.map +1 -0
- package/dist/4739.js +1 -1
- package/dist/4739.js.map +1 -1
- package/dist/4944.js +1 -1
- package/dist/5173.js +1 -1
- package/dist/5241.js +1 -1
- package/dist/5422.js +1 -0
- package/dist/5422.js.map +1 -0
- package/dist/5442.js +1 -1
- package/dist/5661.js +1 -1
- package/dist/6022.js +1 -1
- package/dist/6468.js +1 -1
- package/dist/6540.js +1 -1
- package/dist/6540.js.map +1 -1
- package/dist/6606.js +1 -0
- package/dist/6606.js.map +1 -0
- package/dist/6679.js +1 -1
- package/dist/6840.js +1 -1
- package/dist/6859.js +1 -1
- package/dist/7097.js +1 -1
- package/dist/7159.js +1 -1
- package/dist/723.js +1 -1
- package/dist/7452.js +2 -0
- package/dist/7452.js.map +1 -0
- package/dist/7617.js +1 -1
- package/dist/795.js +1 -1
- package/dist/8163.js +1 -1
- package/dist/8349.js +1 -1
- package/dist/8618.js +1 -1
- package/dist/890.js +1 -1
- package/dist/8930.js +2 -0
- package/dist/{6525.js.LICENSE.txt → 8930.js.LICENSE.txt} +16 -4
- package/dist/8930.js.map +1 -0
- package/dist/9214.js +1 -1
- package/dist/942.js +1 -0
- package/dist/942.js.map +1 -0
- package/dist/9538.js +1 -1
- package/dist/9569.js +1 -1
- package/dist/961.js +1 -1
- package/dist/961.js.map +1 -1
- package/dist/986.js +1 -1
- package/dist/9879.js +1 -1
- package/dist/9895.js +1 -1
- package/dist/9900.js +1 -1
- package/dist/9913.js +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-billing-app.js +1 -1
- package/dist/openmrs-esm-billing-app.js.buildmanifest.json +368 -262
- package/dist/openmrs-esm-billing-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/e2e/README.md +19 -18
- package/e2e/specs/sample-test.spec.ts +0 -1
- package/package.json +10 -10
- package/src/bill-history/bill-history.component.tsx +17 -25
- package/src/bill-history/bill-history.scss +4 -94
- package/src/bill-history/bill-history.test.tsx +37 -78
- package/src/bill-item-actions/bill-item-actions.scss +0 -4
- package/src/bill-item-actions/{edit-bill-item.component.tsx → edit-bill-item.modal.tsx} +57 -56
- package/src/bill-item-actions/edit-bill-item.test.tsx +22 -25
- package/src/billable-services/bill-waiver/bill-selection.component.tsx +2 -2
- package/src/billable-services/bill-waiver/patient-bills.component.tsx +3 -3
- package/src/billable-services/billable-service.resource.ts +17 -9
- package/src/billable-services/billable-services-home.component.tsx +1 -1
- package/src/billable-services/billable-services.component.tsx +142 -145
- package/src/billable-services/billable-services.scss +3 -0
- package/src/billable-services/billable-services.test.tsx +2 -45
- package/src/billable-services/cash-point/add-cash-point.modal.tsx +168 -0
- package/src/billable-services/cash-point/cash-point-configuration.component.tsx +18 -192
- package/src/billable-services/cash-point/cash-point-configuration.scss +1 -5
- package/src/billable-services/create-edit/add-billable-service.component.tsx +345 -298
- package/src/billable-services/create-edit/add-billable-service.scss +5 -6
- package/src/billable-services/create-edit/add-billable-service.test.tsx +37 -36
- package/src/billable-services/create-edit/edit-billable-service.modal.tsx +51 -0
- package/src/billable-services/payment-modes/add-payment-mode.modal.tsx +121 -0
- package/src/billable-services/payment-modes/delete-payment-mode.modal.tsx +72 -0
- package/src/billable-services/payment-modes/payment-modes-config.component.tsx +125 -0
- package/src/billable-services/{payyment-modes → payment-modes}/payment-modes-config.scss +5 -4
- package/src/billing-form/billing-checkin-form.component.tsx +2 -3
- package/src/billing-form/billing-checkin-form.test.tsx +97 -24
- package/src/billing-form/billing-form.component.tsx +214 -269
- package/src/billing-form/billing-form.scss +143 -0
- package/src/billing.resource.ts +16 -19
- package/src/bills-table/bills-table.test.tsx +97 -53
- package/src/config-schema.ts +52 -18
- package/src/dashboard.meta.ts +4 -2
- package/src/helpers/functions.ts +5 -4
- package/src/index.ts +17 -6
- package/src/invoice/invoice-table.component.tsx +24 -54
- package/src/invoice/invoice-table.scss +1 -5
- package/src/invoice/invoice-table.test.tsx +21 -47
- package/src/invoice/invoice.component.tsx +36 -29
- package/src/invoice/invoice.scss +7 -4
- package/src/invoice/invoice.test.tsx +22 -48
- package/src/invoice/payments/payment-form/payment-form.component.tsx +29 -29
- package/src/invoice/payments/payment-form/payment-form.scss +5 -6
- package/src/invoice/payments/payment-form/payment-form.test.tsx +215 -67
- package/src/invoice/payments/payment-history/payment-history.component.tsx +6 -4
- package/src/invoice/payments/payment-history/payment-history.test.tsx +9 -14
- package/src/invoice/payments/payments.component.tsx +53 -65
- package/src/invoice/payments/payments.test.tsx +282 -0
- package/src/invoice/payments/utils.ts +5 -23
- package/src/invoice/printable-invoice/print-receipt.component.tsx +3 -2
- package/src/invoice/printable-invoice/print-receipt.test.tsx +14 -25
- package/src/invoice/printable-invoice/printable-footer.component.tsx +2 -2
- package/src/invoice/printable-invoice/printable-footer.test.tsx +4 -13
- package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +12 -11
- package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +16 -14
- package/src/invoice/printable-invoice/printable-invoice.component.tsx +19 -33
- package/src/metrics-cards/metrics-cards.test.tsx +18 -5
- package/src/modal/require-payment-modal.test.tsx +27 -22
- package/src/modal/{require-payment-modal.component.tsx → require-payment.modal.tsx} +17 -18
- package/src/routes.json +22 -2
- package/src/types/index.ts +26 -17
- package/translations/am.json +60 -18
- package/translations/ar.json +60 -18
- package/translations/ar_SY.json +60 -18
- package/translations/bn.json +65 -23
- package/translations/de.json +60 -18
- package/translations/en.json +60 -18
- package/translations/en_US.json +60 -18
- package/translations/es.json +60 -18
- package/translations/es_MX.json +60 -18
- package/translations/fr.json +73 -31
- package/translations/he.json +60 -18
- package/translations/hi.json +60 -18
- package/translations/hi_IN.json +60 -18
- package/translations/id.json +61 -19
- package/translations/it.json +96 -54
- package/translations/ka.json +61 -19
- package/translations/km.json +60 -18
- package/translations/ku.json +60 -18
- package/translations/ky.json +60 -18
- package/translations/lg.json +60 -18
- package/translations/ne.json +60 -18
- package/translations/pl.json +60 -18
- package/translations/pt.json +60 -18
- package/translations/pt_BR.json +60 -18
- package/translations/qu.json +60 -18
- package/translations/ro_RO.json +206 -164
- package/translations/ru_RU.json +60 -18
- package/translations/si.json +60 -18
- package/translations/sw.json +60 -18
- package/translations/sw_KE.json +60 -18
- package/translations/tr.json +60 -18
- package/translations/tr_TR.json +60 -18
- package/translations/uk.json +60 -18
- package/translations/uz.json +60 -18
- package/translations/uz@Latn.json +60 -18
- package/translations/uz_UZ.json +60 -18
- package/translations/vi.json +60 -18
- package/translations/zh.json +60 -18
- package/translations/zh_CN.json +117 -75
- package/dist/1146.js.LICENSE.txt +0 -21
- package/dist/2352.js +0 -1
- package/dist/2352.js.map +0 -1
- package/dist/246.js +0 -1
- package/dist/246.js.map +0 -1
- package/dist/6525.js +0 -2
- package/dist/6525.js.map +0 -1
- package/dist/8556.js +0 -2
- package/dist/8556.js.map +0 -1
- package/dist/8638.js +0 -1
- package/dist/8638.js.map +0 -1
- package/dist/9968.js +0 -1
- package/dist/9968.js.map +0 -1
- package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +0 -280
- package/src/invoice/payments/payments.component.test.tsx +0 -121
- /package/dist/{8556.js.LICENSE.txt → 7452.js.LICENSE.txt} +0 -0
|
@@ -6,12 +6,8 @@
|
|
|
6
6
|
.form {
|
|
7
7
|
display: flex;
|
|
8
8
|
flex-direction: column;
|
|
9
|
-
justify-content: space-between;
|
|
10
9
|
height: 100%;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
.section {
|
|
14
|
-
margin: layout.$spacing-03;
|
|
10
|
+
margin: layout.$spacing-05;
|
|
15
11
|
}
|
|
16
12
|
|
|
17
13
|
.sectionTitle {
|
|
@@ -99,7 +95,7 @@
|
|
|
99
95
|
|
|
100
96
|
.conceptLabel {
|
|
101
97
|
@include type.type-style('label-02');
|
|
102
|
-
margin: layout.$spacing-05;
|
|
98
|
+
margin-bottom: layout.$spacing-05;
|
|
103
99
|
}
|
|
104
100
|
|
|
105
101
|
.errorContainer {
|
|
@@ -135,3 +131,6 @@
|
|
|
135
131
|
font-size: 0.875rem;
|
|
136
132
|
}
|
|
137
133
|
|
|
134
|
+
.serviceNameLabel {
|
|
135
|
+
@include type.type-style('body-compact-02');
|
|
136
|
+
}
|
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
3
|
import { render, screen } from '@testing-library/react';
|
|
4
|
-
import { type FetchResponse
|
|
4
|
+
import { navigate, type FetchResponse } from '@openmrs/esm-framework';
|
|
5
5
|
import {
|
|
6
|
+
createBillableService,
|
|
6
7
|
useBillableServices,
|
|
8
|
+
useConceptsSearch,
|
|
7
9
|
usePaymentModes,
|
|
8
10
|
useServiceTypes,
|
|
9
|
-
createBillableSerice,
|
|
10
11
|
} from '../billable-service.resource';
|
|
11
12
|
import AddBillableService from './add-billable-service.component';
|
|
12
13
|
|
|
13
|
-
const mockUseBillableServices =
|
|
14
|
-
const mockUsePaymentModes =
|
|
15
|
-
const mockUseServiceTypes =
|
|
16
|
-
const
|
|
17
|
-
const
|
|
14
|
+
const mockUseBillableServices = jest.mocked(useBillableServices);
|
|
15
|
+
const mockUsePaymentModes = jest.mocked(usePaymentModes);
|
|
16
|
+
const mockUseServiceTypes = jest.mocked(useServiceTypes);
|
|
17
|
+
const mockCreateBillableService = jest.mocked(createBillableService);
|
|
18
|
+
const mockUseConceptsSearch = jest.mocked(useConceptsSearch);
|
|
18
19
|
|
|
19
20
|
jest.mock('../billable-service.resource', () => ({
|
|
20
21
|
useBillableServices: jest.fn(),
|
|
21
22
|
usePaymentModes: jest.fn(),
|
|
22
23
|
useServiceTypes: jest.fn(),
|
|
23
|
-
|
|
24
|
+
createBillableService: jest.fn(),
|
|
25
|
+
updateBillableService: jest.fn(),
|
|
26
|
+
useConceptsSearch: jest.fn(),
|
|
24
27
|
}));
|
|
25
28
|
|
|
26
29
|
const mockPaymentModes = [
|
|
@@ -49,11 +52,7 @@ const mockServiceTypes = [
|
|
|
49
52
|
{ uuid: 'a487a743-62ce-4f93-a66b-c5154ee8987d', display: 'Adherence counselling service' },
|
|
50
53
|
];
|
|
51
54
|
|
|
52
|
-
|
|
53
|
-
beforeEach(() => {
|
|
54
|
-
jest.resetAllMocks();
|
|
55
|
-
});
|
|
56
|
-
|
|
55
|
+
describe('AddBillableService', () => {
|
|
57
56
|
test('should render billable services form and generate correct payload', async () => {
|
|
58
57
|
const user = userEvent.setup();
|
|
59
58
|
const mockOnClose = jest.fn();
|
|
@@ -64,8 +63,9 @@ xdescribe('AddBillableService', () => {
|
|
|
64
63
|
mutate: jest.fn(),
|
|
65
64
|
isValidating: false,
|
|
66
65
|
});
|
|
67
|
-
mockUsePaymentModes.mockReturnValue({ paymentModes: mockPaymentModes, error: null,
|
|
68
|
-
mockUseServiceTypes.mockReturnValue({ serviceTypes: mockServiceTypes, error: false,
|
|
66
|
+
mockUsePaymentModes.mockReturnValue({ paymentModes: mockPaymentModes, error: null, isLoadingPaymentModes: false });
|
|
67
|
+
mockUseServiceTypes.mockReturnValue({ serviceTypes: mockServiceTypes, error: false, isLoadingServiceTypes: false });
|
|
68
|
+
mockUseConceptsSearch.mockReturnValue({ searchResults: [], isSearching: false, error: null });
|
|
69
69
|
|
|
70
70
|
render(<AddBillableService onClose={mockOnClose} />);
|
|
71
71
|
|
|
@@ -84,50 +84,50 @@ xdescribe('AddBillableService', () => {
|
|
|
84
84
|
expect(serviceNameTextInp).toHaveValue('Test Service Name');
|
|
85
85
|
expect(serviceShortNameTextInp).toHaveValue('Test Short Name');
|
|
86
86
|
|
|
87
|
-
const serviceTypeComboBox = screen.getByRole('combobox', { name: /Service
|
|
87
|
+
const serviceTypeComboBox = screen.getByRole('combobox', { name: /Service type/i });
|
|
88
88
|
expect(serviceTypeComboBox).toBeInTheDocument();
|
|
89
89
|
await user.click(serviceTypeComboBox);
|
|
90
90
|
const serviceTypeOptions = screen.getByRole('option', { name: /Lab service/i });
|
|
91
91
|
expect(serviceTypeOptions).toBeInTheDocument();
|
|
92
92
|
await user.click(serviceTypeOptions);
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
await user.click(
|
|
98
|
-
|
|
99
|
-
const paymentMethodComboBox = screen.getByRole('combobox', { name: /Payment Mode/i });
|
|
100
|
-
expect(paymentMethodComboBox).toBeInTheDocument();
|
|
101
|
-
await user.click(paymentMethodComboBox);
|
|
94
|
+
// Fill in the default payment option (first one)
|
|
95
|
+
const paymentMethodComboBoxes = screen.getAllByRole('combobox', { name: /Payment mode/i });
|
|
96
|
+
expect(paymentMethodComboBoxes).toHaveLength(1); // Should have one default
|
|
97
|
+
await user.click(paymentMethodComboBoxes[0]);
|
|
102
98
|
const paymentMethodOptions = screen.getByRole('option', { name: /Cash/i });
|
|
103
99
|
expect(paymentMethodOptions).toBeInTheDocument();
|
|
104
100
|
await user.click(paymentMethodOptions);
|
|
105
101
|
|
|
106
|
-
const
|
|
102
|
+
const priceTextInps = screen.getAllByRole('textbox', { name: /Selling Price/i });
|
|
103
|
+
expect(priceTextInps).toHaveLength(1); // Should have one price input for the default payment method
|
|
104
|
+
const priceTextInp = priceTextInps[0];
|
|
107
105
|
expect(priceTextInp).toBeInTheDocument();
|
|
108
106
|
await user.type(priceTextInp, '1000');
|
|
109
107
|
|
|
110
|
-
|
|
111
|
-
const saveBtn = screen.
|
|
108
|
+
mockCreateBillableService.mockReturnValue(Promise.resolve({} as FetchResponse<any>));
|
|
109
|
+
const saveBtn = screen.getAllByRole('button').find((btn) => btn.getAttribute('type') === 'submit');
|
|
112
110
|
expect(saveBtn).toBeInTheDocument();
|
|
111
|
+
|
|
113
112
|
await user.click(saveBtn);
|
|
114
113
|
|
|
115
|
-
expect(
|
|
116
|
-
expect(
|
|
114
|
+
expect(mockCreateBillableService).toHaveBeenCalledTimes(1);
|
|
115
|
+
expect(mockCreateBillableService).toHaveBeenCalledWith({
|
|
117
116
|
name: 'Test Service Name',
|
|
118
117
|
shortName: 'Test Short Name',
|
|
119
|
-
serviceType:
|
|
118
|
+
serviceType: 'c9604249-db0a-4d03-b074-fc6bc2fa13e6',
|
|
120
119
|
servicePrices: [
|
|
121
120
|
{
|
|
122
121
|
paymentMode: '63eff7a4-6f82-43c4-a333-dbcc58fe9f74',
|
|
123
|
-
price:
|
|
122
|
+
price: 1000,
|
|
124
123
|
name: 'Cash',
|
|
125
124
|
},
|
|
126
125
|
],
|
|
127
126
|
serviceStatus: 'ENABLED',
|
|
127
|
+
concept: undefined,
|
|
128
128
|
});
|
|
129
|
-
expect(
|
|
130
|
-
expect(
|
|
129
|
+
expect(navigate).toHaveBeenCalledTimes(1);
|
|
130
|
+
expect(navigate).toHaveBeenCalledWith({ to: '/openmrs/spa/billable-services' });
|
|
131
131
|
});
|
|
132
132
|
|
|
133
133
|
test("should navigate back to billable services dashboard when 'Cancel' button is clicked", async () => {
|
|
@@ -140,12 +140,13 @@ xdescribe('AddBillableService', () => {
|
|
|
140
140
|
mutate: jest.fn(),
|
|
141
141
|
isValidating: false,
|
|
142
142
|
});
|
|
143
|
-
mockUsePaymentModes.mockReturnValue({ paymentModes: mockPaymentModes, error: null,
|
|
144
|
-
mockUseServiceTypes.mockReturnValue({ serviceTypes: mockServiceTypes, error: false,
|
|
143
|
+
mockUsePaymentModes.mockReturnValue({ paymentModes: mockPaymentModes, error: null, isLoadingPaymentModes: false });
|
|
144
|
+
mockUseServiceTypes.mockReturnValue({ serviceTypes: mockServiceTypes, error: false, isLoadingServiceTypes: false });
|
|
145
|
+
mockUseConceptsSearch.mockReturnValue({ searchResults: [], isSearching: false, error: null });
|
|
145
146
|
|
|
146
147
|
render(<AddBillableService onClose={mockOnClose} />);
|
|
147
148
|
|
|
148
|
-
const cancelBtn = screen.
|
|
149
|
+
const cancelBtn = screen.getAllByRole('button').find((btn) => btn.className.includes('cds--btn--secondary'));
|
|
149
150
|
expect(cancelBtn).toBeInTheDocument();
|
|
150
151
|
await user.click(cancelBtn);
|
|
151
152
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Button, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
|
|
4
|
+
import { getCoreTranslation } from '@openmrs/esm-framework';
|
|
5
|
+
import { type BillableService } from '../../types';
|
|
6
|
+
import AddBillableService from './add-billable-service.component';
|
|
7
|
+
|
|
8
|
+
interface EditBillableServiceModalProps {
|
|
9
|
+
closeModal: () => void;
|
|
10
|
+
onServiceUpdated: () => void;
|
|
11
|
+
serviceToEdit?: BillableService;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const EditBillableServiceModal: React.FC<EditBillableServiceModalProps> = ({
|
|
15
|
+
closeModal,
|
|
16
|
+
serviceToEdit,
|
|
17
|
+
onServiceUpdated,
|
|
18
|
+
}) => {
|
|
19
|
+
const { t } = useTranslation();
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
<ModalHeader closeModal={closeModal} title={t('billableService', 'Billable Service')} />
|
|
24
|
+
<ModalBody>
|
|
25
|
+
<AddBillableService
|
|
26
|
+
serviceToEdit={serviceToEdit}
|
|
27
|
+
isModal
|
|
28
|
+
onClose={closeModal}
|
|
29
|
+
onServiceUpdated={onServiceUpdated}
|
|
30
|
+
/>
|
|
31
|
+
</ModalBody>
|
|
32
|
+
<ModalFooter>
|
|
33
|
+
<Button kind="secondary" onClick={closeModal}>
|
|
34
|
+
{getCoreTranslation('cancel')}
|
|
35
|
+
</Button>
|
|
36
|
+
<Button
|
|
37
|
+
onClick={() => {
|
|
38
|
+
// Trigger form submission programmatically
|
|
39
|
+
const form = document.getElementById('billable-service-form') as HTMLFormElement;
|
|
40
|
+
if (form) {
|
|
41
|
+
form.requestSubmit();
|
|
42
|
+
}
|
|
43
|
+
}}>
|
|
44
|
+
{getCoreTranslation('save')}
|
|
45
|
+
</Button>
|
|
46
|
+
</ModalFooter>
|
|
47
|
+
</>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default EditBillableServiceModal;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { useForm, Controller } from 'react-hook-form';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
6
|
+
import { Button, Form, ModalBody, ModalFooter, ModalHeader, Stack, TextInput } from '@carbon/react';
|
|
7
|
+
import { showSnackbar, openmrsFetch, restBaseUrl, getCoreTranslation } from '@openmrs/esm-framework';
|
|
8
|
+
|
|
9
|
+
type PaymentModeFormValues = {
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
interface AddPaymentModeModalProps {
|
|
15
|
+
closeModal: () => void;
|
|
16
|
+
onPaymentModeAdded: () => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const AddPaymentModeModal: React.FC<AddPaymentModeModalProps> = ({ closeModal, onPaymentModeAdded }) => {
|
|
20
|
+
const { t } = useTranslation();
|
|
21
|
+
|
|
22
|
+
const paymentModeSchema = z.object({
|
|
23
|
+
name: z.string().min(1, t('paymentModeNameRequired', 'Payment Mode Name is required')),
|
|
24
|
+
description: z.string().optional(),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
control,
|
|
29
|
+
handleSubmit,
|
|
30
|
+
reset,
|
|
31
|
+
formState: { errors, isSubmitting },
|
|
32
|
+
} = useForm<PaymentModeFormValues>({
|
|
33
|
+
resolver: zodResolver(paymentModeSchema),
|
|
34
|
+
defaultValues: {
|
|
35
|
+
name: '',
|
|
36
|
+
description: '',
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const onSubmit = async (data: PaymentModeFormValues) => {
|
|
41
|
+
try {
|
|
42
|
+
await openmrsFetch(`${restBaseUrl}/billing/paymentMode`, {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers: {
|
|
45
|
+
'Content-Type': 'application/json',
|
|
46
|
+
},
|
|
47
|
+
body: {
|
|
48
|
+
name: data.name,
|
|
49
|
+
description: data.description || '',
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
showSnackbar({
|
|
54
|
+
title: t('success', 'Success'),
|
|
55
|
+
subtitle: t('paymentModeSaved', 'Payment mode was successfully saved.'),
|
|
56
|
+
kind: 'success',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
closeModal();
|
|
60
|
+
reset({ name: '', description: '' });
|
|
61
|
+
onPaymentModeAdded();
|
|
62
|
+
} catch (err) {
|
|
63
|
+
showSnackbar({
|
|
64
|
+
title: getCoreTranslation('error'),
|
|
65
|
+
subtitle: err?.message || t('errorSavingPaymentMode', 'An error occurred while saving the payment mode.'),
|
|
66
|
+
kind: 'error',
|
|
67
|
+
isLowContrast: false,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<>
|
|
74
|
+
<ModalHeader closeModal={closeModal} title={t('addPaymentMode', 'Add Payment Mode')} />
|
|
75
|
+
<Form onSubmit={handleSubmit(onSubmit)}>
|
|
76
|
+
<ModalBody>
|
|
77
|
+
<Stack gap={5}>
|
|
78
|
+
<Controller
|
|
79
|
+
name="name"
|
|
80
|
+
control={control}
|
|
81
|
+
render={({ field }) => (
|
|
82
|
+
<TextInput
|
|
83
|
+
id="payment-mode-name"
|
|
84
|
+
labelText={t('paymentModeName', 'Payment Mode Name')}
|
|
85
|
+
placeholder={t('paymentModeNamePlaceholder', 'e.g., Cash, Credit Card')}
|
|
86
|
+
invalid={!!errors.name}
|
|
87
|
+
invalidText={errors.name?.message}
|
|
88
|
+
{...field}
|
|
89
|
+
/>
|
|
90
|
+
)}
|
|
91
|
+
/>
|
|
92
|
+
<Controller
|
|
93
|
+
name="description"
|
|
94
|
+
control={control}
|
|
95
|
+
render={({ field }) => (
|
|
96
|
+
<TextInput
|
|
97
|
+
id="payment-mode-description"
|
|
98
|
+
labelText={t('description', 'Description')}
|
|
99
|
+
placeholder={t('descriptionPlaceholder', 'e.g., Used for all cash transactions')}
|
|
100
|
+
invalid={!!errors.description}
|
|
101
|
+
invalidText={errors.description?.message}
|
|
102
|
+
{...field}
|
|
103
|
+
/>
|
|
104
|
+
)}
|
|
105
|
+
/>
|
|
106
|
+
</Stack>
|
|
107
|
+
</ModalBody>
|
|
108
|
+
<ModalFooter>
|
|
109
|
+
<Button kind="secondary" onClick={closeModal}>
|
|
110
|
+
{getCoreTranslation('cancel')}
|
|
111
|
+
</Button>
|
|
112
|
+
<Button type="submit" disabled={isSubmitting}>
|
|
113
|
+
{isSubmitting ? t('saving', 'Saving') + '...' : getCoreTranslation('save')}
|
|
114
|
+
</Button>
|
|
115
|
+
</ModalFooter>
|
|
116
|
+
</Form>
|
|
117
|
+
</>
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export default AddPaymentModeModal;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Button, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
|
|
4
|
+
import { showSnackbar, openmrsFetch, restBaseUrl, getCoreTranslation } from '@openmrs/esm-framework';
|
|
5
|
+
|
|
6
|
+
interface DeletePaymentModeModalProps {
|
|
7
|
+
closeModal: () => void;
|
|
8
|
+
paymentModeUuid: string;
|
|
9
|
+
paymentModeName: string;
|
|
10
|
+
onPaymentModeDeleted: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const DeletePaymentModeModal: React.FC<DeletePaymentModeModalProps> = ({
|
|
14
|
+
closeModal,
|
|
15
|
+
paymentModeUuid,
|
|
16
|
+
paymentModeName,
|
|
17
|
+
onPaymentModeDeleted,
|
|
18
|
+
}) => {
|
|
19
|
+
const { t } = useTranslation();
|
|
20
|
+
const [isDeleting, setIsDeleting] = useState(false);
|
|
21
|
+
|
|
22
|
+
const handleDelete = async () => {
|
|
23
|
+
setIsDeleting(true);
|
|
24
|
+
try {
|
|
25
|
+
await openmrsFetch(`${restBaseUrl}/billing/paymentMode/${paymentModeUuid}`, {
|
|
26
|
+
method: 'DELETE',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
showSnackbar({
|
|
30
|
+
title: t('success', 'Success'),
|
|
31
|
+
subtitle: t('paymentModeDeleted', 'Payment mode was successfully deleted.'),
|
|
32
|
+
kind: 'success',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
closeModal();
|
|
36
|
+
onPaymentModeDeleted();
|
|
37
|
+
} catch (err) {
|
|
38
|
+
showSnackbar({
|
|
39
|
+
title: getCoreTranslation('error'),
|
|
40
|
+
subtitle: err?.message || t('errorDeletingPaymentMode', 'An error occurred while deleting the payment mode.'),
|
|
41
|
+
kind: 'error',
|
|
42
|
+
isLowContrast: false,
|
|
43
|
+
});
|
|
44
|
+
} finally {
|
|
45
|
+
setIsDeleting(false);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<>
|
|
51
|
+
<ModalHeader closeModal={closeModal} title={t('deletePaymentMode', 'Delete Payment Mode')} />
|
|
52
|
+
<ModalBody>
|
|
53
|
+
<p>{t('confirmDeleteMessage', 'Are you sure you want to delete this payment mode? Proceed cautiously.')}</p>
|
|
54
|
+
{paymentModeName && (
|
|
55
|
+
<p>
|
|
56
|
+
<strong>{t('paymentModeName', 'Payment Mode Name: {{paymentModeName}}', { paymentModeName })}</strong>
|
|
57
|
+
</p>
|
|
58
|
+
)}
|
|
59
|
+
</ModalBody>
|
|
60
|
+
<ModalFooter>
|
|
61
|
+
<Button kind="secondary" onClick={closeModal}>
|
|
62
|
+
{getCoreTranslation('cancel')}
|
|
63
|
+
</Button>
|
|
64
|
+
<Button kind="danger" onClick={handleDelete} disabled={isDeleting}>
|
|
65
|
+
{isDeleting ? t('deleting', 'Deleting') + '...' : getCoreTranslation('delete')}
|
|
66
|
+
</Button>
|
|
67
|
+
</ModalFooter>
|
|
68
|
+
</>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export default DeletePaymentModeModal;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
DataTable,
|
|
5
|
+
OverflowMenu,
|
|
6
|
+
OverflowMenuItem,
|
|
7
|
+
Table,
|
|
8
|
+
TableBody,
|
|
9
|
+
TableCell,
|
|
10
|
+
TableContainer,
|
|
11
|
+
TableHead,
|
|
12
|
+
TableHeader,
|
|
13
|
+
TableRow,
|
|
14
|
+
} from '@carbon/react';
|
|
15
|
+
import { Add } from '@carbon/react/icons';
|
|
16
|
+
import { useTranslation } from 'react-i18next';
|
|
17
|
+
import { showSnackbar, openmrsFetch, restBaseUrl, showModal, getCoreTranslation } from '@openmrs/esm-framework';
|
|
18
|
+
import { CardHeader } from '@openmrs/esm-patient-common-lib';
|
|
19
|
+
import styles from './payment-modes-config.scss';
|
|
20
|
+
|
|
21
|
+
const PaymentModesConfig: React.FC = () => {
|
|
22
|
+
const { t } = useTranslation();
|
|
23
|
+
const [paymentModes, setPaymentModes] = useState([]);
|
|
24
|
+
|
|
25
|
+
const fetchPaymentModes = useCallback(async () => {
|
|
26
|
+
try {
|
|
27
|
+
const response = await openmrsFetch(`${restBaseUrl}/billing/paymentMode?v=full`);
|
|
28
|
+
setPaymentModes(response.data.results || []);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
showSnackbar({
|
|
31
|
+
title: getCoreTranslation('error'),
|
|
32
|
+
subtitle: t('errorFetchingPaymentModes', 'An error occurred while fetching payment modes.'),
|
|
33
|
+
kind: 'error',
|
|
34
|
+
isLowContrast: false,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}, [t]);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
fetchPaymentModes();
|
|
41
|
+
}, [fetchPaymentModes]);
|
|
42
|
+
|
|
43
|
+
const handleAddPaymentMode = () => {
|
|
44
|
+
const dispose = showModal('add-payment-mode-modal', {
|
|
45
|
+
onPaymentModeAdded: fetchPaymentModes,
|
|
46
|
+
closeModal: () => dispose(),
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handleDeletePaymentMode = (paymentMode) => {
|
|
51
|
+
const dispose = showModal('delete-payment-mode-modal', {
|
|
52
|
+
paymentModeUuid: paymentMode.uuid,
|
|
53
|
+
paymentModeName: paymentMode.name,
|
|
54
|
+
onPaymentModeDeleted: fetchPaymentModes,
|
|
55
|
+
closeModal: () => dispose(),
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const rowData = paymentModes.map((mode) => ({
|
|
60
|
+
id: mode.uuid,
|
|
61
|
+
name: mode.name,
|
|
62
|
+
description: mode.description || '--',
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
const headerData = [
|
|
66
|
+
{ key: 'name', header: t('name', 'Name') },
|
|
67
|
+
{ key: 'description', header: t('description', 'Description') },
|
|
68
|
+
{ key: 'actions', header: getCoreTranslation('actions') },
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div className={styles.container}>
|
|
73
|
+
<div className={styles.card}>
|
|
74
|
+
<CardHeader title={t('paymentModeHistory', 'Payment Mode History')}>
|
|
75
|
+
<Button renderIcon={Add} onClick={handleAddPaymentMode} kind="ghost">
|
|
76
|
+
{t('addNewPaymentMode', 'Add New Payment Mode')}
|
|
77
|
+
</Button>
|
|
78
|
+
</CardHeader>
|
|
79
|
+
<DataTable rows={rowData} headers={headerData} isSortable size="lg">
|
|
80
|
+
{({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
|
|
81
|
+
<TableContainer>
|
|
82
|
+
<Table className={styles.table} {...getTableProps()}>
|
|
83
|
+
<TableHead>
|
|
84
|
+
<TableRow>
|
|
85
|
+
{headers.map((header) => (
|
|
86
|
+
<TableHeader key={header.key} {...getHeaderProps({ header })}>
|
|
87
|
+
{header.header}
|
|
88
|
+
</TableHeader>
|
|
89
|
+
))}
|
|
90
|
+
</TableRow>
|
|
91
|
+
</TableHead>
|
|
92
|
+
<TableBody>
|
|
93
|
+
{rows.map((row) => (
|
|
94
|
+
<TableRow key={row.id} {...getRowProps({ row })}>
|
|
95
|
+
{row.cells.map((cell) =>
|
|
96
|
+
cell.info.header !== 'actions' ? (
|
|
97
|
+
<TableCell key={cell.id}>{cell.value}</TableCell>
|
|
98
|
+
) : (
|
|
99
|
+
<TableCell key={cell.id}>
|
|
100
|
+
<OverflowMenu>
|
|
101
|
+
<OverflowMenuItem
|
|
102
|
+
className={styles.menuItem}
|
|
103
|
+
itemText={getCoreTranslation('delete')}
|
|
104
|
+
onClick={() => {
|
|
105
|
+
const selected = paymentModes.find((p) => p.uuid === row.id);
|
|
106
|
+
handleDeletePaymentMode(selected);
|
|
107
|
+
}}
|
|
108
|
+
/>
|
|
109
|
+
</OverflowMenu>
|
|
110
|
+
</TableCell>
|
|
111
|
+
),
|
|
112
|
+
)}
|
|
113
|
+
</TableRow>
|
|
114
|
+
))}
|
|
115
|
+
</TableBody>
|
|
116
|
+
</Table>
|
|
117
|
+
</TableContainer>
|
|
118
|
+
)}
|
|
119
|
+
</DataTable>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export default PaymentModesConfig;
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import React, { useCallback, useState } from 'react';
|
|
2
2
|
import { Dropdown, InlineLoading, InlineNotification } from '@carbon/react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
|
-
import { showSnackbar,
|
|
4
|
+
import { showSnackbar, getCoreTranslation } from '@openmrs/esm-framework';
|
|
5
5
|
import { useCashPoint, useBillableItems, createPatientBill } from './billing-form.resource';
|
|
6
6
|
import VisitAttributesForm from './visit-attributes/visit-attributes-form.component';
|
|
7
7
|
import styles from './billing-checkin-form.scss';
|
|
8
8
|
|
|
9
|
-
const DEFAULT_PRICE = 500.00001;
|
|
10
9
|
const PENDING_PAYMENT_STATUS = 'PENDING';
|
|
11
10
|
|
|
12
11
|
type BillingCheckInFormProps = {
|
|
@@ -74,7 +73,7 @@ const BillingCheckInForm: React.FC<BillingCheckInFormProps> = ({ patientUuid, se
|
|
|
74
73
|
return (
|
|
75
74
|
<InlineLoading
|
|
76
75
|
status="active"
|
|
77
|
-
iconDescription={
|
|
76
|
+
iconDescription={getCoreTranslation('loading')}
|
|
78
77
|
description={t('loadingBillingServices', 'Loading billing services...')}
|
|
79
78
|
/>
|
|
80
79
|
);
|