@openmrs/esm-billing-app 1.0.2-pre.78 → 1.0.2-pre.786
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/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/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 +13 -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} +100 -78
- package/src/bill-item-actions/edit-bill-item.test.tsx +116 -31
- 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 +216 -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 +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 +17 -6
- package/src/invoice/invoice-table.component.tsx +35 -69
- package/src/invoice/invoice-table.scss +1 -5
- package/src/invoice/invoice-table.test.tsx +273 -62
- package/src/invoice/invoice.component.tsx +36 -29
- package/src/invoice/invoice.scss +7 -4
- package/src/invoice/invoice.test.tsx +324 -120
- package/src/invoice/payments/payment-form/payment-form.component.tsx +31 -29
- 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 +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/left-panel-link.test.tsx +1 -4
- 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 +70 -21
- package/translations/ar.json +70 -21
- package/translations/ar_SY.json +70 -21
- package/translations/bn.json +75 -26
- package/translations/de.json +70 -21
- package/translations/en.json +70 -21
- package/translations/en_US.json +70 -21
- package/translations/es.json +70 -21
- package/translations/es_MX.json +70 -21
- package/translations/fr.json +83 -34
- package/translations/he.json +70 -21
- package/translations/hi.json +70 -21
- package/translations/hi_IN.json +70 -21
- package/translations/id.json +70 -21
- package/translations/it.json +105 -56
- package/translations/ka.json +70 -21
- package/translations/km.json +70 -21
- package/translations/ku.json +70 -21
- package/translations/ky.json +70 -21
- package/translations/lg.json +70 -21
- package/translations/ne.json +70 -21
- package/translations/pl.json +70 -21
- package/translations/pt.json +70 -21
- package/translations/pt_BR.json +70 -21
- package/translations/qu.json +70 -21
- package/translations/ro_RO.json +214 -165
- package/translations/ru_RU.json +70 -21
- package/translations/si.json +70 -21
- package/translations/sw.json +70 -21
- package/translations/sw_KE.json +70 -21
- package/translations/tr.json +70 -21
- package/translations/tr_TR.json +70 -21
- package/translations/uk.json +70 -21
- package/translations/uz.json +70 -21
- package/translations/uz@Latn.json +70 -21
- package/translations/uz_UZ.json +70 -21
- package/translations/vi.json +70 -21
- package/translations/zh.json +70 -21
- package/translations/zh_CN.json +125 -76
- 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
|
@@ -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);
|
|
@@ -39,8 +40,23 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
|
|
|
39
40
|
const schema = useMemo(
|
|
40
41
|
() =>
|
|
41
42
|
z.object({
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
// NOTE: Frontend-only validation - quantities <1 or >100 can still be submitted via API.
|
|
44
|
+
// Backend (BillServiceImpl.java:100) has empty validate() method.
|
|
45
|
+
// TODO: Add server-side validation to enforce data integrity
|
|
46
|
+
quantity: z.coerce
|
|
47
|
+
.number({
|
|
48
|
+
required_error: t('quantityRequired', 'Quantity is required'),
|
|
49
|
+
invalid_type_error: t('quantityMustBeNumber', 'Quantity must be a valid number'),
|
|
50
|
+
})
|
|
51
|
+
.int(t('quantityMustBeInteger', 'Quantity must be a whole number'))
|
|
52
|
+
.min(1, t('quantityMustBeAtLeastOne', 'Quantity must be at least 1'))
|
|
53
|
+
.max(100, t('quantityCannotExceed100', 'Quantity cannot exceed 100')),
|
|
54
|
+
price: z.coerce
|
|
55
|
+
.number({
|
|
56
|
+
required_error: t('priceIsRequired', 'Price is required'),
|
|
57
|
+
invalid_type_error: t('priceMustBeNumber', 'Price must be a valid number'),
|
|
58
|
+
})
|
|
59
|
+
.positive(t('priceMustBePositive', 'Price must be greater than 0')),
|
|
44
60
|
}),
|
|
45
61
|
[t],
|
|
46
62
|
);
|
|
@@ -56,12 +72,12 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
|
|
|
56
72
|
const {
|
|
57
73
|
control,
|
|
58
74
|
handleSubmit,
|
|
59
|
-
formState: { isSubmitting, errors
|
|
75
|
+
formState: { isSubmitting, errors },
|
|
60
76
|
watch,
|
|
61
77
|
} = useForm<BillLineItemForm>({
|
|
62
78
|
defaultValues: {
|
|
63
|
-
quantity: item.quantity
|
|
64
|
-
price: item.price
|
|
79
|
+
quantity: item.quantity,
|
|
80
|
+
price: item.price,
|
|
65
81
|
},
|
|
66
82
|
resolver: zodResolver(schema),
|
|
67
83
|
});
|
|
@@ -70,17 +86,19 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
|
|
|
70
86
|
const price = watch('price');
|
|
71
87
|
|
|
72
88
|
useEffect(() => {
|
|
73
|
-
const
|
|
74
|
-
|
|
89
|
+
const quantityNum = typeof quantity === 'number' ? quantity : parseFloat(quantity) || 0;
|
|
90
|
+
const priceNum = typeof price === 'number' ? price : parseFloat(price) || 0;
|
|
91
|
+
const newTotal = quantityNum * priceNum;
|
|
92
|
+
setTotal(isNaN(newTotal) ? 0 : newTotal);
|
|
75
93
|
}, [quantity, price]);
|
|
76
94
|
|
|
77
|
-
const onSubmit = (data: BillLineItemForm) => {
|
|
95
|
+
const onSubmit = async (data: BillLineItemForm) => {
|
|
78
96
|
const url = `${apiBasePath}bill`;
|
|
79
97
|
|
|
80
98
|
const newItem = {
|
|
81
99
|
...item,
|
|
82
|
-
quantity:
|
|
83
|
-
price:
|
|
100
|
+
quantity: data.quantity,
|
|
101
|
+
price: data.price,
|
|
84
102
|
billableService: getBillableServiceUuid(billableServices, item.billableService),
|
|
85
103
|
item: item?.item,
|
|
86
104
|
};
|
|
@@ -89,8 +107,9 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
|
|
|
89
107
|
.filter((currItem) => currItem.uuid !== item?.uuid)
|
|
90
108
|
.map((currItem) => ({
|
|
91
109
|
...currItem,
|
|
92
|
-
billableService: getBillableServiceUuid(billableServices,
|
|
110
|
+
billableService: getBillableServiceUuid(billableServices, currItem.billableService),
|
|
93
111
|
}));
|
|
112
|
+
|
|
94
113
|
const updatedLineItems = previousLineitems.concat(newItem);
|
|
95
114
|
|
|
96
115
|
const payload = {
|
|
@@ -101,62 +120,67 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
|
|
|
101
120
|
status: bill.status,
|
|
102
121
|
uuid: bill.uuid,
|
|
103
122
|
};
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await updateBillItems(payload);
|
|
126
|
+
mutate((key) => typeof key === 'string' && key.startsWith(url), undefined, { revalidate: true });
|
|
127
|
+
showSnackbar({
|
|
128
|
+
title: t('lineItemUpdated', 'Line item updated'),
|
|
129
|
+
subtitle: t('lineItemUpdateSuccess', 'The bill line item has been updated successfully'),
|
|
130
|
+
kind: 'success',
|
|
131
|
+
});
|
|
132
|
+
closeModal();
|
|
133
|
+
} catch (error) {
|
|
134
|
+
showSnackbar({
|
|
135
|
+
title: t('lineItemUpdateFailed', 'Failed to update line item'),
|
|
136
|
+
subtitle:
|
|
137
|
+
error?.message || t('lineItemUpdateErrorDefault', 'Unable to update the bill line item. Please try again.'),
|
|
138
|
+
kind: 'error',
|
|
139
|
+
});
|
|
140
|
+
}
|
|
119
141
|
};
|
|
120
142
|
|
|
121
143
|
if (Object.keys(bill)?.length === 0) {
|
|
122
144
|
return <ModalHeader closeModal={closeModal} title={t('billLineItemEmpty', 'This bill has no line items')} />;
|
|
123
145
|
}
|
|
124
146
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
<
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
<
|
|
147
|
+
return (
|
|
148
|
+
<>
|
|
149
|
+
<ModalHeader closeModal={closeModal} title={t('editBillLineItem', 'Edit bill line item')} />
|
|
150
|
+
<Form onSubmit={handleSubmit(onSubmit, onError)}>
|
|
151
|
+
<ModalBody>
|
|
152
|
+
<Stack gap={5}>
|
|
131
153
|
<div className={styles.modalBody}>
|
|
132
154
|
<h5>
|
|
133
155
|
{bill?.patientName} · {bill?.cashPointName} · {bill?.receiptNumber}
|
|
134
156
|
</h5>
|
|
135
157
|
</div>
|
|
136
|
-
<section
|
|
158
|
+
<section>
|
|
137
159
|
<p className={styles.label}>
|
|
138
|
-
{t('item', 'Item')}
|
|
160
|
+
{t('item', 'Item')}: {item?.billableService ? item?.billableService : item?.item}
|
|
139
161
|
</p>
|
|
140
162
|
<p className={styles.label}>
|
|
141
|
-
{t('currentPrice', 'Current price')}
|
|
163
|
+
{t('currentPrice', 'Current price')}: {item?.price}
|
|
142
164
|
</p>
|
|
143
165
|
<p className={styles.label}>
|
|
144
|
-
{t('status', 'status')}
|
|
166
|
+
{t('status', 'status')}: {item?.paymentStatus}
|
|
145
167
|
</p>
|
|
146
168
|
<Controller
|
|
147
169
|
name="quantity"
|
|
148
170
|
control={control}
|
|
149
|
-
render={({ field: { onChange,
|
|
171
|
+
render={({ field: { onChange, value } }) => (
|
|
150
172
|
<NumberInput
|
|
151
|
-
|
|
152
|
-
id="quantityInput"
|
|
153
|
-
min={0}
|
|
154
|
-
max={100}
|
|
155
|
-
value={value}
|
|
156
|
-
onChange={onChange}
|
|
173
|
+
disableWheel
|
|
157
174
|
className={styles.controlField}
|
|
158
|
-
|
|
175
|
+
hideSteppers
|
|
176
|
+
id="quantityInput"
|
|
177
|
+
invalid={!!errors.quantity}
|
|
159
178
|
invalidText={errors.quantity?.message}
|
|
179
|
+
label={t('quantity', 'Quantity')}
|
|
180
|
+
onChange={(_event, state: { value: number | string; direction: string }) => {
|
|
181
|
+
onChange(state.value);
|
|
182
|
+
}}
|
|
183
|
+
value={value}
|
|
160
184
|
/>
|
|
161
185
|
)}
|
|
162
186
|
/>
|
|
@@ -166,18 +190,18 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
|
|
|
166
190
|
control={control}
|
|
167
191
|
render={({ field: { value } }) => (
|
|
168
192
|
<TextInput
|
|
193
|
+
className={styles.controlField}
|
|
194
|
+
helperText={t('unitPriceHelperText', 'This is the unit price for this item.')}
|
|
169
195
|
id="priceInput"
|
|
170
196
|
labelText={t('price', 'Unit Price')}
|
|
197
|
+
readOnly
|
|
171
198
|
value={value}
|
|
172
|
-
readOnly={true}
|
|
173
|
-
className={styles.controlField}
|
|
174
|
-
helperText="This is the unit Price for this item."
|
|
175
199
|
/>
|
|
176
200
|
)}
|
|
177
201
|
/>
|
|
178
202
|
|
|
179
203
|
<p className={styles.label}>
|
|
180
|
-
{t('total', 'Total')}
|
|
204
|
+
{t('total', 'Total')}: {total}{' '}
|
|
181
205
|
</p>
|
|
182
206
|
|
|
183
207
|
{showErrorNotification && (
|
|
@@ -191,31 +215,29 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
|
|
|
191
215
|
</Column>
|
|
192
216
|
)}
|
|
193
217
|
</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
|
-
}
|
|
218
|
+
</Stack>
|
|
219
|
+
</ModalBody>
|
|
220
|
+
<ModalFooter>
|
|
221
|
+
<Button kind="secondary" onClick={closeModal}>
|
|
222
|
+
{getCoreTranslation('cancel')}
|
|
223
|
+
</Button>
|
|
224
|
+
<Button type="submit" disabled={isSubmitting}>
|
|
225
|
+
{isSubmitting ? (
|
|
226
|
+
<div className={styles.inline}>
|
|
227
|
+
<InlineLoading
|
|
228
|
+
status="active"
|
|
229
|
+
iconDescription={t('submitting', 'Submitting')}
|
|
230
|
+
description={t('submitting', 'Submitting') + '...'}
|
|
231
|
+
/>
|
|
232
|
+
</div>
|
|
233
|
+
) : (
|
|
234
|
+
getCoreTranslation('save')
|
|
235
|
+
)}
|
|
236
|
+
</Button>
|
|
237
|
+
</ModalFooter>
|
|
238
|
+
</Form>
|
|
239
|
+
</>
|
|
240
|
+
);
|
|
219
241
|
};
|
|
220
242
|
|
|
221
|
-
export default
|
|
243
|
+
export default EditBillLineItemModal;
|