@openmrs/esm-billing-app 1.0.2-pre.74 → 1.0.2-pre.749
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 -0
- 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 -0
- 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 +381 -231
- 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} +58 -56
- package/src/bill-item-actions/edit-bill-item.test.tsx +22 -24
- 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 +4 -3
- package/src/billable-services/billable-services-home.component.tsx +1 -1
- package/src/billable-services/billable-services.component.tsx +115 -132
- 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 +17 -192
- package/src/billable-services/cash-point/cash-point-configuration.scss +1 -5
- package/src/billable-services/create-edit/add-billable-service.component.tsx +28 -24
- package/src/billable-services/create-edit/add-billable-service.scss +2 -5
- package/src/billable-services/create-edit/add-billable-service.test.tsx +6 -6
- package/src/billable-services/create-edit/edit-billable-service.modal.tsx +50 -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 -1
- package/src/billing-form/billing-checkin-form.component.tsx +2 -3
- package/src/billing-form/billing-checkin-form.test.tsx +0 -2
- package/src/billing-form/billing-form.component.tsx +210 -268
- 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 +2 -9
- package/src/invoice/payments/payment-form/payment-form.test.tsx +14 -46
- 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 +16 -27
- package/src/invoice/payments/{payments.component.test.tsx → payments.test.tsx} +24 -10
- package/src/invoice/payments/utils.ts +4 -22
- 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 +25 -20
- 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 +13 -12
- package/translations/am.json +33 -16
- package/translations/ar.json +33 -16
- package/translations/ar_SY.json +33 -16
- package/translations/bn.json +38 -21
- package/translations/de.json +33 -16
- package/translations/en.json +33 -16
- package/translations/en_US.json +187 -0
- package/translations/es.json +48 -31
- package/translations/es_MX.json +33 -16
- package/translations/fr.json +47 -30
- package/translations/he.json +33 -16
- package/translations/hi.json +33 -16
- package/translations/hi_IN.json +33 -16
- package/translations/id.json +180 -163
- package/translations/it.json +70 -53
- package/translations/ka.json +187 -0
- package/translations/km.json +33 -16
- package/translations/ku.json +33 -16
- package/translations/ky.json +33 -16
- package/translations/lg.json +33 -16
- package/translations/ne.json +33 -16
- package/translations/pl.json +33 -16
- package/translations/pt.json +33 -16
- package/translations/pt_BR.json +33 -16
- package/translations/qu.json +33 -16
- package/translations/ro_RO.json +182 -165
- package/translations/ru_RU.json +33 -16
- package/translations/si.json +33 -16
- package/translations/sw.json +33 -16
- package/translations/sw_KE.json +33 -16
- package/translations/tr.json +33 -16
- package/translations/tr_TR.json +33 -16
- package/translations/uk.json +33 -16
- package/translations/uz.json +33 -16
- package/translations/uz@Latn.json +33 -16
- package/translations/uz_UZ.json +33 -16
- package/translations/vi.json +33 -16
- package/translations/zh.json +33 -16
- package/translations/zh_CN.json +91 -74
- 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/dist/{8556.js.LICENSE.txt → 7452.js.LICENSE.txt} +0 -0
|
@@ -1,17 +1,23 @@
|
|
|
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 { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
|
|
5
|
+
import { configSchema, type BillingConfig } from '../config-schema';
|
|
4
6
|
import { useBills } from '../billing.resource';
|
|
5
7
|
import BillHistory from './bill-history.component';
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
jest.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const mockUseConfig = jest.mocked(useConfig<BillingConfig>);
|
|
10
|
+
const mockUseBills = jest.mocked(useBills);
|
|
11
|
+
|
|
12
|
+
jest.mock('../billing.resource', () => ({
|
|
13
|
+
useBills: jest.fn(() => ({
|
|
14
|
+
bills: mockBillData,
|
|
15
|
+
isLoading: false,
|
|
16
|
+
isValidating: false,
|
|
17
|
+
error: null,
|
|
18
|
+
})),
|
|
12
19
|
}));
|
|
13
20
|
|
|
14
|
-
// Mock window.i18next
|
|
15
21
|
window.i18next = {
|
|
16
22
|
language: 'en-US',
|
|
17
23
|
} as any;
|
|
@@ -20,9 +26,7 @@ const testProps = {
|
|
|
20
26
|
patientUuid: 'some-uuid',
|
|
21
27
|
};
|
|
22
28
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
const mockBillsData = [
|
|
29
|
+
const mockBillData = [
|
|
26
30
|
{ uuid: '1', patientName: 'John Doe', identifier: '12345678', billingService: 'Checkup', totalAmount: 500 },
|
|
27
31
|
{ uuid: '2', patientName: 'John Doe', identifier: '12345678', billingService: 'Consulatation', totalAmount: 600 },
|
|
28
32
|
{ uuid: '3', patientName: 'John Doe', identifier: '12345678', billingService: 'Child services', totalAmount: 700 },
|
|
@@ -37,70 +41,19 @@ const mockBillsData = [
|
|
|
37
41
|
{ uuid: '12', patientName: 'John Doe', identifier: '12345678', billingService: 'MCH', totalAmount: 1300 },
|
|
38
42
|
];
|
|
39
43
|
|
|
40
|
-
// Mock the invoice table component
|
|
41
|
-
jest.mock('../invoice/invoice-table.component', () => jest.fn(() => <div>Invoice table</div>));
|
|
42
|
-
|
|
43
|
-
// Mock the billing resource
|
|
44
|
-
jest.mock('../billing.resource', () => ({
|
|
45
|
-
useBills: jest.fn(() => ({
|
|
46
|
-
bills: mockBillsData,
|
|
47
|
-
isLoading: false,
|
|
48
|
-
isValidating: false,
|
|
49
|
-
error: null,
|
|
50
|
-
})),
|
|
51
|
-
}));
|
|
52
|
-
|
|
53
|
-
// Mock esm-patient-common-lib
|
|
54
|
-
jest.mock('@openmrs/esm-patient-common-lib', () => ({
|
|
55
|
-
CardHeader: jest.fn(({ children }) => <div>{children}</div>),
|
|
56
|
-
EmptyDataIllustration: jest.fn(() => <div>Empty state illustration</div>),
|
|
57
|
-
ErrorState: jest.fn(({ error }) => <div>Error: {error?.message}</div>),
|
|
58
|
-
launchPatientWorkspace: jest.fn(),
|
|
59
|
-
usePaginationInfo: jest.fn(() => ({
|
|
60
|
-
pageSizes: [10, 20, 30],
|
|
61
|
-
currentPage: 1,
|
|
62
|
-
})),
|
|
63
|
-
}));
|
|
64
|
-
|
|
65
|
-
// Mock esm-framework
|
|
66
|
-
jest.mock('@openmrs/esm-framework', () => ({
|
|
67
|
-
useLayoutType: jest.fn(() => 'small-desktop'),
|
|
68
|
-
isDesktop: jest.fn(() => true),
|
|
69
|
-
usePagination: jest.fn().mockImplementation((data) => ({
|
|
70
|
-
currentPage: 1,
|
|
71
|
-
goTo: jest.fn(),
|
|
72
|
-
results: data,
|
|
73
|
-
paginated: true,
|
|
74
|
-
})),
|
|
75
|
-
showToast: jest.fn(),
|
|
76
|
-
showNotification: jest.fn(),
|
|
77
|
-
createErrorHandler: jest.fn(),
|
|
78
|
-
createGlobalStore: jest.fn(),
|
|
79
|
-
getGlobalStore: jest.fn(() => ({
|
|
80
|
-
subscribe: jest.fn(),
|
|
81
|
-
getState: jest.fn(),
|
|
82
|
-
setState: jest.fn(),
|
|
83
|
-
})),
|
|
84
|
-
useConfig: jest.fn(() => ({
|
|
85
|
-
pageSize: 10,
|
|
86
|
-
defaultCurrency: 'USD',
|
|
87
|
-
})),
|
|
88
|
-
useSession: jest.fn(() => ({
|
|
89
|
-
sessionLocation: { uuid: 'some-uuid', display: 'Location' },
|
|
90
|
-
})),
|
|
91
|
-
formatDate: jest.fn((date) => date?.toString() ?? ''),
|
|
92
|
-
formatDatetime: jest.fn((date) => date?.toString() ?? ''),
|
|
93
|
-
parseDate: jest.fn((dateString) => new Date(dateString)),
|
|
94
|
-
ExtensionSlot: jest.fn(({ children }) => <>{children}</>),
|
|
95
|
-
}));
|
|
96
|
-
|
|
97
44
|
describe('BillHistory', () => {
|
|
98
|
-
|
|
99
|
-
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(configSchema), defaultCurrency: 'USD' });
|
|
100
47
|
});
|
|
101
48
|
|
|
102
49
|
test('should render loading datatable skeleton', () => {
|
|
103
|
-
|
|
50
|
+
mockUseBills.mockReturnValueOnce({
|
|
51
|
+
isLoading: true,
|
|
52
|
+
isValidating: false,
|
|
53
|
+
error: null,
|
|
54
|
+
bills: [],
|
|
55
|
+
mutate: jest.fn(),
|
|
56
|
+
});
|
|
104
57
|
render(<BillHistory {...testProps} />);
|
|
105
58
|
const loadingSkeleton = screen.getByRole('table');
|
|
106
59
|
expect(loadingSkeleton).toBeInTheDocument();
|
|
@@ -108,7 +61,7 @@ describe('BillHistory', () => {
|
|
|
108
61
|
});
|
|
109
62
|
|
|
110
63
|
test('should render error state when API call fails', () => {
|
|
111
|
-
|
|
64
|
+
mockUseBills.mockReturnValueOnce({
|
|
112
65
|
isLoading: false,
|
|
113
66
|
isValidating: false,
|
|
114
67
|
error: new Error('some error'),
|
|
@@ -116,31 +69,31 @@ describe('BillHistory', () => {
|
|
|
116
69
|
mutate: jest.fn(),
|
|
117
70
|
});
|
|
118
71
|
render(<BillHistory {...testProps} />);
|
|
119
|
-
const errorState = screen.getByText(
|
|
72
|
+
const errorState = screen.getByText(/Error/);
|
|
120
73
|
expect(errorState).toBeInTheDocument();
|
|
121
74
|
});
|
|
122
75
|
|
|
123
76
|
test('should render bills table', async () => {
|
|
124
77
|
const user = userEvent.setup();
|
|
125
|
-
|
|
78
|
+
mockUseBills.mockReturnValueOnce({
|
|
126
79
|
isLoading: false,
|
|
127
80
|
isValidating: false,
|
|
128
81
|
error: null,
|
|
129
|
-
bills:
|
|
82
|
+
bills: mockBillData as any,
|
|
130
83
|
mutate: jest.fn(),
|
|
131
84
|
});
|
|
132
85
|
render(<BillHistory {...testProps} />);
|
|
133
86
|
|
|
134
87
|
// Verify headers
|
|
135
|
-
expect(screen.getByText('
|
|
136
|
-
expect(screen.getByText('
|
|
88
|
+
expect(screen.getByText('Visit time')).toBeInTheDocument();
|
|
89
|
+
expect(screen.getByText('Identifier')).toBeInTheDocument();
|
|
137
90
|
|
|
138
91
|
const tableRowGroup = screen.getAllByRole('rowgroup');
|
|
139
92
|
expect(tableRowGroup).toHaveLength(2);
|
|
140
93
|
|
|
141
94
|
// Page navigation should work as expected
|
|
142
|
-
const nextPageButton = screen.getByRole('button', { name: /
|
|
143
|
-
const prevPageButton = screen.getByRole('button', { name: /
|
|
95
|
+
const nextPageButton = screen.getByRole('button', { name: /Next page/ });
|
|
96
|
+
const prevPageButton = screen.getByRole('button', { name: /Previous page/ });
|
|
144
97
|
|
|
145
98
|
expect(nextPageButton).toBeInTheDocument();
|
|
146
99
|
expect(prevPageButton).toBeInTheDocument();
|
|
@@ -156,7 +109,13 @@ describe('BillHistory', () => {
|
|
|
156
109
|
});
|
|
157
110
|
|
|
158
111
|
test('should render empty state view when there are no bills', () => {
|
|
159
|
-
|
|
112
|
+
mockUseBills.mockReturnValueOnce({
|
|
113
|
+
isLoading: false,
|
|
114
|
+
isValidating: false,
|
|
115
|
+
error: null,
|
|
116
|
+
bills: [],
|
|
117
|
+
mutate: jest.fn(),
|
|
118
|
+
});
|
|
160
119
|
render(<BillHistory {...testProps} />);
|
|
161
120
|
const emptyState = screen.getByText(/There are no bills to display./);
|
|
162
121
|
expect(emptyState).toBeInTheDocument();
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
ModalFooter,
|
|
10
10
|
ModalHeader,
|
|
11
11
|
NumberInput,
|
|
12
|
+
Stack,
|
|
12
13
|
TextInput,
|
|
13
14
|
} from '@carbon/react';
|
|
14
15
|
import { useTranslation } from 'react-i18next';
|
|
@@ -16,7 +17,7 @@ import { Controller, type FieldErrors, useForm } from 'react-hook-form';
|
|
|
16
17
|
import { mutate } from 'swr';
|
|
17
18
|
import { z } from 'zod';
|
|
18
19
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
19
|
-
import { showSnackbar } from '@openmrs/esm-framework';
|
|
20
|
+
import { getCoreTranslation, showSnackbar } from '@openmrs/esm-framework';
|
|
20
21
|
import { apiBasePath } from '../constants';
|
|
21
22
|
import { getBillableServiceUuid } from '../invoice/payments/utils';
|
|
22
23
|
import { type LineItem, type MappedBill } from '../types';
|
|
@@ -24,13 +25,13 @@ import { updateBillItems } from '../billing.resource';
|
|
|
24
25
|
import { useBillableServices } from '../billable-services/billable-service.resource';
|
|
25
26
|
import styles from './bill-item-actions.scss';
|
|
26
27
|
|
|
27
|
-
interface
|
|
28
|
+
interface EditBillLineItemModalProps {
|
|
28
29
|
bill: MappedBill;
|
|
29
|
-
item: LineItem;
|
|
30
30
|
closeModal: () => void;
|
|
31
|
+
item: LineItem;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
const
|
|
34
|
+
const EditBillLineItemModal: React.FC<EditBillLineItemModalProps> = ({ bill, closeModal, item }) => {
|
|
34
35
|
const { t } = useTranslation();
|
|
35
36
|
const { billableServices } = useBillableServices();
|
|
36
37
|
const [showErrorNotification, setShowErrorNotification] = useState(false);
|
|
@@ -56,7 +57,7 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
|
|
|
56
57
|
const {
|
|
57
58
|
control,
|
|
58
59
|
handleSubmit,
|
|
59
|
-
formState: { isSubmitting, errors
|
|
60
|
+
formState: { isSubmitting, errors },
|
|
60
61
|
watch,
|
|
61
62
|
} = useForm<BillLineItemForm>({
|
|
62
63
|
defaultValues: {
|
|
@@ -74,7 +75,7 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
|
|
|
74
75
|
setTotal(newTotal);
|
|
75
76
|
}, [quantity, price]);
|
|
76
77
|
|
|
77
|
-
const onSubmit = (data: BillLineItemForm) => {
|
|
78
|
+
const onSubmit = async (data: BillLineItemForm) => {
|
|
78
79
|
const url = `${apiBasePath}bill`;
|
|
79
80
|
|
|
80
81
|
const newItem = {
|
|
@@ -101,39 +102,42 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
|
|
|
101
102
|
status: bill.status,
|
|
102
103
|
uuid: bill.uuid,
|
|
103
104
|
};
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
await updateBillItems(payload);
|
|
108
|
+
mutate((key) => typeof key === 'string' && key.startsWith(url), undefined, { revalidate: true });
|
|
109
|
+
showSnackbar({
|
|
110
|
+
title: t('saveBill', 'Save Bill'),
|
|
111
|
+
subtitle: t('billProcessingSuccess', 'Bill processing has been successful'),
|
|
112
|
+
kind: 'success',
|
|
113
|
+
timeoutInMs: 3000,
|
|
114
|
+
});
|
|
115
|
+
closeModal();
|
|
116
|
+
} catch (error) {
|
|
117
|
+
showSnackbar({
|
|
118
|
+
title: t('billProcessingError', 'Bill processing error'),
|
|
119
|
+
kind: 'error',
|
|
120
|
+
subtitle: error?.message,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
119
123
|
};
|
|
120
124
|
|
|
121
125
|
if (Object.keys(bill)?.length === 0) {
|
|
122
126
|
return <ModalHeader closeModal={closeModal} title={t('billLineItemEmpty', 'This bill has no line items')} />;
|
|
123
127
|
}
|
|
124
128
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
<
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
<
|
|
129
|
+
return (
|
|
130
|
+
<>
|
|
131
|
+
<ModalHeader closeModal={closeModal} title={t('editBillLineItem', 'Edit bill line item?')} />
|
|
132
|
+
<Form onSubmit={handleSubmit(onSubmit, onError)}>
|
|
133
|
+
<ModalBody>
|
|
134
|
+
<Stack gap={5}>
|
|
131
135
|
<div className={styles.modalBody}>
|
|
132
136
|
<h5>
|
|
133
137
|
{bill?.patientName} · {bill?.cashPointName} · {bill?.receiptNumber}
|
|
134
138
|
</h5>
|
|
135
139
|
</div>
|
|
136
|
-
<section
|
|
140
|
+
<section>
|
|
137
141
|
<p className={styles.label}>
|
|
138
142
|
{t('item', 'Item')} : {item?.billableService ? item?.billableService : item?.item}
|
|
139
143
|
</p>
|
|
@@ -146,7 +150,7 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
|
|
|
146
150
|
<Controller
|
|
147
151
|
name="quantity"
|
|
148
152
|
control={control}
|
|
149
|
-
render={({ field: { onChange,
|
|
153
|
+
render={({ field: { onChange, value } }) => (
|
|
150
154
|
<NumberInput
|
|
151
155
|
label={t('quantity', 'Quantity')}
|
|
152
156
|
id="quantityInput"
|
|
@@ -171,7 +175,7 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
|
|
|
171
175
|
value={value}
|
|
172
176
|
readOnly={true}
|
|
173
177
|
className={styles.controlField}
|
|
174
|
-
helperText=
|
|
178
|
+
helperText={t('unitPriceHelperText', 'This is the unit price for this item.')}
|
|
175
179
|
/>
|
|
176
180
|
)}
|
|
177
181
|
/>
|
|
@@ -191,31 +195,29 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
|
|
|
191
195
|
</Column>
|
|
192
196
|
)}
|
|
193
197
|
</section>
|
|
194
|
-
</
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
);
|
|
218
|
-
}
|
|
198
|
+
</Stack>
|
|
199
|
+
</ModalBody>
|
|
200
|
+
<ModalFooter>
|
|
201
|
+
<Button kind="secondary" onClick={closeModal}>
|
|
202
|
+
{getCoreTranslation('cancel')}
|
|
203
|
+
</Button>
|
|
204
|
+
<Button type="submit" disabled={isSubmitting}>
|
|
205
|
+
{isSubmitting ? (
|
|
206
|
+
<div className={styles.inline}>
|
|
207
|
+
<InlineLoading
|
|
208
|
+
status="active"
|
|
209
|
+
iconDescription={t('submitting', 'Submitting')}
|
|
210
|
+
description={t('submitting', 'Submitting') + '...'}
|
|
211
|
+
/>
|
|
212
|
+
</div>
|
|
213
|
+
) : (
|
|
214
|
+
getCoreTranslation('save')
|
|
215
|
+
)}
|
|
216
|
+
</Button>
|
|
217
|
+
</ModalFooter>
|
|
218
|
+
</Form>
|
|
219
|
+
</>
|
|
220
|
+
);
|
|
219
221
|
};
|
|
220
222
|
|
|
221
|
-
export default
|
|
223
|
+
export default EditBillLineItemModal;
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
4
|
+
import { type FetchResponse, showSnackbar } from '@openmrs/esm-framework';
|
|
4
5
|
import { type MappedBill } from '../types';
|
|
5
6
|
import { updateBillItems } from '../billing.resource';
|
|
6
|
-
import
|
|
7
|
+
import EditBillLineItemModal from './edit-bill-item.modal';
|
|
8
|
+
|
|
9
|
+
const mockUpdateBillItems = jest.mocked(updateBillItems);
|
|
10
|
+
const mockShowSnackbar = jest.mocked(showSnackbar);
|
|
7
11
|
|
|
8
|
-
// Mock external dependencies
|
|
9
12
|
jest.mock('../billing.resource', () => ({
|
|
10
13
|
updateBillItems: jest.fn(() => Promise.resolve({})),
|
|
11
14
|
}));
|
|
12
15
|
|
|
13
|
-
jest.mock('@openmrs/esm-framework', () => ({
|
|
14
|
-
showSnackbar: jest.fn(),
|
|
15
|
-
}));
|
|
16
|
-
|
|
17
16
|
jest.mock('../billable-services/billable-service.resource', () => ({
|
|
18
17
|
useBillableServices: jest.fn(() => ({
|
|
19
18
|
billableServices: [],
|
|
@@ -77,12 +76,8 @@ const mockItem = {
|
|
|
77
76
|
describe('ChangeStatus component', () => {
|
|
78
77
|
const closeModalMock = jest.fn();
|
|
79
78
|
|
|
80
|
-
beforeEach(() => {
|
|
81
|
-
jest.clearAllMocks();
|
|
82
|
-
});
|
|
83
|
-
|
|
84
79
|
test('renders the form with correct fields and default values', () => {
|
|
85
|
-
render(<
|
|
80
|
+
render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
|
|
86
81
|
|
|
87
82
|
expect(screen.getByText('Edit bill line item?')).toBeInTheDocument();
|
|
88
83
|
expect(screen.getByText('John Doe · Main Cashpoint · 123456')).toBeInTheDocument();
|
|
@@ -91,24 +86,26 @@ describe('ChangeStatus component', () => {
|
|
|
91
86
|
expect(screen.getByText(/Total/)).toHaveTextContent('200');
|
|
92
87
|
});
|
|
93
88
|
|
|
94
|
-
test('updates total when quantity is changed', () => {
|
|
95
|
-
|
|
89
|
+
test('updates total when quantity is changed', async () => {
|
|
90
|
+
const user = userEvent.setup();
|
|
91
|
+
render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
|
|
96
92
|
|
|
97
93
|
const quantityInput = screen.getByRole('spinbutton', { name: /Quantity/ });
|
|
98
|
-
|
|
94
|
+
await user.type(quantityInput, '3');
|
|
99
95
|
|
|
100
96
|
expect(screen.getByText(/Total/)).toHaveTextContent('300');
|
|
101
97
|
});
|
|
102
98
|
|
|
103
99
|
test('submits the form and shows a success notification', async () => {
|
|
104
|
-
|
|
100
|
+
const user = userEvent.setup();
|
|
101
|
+
mockUpdateBillItems.mockResolvedValueOnce({} as FetchResponse<any>);
|
|
105
102
|
|
|
106
|
-
render(<
|
|
103
|
+
render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
|
|
107
104
|
|
|
108
|
-
|
|
105
|
+
await user.click(screen.getByText(/Save/));
|
|
109
106
|
|
|
110
107
|
await waitFor(() => {
|
|
111
|
-
expect(
|
|
108
|
+
expect(mockUpdateBillItems).toHaveBeenCalled();
|
|
112
109
|
expect(showSnackbar).toHaveBeenCalledWith({
|
|
113
110
|
title: 'Save Bill',
|
|
114
111
|
subtitle: 'Bill processing has been successful',
|
|
@@ -120,14 +117,15 @@ describe('ChangeStatus component', () => {
|
|
|
120
117
|
});
|
|
121
118
|
|
|
122
119
|
test('shows error notification when submission fails', async () => {
|
|
123
|
-
|
|
120
|
+
const user = userEvent.setup();
|
|
121
|
+
mockUpdateBillItems.mockRejectedValueOnce({ message: 'Error occurred' });
|
|
124
122
|
|
|
125
|
-
render(<
|
|
123
|
+
render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
|
|
126
124
|
|
|
127
|
-
|
|
125
|
+
await user.click(screen.getByText(/Save/));
|
|
128
126
|
|
|
129
127
|
await waitFor(() => {
|
|
130
|
-
expect(
|
|
128
|
+
expect(mockShowSnackbar).toHaveBeenCalledWith({
|
|
131
129
|
title: 'Bill processing error',
|
|
132
130
|
kind: 'error',
|
|
133
131
|
subtitle: 'Error occurred',
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
StructuredListWrapper,
|
|
10
10
|
} from '@carbon/react';
|
|
11
11
|
import { useTranslation } from 'react-i18next';
|
|
12
|
-
import { useConfig } from '@openmrs/esm-framework';
|
|
12
|
+
import { getCoreTranslation, useConfig } from '@openmrs/esm-framework';
|
|
13
13
|
import { convertToCurrency } from '../../helpers';
|
|
14
14
|
import { type MappedBill, type LineItem } from '../../types';
|
|
15
15
|
import BillWaiverForm from './bill-waiver-form.component';
|
|
@@ -44,7 +44,7 @@ const PatientBillsSelections: React.FC<{ bills: MappedBill; setPatientUuid: (pat
|
|
|
44
44
|
<StructuredListCell head>{t('quantity', 'Quantity')}</StructuredListCell>
|
|
45
45
|
<StructuredListCell head>{t('unitPrice', 'Unit Price')}</StructuredListCell>
|
|
46
46
|
<StructuredListCell head>{t('total', 'Total')}</StructuredListCell>
|
|
47
|
-
<StructuredListCell head>{
|
|
47
|
+
<StructuredListCell head>{getCoreTranslation('actions')}</StructuredListCell>
|
|
48
48
|
</StructuredListRow>
|
|
49
49
|
</StructuredListHead>
|
|
50
50
|
<StructuredListBody>
|
|
@@ -37,9 +37,9 @@ const PatientBills: React.FC<PatientBillsProps> = ({ patientUuid, bills, setPati
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
const tableHeaders = [
|
|
40
|
-
{ header: 'Date', key: 'date' },
|
|
41
|
-
{ header: 'Billable Service', key: 'billableService' },
|
|
42
|
-
{ header: 'Total Amount', key: 'totalAmount' },
|
|
40
|
+
{ header: t('date', 'Date'), key: 'date' },
|
|
41
|
+
{ header: t('billableService', 'Billable Service'), key: 'billableService' },
|
|
42
|
+
{ header: t('totalAmount', 'Total Amount'), key: 'totalAmount' },
|
|
43
43
|
];
|
|
44
44
|
|
|
45
45
|
const tableRows = bills.map((bill) => ({
|
|
@@ -3,6 +3,7 @@ import { type OpenmrsResource, openmrsFetch, restBaseUrl, useOpenmrsFetchAll, us
|
|
|
3
3
|
import { type ServiceConcept } from '../types';
|
|
4
4
|
import { apiBasePath } from '../constants';
|
|
5
5
|
import { type BillableService } from '../types/index';
|
|
6
|
+
import type { BillingConfig } from '../config-schema';
|
|
6
7
|
|
|
7
8
|
type ResponseObject = {
|
|
8
9
|
results: Array<OpenmrsResource>;
|
|
@@ -22,8 +23,8 @@ export const useBillableServices = () => {
|
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
export function useServiceTypes() {
|
|
25
|
-
const
|
|
26
|
-
const serviceConceptUuid =
|
|
26
|
+
const { serviceTypes } = useConfig<BillingConfig>();
|
|
27
|
+
const serviceConceptUuid = serviceTypes.billableService;
|
|
27
28
|
const url = `${restBaseUrl}/concept/${serviceConceptUuid}?v=custom:(setMembers:(uuid,display))`;
|
|
28
29
|
|
|
29
30
|
const { data, error, isLoading } = useSWR<{ data }>(url, openmrsFetch);
|
|
@@ -47,7 +48,7 @@ export const usePaymentModes = () => {
|
|
|
47
48
|
};
|
|
48
49
|
};
|
|
49
50
|
|
|
50
|
-
export const
|
|
51
|
+
export const createBillableService = (payload: any) => {
|
|
51
52
|
const url = `${apiBasePath}api/billable-service`;
|
|
52
53
|
return openmrsFetch(url, {
|
|
53
54
|
method: 'POST',
|
|
@@ -9,7 +9,7 @@ import BillWaiver from './bill-waiver/bill-waiver.component';
|
|
|
9
9
|
import BillableServicesDashboard from './dashboard/dashboard.component';
|
|
10
10
|
import BillingHeader from '../billing-header/billing-header.component';
|
|
11
11
|
import CashPointConfiguration from './cash-point/cash-point-configuration.component';
|
|
12
|
-
import PaymentModesConfig from './
|
|
12
|
+
import PaymentModesConfig from './payment-modes/payment-modes-config.component';
|
|
13
13
|
import styles from './billable-services.scss';
|
|
14
14
|
|
|
15
15
|
const BillableServiceHome: React.FC = () => {
|