@openmrs/esm-billing-app 1.0.2-pre.92 → 1.0.2-pre.933
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/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/8572.js +1 -0
- package/dist/8572.js.map +1 -0
- 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 +271 -285
- 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 +898 -0
- package/src/billable-services/billable-service-form/billable-service-form.workspace.tsx +504 -0
- package/src/billable-services/billable-service.resource.ts +71 -27
- 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/delete-payment-mode.modal.tsx +77 -0
- package/src/billable-services/payment-modes/payment-mode-form.modal.tsx +131 -0
- package/src/billable-services/payment-modes/payment-modes-config.component.tsx +139 -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 +86 -23
- package/translations/am.json +132 -77
- package/translations/ar.json +133 -78
- package/translations/ar_SY.json +133 -78
- package/translations/bn.json +135 -80
- package/translations/de.json +133 -78
- package/translations/en.json +134 -79
- package/translations/en_US.json +133 -78
- package/translations/es.json +132 -77
- package/translations/es_MX.json +133 -78
- package/translations/fr.json +138 -83
- package/translations/he.json +132 -77
- package/translations/hi.json +133 -78
- package/translations/hi_IN.json +133 -78
- package/translations/id.json +133 -78
- package/translations/it.json +159 -104
- package/translations/ka.json +133 -78
- package/translations/km.json +132 -77
- package/translations/ku.json +133 -78
- package/translations/ky.json +133 -78
- package/translations/lg.json +133 -78
- package/translations/ne.json +133 -78
- package/translations/pl.json +133 -78
- package/translations/pt.json +133 -78
- package/translations/pt_BR.json +133 -78
- package/translations/qu.json +133 -78
- package/translations/ro_RO.json +220 -165
- package/translations/ru_RU.json +133 -78
- package/translations/si.json +133 -78
- package/translations/sw.json +133 -78
- package/translations/sw_KE.json +133 -78
- package/translations/tr.json +133 -78
- package/translations/tr_TR.json +133 -78
- package/translations/uk.json +133 -78
- package/translations/uz.json +133 -78
- package/translations/uz@Latn.json +133 -78
- package/translations/uz_UZ.json +133 -78
- package/translations/vi.json +133 -78
- package/translations/zh.json +133 -78
- package/translations/zh_CN.json +163 -108
- 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
|
@@ -0,0 +1,131 @@
|
|
|
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 { useSWRConfig } from 'swr';
|
|
7
|
+
import { Button, Form, ModalBody, ModalFooter, ModalHeader, Stack, TextInput } from '@carbon/react';
|
|
8
|
+
import { showSnackbar, getCoreTranslation } from '@openmrs/esm-framework';
|
|
9
|
+
import { type PaymentModePayload } from '../../types';
|
|
10
|
+
import { apiBasePath } from '../../constants';
|
|
11
|
+
import { createPaymentMode, updatePaymentMode } from '../billable-service.resource';
|
|
12
|
+
|
|
13
|
+
type PaymentModeFormValues = {
|
|
14
|
+
uuid?: string;
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
interface PaymentModeFormModalProps {
|
|
20
|
+
closeModal: () => void;
|
|
21
|
+
editPaymentMode?: PaymentModeFormValues;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const PaymentModeFormModal: React.FC<PaymentModeFormModalProps> = ({ closeModal, editPaymentMode }) => {
|
|
25
|
+
const { t } = useTranslation();
|
|
26
|
+
const { mutate } = useSWRConfig();
|
|
27
|
+
|
|
28
|
+
const paymentModeSchema = z.object({
|
|
29
|
+
name: z.string().min(1, t('paymentModeNameRequired', 'Payment mode name is required')),
|
|
30
|
+
description: z.string().optional(),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const {
|
|
34
|
+
control,
|
|
35
|
+
handleSubmit,
|
|
36
|
+
reset,
|
|
37
|
+
formState: { errors, isSubmitting },
|
|
38
|
+
} = useForm<PaymentModeFormValues>({
|
|
39
|
+
resolver: zodResolver(paymentModeSchema),
|
|
40
|
+
defaultValues: {
|
|
41
|
+
name: editPaymentMode?.name ?? '',
|
|
42
|
+
description: editPaymentMode?.description ?? '',
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const onSubmit = async (data: PaymentModeFormValues) => {
|
|
47
|
+
const url = `${apiBasePath}paymentMode`;
|
|
48
|
+
try {
|
|
49
|
+
const payload: PaymentModePayload = {
|
|
50
|
+
name: data.name,
|
|
51
|
+
description: data.description,
|
|
52
|
+
};
|
|
53
|
+
if (editPaymentMode?.uuid) {
|
|
54
|
+
await updatePaymentMode(editPaymentMode.uuid, payload);
|
|
55
|
+
} else {
|
|
56
|
+
await createPaymentMode(payload);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
mutate((key) => typeof key === 'string' && key.startsWith(url), undefined, { revalidate: true });
|
|
60
|
+
|
|
61
|
+
showSnackbar({
|
|
62
|
+
title: t('success', 'Success'),
|
|
63
|
+
subtitle: t('paymentModeSaved', 'Payment mode was successfully saved.'),
|
|
64
|
+
kind: 'success',
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
closeModal();
|
|
68
|
+
reset({ name: '', description: '' });
|
|
69
|
+
} catch (err) {
|
|
70
|
+
showSnackbar({
|
|
71
|
+
title: getCoreTranslation('error'),
|
|
72
|
+
subtitle: err?.message || t('errorSavingPaymentMode', 'An error occurred while saving the payment mode.'),
|
|
73
|
+
kind: 'error',
|
|
74
|
+
isLowContrast: false,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<>
|
|
81
|
+
<ModalHeader
|
|
82
|
+
closeModal={closeModal}
|
|
83
|
+
title={editPaymentMode ? t('editPaymentMode', 'Edit payment mode') : t('addPaymentMode', 'Add payment mode')}
|
|
84
|
+
/>
|
|
85
|
+
<Form onSubmit={handleSubmit(onSubmit)}>
|
|
86
|
+
<ModalBody>
|
|
87
|
+
<Stack gap={5}>
|
|
88
|
+
<Controller
|
|
89
|
+
name="name"
|
|
90
|
+
control={control}
|
|
91
|
+
render={({ field }) => (
|
|
92
|
+
<TextInput
|
|
93
|
+
id="payment-mode-name"
|
|
94
|
+
labelText={t('paymentModeNameLabel', 'Payment mode name')}
|
|
95
|
+
placeholder={t('paymentModeNamePlaceholder', 'For example, Cash, Credit Card')}
|
|
96
|
+
invalid={!!errors.name}
|
|
97
|
+
invalidText={errors.name?.message}
|
|
98
|
+
{...field}
|
|
99
|
+
/>
|
|
100
|
+
)}
|
|
101
|
+
/>
|
|
102
|
+
<Controller
|
|
103
|
+
name="description"
|
|
104
|
+
control={control}
|
|
105
|
+
render={({ field }) => (
|
|
106
|
+
<TextInput
|
|
107
|
+
id="payment-mode-description"
|
|
108
|
+
labelText={t('description', 'Description')}
|
|
109
|
+
placeholder={t('descriptionPlaceholder', 'For example, Used for all cash transactions')}
|
|
110
|
+
invalid={!!errors.description}
|
|
111
|
+
invalidText={errors.description?.message}
|
|
112
|
+
{...field}
|
|
113
|
+
/>
|
|
114
|
+
)}
|
|
115
|
+
/>
|
|
116
|
+
</Stack>
|
|
117
|
+
</ModalBody>
|
|
118
|
+
<ModalFooter>
|
|
119
|
+
<Button kind="secondary" onClick={closeModal}>
|
|
120
|
+
{getCoreTranslation('cancel')}
|
|
121
|
+
</Button>
|
|
122
|
+
<Button type="submit" disabled={isSubmitting}>
|
|
123
|
+
{isSubmitting ? t('saving', 'Saving') + '...' : getCoreTranslation('save')}
|
|
124
|
+
</Button>
|
|
125
|
+
</ModalFooter>
|
|
126
|
+
</Form>
|
|
127
|
+
</>
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export default PaymentModeFormModal;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
DataTable,
|
|
5
|
+
InlineLoading,
|
|
6
|
+
OverflowMenu,
|
|
7
|
+
OverflowMenuItem,
|
|
8
|
+
Table,
|
|
9
|
+
TableBody,
|
|
10
|
+
TableCell,
|
|
11
|
+
TableContainer,
|
|
12
|
+
TableHead,
|
|
13
|
+
TableHeader,
|
|
14
|
+
TableRow,
|
|
15
|
+
} from '@carbon/react';
|
|
16
|
+
import { Add } from '@carbon/react/icons';
|
|
17
|
+
import { useTranslation } from 'react-i18next';
|
|
18
|
+
import { showModal, getCoreTranslation, ErrorState } from '@openmrs/esm-framework';
|
|
19
|
+
import { CardHeader } from '@openmrs/esm-patient-common-lib';
|
|
20
|
+
import { usePaymentModes, type PaymentMode } from '../billable-service.resource';
|
|
21
|
+
import styles from './payment-modes-config.scss';
|
|
22
|
+
|
|
23
|
+
const PaymentModesConfig: React.FC = () => {
|
|
24
|
+
const { t } = useTranslation();
|
|
25
|
+
const { paymentModes, error, isLoadingPaymentModes } = usePaymentModes();
|
|
26
|
+
|
|
27
|
+
const handleAddPaymentMode = () => {
|
|
28
|
+
const dispose = showModal('payment-mode-form-modal', {
|
|
29
|
+
closeModal: () => dispose(),
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const handleDeletePaymentMode = (paymentMode: PaymentMode) => {
|
|
34
|
+
const dispose = showModal('delete-payment-mode-modal', {
|
|
35
|
+
paymentModeUuid: paymentMode.uuid,
|
|
36
|
+
paymentModeName: paymentMode.name,
|
|
37
|
+
closeModal: () => dispose(),
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const handleEditPaymentMode = (paymentMode: PaymentMode) => {
|
|
42
|
+
const dispose = showModal('payment-mode-form-modal', {
|
|
43
|
+
editPaymentMode: paymentMode,
|
|
44
|
+
closeModal: () => dispose(),
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const rowData = paymentModes.map((mode) => ({
|
|
49
|
+
id: mode.uuid,
|
|
50
|
+
name: mode.name,
|
|
51
|
+
description: mode.description || '--',
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
const headerData = [
|
|
55
|
+
{ key: 'name', header: t('name', 'Name') },
|
|
56
|
+
{ key: 'description', header: t('description', 'Description') },
|
|
57
|
+
{ key: 'actions', header: getCoreTranslation('actions') },
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
if (isLoadingPaymentModes) {
|
|
61
|
+
return (
|
|
62
|
+
<InlineLoading
|
|
63
|
+
status="active"
|
|
64
|
+
iconDescription={getCoreTranslation('loading')}
|
|
65
|
+
description={t('loading', 'Loading data') + '...'}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (error) {
|
|
71
|
+
return <ErrorState headerTitle={t('paymentMode', 'Payment mode')} error={error} />;
|
|
72
|
+
}
|
|
73
|
+
return (
|
|
74
|
+
<div className={styles.container}>
|
|
75
|
+
<div className={styles.card}>
|
|
76
|
+
<CardHeader title={t('paymentModeHistory', 'Payment mode history')}>
|
|
77
|
+
<Button renderIcon={Add} onClick={handleAddPaymentMode} kind="ghost">
|
|
78
|
+
{t('addNewPaymentMode', 'Add new payment mode')}
|
|
79
|
+
</Button>
|
|
80
|
+
</CardHeader>
|
|
81
|
+
<DataTable rows={rowData} headers={headerData} isSortable size="lg">
|
|
82
|
+
{({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
|
|
83
|
+
<TableContainer>
|
|
84
|
+
<Table className={styles.table} {...getTableProps()}>
|
|
85
|
+
<TableHead>
|
|
86
|
+
<TableRow>
|
|
87
|
+
{headers.map((header) => (
|
|
88
|
+
<TableHeader key={header.key} {...getHeaderProps({ header })}>
|
|
89
|
+
{header.header}
|
|
90
|
+
</TableHeader>
|
|
91
|
+
))}
|
|
92
|
+
</TableRow>
|
|
93
|
+
</TableHead>
|
|
94
|
+
<TableBody>
|
|
95
|
+
{rows.map((row) => (
|
|
96
|
+
<TableRow key={row.id} {...getRowProps({ row })}>
|
|
97
|
+
{row.cells.map((cell) =>
|
|
98
|
+
cell.info.header !== 'actions' ? (
|
|
99
|
+
<TableCell key={cell.id}>{cell.value}</TableCell>
|
|
100
|
+
) : (
|
|
101
|
+
<TableCell key={cell.id}>
|
|
102
|
+
<OverflowMenu>
|
|
103
|
+
<OverflowMenuItem
|
|
104
|
+
className={styles.menuItem}
|
|
105
|
+
itemText={getCoreTranslation('edit')}
|
|
106
|
+
onClick={() => {
|
|
107
|
+
const selected = paymentModes.find((p) => p.uuid === row.id);
|
|
108
|
+
if (selected) {
|
|
109
|
+
handleEditPaymentMode(selected);
|
|
110
|
+
}
|
|
111
|
+
}}
|
|
112
|
+
/>
|
|
113
|
+
<OverflowMenuItem
|
|
114
|
+
className={styles.menuItem}
|
|
115
|
+
itemText={getCoreTranslation('delete')}
|
|
116
|
+
onClick={() => {
|
|
117
|
+
const selected = paymentModes.find((p) => p.uuid === row.id);
|
|
118
|
+
if (selected) {
|
|
119
|
+
handleDeletePaymentMode(selected);
|
|
120
|
+
}
|
|
121
|
+
}}
|
|
122
|
+
/>
|
|
123
|
+
</OverflowMenu>
|
|
124
|
+
</TableCell>
|
|
125
|
+
),
|
|
126
|
+
)}
|
|
127
|
+
</TableRow>
|
|
128
|
+
))}
|
|
129
|
+
</TableBody>
|
|
130
|
+
</Table>
|
|
131
|
+
</TableContainer>
|
|
132
|
+
)}
|
|
133
|
+
</DataTable>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export default PaymentModesConfig;
|
|
@@ -8,10 +8,10 @@ describe('BillableServicesCardLink', () => {
|
|
|
8
8
|
const manageBillableServicesText = screen.getByText('Manage billable services');
|
|
9
9
|
expect(manageBillableServicesText).toHaveClass('heading');
|
|
10
10
|
|
|
11
|
-
const billiableText = screen.getByText('Billable
|
|
11
|
+
const billiableText = screen.getByText('Billable services', { exact: true });
|
|
12
12
|
expect(billiableText).toHaveClass('content');
|
|
13
13
|
|
|
14
|
-
const billiableServiceLink = screen.getByRole('link', { name: /
|
|
14
|
+
const billiableServiceLink = screen.getByRole('link', { name: /Manage billable services/i });
|
|
15
15
|
expect(billiableServiceLink).toHaveAttribute('href', '/spa/billable-services');
|
|
16
16
|
});
|
|
17
17
|
});
|
|
@@ -12,7 +12,7 @@ const BillableServicesCardLink: React.FC = () => {
|
|
|
12
12
|
<ClickableTile href={`${window.spaBase}/billable-services`} target="_blank" rel="noopener noreferrer">
|
|
13
13
|
<div>
|
|
14
14
|
<div className="heading">{header}</div>
|
|
15
|
-
<div className="content">{t('billableServices', 'Billable
|
|
15
|
+
<div className="content">{t('billableServices', 'Billable services')}</div>
|
|
16
16
|
</div>
|
|
17
17
|
<div className="iconWrapper">
|
|
18
18
|
<ArrowRight size={16} />
|
|
@@ -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 = {
|
|
@@ -22,20 +21,25 @@ const BillingCheckInForm: React.FC<BillingCheckInFormProps> = ({ patientUuid, se
|
|
|
22
21
|
const [paymentMethod, setPaymentMethod] = useState<any>();
|
|
23
22
|
let lineList = [];
|
|
24
23
|
|
|
25
|
-
const handleCreateExtraVisitInfo = useCallback(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
},
|
|
30
|
-
(error) => {
|
|
24
|
+
const handleCreateExtraVisitInfo = useCallback(
|
|
25
|
+
async (createBillPayload) => {
|
|
26
|
+
try {
|
|
27
|
+
await createPatientBill(createBillPayload);
|
|
31
28
|
showSnackbar({
|
|
32
|
-
title: 'Patient
|
|
33
|
-
subtitle: '
|
|
29
|
+
title: t('patientBill', 'Patient bill'),
|
|
30
|
+
subtitle: t('billCreatedSuccessfully', 'Bill created successfully'),
|
|
31
|
+
kind: 'success',
|
|
32
|
+
});
|
|
33
|
+
} catch (error) {
|
|
34
|
+
showSnackbar({
|
|
35
|
+
title: t('billCreationError', 'Bill creation error'),
|
|
36
|
+
subtitle: t('errorCreatingBill', 'An error occurred while creating the bill'),
|
|
34
37
|
kind: 'error',
|
|
35
38
|
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
[t],
|
|
42
|
+
);
|
|
39
43
|
|
|
40
44
|
const handleBillingService = ({ selectedItem }) => {
|
|
41
45
|
const cashPointUuid = cashPoints?.[0]?.uuid ?? '';
|
|
@@ -74,8 +78,8 @@ const BillingCheckInForm: React.FC<BillingCheckInFormProps> = ({ patientUuid, se
|
|
|
74
78
|
return (
|
|
75
79
|
<InlineLoading
|
|
76
80
|
status="active"
|
|
77
|
-
iconDescription={
|
|
78
|
-
description={t('loadingBillingServices', 'Loading billing services
|
|
81
|
+
iconDescription={getCoreTranslation('loading')}
|
|
82
|
+
description={`${t('loadingBillingServices', 'Loading billing services')}...`}
|
|
79
83
|
/>
|
|
80
84
|
);
|
|
81
85
|
}
|
|
@@ -97,7 +101,7 @@ const BillingCheckInForm: React.FC<BillingCheckInFormProps> = ({ patientUuid, se
|
|
|
97
101
|
<InlineNotification
|
|
98
102
|
kind="error"
|
|
99
103
|
lowContrast
|
|
100
|
-
title={t('billErrorService', '
|
|
104
|
+
title={t('billErrorService', 'Billing service error')}
|
|
101
105
|
subtitle={t('errorLoadingBillServices', 'Error loading bill services')}
|
|
102
106
|
/>
|
|
103
107
|
);
|
|
@@ -111,7 +115,7 @@ const BillingCheckInForm: React.FC<BillingCheckInFormProps> = ({ patientUuid, se
|
|
|
111
115
|
<div className={styles.sectionTitle}>{t('billing', 'Billing')}</div>
|
|
112
116
|
<div className={styles.sectionField}></div>
|
|
113
117
|
<Dropdown
|
|
114
|
-
label={t('selectBillableService', 'Select a billable service
|
|
118
|
+
label={t('selectBillableService', 'Select a billable service')}
|
|
115
119
|
onChange={handleBillingService}
|
|
116
120
|
id="billable-items"
|
|
117
121
|
items={lineList}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
3
|
import { screen, render } from '@testing-library/react';
|
|
4
|
-
import {
|
|
4
|
+
import { useConfig } from '@openmrs/esm-framework';
|
|
5
|
+
import { type BillingConfig } from '../config-schema';
|
|
6
|
+
import { useBillableItems, useCashPoint, usePaymentMethods } from './billing-form.resource';
|
|
5
7
|
import BillingCheckInForm from './billing-checkin-form.component';
|
|
6
8
|
|
|
9
|
+
const mockUseConfig = jest.mocked(useConfig<BillingConfig>);
|
|
10
|
+
const mockUseCashPoint = jest.mocked(useCashPoint);
|
|
11
|
+
const mockUseBillableItems = jest.mocked(useBillableItems);
|
|
12
|
+
const mockUsePaymentMethods = jest.mocked(usePaymentMethods);
|
|
13
|
+
|
|
7
14
|
const mockCashPoints = [
|
|
8
15
|
{
|
|
9
16
|
uuid: '54065383-b4d4-42d2-af4d-d250a1fd2590',
|
|
@@ -16,6 +23,7 @@ const mockCashPoints = [
|
|
|
16
23
|
const mockBillableItems = [
|
|
17
24
|
{
|
|
18
25
|
uuid: 'b37dddd6-4490-4bf7-b694-43bf19d04059',
|
|
26
|
+
name: 'Consultation',
|
|
19
27
|
conceptUuid: '1926AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
20
28
|
conceptName: 'Consultation billable item',
|
|
21
29
|
hasExpiration: false,
|
|
@@ -25,9 +33,21 @@ const mockBillableItems = [
|
|
|
25
33
|
categoryName: 'Non Drug',
|
|
26
34
|
commonName: 'Consultation',
|
|
27
35
|
acronym: 'CONSULT',
|
|
36
|
+
servicePrices: [
|
|
37
|
+
{
|
|
38
|
+
uuid: 'price-1',
|
|
39
|
+
name: 'Default',
|
|
40
|
+
price: '100.00',
|
|
41
|
+
paymentMode: {
|
|
42
|
+
uuid: '1c30ee58-82d4-4ea4-a8c1-4bf2f9dfc8cf',
|
|
43
|
+
name: 'Insurance',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
],
|
|
28
47
|
},
|
|
29
48
|
{
|
|
30
49
|
uuid: 'b47dddd6-4490-4bf7-b694-43bf19d04059',
|
|
50
|
+
name: 'Lab Testing',
|
|
31
51
|
conceptUuid: '1926AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
32
52
|
conceptName: 'Lab Testing billable item',
|
|
33
53
|
hasExpiration: false,
|
|
@@ -37,25 +57,65 @@ const mockBillableItems = [
|
|
|
37
57
|
categoryName: 'Non Drug',
|
|
38
58
|
commonName: 'Lab Testing',
|
|
39
59
|
acronym: 'CONSULT',
|
|
60
|
+
servicePrices: [
|
|
61
|
+
{
|
|
62
|
+
uuid: 'price-2',
|
|
63
|
+
name: 'Default',
|
|
64
|
+
price: '500.00001',
|
|
65
|
+
paymentMode: {
|
|
66
|
+
uuid: '1c30ee58-82d4-4ea4-a8c1-4bf2f9dfc8cf',
|
|
67
|
+
name: 'Insurance',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
],
|
|
40
71
|
},
|
|
41
72
|
];
|
|
42
73
|
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
74
|
+
const mockPaymentMethods = [
|
|
75
|
+
{
|
|
76
|
+
uuid: '1c30ee58-82d4-4ea4-a8c1-4bf2f9dfc8cf',
|
|
77
|
+
name: 'Insurance',
|
|
78
|
+
description: 'Insurance payment',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
uuid: '2c30ee58-82d4-4ea4-a8c1-4bf2f9dfc8cf',
|
|
82
|
+
name: 'Cash',
|
|
83
|
+
description: 'Cash payment',
|
|
84
|
+
},
|
|
85
|
+
];
|
|
47
86
|
|
|
48
87
|
jest.mock('./billing-form.resource', () => ({
|
|
49
88
|
useBillableItems: jest.fn(),
|
|
50
89
|
useCashPoint: jest.fn(),
|
|
51
90
|
createPatientBill: jest.fn(),
|
|
91
|
+
usePaymentMethods: jest.fn(),
|
|
52
92
|
}));
|
|
53
93
|
|
|
54
94
|
const testProps = { patientUuid: 'some-patient-uuid', setExtraVisitInfo: jest.fn() };
|
|
55
95
|
|
|
56
|
-
|
|
96
|
+
describe('BillingCheckInForm', () => {
|
|
57
97
|
beforeEach(() => {
|
|
58
98
|
jest.resetAllMocks();
|
|
99
|
+
mockUseConfig.mockReturnValue({
|
|
100
|
+
patientCatergory: {
|
|
101
|
+
paymentDetails: 'fbc0702d-b4c9-4968-be63-af8ad3ad6239',
|
|
102
|
+
paymentMethods: '8553afa0-bdb9-4d3c-8a98-05fa9350aa85',
|
|
103
|
+
policyNumber: '3a988e33-a6c0-4b76-b924-01abb998944b',
|
|
104
|
+
insuranceScheme: 'aac48226-d143-4274-80e0-264db4e368ee',
|
|
105
|
+
patientCategory: '3b9dfac8-9e4d-11ee-8c90-0242ac120002',
|
|
106
|
+
formPayloadPending: '919b51c9-8e2e-468f-8354-181bf3e55786',
|
|
107
|
+
},
|
|
108
|
+
catergoryConcepts: {
|
|
109
|
+
payingDetails: '44b34972-6630-4e5a-a9f6-a6eb0f109650',
|
|
110
|
+
nonPayingDetails: 'f3fb2d88-cccd-422c-8766-be101ba7bd2e',
|
|
111
|
+
insuranceDetails: 'beac329b-f1dc-4a33-9e7c-d95821a137a6',
|
|
112
|
+
},
|
|
113
|
+
nonPayingPatientCategories: {
|
|
114
|
+
childUnder5: '1528AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
115
|
+
student: '159465AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
116
|
+
},
|
|
117
|
+
} as BillingConfig);
|
|
118
|
+
mockUsePaymentMethods.mockReturnValue({ paymentModes: mockPaymentMethods, isLoading: false, error: null });
|
|
59
119
|
});
|
|
60
120
|
|
|
61
121
|
test('should show the loading spinner while retrieving data', () => {
|
|
@@ -72,59 +132,72 @@ xdescribe('BillingCheckInForm', () => {
|
|
|
72
132
|
mockUseCashPoint.mockReturnValueOnce({ cashPoints: [], isLoading: false, error });
|
|
73
133
|
renderBillingCheckinForm();
|
|
74
134
|
|
|
75
|
-
expect(screen.getByText(
|
|
76
|
-
expect(screen.getByText(
|
|
135
|
+
expect(screen.getByText(/billing service error/i)).toBeInTheDocument();
|
|
136
|
+
expect(screen.getByText(/error loading bill services/i)).toBeInTheDocument();
|
|
77
137
|
});
|
|
78
138
|
|
|
79
139
|
test('should render the form correctly and generate the required payload', async () => {
|
|
80
140
|
const user = userEvent.setup();
|
|
81
|
-
mockUseCashPoint.mockReturnValue({ cashPoints:
|
|
141
|
+
mockUseCashPoint.mockReturnValue({ cashPoints: mockCashPoints, isLoading: false, error: null });
|
|
82
142
|
mockUseBillableItems.mockReturnValue({ lineItems: mockBillableItems, isLoading: false, error: null });
|
|
83
143
|
renderBillingCheckinForm();
|
|
84
144
|
|
|
85
145
|
const paymentTypeSelect = screen.getByRole('group', { name: 'Payment Details' });
|
|
86
146
|
expect(paymentTypeSelect).toBeInTheDocument();
|
|
87
147
|
|
|
148
|
+
// Select "Paying" radio button
|
|
88
149
|
const paymentTypeRadio = screen.getByRole('radio', { name: 'Paying' });
|
|
89
150
|
expect(paymentTypeRadio).toBeInTheDocument();
|
|
90
151
|
await user.click(paymentTypeRadio);
|
|
91
152
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
153
|
+
// Wait for payment methods dropdown to appear and select a payment method
|
|
154
|
+
const paymentMethodsDropdown = await screen.findByRole('combobox', { name: /Payment methods/i });
|
|
155
|
+
expect(paymentMethodsDropdown).toBeInTheDocument();
|
|
156
|
+
await user.click(paymentMethodsDropdown);
|
|
95
157
|
|
|
96
|
-
|
|
158
|
+
// Select "Insurance" payment method
|
|
159
|
+
const insuranceOption = await screen.findByText('Insurance');
|
|
160
|
+
await user.click(insuranceOption);
|
|
161
|
+
|
|
162
|
+
// Now select billable service
|
|
163
|
+
const billableSelect = screen.getByRole('combobox', { name: 'Billable service' });
|
|
164
|
+
expect(billableSelect).toBeInTheDocument();
|
|
165
|
+
await user.click(billableSelect);
|
|
166
|
+
|
|
167
|
+
// Click on Lab Testing option
|
|
168
|
+
const labTestingOption = await screen.findByText(/Lab Testing \(Default:500\.00001\)/);
|
|
169
|
+
await user.click(labTestingOption);
|
|
97
170
|
|
|
98
171
|
expect(testProps.setExtraVisitInfo).toHaveBeenCalled();
|
|
99
172
|
expect(testProps.setExtraVisitInfo).toHaveBeenCalledWith({
|
|
100
173
|
createBillPayload: {
|
|
101
174
|
lineItems: [
|
|
102
175
|
{
|
|
103
|
-
|
|
176
|
+
billableService: 'b47dddd6-4490-4bf7-b694-43bf19d04059',
|
|
104
177
|
quantity: 1,
|
|
105
|
-
price: 500.00001,
|
|
178
|
+
price: '500.00001',
|
|
106
179
|
priceName: 'Default',
|
|
107
|
-
priceUuid: '',
|
|
180
|
+
priceUuid: 'price-2',
|
|
108
181
|
lineItemOrder: 0,
|
|
109
182
|
paymentStatus: 'PENDING',
|
|
110
183
|
},
|
|
111
184
|
],
|
|
112
|
-
cashPoint: '',
|
|
185
|
+
cashPoint: '54065383-b4d4-42d2-af4d-d250a1fd2590',
|
|
113
186
|
patient: 'some-patient-uuid',
|
|
114
187
|
status: 'PENDING',
|
|
115
188
|
payments: [],
|
|
116
189
|
},
|
|
117
190
|
handleCreateExtraVisitInfo: expect.anything(),
|
|
118
|
-
attributes: [
|
|
119
|
-
{
|
|
120
|
-
attributeType: '
|
|
191
|
+
attributes: expect.arrayContaining([
|
|
192
|
+
expect.objectContaining({
|
|
193
|
+
attributeType: 'fbc0702d-b4c9-4968-be63-af8ad3ad6239',
|
|
194
|
+
value: '44b34972-6630-4e5a-a9f6-a6eb0f109650',
|
|
195
|
+
}),
|
|
196
|
+
expect.objectContaining({
|
|
197
|
+
attributeType: '8553afa0-bdb9-4d3c-8a98-05fa9350aa85',
|
|
121
198
|
value: '1c30ee58-82d4-4ea4-a8c1-4bf2f9dfc8cf',
|
|
122
|
-
},
|
|
123
|
-
|
|
124
|
-
attributeType: '919b51c9-8e2e-468f-8354-181bf3e55786',
|
|
125
|
-
value: true,
|
|
126
|
-
},
|
|
127
|
-
],
|
|
199
|
+
}),
|
|
200
|
+
]),
|
|
128
201
|
});
|
|
129
202
|
});
|
|
130
203
|
});
|