@openmrs/esm-billing-app 1.0.2-pre.74 → 1.0.2-pre.740
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
|
@@ -2,59 +2,23 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|
|
2
2
|
import {
|
|
3
3
|
Button,
|
|
4
4
|
DataTable,
|
|
5
|
-
TableContainer,
|
|
6
5
|
Table,
|
|
7
|
-
TableHead,
|
|
8
|
-
TableRow,
|
|
9
|
-
TableHeader,
|
|
10
6
|
TableBody,
|
|
11
7
|
TableCell,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
Dropdown,
|
|
8
|
+
TableContainer,
|
|
9
|
+
TableHead,
|
|
10
|
+
TableHeader,
|
|
11
|
+
TableRow,
|
|
17
12
|
} from '@carbon/react';
|
|
18
13
|
import { Add } from '@carbon/react/icons';
|
|
19
14
|
import { useTranslation } from 'react-i18next';
|
|
20
|
-
import {
|
|
21
|
-
import { z } from 'zod';
|
|
22
|
-
import { zodResolver } from '@hookform/resolvers/zod';
|
|
23
|
-
import { showSnackbar, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
15
|
+
import { showSnackbar, openmrsFetch, restBaseUrl, showModal, getCoreTranslation } from '@openmrs/esm-framework';
|
|
24
16
|
import { CardHeader } from '@openmrs/esm-patient-common-lib';
|
|
25
17
|
import styles from './cash-point-configuration.scss';
|
|
26
18
|
|
|
27
|
-
// Validation schema
|
|
28
|
-
const cashPointSchema = z.object({
|
|
29
|
-
name: z.string().min(1, 'Cash Point Name is required'),
|
|
30
|
-
uuid: z
|
|
31
|
-
.string()
|
|
32
|
-
.min(1, 'UUID is required')
|
|
33
|
-
.regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i, 'Invalid UUID format'),
|
|
34
|
-
location: z.string().min(1, 'Location is required'),
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
type CashPointFormValues = z.infer<typeof cashPointSchema>;
|
|
38
|
-
|
|
39
19
|
const CashPointConfiguration: React.FC = () => {
|
|
40
20
|
const { t } = useTranslation();
|
|
41
21
|
const [cashPoints, setCashPoints] = useState([]);
|
|
42
|
-
const [locations, setLocations] = useState([]);
|
|
43
|
-
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
44
|
-
|
|
45
|
-
const {
|
|
46
|
-
control,
|
|
47
|
-
handleSubmit,
|
|
48
|
-
reset,
|
|
49
|
-
formState: { errors, isSubmitting },
|
|
50
|
-
} = useForm<CashPointFormValues>({
|
|
51
|
-
resolver: zodResolver(cashPointSchema),
|
|
52
|
-
defaultValues: {
|
|
53
|
-
name: '',
|
|
54
|
-
uuid: '',
|
|
55
|
-
location: '',
|
|
56
|
-
},
|
|
57
|
-
});
|
|
58
22
|
|
|
59
23
|
const fetchCashPoints = useCallback(async () => {
|
|
60
24
|
try {
|
|
@@ -62,7 +26,7 @@ const CashPointConfiguration: React.FC = () => {
|
|
|
62
26
|
setCashPoints(response.data.results || []);
|
|
63
27
|
} catch (err) {
|
|
64
28
|
showSnackbar({
|
|
65
|
-
title:
|
|
29
|
+
title: getCoreTranslation('error'),
|
|
66
30
|
subtitle: t('errorFetchingCashPoints', 'An error occurred while fetching cash points.'),
|
|
67
31
|
kind: 'error',
|
|
68
32
|
isLowContrast: false,
|
|
@@ -70,87 +34,14 @@ const CashPointConfiguration: React.FC = () => {
|
|
|
70
34
|
}
|
|
71
35
|
}, [t]);
|
|
72
36
|
|
|
73
|
-
const fetchLocations = useCallback(async () => {
|
|
74
|
-
try {
|
|
75
|
-
const response = await openmrsFetch(`${restBaseUrl}/location?v=default`);
|
|
76
|
-
const allLocations = response.data.results.map((loc) => ({
|
|
77
|
-
id: loc.uuid,
|
|
78
|
-
label: loc.display,
|
|
79
|
-
}));
|
|
80
|
-
setLocations(allLocations);
|
|
81
|
-
} catch (err) {
|
|
82
|
-
showSnackbar({
|
|
83
|
-
title: t('error', 'Error'),
|
|
84
|
-
subtitle: t('errorFetchingLocations', 'An error occurred while fetching locations.'),
|
|
85
|
-
kind: 'error',
|
|
86
|
-
isLowContrast: false,
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
}, [t]);
|
|
90
|
-
|
|
91
37
|
useEffect(() => {
|
|
92
38
|
fetchCashPoints();
|
|
93
|
-
|
|
94
|
-
}, [fetchCashPoints, fetchLocations]);
|
|
95
|
-
|
|
96
|
-
const onSubmit = async (data: CashPointFormValues) => {
|
|
97
|
-
const isDuplicate = cashPoints.some(
|
|
98
|
-
(point) => point.name.toLowerCase() === data.name.toLowerCase() || point.uuid === data.uuid,
|
|
99
|
-
);
|
|
39
|
+
}, [fetchCashPoints]);
|
|
100
40
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
'duplicateCashPointError',
|
|
106
|
-
'A cash point with the same name or UUID already exists. Please use a unique name and UUID.',
|
|
107
|
-
),
|
|
108
|
-
kind: 'error',
|
|
109
|
-
isLowContrast: false,
|
|
110
|
-
});
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
const response = await openmrsFetch(`${restBaseUrl}/billing/cashPoint`, {
|
|
116
|
-
method: 'POST',
|
|
117
|
-
headers: {
|
|
118
|
-
'Content-Type': 'application/json',
|
|
119
|
-
},
|
|
120
|
-
body: {
|
|
121
|
-
name: data.name,
|
|
122
|
-
uuid: data.uuid,
|
|
123
|
-
location: { uuid: data.location },
|
|
124
|
-
},
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
if (response.ok) {
|
|
128
|
-
showSnackbar({
|
|
129
|
-
title: t('success', 'Success'),
|
|
130
|
-
subtitle: t('cashPointSaved', 'Cash point was successfully saved.'),
|
|
131
|
-
kind: 'success',
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
setIsModalOpen(false);
|
|
135
|
-
reset({ name: '', uuid: '', location: '' });
|
|
136
|
-
fetchCashPoints();
|
|
137
|
-
} else {
|
|
138
|
-
const errorData = response.data || {};
|
|
139
|
-
showSnackbar({
|
|
140
|
-
title: t('error', 'Error'),
|
|
141
|
-
subtitle: errorData.message || t('errorSavingCashPoint', 'An error occurred while saving the cash point.'),
|
|
142
|
-
kind: 'error',
|
|
143
|
-
isLowContrast: false,
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
} catch (err) {
|
|
147
|
-
showSnackbar({
|
|
148
|
-
title: t('error', 'Error'),
|
|
149
|
-
subtitle: t('errorSavingCashPoint', 'An error occurred while saving the cash point.'),
|
|
150
|
-
kind: 'error',
|
|
151
|
-
isLowContrast: false,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
41
|
+
const handleAddCashPoint = () => {
|
|
42
|
+
showModal('add-cash-point-modal', {
|
|
43
|
+
onCashPointAdded: fetchCashPoints,
|
|
44
|
+
});
|
|
154
45
|
};
|
|
155
46
|
|
|
156
47
|
const rowData = cashPoints.map((point) => ({
|
|
@@ -164,18 +55,17 @@ const CashPointConfiguration: React.FC = () => {
|
|
|
164
55
|
{ key: 'name', header: t('name', 'Name') },
|
|
165
56
|
{ key: 'uuid', header: t('uuid', 'UUID') },
|
|
166
57
|
{ key: 'location', header: t('location', 'Location') },
|
|
167
|
-
{ key: 'actions', header: t('actions', 'Actions') },
|
|
168
58
|
];
|
|
169
59
|
|
|
170
60
|
return (
|
|
171
61
|
<div className={styles.container}>
|
|
172
62
|
<div className={styles.card}>
|
|
173
63
|
<CardHeader title={t('cashPointHistory', 'Cash Point History')}>
|
|
174
|
-
<Button renderIcon={Add} onClick={
|
|
175
|
-
{t('
|
|
64
|
+
<Button renderIcon={Add} onClick={handleAddCashPoint} kind="ghost">
|
|
65
|
+
{t('addNewCashPoint', 'Add New Cash Point')}
|
|
176
66
|
</Button>
|
|
177
67
|
</CardHeader>
|
|
178
|
-
<div
|
|
68
|
+
<div>
|
|
179
69
|
<DataTable rows={rowData} headers={headerData} isSortable size="lg">
|
|
180
70
|
{({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
|
|
181
71
|
<TableContainer>
|
|
@@ -192,17 +82,9 @@ const CashPointConfiguration: React.FC = () => {
|
|
|
192
82
|
<TableBody>
|
|
193
83
|
{rows.map((row) => (
|
|
194
84
|
<TableRow key={row.id} {...getRowProps({ row })}>
|
|
195
|
-
{row.cells.map((cell) =>
|
|
196
|
-
cell.
|
|
197
|
-
|
|
198
|
-
) : (
|
|
199
|
-
<TableCell key={cell.id}>
|
|
200
|
-
<OverflowMenu>
|
|
201
|
-
<OverflowMenuItem itemText={t('delete', 'Delete')} disabled />
|
|
202
|
-
</OverflowMenu>
|
|
203
|
-
</TableCell>
|
|
204
|
-
),
|
|
205
|
-
)}
|
|
85
|
+
{row.cells.map((cell) => (
|
|
86
|
+
<TableCell key={cell.id}>{cell.value}</TableCell>
|
|
87
|
+
))}
|
|
206
88
|
</TableRow>
|
|
207
89
|
))}
|
|
208
90
|
</TableBody>
|
|
@@ -212,63 +94,6 @@ const CashPointConfiguration: React.FC = () => {
|
|
|
212
94
|
</DataTable>
|
|
213
95
|
</div>
|
|
214
96
|
</div>
|
|
215
|
-
|
|
216
|
-
{/* Modal for Adding New Cash Point */}
|
|
217
|
-
<Modal
|
|
218
|
-
open={isModalOpen}
|
|
219
|
-
modalHeading={t('addCashPoint', 'Add Cash Point')}
|
|
220
|
-
onRequestClose={() => setIsModalOpen(false)}
|
|
221
|
-
onRequestSubmit={handleSubmit(onSubmit)}
|
|
222
|
-
primaryButtonText={t('save', 'Save')}
|
|
223
|
-
secondaryButtonText={t('cancel', 'Cancel')}
|
|
224
|
-
isPrimaryButtonDisabled={isSubmitting}>
|
|
225
|
-
<form>
|
|
226
|
-
<Controller
|
|
227
|
-
name="name"
|
|
228
|
-
control={control}
|
|
229
|
-
render={({ field }) => (
|
|
230
|
-
<TextInput
|
|
231
|
-
id="cash-point-name"
|
|
232
|
-
labelText={t('cashPointName', 'Cash Point Name')}
|
|
233
|
-
placeholder={t('cashPointNamePlaceholder', 'e.g., Pharmacy Cash Point')}
|
|
234
|
-
invalid={!!errors.name}
|
|
235
|
-
invalidText={errors.name?.message}
|
|
236
|
-
{...field}
|
|
237
|
-
/>
|
|
238
|
-
)}
|
|
239
|
-
/>
|
|
240
|
-
<Controller
|
|
241
|
-
name="uuid"
|
|
242
|
-
control={control}
|
|
243
|
-
render={({ field }) => (
|
|
244
|
-
<TextInput
|
|
245
|
-
id="cash-point-uuid"
|
|
246
|
-
labelText={t('cashPointUuid', 'Cash Point UUID')}
|
|
247
|
-
placeholder={t('cashPointUuidPlaceholder', 'Enter UUID')}
|
|
248
|
-
invalid={!!errors.uuid}
|
|
249
|
-
invalidText={errors.uuid?.message}
|
|
250
|
-
{...field}
|
|
251
|
-
/>
|
|
252
|
-
)}
|
|
253
|
-
/>
|
|
254
|
-
<Controller
|
|
255
|
-
name="location"
|
|
256
|
-
control={control}
|
|
257
|
-
render={({ field }) => (
|
|
258
|
-
<Dropdown
|
|
259
|
-
id="cash-point-location"
|
|
260
|
-
label={t('location', 'Select Location')}
|
|
261
|
-
titleText={t('cashPointLocation', 'Cash Point Location')}
|
|
262
|
-
items={locations}
|
|
263
|
-
selectedItem={locations.find((loc) => loc.id === field.value)}
|
|
264
|
-
onChange={({ selectedItem }) => field.onChange(selectedItem?.id)}
|
|
265
|
-
invalid={!!errors.location}
|
|
266
|
-
invalidText={errors.location?.message}
|
|
267
|
-
/>
|
|
268
|
-
)}
|
|
269
|
-
/>
|
|
270
|
-
</form>
|
|
271
|
-
</Modal>
|
|
272
97
|
</div>
|
|
273
98
|
);
|
|
274
99
|
};
|
|
@@ -16,9 +16,9 @@ import { Controller, useFieldArray, useForm } from 'react-hook-form';
|
|
|
16
16
|
import { useTranslation } from 'react-i18next';
|
|
17
17
|
import { z } from 'zod';
|
|
18
18
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
19
|
-
import { navigate, showSnackbar, useDebounce, useLayoutType } from '@openmrs/esm-framework';
|
|
19
|
+
import { getCoreTranslation, navigate, showSnackbar, useDebounce, useLayoutType } from '@openmrs/esm-framework';
|
|
20
20
|
import {
|
|
21
|
-
|
|
21
|
+
createBillableService,
|
|
22
22
|
updateBillableService,
|
|
23
23
|
useConceptsSearch,
|
|
24
24
|
usePaymentModes,
|
|
@@ -32,10 +32,6 @@ type PaymentMode = {
|
|
|
32
32
|
price: string | number;
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
type PaymentModeFormValue = {
|
|
36
|
-
payment: Array<PaymentMode>;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
35
|
const servicePriceSchema = z.object({
|
|
40
36
|
paymentMode: z.string().refine((value) => !!value, 'Payment method is required'),
|
|
41
37
|
price: z.union([
|
|
@@ -50,7 +46,12 @@ const paymentFormSchema = z.object({
|
|
|
50
46
|
|
|
51
47
|
const DEFAULT_PAYMENT_OPTION = { paymentMode: '', price: 0 };
|
|
52
48
|
|
|
53
|
-
const AddBillableService: React.FC<{
|
|
49
|
+
const AddBillableService: React.FC<{
|
|
50
|
+
editingService?: any;
|
|
51
|
+
onClose: () => void;
|
|
52
|
+
onServiceUpdated?: () => void;
|
|
53
|
+
isModal?: boolean;
|
|
54
|
+
}> = ({ editingService, onClose, onServiceUpdated, isModal = false }) => {
|
|
54
55
|
const { t } = useTranslation();
|
|
55
56
|
|
|
56
57
|
const { paymentModes, isLoading: isLoadingPaymentModes } = usePaymentModes();
|
|
@@ -138,7 +139,7 @@ const AddBillableService: React.FC<{ editingService?: any; onClose: () => void }
|
|
|
138
139
|
|
|
139
140
|
const saveAction = editingService
|
|
140
141
|
? updateBillableService(editingService.uuid, payload)
|
|
141
|
-
:
|
|
142
|
+
: createBillableService(payload);
|
|
142
143
|
|
|
143
144
|
saveAction.then(
|
|
144
145
|
(resp) => {
|
|
@@ -150,6 +151,9 @@ const AddBillableService: React.FC<{ editingService?: any; onClose: () => void }
|
|
|
150
151
|
kind: 'success',
|
|
151
152
|
timeoutInMs: 3000,
|
|
152
153
|
});
|
|
154
|
+
if (onServiceUpdated) {
|
|
155
|
+
onServiceUpdated();
|
|
156
|
+
}
|
|
153
157
|
onClose();
|
|
154
158
|
handleNavigateToServiceDashboard();
|
|
155
159
|
},
|
|
@@ -172,19 +176,19 @@ const AddBillableService: React.FC<{ editingService?: any; onClose: () => void }
|
|
|
172
176
|
<InlineLoading
|
|
173
177
|
status="active"
|
|
174
178
|
iconDescription={t('loadingDescription', 'Loading')}
|
|
175
|
-
description={t('
|
|
179
|
+
description={t('loadingData', 'Loading data') + '...'}
|
|
176
180
|
/>
|
|
177
181
|
);
|
|
178
182
|
}
|
|
179
183
|
|
|
180
184
|
return (
|
|
181
|
-
<Form className={styles.form} onSubmit={handleSubmit(onSubmit)}>
|
|
185
|
+
<Form id="billable-service-form" className={styles.form} onSubmit={handleSubmit(onSubmit)}>
|
|
182
186
|
<h4>
|
|
183
187
|
{editingService
|
|
184
188
|
? t('editBillableServices', 'Edit Billable Services')
|
|
185
189
|
: t('addBillableServices', 'Add Billable Services')}
|
|
186
190
|
</h4>
|
|
187
|
-
<section
|
|
191
|
+
<section>
|
|
188
192
|
<Layer>
|
|
189
193
|
<TextInput
|
|
190
194
|
id="serviceName"
|
|
@@ -211,7 +215,7 @@ const AddBillableService: React.FC<{ editingService?: any; onClose: () => void }
|
|
|
211
215
|
)}
|
|
212
216
|
</Layer>
|
|
213
217
|
</section>
|
|
214
|
-
<section
|
|
218
|
+
<section>
|
|
215
219
|
<Layer>
|
|
216
220
|
<TextInput
|
|
217
221
|
id="serviceShortName"
|
|
@@ -307,7 +311,7 @@ const AddBillableService: React.FC<{ editingService?: any; onClose: () => void }
|
|
|
307
311
|
);
|
|
308
312
|
})()}
|
|
309
313
|
</section>
|
|
310
|
-
<section
|
|
314
|
+
<section>
|
|
311
315
|
<Layer>
|
|
312
316
|
<ComboBox
|
|
313
317
|
id="serviceType"
|
|
@@ -327,9 +331,8 @@ const AddBillableService: React.FC<{ editingService?: any; onClose: () => void }
|
|
|
327
331
|
/>
|
|
328
332
|
</Layer>
|
|
329
333
|
</section>
|
|
330
|
-
|
|
331
334
|
<section>
|
|
332
|
-
<div
|
|
335
|
+
<div>
|
|
333
336
|
{fields.map((field, index) => (
|
|
334
337
|
<div key={field.id} className={styles.paymentMethodContainer}>
|
|
335
338
|
<Controller
|
|
@@ -381,15 +384,16 @@ const AddBillableService: React.FC<{ editingService?: any; onClose: () => void }
|
|
|
381
384
|
{getPaymentErrorMessage() && <div className={styles.errorMessage}>{getPaymentErrorMessage()}</div>}
|
|
382
385
|
</div>
|
|
383
386
|
</section>
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
387
|
+
{!isModal && (
|
|
388
|
+
<section>
|
|
389
|
+
<Button kind="secondary" onClick={onClose}>
|
|
390
|
+
{getCoreTranslation('cancel')}
|
|
391
|
+
</Button>
|
|
392
|
+
<Button type="submit" disabled={!isValid || Object.keys(errors).length > 0}>
|
|
393
|
+
{getCoreTranslation('save')}
|
|
394
|
+
</Button>
|
|
395
|
+
</section>
|
|
396
|
+
)}
|
|
393
397
|
</Form>
|
|
394
398
|
);
|
|
395
399
|
};
|
|
@@ -8,10 +8,7 @@
|
|
|
8
8
|
flex-direction: column;
|
|
9
9
|
justify-content: space-between;
|
|
10
10
|
height: 100%;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
.section {
|
|
14
|
-
margin: layout.$spacing-03;
|
|
11
|
+
margin: layout.$spacing-05;
|
|
15
12
|
}
|
|
16
13
|
|
|
17
14
|
.sectionTitle {
|
|
@@ -99,7 +96,7 @@
|
|
|
99
96
|
|
|
100
97
|
.conceptLabel {
|
|
101
98
|
@include type.type-style('label-02');
|
|
102
|
-
margin: layout.$spacing-05;
|
|
99
|
+
margin-bottom: layout.$spacing-05;
|
|
103
100
|
}
|
|
104
101
|
|
|
105
102
|
.errorContainer {
|
|
@@ -6,21 +6,21 @@ import {
|
|
|
6
6
|
useBillableServices,
|
|
7
7
|
usePaymentModes,
|
|
8
8
|
useServiceTypes,
|
|
9
|
-
|
|
9
|
+
createBillableService,
|
|
10
10
|
} from '../billable-service.resource';
|
|
11
11
|
import AddBillableService from './add-billable-service.component';
|
|
12
12
|
|
|
13
13
|
const mockUseBillableServices = useBillableServices as jest.MockedFunction<typeof useBillableServices>;
|
|
14
14
|
const mockUsePaymentModes = usePaymentModes as jest.MockedFunction<typeof usePaymentModes>;
|
|
15
15
|
const mockUseServiceTypes = useServiceTypes as jest.MockedFunction<typeof useServiceTypes>;
|
|
16
|
-
const
|
|
16
|
+
const mockcreateBillableService = createBillableService as jest.MockedFunction<typeof createBillableService>;
|
|
17
17
|
const mockNavigate = navigate as jest.MockedFunction<typeof navigate>;
|
|
18
18
|
|
|
19
19
|
jest.mock('../billable-service.resource', () => ({
|
|
20
20
|
useBillableServices: jest.fn(),
|
|
21
21
|
usePaymentModes: jest.fn(),
|
|
22
22
|
useServiceTypes: jest.fn(),
|
|
23
|
-
|
|
23
|
+
createBillableService: jest.fn(),
|
|
24
24
|
}));
|
|
25
25
|
|
|
26
26
|
const mockPaymentModes = [
|
|
@@ -107,13 +107,13 @@ xdescribe('AddBillableService', () => {
|
|
|
107
107
|
expect(priceTextInp).toBeInTheDocument();
|
|
108
108
|
await user.type(priceTextInp, '1000');
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
mockcreateBillableService.mockReturnValue(Promise.resolve({} as FetchResponse<any>));
|
|
111
111
|
const saveBtn = screen.getByRole('button', { name: /Save/i });
|
|
112
112
|
expect(saveBtn).toBeInTheDocument();
|
|
113
113
|
await user.click(saveBtn);
|
|
114
114
|
|
|
115
|
-
expect(
|
|
116
|
-
expect(
|
|
115
|
+
expect(mockcreateBillableService).toHaveBeenCalledTimes(1);
|
|
116
|
+
expect(mockcreateBillableService).toHaveBeenCalledWith({
|
|
117
117
|
name: 'Test Service Name',
|
|
118
118
|
shortName: 'Test Short Name',
|
|
119
119
|
serviceType: undefined,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Button, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
|
|
4
|
+
import AddBillableService from './add-billable-service.component';
|
|
5
|
+
import { getCoreTranslation } from '@openmrs/esm-framework';
|
|
6
|
+
|
|
7
|
+
interface EditBillableServiceModalProps {
|
|
8
|
+
closeModal: () => void;
|
|
9
|
+
editingService?: any;
|
|
10
|
+
onServiceUpdated: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const EditBillableServiceModal: React.FC<EditBillableServiceModalProps> = ({
|
|
14
|
+
closeModal,
|
|
15
|
+
editingService,
|
|
16
|
+
onServiceUpdated,
|
|
17
|
+
}) => {
|
|
18
|
+
const { t } = useTranslation();
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<>
|
|
22
|
+
<ModalHeader closeModal={closeModal} title={t('billableService', 'Billable Service')} />
|
|
23
|
+
<ModalBody>
|
|
24
|
+
<AddBillableService
|
|
25
|
+
editingService={editingService}
|
|
26
|
+
onClose={closeModal}
|
|
27
|
+
onServiceUpdated={onServiceUpdated}
|
|
28
|
+
isModal={true}
|
|
29
|
+
/>
|
|
30
|
+
</ModalBody>
|
|
31
|
+
<ModalFooter>
|
|
32
|
+
<Button kind="secondary" onClick={closeModal}>
|
|
33
|
+
{getCoreTranslation('cancel')}
|
|
34
|
+
</Button>
|
|
35
|
+
<Button
|
|
36
|
+
onClick={() => {
|
|
37
|
+
// Trigger form submission programmatically
|
|
38
|
+
const form = document.getElementById('billable-service-form') as HTMLFormElement;
|
|
39
|
+
if (form) {
|
|
40
|
+
form.requestSubmit();
|
|
41
|
+
}
|
|
42
|
+
}}>
|
|
43
|
+
{getCoreTranslation('save')}
|
|
44
|
+
</Button>
|
|
45
|
+
</ModalFooter>
|
|
46
|
+
</>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
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;
|