@openmrs/esm-billing-app 1.0.2-pre.657 → 1.0.2-pre.661
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/dist/2747.js +1 -1
- package/dist/2747.js.map +1 -1
- package/dist/4300.js +1 -1
- package/dist/8638.js +1 -1
- package/dist/8638.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-billing-app.js.buildmanifest.json +14 -14
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/bill-history/bill-history.component.tsx +1 -1
- package/src/invoice/invoice-table.component.tsx +6 -43
- package/src/invoice/invoice-table.test.tsx +0 -11
- package/src/invoice/invoice.component.tsx +7 -15
- package/src/invoice/payments/payment-form/payment-form.component.tsx +2 -9
- package/src/invoice/payments/payment-form/payment-form.test.tsx +6 -36
- package/src/invoice/payments/payments.component.tsx +16 -27
- package/src/invoice/payments/{payments.component.test.tsx → payments.test.tsx} +4 -4
- package/src/invoice/payments/utils.ts +4 -22
- package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +2 -2
- package/src/invoice/printable-invoice/printable-invoice.component.tsx +1 -1
- package/translations/en.json +0 -1
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"auxiliaryFiles": [
|
|
25
25
|
"590.js.map"
|
|
26
26
|
],
|
|
27
|
-
"hash": "
|
|
27
|
+
"hash": "414e99f458bbc26a",
|
|
28
28
|
"childrenByOrder": {}
|
|
29
29
|
},
|
|
30
30
|
{
|
|
@@ -330,7 +330,7 @@
|
|
|
330
330
|
"auxiliaryFiles": [
|
|
331
331
|
"2352.js.map"
|
|
332
332
|
],
|
|
333
|
-
"hash": "
|
|
333
|
+
"hash": "422ac1657e297fb7",
|
|
334
334
|
"childrenByOrder": {}
|
|
335
335
|
},
|
|
336
336
|
{
|
|
@@ -379,7 +379,7 @@
|
|
|
379
379
|
"auxiliaryFiles": [
|
|
380
380
|
"2747.js.map"
|
|
381
381
|
],
|
|
382
|
-
"hash": "
|
|
382
|
+
"hash": "c47b184600598dfd",
|
|
383
383
|
"childrenByOrder": {}
|
|
384
384
|
},
|
|
385
385
|
{
|
|
@@ -571,9 +571,9 @@
|
|
|
571
571
|
"initial": false,
|
|
572
572
|
"entry": false,
|
|
573
573
|
"recorded": false,
|
|
574
|
-
"size":
|
|
574
|
+
"size": 7298,
|
|
575
575
|
"sizes": {
|
|
576
|
-
"javascript":
|
|
576
|
+
"javascript": 7298
|
|
577
577
|
},
|
|
578
578
|
"names": [],
|
|
579
579
|
"idHints": [],
|
|
@@ -585,7 +585,7 @@
|
|
|
585
585
|
"4300.js"
|
|
586
586
|
],
|
|
587
587
|
"auxiliaryFiles": [],
|
|
588
|
-
"hash": "
|
|
588
|
+
"hash": "a8ba7d5cab1cf5ae",
|
|
589
589
|
"childrenByOrder": {}
|
|
590
590
|
},
|
|
591
591
|
{
|
|
@@ -675,7 +675,7 @@
|
|
|
675
675
|
"auxiliaryFiles": [
|
|
676
676
|
"4739.js.map"
|
|
677
677
|
],
|
|
678
|
-
"hash": "
|
|
678
|
+
"hash": "53528d1cc7772c75",
|
|
679
679
|
"childrenByOrder": {}
|
|
680
680
|
},
|
|
681
681
|
{
|
|
@@ -1135,7 +1135,7 @@
|
|
|
1135
1135
|
"auxiliaryFiles": [
|
|
1136
1136
|
"7692.js.map"
|
|
1137
1137
|
],
|
|
1138
|
-
"hash": "
|
|
1138
|
+
"hash": "7c2f626c815f585d",
|
|
1139
1139
|
"childrenByOrder": {}
|
|
1140
1140
|
},
|
|
1141
1141
|
{
|
|
@@ -1236,9 +1236,9 @@
|
|
|
1236
1236
|
"initial": false,
|
|
1237
1237
|
"entry": false,
|
|
1238
1238
|
"recorded": false,
|
|
1239
|
-
"size":
|
|
1239
|
+
"size": 1091132,
|
|
1240
1240
|
"sizes": {
|
|
1241
|
-
"javascript":
|
|
1241
|
+
"javascript": 1091090,
|
|
1242
1242
|
"consume-shared": 42
|
|
1243
1243
|
},
|
|
1244
1244
|
"names": [],
|
|
@@ -1252,7 +1252,7 @@
|
|
|
1252
1252
|
"auxiliaryFiles": [
|
|
1253
1253
|
"8638.js.map"
|
|
1254
1254
|
],
|
|
1255
|
-
"hash": "
|
|
1255
|
+
"hash": "d7b8b1033eef7f90",
|
|
1256
1256
|
"childrenByOrder": {}
|
|
1257
1257
|
},
|
|
1258
1258
|
{
|
|
@@ -1260,10 +1260,10 @@
|
|
|
1260
1260
|
"initial": true,
|
|
1261
1261
|
"entry": true,
|
|
1262
1262
|
"recorded": false,
|
|
1263
|
-
"size":
|
|
1263
|
+
"size": 5111703,
|
|
1264
1264
|
"sizes": {
|
|
1265
1265
|
"consume-shared": 210,
|
|
1266
|
-
"javascript":
|
|
1266
|
+
"javascript": 5089051,
|
|
1267
1267
|
"share-init": 336,
|
|
1268
1268
|
"runtime": 22106
|
|
1269
1269
|
},
|
|
@@ -1280,7 +1280,7 @@
|
|
|
1280
1280
|
"auxiliaryFiles": [
|
|
1281
1281
|
"main.js.map"
|
|
1282
1282
|
],
|
|
1283
|
-
"hash": "
|
|
1283
|
+
"hash": "fc6ad4df4d9516ac",
|
|
1284
1284
|
"childrenByOrder": {}
|
|
1285
1285
|
},
|
|
1286
1286
|
{
|
package/dist/routes.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":">=2.24.0","fhir2":">=1.2"},"pages":[{"component":"billableServicesHome","route":"billable-services"}],"extensions":[{"component":"billingDashboardLink","name":"billing-dashboard-link","slot":"homepage-dashboard-slot","meta":{"name":"billing","title":"billing","slot":"billing-dashboard-slot"},"featureFlag":"billing"},{"component":"root","name":"billing-dashboard-root","slot":"billing-dashboard-slot"},{"name":"billing-patient-summary","component":"billingPatientSummary","slot":"patient-chart-billing-dashboard-slot","order":10,"meta":{"columnSpan":4}},{"name":"billing-summary-dashboard-link","component":"billingSummaryDashboardLink","slot":"patient-chart-dashboard-slot","order":11,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-billing-dashboard-slot","path":"Billing history"},"featureFlag":"billing"},{"name":"billable-services-app-menu-item","component":"billableServicesAppMenuItem","slot":"app-menu-item-slot","meta":{"name":"Billable Services"}},{"name":"billing-checkin-form","slot":"extra-visit-attribute-slot","component":"billingCheckInForm","featureFlag":"billing"},{"slot":"system-admin-page-card-link-slot","component":"billableServicesCardLink","name":"billable-services-admin-card-link"},{"name":"patient-banner-billing-tags","component":"visitAttributeTags","slot":"patient-banner-tags-slot","order":2},{"name":"billing-home-tiles-ext","slot":"billing-home-tiles-slot","component":"serviceMetrics"},{"name":"edit-bill-line-item-dialog","component":"editBillLineItemDialog","online":true,"offline":true}],"modals":[{"name":"require-billing-modal","component":"requirePaymentModal"}],"workspaces":[{"name":"billing-form-workspace","title":"billingForm","component":"billingFormWorkspace","type":"form"}],"featureFlags":[{"flagName":"billing","label":"Billing module","description":"This feature introduces navigation links on the patient chart and home page to allow accessing the billing module features"}],"version":"1.0.2-pre.
|
|
1
|
+
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":">=2.24.0","fhir2":">=1.2"},"pages":[{"component":"billableServicesHome","route":"billable-services"}],"extensions":[{"component":"billingDashboardLink","name":"billing-dashboard-link","slot":"homepage-dashboard-slot","meta":{"name":"billing","title":"billing","slot":"billing-dashboard-slot"},"featureFlag":"billing"},{"component":"root","name":"billing-dashboard-root","slot":"billing-dashboard-slot"},{"name":"billing-patient-summary","component":"billingPatientSummary","slot":"patient-chart-billing-dashboard-slot","order":10,"meta":{"columnSpan":4}},{"name":"billing-summary-dashboard-link","component":"billingSummaryDashboardLink","slot":"patient-chart-dashboard-slot","order":11,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-billing-dashboard-slot","path":"Billing history"},"featureFlag":"billing"},{"name":"billable-services-app-menu-item","component":"billableServicesAppMenuItem","slot":"app-menu-item-slot","meta":{"name":"Billable Services"}},{"name":"billing-checkin-form","slot":"extra-visit-attribute-slot","component":"billingCheckInForm","featureFlag":"billing"},{"slot":"system-admin-page-card-link-slot","component":"billableServicesCardLink","name":"billable-services-admin-card-link"},{"name":"patient-banner-billing-tags","component":"visitAttributeTags","slot":"patient-banner-tags-slot","order":2},{"name":"billing-home-tiles-ext","slot":"billing-home-tiles-slot","component":"serviceMetrics"},{"name":"edit-bill-line-item-dialog","component":"editBillLineItemDialog","online":true,"offline":true}],"modals":[{"name":"require-billing-modal","component":"requirePaymentModal"}],"workspaces":[{"name":"billing-form-workspace","title":"billingForm","component":"billingFormWorkspace","type":"form"}],"featureFlags":[{"flagName":"billing","label":"Billing module","description":"This feature introduces navigation links on the patient chart and home page to allow accessing the billing module features"}],"version":"1.0.2-pre.661"}
|
package/package.json
CHANGED
|
@@ -155,7 +155,7 @@ const BillHistory: React.FC<BillHistoryProps> = ({ patientUuid }) => {
|
|
|
155
155
|
{row.isExpanded ? (
|
|
156
156
|
<TableExpandedRow className={styles.expandedRow} colSpan={headers.length + 1}>
|
|
157
157
|
<div className={styles.container} key={i}>
|
|
158
|
-
<InvoiceTable bill={currentBill}
|
|
158
|
+
<InvoiceTable bill={currentBill} />
|
|
159
159
|
</div>
|
|
160
160
|
</TableExpandedRow>
|
|
161
161
|
) : (
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useMemo, useState,
|
|
1
|
+
import React, { useMemo, useState, useCallback } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import fuzzy from 'fuzzy';
|
|
4
4
|
import {
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
TableHead,
|
|
14
14
|
TableHeader,
|
|
15
15
|
TableRow,
|
|
16
|
-
TableSelectRow,
|
|
17
16
|
TableToolbarSearch,
|
|
18
17
|
Tile,
|
|
19
18
|
type DataTableRow,
|
|
@@ -22,33 +21,23 @@ import { Edit } from '@carbon/react/icons';
|
|
|
22
21
|
import { isDesktop, showModal, useConfig, useDebounce, useLayoutType } from '@openmrs/esm-framework';
|
|
23
22
|
import { type LineItem, type MappedBill } from '../types';
|
|
24
23
|
import { convertToCurrency } from '../helpers';
|
|
24
|
+
import type { BillingConfig } from '../config-schema';
|
|
25
25
|
import styles from './invoice-table.scss';
|
|
26
26
|
|
|
27
27
|
type InvoiceTableProps = {
|
|
28
28
|
bill: MappedBill;
|
|
29
|
-
isSelectable?: boolean;
|
|
30
29
|
isLoadingBill?: boolean;
|
|
31
|
-
onSelectItem?: (selectedLineItems: LineItem[]) => void;
|
|
32
30
|
};
|
|
33
31
|
|
|
34
|
-
const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill,
|
|
32
|
+
const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isLoadingBill }) => {
|
|
35
33
|
const { t } = useTranslation();
|
|
36
|
-
const { defaultCurrency, showEditBillButton } = useConfig();
|
|
34
|
+
const { defaultCurrency, showEditBillButton } = useConfig<BillingConfig>();
|
|
37
35
|
const layout = useLayoutType();
|
|
38
36
|
const lineItems = useMemo(() => bill?.lineItems ?? [], [bill?.lineItems]);
|
|
39
|
-
const paidLineItems = useMemo(() => lineItems?.filter((item) => item.paymentStatus === 'PAID') ?? [], [lineItems]);
|
|
40
37
|
const responsiveSize = isDesktop(layout) ? 'sm' : 'lg';
|
|
41
|
-
|
|
42
|
-
const [selectedLineItems, setSelectedLineItems] = useState(paidLineItems ?? []);
|
|
43
38
|
const [searchTerm, setSearchTerm] = useState('');
|
|
44
39
|
const debouncedSearchTerm = useDebounce(searchTerm);
|
|
45
40
|
|
|
46
|
-
useEffect(() => {
|
|
47
|
-
if (onSelectItem) {
|
|
48
|
-
onSelectItem(selectedLineItems);
|
|
49
|
-
}
|
|
50
|
-
}, [selectedLineItems, onSelectItem]);
|
|
51
|
-
|
|
52
41
|
const filteredLineItems = useMemo(() => {
|
|
53
42
|
if (!debouncedSearchTerm) {
|
|
54
43
|
return lineItems;
|
|
@@ -135,23 +124,10 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
|
|
|
135
124
|
);
|
|
136
125
|
}
|
|
137
126
|
|
|
138
|
-
const handleRowSelection = (row: typeof DataTableRow, checked: boolean) => {
|
|
139
|
-
const matchingRow = filteredLineItems.find((item) => item.uuid === row.id);
|
|
140
|
-
let newSelectedLineItems;
|
|
141
|
-
|
|
142
|
-
if (checked) {
|
|
143
|
-
newSelectedLineItems = [...selectedLineItems, matchingRow];
|
|
144
|
-
} else {
|
|
145
|
-
newSelectedLineItems = selectedLineItems.filter((item) => item.uuid !== row.id);
|
|
146
|
-
}
|
|
147
|
-
setSelectedLineItems(newSelectedLineItems);
|
|
148
|
-
onSelectItem(newSelectedLineItems);
|
|
149
|
-
};
|
|
150
|
-
|
|
151
127
|
return (
|
|
152
128
|
<>
|
|
153
|
-
<DataTable headers={tableHeaders}
|
|
154
|
-
{({ rows, headers, getRowProps,
|
|
129
|
+
<DataTable headers={tableHeaders} rows={tableRows} size={responsiveSize} useZebraStyles>
|
|
130
|
+
{({ rows, headers, getRowProps, getTableProps }) => (
|
|
155
131
|
<TableContainer
|
|
156
132
|
description={
|
|
157
133
|
<span className={styles.tableDescription}>
|
|
@@ -172,7 +148,6 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
|
|
|
172
148
|
className={`${styles.invoiceTable} billingTable`}>
|
|
173
149
|
<TableHead>
|
|
174
150
|
<TableRow>
|
|
175
|
-
{rows.length > 1 && isSelectable ? <TableHeader /> : null}
|
|
176
151
|
{headers.map((header) => (
|
|
177
152
|
<TableHeader key={header.key}>{header.header}</TableHeader>
|
|
178
153
|
))}
|
|
@@ -186,18 +161,6 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
|
|
|
186
161
|
{...getRowProps({
|
|
187
162
|
row,
|
|
188
163
|
})}>
|
|
189
|
-
{rows.length > 1 && isSelectable && (
|
|
190
|
-
<TableSelectRow
|
|
191
|
-
aria-label="Select row"
|
|
192
|
-
{...getSelectionProps({ row })}
|
|
193
|
-
disabled={tableRows[index].status === 'PAID'}
|
|
194
|
-
onChange={(checked: boolean) => handleRowSelection(row, checked)}
|
|
195
|
-
checked={
|
|
196
|
-
tableRows[index].status === 'PAID' ||
|
|
197
|
-
Boolean(selectedLineItems?.find((item) => item?.uuid === row?.id))
|
|
198
|
-
}
|
|
199
|
-
/>
|
|
200
|
-
)}
|
|
201
164
|
{row.cells.map((cell) => (
|
|
202
165
|
<TableCell key={cell.id}>{cell.value}</TableCell>
|
|
203
166
|
))}
|
|
@@ -110,17 +110,6 @@ describe('InvoiceTable', () => {
|
|
|
110
110
|
expect(screen.getByText('Item 2')).toBeInTheDocument();
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
-
it('correctly handles row selection', async () => {
|
|
114
|
-
const user = userEvent.setup();
|
|
115
|
-
const onSelectItem = jest.fn();
|
|
116
|
-
render(<InvoiceTable bill={bill} onSelectItem={onSelectItem} />);
|
|
117
|
-
|
|
118
|
-
const checkboxes = screen.getAllByLabelText('Select row');
|
|
119
|
-
await user.click(checkboxes[0]);
|
|
120
|
-
|
|
121
|
-
expect(onSelectItem).toHaveBeenCalledWith([bill.lineItems[0]]);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
113
|
it('resets isRedirecting to false after timeout', async () => {
|
|
125
114
|
const user = userEvent.setup();
|
|
126
115
|
render(<InvoiceTable bill={bill} />);
|
|
@@ -12,7 +12,6 @@ import PrintableInvoice from './printable-invoice/printable-invoice.component';
|
|
|
12
12
|
import { ErrorState } from '@openmrs/esm-patient-common-lib';
|
|
13
13
|
import { convertToCurrency } from '../helpers';
|
|
14
14
|
import { useBill, useDefaultFacility } from '../billing.resource';
|
|
15
|
-
import { type LineItem } from '../types';
|
|
16
15
|
import type { BillingConfig } from '../config-schema';
|
|
17
16
|
import styles from './invoice.scss';
|
|
18
17
|
|
|
@@ -28,13 +27,9 @@ const Invoice: React.FC = () => {
|
|
|
28
27
|
const { patient, isLoading: isLoadingPatient } = usePatient(patientUuid);
|
|
29
28
|
const { bill, isLoading: isLoadingBill, error, mutate } = useBill(billUuid);
|
|
30
29
|
const [isPrinting, setIsPrinting] = useState(false);
|
|
31
|
-
const [selectedLineItems, setSelectedLineItems] = useState<LineItem[]>([]);
|
|
32
30
|
const componentRef = useRef<HTMLDivElement>(null);
|
|
33
31
|
const onBeforeGetContentResolve = useRef<(() => void) | null>(null);
|
|
34
32
|
const { defaultCurrency } = useConfig<BillingConfig>();
|
|
35
|
-
const handleSelectItem = (lineItems: LineItem[]) => {
|
|
36
|
-
setSelectedLineItems(lineItems);
|
|
37
|
-
};
|
|
38
33
|
|
|
39
34
|
const handleAfterPrint = useCallback(() => {
|
|
40
35
|
onBeforeGetContentResolve.current = null;
|
|
@@ -66,11 +61,6 @@ const Invoice: React.FC = () => {
|
|
|
66
61
|
}
|
|
67
62
|
}, [isPrinting]);
|
|
68
63
|
|
|
69
|
-
useEffect(() => {
|
|
70
|
-
const unPaidLineItems = bill?.lineItems?.filter((item) => item.paymentStatus === 'PENDING') ?? [];
|
|
71
|
-
setSelectedLineItems(unPaidLineItems);
|
|
72
|
-
}, [bill?.lineItems]);
|
|
73
|
-
|
|
74
64
|
// Do not remove this comment. Adds the translation keys for the invoice details
|
|
75
65
|
/**
|
|
76
66
|
* t('totalAmount', 'Total Amount')
|
|
@@ -130,12 +120,14 @@ const Invoice: React.FC = () => {
|
|
|
130
120
|
</div>
|
|
131
121
|
</div>
|
|
132
122
|
|
|
133
|
-
<InvoiceTable bill={bill} isLoadingBill={isLoadingBill}
|
|
134
|
-
<Payments bill={bill} mutate={mutate}
|
|
123
|
+
<InvoiceTable bill={bill} isLoadingBill={isLoadingBill} />
|
|
124
|
+
<Payments bill={bill} mutate={mutate} />
|
|
135
125
|
|
|
136
|
-
|
|
137
|
-
<
|
|
138
|
-
|
|
126
|
+
{bill && patient && (
|
|
127
|
+
<div className={styles.printContainer}>
|
|
128
|
+
<PrintableInvoice bill={bill} patient={patient} defaultFacility={data} componentRef={componentRef} />
|
|
129
|
+
</div>
|
|
130
|
+
)}
|
|
139
131
|
</div>
|
|
140
132
|
);
|
|
141
133
|
};
|
|
@@ -10,19 +10,12 @@ import styles from './payment-form.scss';
|
|
|
10
10
|
|
|
11
11
|
type PaymentFormProps = {
|
|
12
12
|
disablePayment: boolean;
|
|
13
|
-
clientBalance: number;
|
|
14
|
-
isSingleLineItemSelected: boolean;
|
|
15
13
|
isSingleLineItem: boolean;
|
|
16
14
|
};
|
|
17
15
|
|
|
18
16
|
const DEFAULT_PAYMENT = { method: '', amount: 0, referenceCode: '' };
|
|
19
17
|
|
|
20
|
-
const PaymentForm: React.FC<PaymentFormProps> = ({
|
|
21
|
-
disablePayment,
|
|
22
|
-
clientBalance,
|
|
23
|
-
isSingleLineItemSelected,
|
|
24
|
-
isSingleLineItem,
|
|
25
|
-
}) => {
|
|
18
|
+
const PaymentForm: React.FC<PaymentFormProps> = ({ disablePayment, isSingleLineItem }) => {
|
|
26
19
|
const { t } = useTranslation();
|
|
27
20
|
const {
|
|
28
21
|
control,
|
|
@@ -119,7 +112,7 @@ const PaymentForm: React.FC<PaymentFormProps> = ({
|
|
|
119
112
|
</div>
|
|
120
113
|
))}
|
|
121
114
|
<Button
|
|
122
|
-
disabled={disablePayment
|
|
115
|
+
disabled={disablePayment}
|
|
123
116
|
size="md"
|
|
124
117
|
onClick={handleAppendPaymentMode}
|
|
125
118
|
className={styles.paymentButtons}
|
|
@@ -31,12 +31,7 @@ describe('PaymentForm Component', () => {
|
|
|
31
31
|
|
|
32
32
|
render(
|
|
33
33
|
<Wrapper>
|
|
34
|
-
<PaymentForm
|
|
35
|
-
disablePayment={false}
|
|
36
|
-
clientBalance={100}
|
|
37
|
-
isSingleLineItemSelected={false}
|
|
38
|
-
isSingleLineItem={false}
|
|
39
|
-
/>
|
|
34
|
+
<PaymentForm disablePayment={false} isSingleLineItem={false} />
|
|
40
35
|
</Wrapper>,
|
|
41
36
|
);
|
|
42
37
|
|
|
@@ -53,12 +48,7 @@ describe('PaymentForm Component', () => {
|
|
|
53
48
|
|
|
54
49
|
render(
|
|
55
50
|
<Wrapper>
|
|
56
|
-
<PaymentForm
|
|
57
|
-
disablePayment={false}
|
|
58
|
-
clientBalance={100}
|
|
59
|
-
isSingleLineItemSelected={false}
|
|
60
|
-
isSingleLineItem={false}
|
|
61
|
-
/>
|
|
51
|
+
<PaymentForm disablePayment={false} isSingleLineItem={false} />
|
|
62
52
|
</Wrapper>,
|
|
63
53
|
);
|
|
64
54
|
|
|
@@ -75,12 +65,7 @@ describe('PaymentForm Component', () => {
|
|
|
75
65
|
|
|
76
66
|
render(
|
|
77
67
|
<Wrapper>
|
|
78
|
-
<PaymentForm
|
|
79
|
-
disablePayment={false}
|
|
80
|
-
clientBalance={100}
|
|
81
|
-
isSingleLineItemSelected={false}
|
|
82
|
-
isSingleLineItem={true}
|
|
83
|
-
/>
|
|
68
|
+
<PaymentForm disablePayment={false} isSingleLineItem={true} />
|
|
84
69
|
</Wrapper>,
|
|
85
70
|
);
|
|
86
71
|
|
|
@@ -103,12 +88,7 @@ describe('PaymentForm Component', () => {
|
|
|
103
88
|
|
|
104
89
|
render(
|
|
105
90
|
<Wrapper>
|
|
106
|
-
<PaymentForm
|
|
107
|
-
disablePayment={false}
|
|
108
|
-
clientBalance={100}
|
|
109
|
-
isSingleLineItemSelected={true}
|
|
110
|
-
isSingleLineItem={false}
|
|
111
|
-
/>
|
|
91
|
+
<PaymentForm disablePayment={false} isSingleLineItem={false} />
|
|
112
92
|
</Wrapper>,
|
|
113
93
|
);
|
|
114
94
|
|
|
@@ -128,12 +108,7 @@ describe('PaymentForm Component', () => {
|
|
|
128
108
|
|
|
129
109
|
render(
|
|
130
110
|
<Wrapper>
|
|
131
|
-
<PaymentForm
|
|
132
|
-
disablePayment={true}
|
|
133
|
-
clientBalance={100}
|
|
134
|
-
isSingleLineItemSelected={true}
|
|
135
|
-
isSingleLineItem={false}
|
|
136
|
-
/>
|
|
111
|
+
<PaymentForm disablePayment={true} isSingleLineItem={false} />
|
|
137
112
|
</Wrapper>,
|
|
138
113
|
);
|
|
139
114
|
|
|
@@ -151,12 +126,7 @@ describe('PaymentForm Component', () => {
|
|
|
151
126
|
|
|
152
127
|
render(
|
|
153
128
|
<Wrapper>
|
|
154
|
-
<PaymentForm
|
|
155
|
-
disablePayment={false}
|
|
156
|
-
clientBalance={100}
|
|
157
|
-
isSingleLineItemSelected={true}
|
|
158
|
-
isSingleLineItem={false}
|
|
159
|
-
/>
|
|
129
|
+
<PaymentForm disablePayment={false} isSingleLineItem={false} />
|
|
160
130
|
</Wrapper>,
|
|
161
131
|
);
|
|
162
132
|
|
|
@@ -6,20 +6,19 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|
|
6
6
|
import { navigate, showSnackbar, useConfig, useVisit } from '@openmrs/esm-framework';
|
|
7
7
|
import { Button } from '@carbon/react';
|
|
8
8
|
import { CardHeader } from '@openmrs/esm-patient-common-lib';
|
|
9
|
-
import { type LineItem, type MappedBill } from '../../types';
|
|
10
|
-
import { convertToCurrency } from '../../helpers';
|
|
11
|
-
import { createPaymentPayload } from './utils';
|
|
12
|
-
import { processBillPayment } from '../../billing.resource';
|
|
13
9
|
import { InvoiceBreakDown } from './invoice-breakdown/invoice-breakdown.component';
|
|
14
10
|
import PaymentHistory from './payment-history/payment-history.component';
|
|
15
11
|
import PaymentForm from './payment-form/payment-form.component';
|
|
12
|
+
import { convertToCurrency } from '../../helpers';
|
|
13
|
+
import { createPaymentPayload } from './utils';
|
|
14
|
+
import { processBillPayment } from '../../billing.resource';
|
|
15
|
+
import { useBillableServices } from '../../billable-services/billable-service.resource';
|
|
16
16
|
import { updateBillVisitAttribute } from './payment.resource';
|
|
17
|
+
import { type MappedBill } from '../../types';
|
|
17
18
|
import styles from './payments.scss';
|
|
18
|
-
import { useBillableServices } from '../../billable-services/billable-service.resource';
|
|
19
19
|
|
|
20
20
|
type PaymentProps = {
|
|
21
21
|
bill: MappedBill;
|
|
22
|
-
selectedLineItems: Array<LineItem>;
|
|
23
22
|
mutate: () => void;
|
|
24
23
|
};
|
|
25
24
|
|
|
@@ -29,7 +28,7 @@ export type PaymentFormValue = {
|
|
|
29
28
|
payment: Array<Payment>;
|
|
30
29
|
};
|
|
31
30
|
|
|
32
|
-
const Payments: React.FC<PaymentProps> = ({ bill, mutate
|
|
31
|
+
const Payments: React.FC<PaymentProps> = ({ bill, mutate }) => {
|
|
33
32
|
const { t } = useTranslation();
|
|
34
33
|
const { billableServices, isLoading, isValidating, error } = useBillableServices();
|
|
35
34
|
const paymentSchema = z.object({
|
|
@@ -54,25 +53,23 @@ const Payments: React.FC<PaymentProps> = ({ bill, mutate, selectedLineItems }) =
|
|
|
54
53
|
control: methods.control,
|
|
55
54
|
});
|
|
56
55
|
|
|
57
|
-
const selectedLineItemsTotal = selectedLineItems.reduce((total, item) => total + item.price * item.quantity, 0);
|
|
58
|
-
const totalAmountTendered = formValues?.reduce((curr: number, prev) => curr + Number(prev.amount) ?? 0, 0) ?? 0;
|
|
59
|
-
const amountDue = bill ? bill.totalAmount - selectedLineItemsTotal : 0;
|
|
60
|
-
const clientBalance = bill ? bill.totalAmount - (bill.tenderedAmount + totalAmountTendered) : 0;
|
|
61
|
-
|
|
62
56
|
const handleNavigateToBillingDashboard = () =>
|
|
63
57
|
navigate({
|
|
64
58
|
to: window.getOpenmrsSpaBase() + 'home/billing',
|
|
65
59
|
});
|
|
66
60
|
|
|
61
|
+
const amountDue = bill.totalAmount - bill.tenderedAmount;
|
|
62
|
+
|
|
67
63
|
const handleProcessPayment = () => {
|
|
68
64
|
if (bill) {
|
|
65
|
+
const amountBeingTendered = formValues?.reduce((acc, curr) => acc + Number(curr.amount), 0);
|
|
66
|
+
const amountRemaining = amountDue - amountBeingTendered;
|
|
69
67
|
const paymentPayload = createPaymentPayload(
|
|
70
68
|
bill,
|
|
71
69
|
bill?.patientUuid,
|
|
72
70
|
formValues,
|
|
73
|
-
|
|
71
|
+
amountRemaining,
|
|
74
72
|
billableServices,
|
|
75
|
-
selectedLineItems,
|
|
76
73
|
);
|
|
77
74
|
paymentPayload.payments.forEach((payment) => {
|
|
78
75
|
payment.dateCreated = new Date(payment.dateCreated);
|
|
@@ -103,9 +100,6 @@ const Payments: React.FC<PaymentProps> = ({ bill, mutate, selectedLineItems }) =
|
|
|
103
100
|
return null;
|
|
104
101
|
}
|
|
105
102
|
|
|
106
|
-
const amountDueLabel = selectedLineItems.length ? t('amountDue', 'Amount Due') : t('clientBalance', 'Client Balance');
|
|
107
|
-
const amountDueValue = selectedLineItems.length ? amountDue : clientBalance;
|
|
108
|
-
|
|
109
103
|
return (
|
|
110
104
|
<FormProvider {...methods}>
|
|
111
105
|
<div className={styles.wrapper}>
|
|
@@ -115,12 +109,7 @@ const Payments: React.FC<PaymentProps> = ({ bill, mutate, selectedLineItems }) =
|
|
|
115
109
|
</CardHeader>
|
|
116
110
|
<div>
|
|
117
111
|
{bill && <PaymentHistory bill={bill} />}
|
|
118
|
-
<PaymentForm
|
|
119
|
-
disablePayment={clientBalance <= 0}
|
|
120
|
-
clientBalance={clientBalance}
|
|
121
|
-
isSingleLineItemSelected={selectedLineItems.length > 0}
|
|
122
|
-
isSingleLineItem={bill.lineItems.length === 1}
|
|
123
|
-
/>
|
|
112
|
+
<PaymentForm disablePayment={amountDue <= 0} isSingleLineItem={bill.lineItems.length === 1} />
|
|
124
113
|
</div>
|
|
125
114
|
</div>
|
|
126
115
|
<div className={styles.divider} />
|
|
@@ -131,13 +120,13 @@ const Payments: React.FC<PaymentProps> = ({ bill, mutate, selectedLineItems }) =
|
|
|
131
120
|
/>
|
|
132
121
|
<InvoiceBreakDown
|
|
133
122
|
label={t('totalTendered', 'Total Tendered')}
|
|
134
|
-
value={convertToCurrency(bill
|
|
123
|
+
value={convertToCurrency(bill.tenderedAmount, defaultCurrency)}
|
|
135
124
|
/>
|
|
136
125
|
<InvoiceBreakDown label={t('discount', 'Discount')} value={'--'} />
|
|
137
126
|
<InvoiceBreakDown
|
|
138
|
-
hasBalance={
|
|
139
|
-
label={
|
|
140
|
-
value={convertToCurrency(
|
|
127
|
+
hasBalance={amountDue < 0}
|
|
128
|
+
label={t('amountDue', 'Amount Due')}
|
|
129
|
+
value={convertToCurrency(amountDue < 0 ? -amountDue : amountDue, defaultCurrency)}
|
|
141
130
|
/>
|
|
142
131
|
<div className={styles.processPayments}>
|
|
143
132
|
<Button onClick={handleNavigateToBillingDashboard} kind="secondary">
|
|
@@ -108,14 +108,14 @@ describe('Payments', () => {
|
|
|
108
108
|
});
|
|
109
109
|
|
|
110
110
|
it('renders payment form and history', () => {
|
|
111
|
-
render(<Payments bill={mockBill} mutate={mockMutate}
|
|
111
|
+
render(<Payments bill={mockBill} mutate={mockMutate} />);
|
|
112
112
|
expect(screen.getByText('Payments')).toBeInTheDocument();
|
|
113
113
|
expect(screen.getByText('Total Amount:')).toBeInTheDocument();
|
|
114
114
|
expect(screen.getByText('Total Tendered:')).toBeInTheDocument();
|
|
115
115
|
});
|
|
116
116
|
|
|
117
117
|
it('calculates and displays correct amounts', () => {
|
|
118
|
-
render(<Payments bill={mockBill} mutate={mockMutate}
|
|
118
|
+
render(<Payments bill={mockBill} mutate={mockMutate} />);
|
|
119
119
|
const amountElements = screen.getAllByText('$1000.00');
|
|
120
120
|
expect(amountElements[amountElements.length - 3]).toBeInTheDocument();
|
|
121
121
|
expect(amountElements[amountElements.length - 2]).toBeInTheDocument();
|
|
@@ -123,12 +123,12 @@ describe('Payments', () => {
|
|
|
123
123
|
});
|
|
124
124
|
|
|
125
125
|
it('disables Process Payment button when form is invalid', () => {
|
|
126
|
-
render(<Payments bill={mockBill} mutate={mockMutate}
|
|
126
|
+
render(<Payments bill={mockBill} mutate={mockMutate} />);
|
|
127
127
|
expect(screen.getByText('Process Payment')).toBeDisabled();
|
|
128
128
|
});
|
|
129
129
|
|
|
130
130
|
it('navigates to billing dashboard when Discard is clicked', async () => {
|
|
131
|
-
render(<Payments bill={mockBill} mutate={mockMutate}
|
|
131
|
+
render(<Payments bill={mockBill} mutate={mockMutate} />);
|
|
132
132
|
await userEvent.click(screen.getByText('Discard'));
|
|
133
133
|
expect(navigate).toHaveBeenCalled();
|
|
134
134
|
});
|
|
@@ -1,24 +1,15 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type MappedBill } from '../../types';
|
|
2
2
|
import { type Payment } from './payments.component';
|
|
3
3
|
|
|
4
|
-
const hasLineItem = (lineItems: Array<LineItem>, item: LineItem) => {
|
|
5
|
-
if (lineItems?.length === 0) {
|
|
6
|
-
return false;
|
|
7
|
-
}
|
|
8
|
-
const foundItem = lineItems.find((lineItem) => lineItem.uuid === item.uuid);
|
|
9
|
-
return Boolean(foundItem);
|
|
10
|
-
};
|
|
11
|
-
|
|
12
4
|
export const createPaymentPayload = (
|
|
13
5
|
bill: MappedBill,
|
|
14
6
|
patientUuid: string,
|
|
15
7
|
formValues: Array<Payment>,
|
|
16
8
|
amountDue: number,
|
|
17
9
|
billableServices: Array<any>,
|
|
18
|
-
selectedLineItems: Array<LineItem>,
|
|
19
10
|
) => {
|
|
20
11
|
const { cashier } = bill;
|
|
21
|
-
const totalAmount = bill
|
|
12
|
+
const totalAmount = bill.totalAmount ?? 0;
|
|
22
13
|
const paymentStatus = amountDue <= 0 ? 'PAID' : 'PENDING';
|
|
23
14
|
const previousPayments = bill?.payments.map((payment) => ({
|
|
24
15
|
amount: payment.amount,
|
|
@@ -37,30 +28,21 @@ export const createPaymentPayload = (
|
|
|
37
28
|
}));
|
|
38
29
|
|
|
39
30
|
const updatedPayments = [...newPayments, ...previousPayments];
|
|
40
|
-
const totalAmountRendered = updatedPayments.reduce((acc, payment) => acc + payment.amountTendered, 0);
|
|
41
31
|
|
|
42
32
|
const updatedLineItems = bill?.lineItems.map((lineItem) => ({
|
|
43
33
|
...lineItem,
|
|
44
34
|
billableService: getBillableServiceUuid(billableServices, lineItem.billableService),
|
|
45
35
|
item: processBillItem?.(lineItem),
|
|
46
|
-
paymentStatus:
|
|
47
|
-
bill?.lineItems.length > 1
|
|
48
|
-
? hasLineItem(selectedLineItems ?? [], lineItem) && totalAmountRendered >= lineItem.price * lineItem.quantity
|
|
49
|
-
? 'PAID'
|
|
50
|
-
: 'PENDING'
|
|
51
|
-
: paymentStatus,
|
|
36
|
+
paymentStatus: lineItem.paymentStatus === 'PAID' ? 'PAID' : paymentStatus,
|
|
52
37
|
}));
|
|
53
38
|
|
|
54
|
-
const allItemsBillPaymentStatus =
|
|
55
|
-
updatedLineItems.filter((item) => item.paymentStatus === 'PENDING').length === 0 ? 'PAID' : 'PENDING';
|
|
56
|
-
|
|
57
39
|
const processedPayment = {
|
|
58
40
|
cashPoint: bill?.cashPointUuid,
|
|
59
41
|
cashier: cashier.uuid,
|
|
60
42
|
lineItems: updatedLineItems,
|
|
61
43
|
payments: [...updatedPayments],
|
|
62
44
|
patient: patientUuid,
|
|
63
|
-
status:
|
|
45
|
+
status: paymentStatus,
|
|
64
46
|
};
|
|
65
47
|
|
|
66
48
|
return processedPayment;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { type PatientDetails } from '../../types';
|
|
3
|
-
import { type SessionLocation, useConfig } from '@openmrs/esm-framework';
|
|
3
|
+
import { type SessionLocation, useConfig, interpolateUrl } from '@openmrs/esm-framework';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import type { BillingConfig } from '../../config-schema';
|
|
6
6
|
import styles from './printable-invoice-header.scss';
|
|
@@ -20,7 +20,7 @@ const PrintableInvoiceHeader: React.FC<PrintableInvoiceHeaderProps> = ({ patient
|
|
|
20
20
|
<div className={styles.printableHeader}>
|
|
21
21
|
<p className={styles.heading}>{t('invoice', 'Invoice')}</p>
|
|
22
22
|
{logo?.src && !isEmpty(logo.src) ? (
|
|
23
|
-
<img className={styles.img} src={logo.src} alt={logo.alt} />
|
|
23
|
+
<img className={styles.img} src={interpolateUrl(logo.src)} alt={logo.alt} />
|
|
24
24
|
) : logo?.alt && !isEmpty(logo.alt) ? (
|
|
25
25
|
logo.alt
|
|
26
26
|
) : (
|
|
@@ -39,7 +39,7 @@ const PrintableInvoice: React.FC<PrintableInvoiceProps> = ({ bill, patient, comp
|
|
|
39
39
|
bill?.lineItems?.map((item) => {
|
|
40
40
|
return {
|
|
41
41
|
id: `${item.uuid}`,
|
|
42
|
-
billItem: item.item,
|
|
42
|
+
billItem: item.billableService ?? item.item,
|
|
43
43
|
quantity: item.quantity,
|
|
44
44
|
price: item.price,
|
|
45
45
|
total: item.price * item.quantity,
|
package/translations/en.json
CHANGED
|
@@ -54,7 +54,6 @@
|
|
|
54
54
|
"cashPointUuidPlaceholder": "Enter UUID",
|
|
55
55
|
"checkFilters": "Check the filters above",
|
|
56
56
|
"clearSearchInput": "Clear search input",
|
|
57
|
-
"clientBalance": "Client Balance",
|
|
58
57
|
"confirmDeleteMessage": "Are you sure you want to delete this payment mode? Proceed cautiously.",
|
|
59
58
|
"createdSuccessfully": "Billable service created successfully",
|
|
60
59
|
"currentPrice": "Current price",
|