@openmrs/esm-billing-app 1.0.1-pre.95 → 1.0.2-pre.56
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/.eslintignore +0 -1
- package/.eslintrc +33 -24
- package/.husky/pre-commit +1 -1
- package/.turbo.json +1 -1
- package/.tx/config +11 -0
- package/README.md +111 -1
- package/dist/1119.js +1 -0
- package/dist/1197.js +1 -0
- package/dist/1362.js +1 -0
- package/dist/1362.js.map +1 -0
- package/dist/2146.js +1 -0
- package/dist/2690.js +1 -0
- package/dist/3029.js +2 -0
- package/dist/3029.js.LICENSE.txt +7 -0
- package/dist/3029.js.map +1 -0
- package/dist/3099.js +1 -0
- package/dist/3511.js +1 -0
- package/dist/3511.js.map +1 -0
- package/dist/3584.js +1 -0
- package/dist/4055.js +1 -0
- package/dist/4132.js +1 -0
- package/dist/4225.js +1 -0
- package/dist/4225.js.map +1 -0
- package/dist/4300.js +1 -0
- package/dist/4335.js +1 -0
- package/dist/4618.js +1 -0
- package/dist/4652.js +1 -0
- package/dist/4817.js +2 -0
- package/dist/4817.js.LICENSE.txt +77 -0
- package/dist/4817.js.map +1 -0
- package/dist/4944.js +1 -0
- package/dist/4993.js +1 -0
- package/dist/4993.js.map +1 -0
- package/dist/5173.js +1 -0
- package/dist/5241.js +1 -0
- package/dist/5442.js +1 -0
- package/dist/5661.js +1 -0
- package/dist/6022.js +1 -0
- package/dist/6468.js +1 -0
- package/dist/6540.js +2 -0
- package/dist/6540.js.map +1 -0
- package/dist/6606.js +2 -0
- package/dist/{591.js.LICENSE.txt → 6606.js.LICENSE.txt} +2 -2
- package/dist/6606.js.map +1 -0
- package/dist/6679.js +1 -0
- package/dist/6840.js +1 -0
- package/dist/6859.js +1 -0
- package/dist/6941.js +1 -0
- package/dist/6941.js.map +1 -0
- package/dist/7097.js +1 -0
- package/dist/7159.js +1 -0
- package/dist/723.js +1 -0
- package/dist/7255.js +1 -0
- package/dist/7255.js.map +1 -0
- package/dist/7617.js +1 -0
- package/dist/763.js +1 -0
- package/dist/763.js.map +1 -0
- package/dist/8163.js +1 -0
- package/dist/8349.js +1 -0
- package/dist/8618.js +1 -0
- package/dist/890.js +1 -0
- package/dist/9055.js +1 -0
- package/dist/9055.js.map +1 -0
- package/dist/9214.js +1 -0
- package/dist/9538.js +1 -0
- package/dist/{935.js → 961.js} +2 -2
- package/dist/{935.js.map → 961.js.map} +1 -1
- package/dist/986.js +1 -0
- package/dist/9879.js +1 -0
- package/dist/9895.js +1 -0
- package/dist/9900.js +1 -0
- package/dist/9913.js +1 -0
- package/dist/main.js +1 -1
- package/dist/main.js.LICENSE.txt +31 -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 +844 -165
- package/dist/openmrs-esm-billing-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/jest.config.js +4 -1
- package/package.json +19 -21
- package/src/bill-history/bill-history.component.tsx +19 -7
- package/src/bill-history/bill-history.scss +24 -9
- package/src/bill-history/bill-history.test.tsx +58 -16
- package/src/bill-item-actions/bill-item-actions.scss +26 -0
- package/src/bill-item-actions/edit-bill-item.component.tsx +221 -0
- package/src/bill-item-actions/edit-bill-item.test.tsx +137 -0
- package/src/billable-services/bill-waiver/bill-selection.component.tsx +1 -1
- package/src/billable-services/bill-waiver/bill-waiver-form.component.tsx +2 -2
- package/src/billable-services/bill-waiver/bill-waiver-form.scss +4 -4
- package/src/billable-services/bill-waiver/bill-waiver.component.tsx +4 -4
- package/src/billable-services/bill-waiver/patient-bills.component.tsx +1 -1
- package/src/billable-services/billable-service.resource.ts +19 -6
- package/src/billable-services/billable-services-home.component.tsx +19 -3
- package/src/billable-services/billable-services-menu-item/item.component.tsx +17 -0
- package/src/billable-services/billable-services-menu-item/item.scss +14 -0
- package/src/billable-services/billable-services.component.tsx +48 -9
- package/src/billable-services/billable-services.scss +10 -9
- package/src/billable-services/billable-services.test.tsx +172 -8
- package/src/billable-services/cash-point/cash-point-configuration.component.tsx +276 -0
- package/src/billable-services/cash-point/cash-point-configuration.scss +23 -0
- package/src/billable-services/create-edit/add-billable-service.component.tsx +126 -47
- package/src/billable-services/create-edit/add-billable-service.scss +14 -8
- package/src/billable-services/create-edit/add-billable-service.test.tsx +12 -10
- package/src/billable-services/dashboard/dashboard.scss +3 -3
- package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +280 -0
- package/src/billable-services/payyment-modes/payment-modes-config.scss +23 -0
- package/src/billing-dashboard/billing-dashboard.component.tsx +17 -4
- package/src/billing-dashboard/billing-dashboard.scss +3 -3
- package/src/billing-form/billing-form.component.tsx +31 -25
- package/src/billing-form/billing-form.scss +9 -10
- package/src/billing-form/visit-attributes/visit-attributes-form.component.tsx +38 -14
- package/src/billing-header/billing-header.component.tsx +21 -5
- package/src/billing-header/billing-header.scss +1 -1
- package/src/billing.resource.ts +24 -8
- package/src/bills-table/bills-table.component.tsx +46 -36
- package/src/bills-table/bills-table.scss +6 -6
- package/src/bills-table/bills-table.test.tsx +108 -68
- package/src/config-schema.ts +36 -1
- package/src/constants.ts +2 -0
- package/src/dashboard.meta.ts +2 -1
- package/src/helpers/functions.ts +0 -2
- package/src/hooks/selectedDateContext.ts +10 -0
- package/src/index.ts +22 -27
- package/src/invoice/invoice-table.component.tsx +95 -56
- package/src/invoice/invoice-table.scss +7 -8
- package/src/invoice/invoice-table.test.tsx +151 -0
- package/src/invoice/invoice.component.tsx +7 -9
- package/src/invoice/invoice.scss +2 -2
- package/src/invoice/invoice.test.tsx +199 -169
- package/src/invoice/payments/payment-form/payment-form.component.tsx +84 -55
- package/src/invoice/payments/payment-form/payment-form.test.tsx +174 -0
- package/src/invoice/payments/payment-history/payment-history.component.tsx +9 -7
- package/src/invoice/payments/payment-history/payment-history.test.tsx +160 -0
- package/src/invoice/payments/payments.component.test.tsx +121 -0
- package/src/invoice/payments/payments.component.tsx +57 -48
- package/src/invoice/payments/utils.ts +17 -13
- package/src/invoice/printable-invoice/print-receipt.component.tsx +23 -8
- package/src/invoice/printable-invoice/print-receipt.test.tsx +50 -0
- package/src/metrics-cards/card.component.tsx +4 -2
- package/src/metrics-cards/metrics-cards.test.tsx +1 -1
- package/src/modal/require-payment-modal.component.tsx +2 -2
- package/src/modal/require-payment-modal.test.tsx +66 -0
- package/src/modal/require-payment.scss +2 -1
- package/src/routes.json +40 -8
- package/src/types/index.ts +15 -0
- package/{i18next-parser.config.js → tools/i18next-parser.config.js} +19 -19
- package/tools/update-openmrs-deps.mjs +42 -0
- package/translations/am.json +53 -0
- package/translations/ar.json +170 -0
- package/translations/ar_SY.json +170 -0
- package/translations/bn.json +170 -0
- package/translations/de.json +170 -0
- package/translations/en.json +53 -0
- package/translations/es.json +53 -0
- package/translations/es_MX.json +170 -0
- package/translations/fr.json +53 -0
- package/translations/he.json +53 -0
- package/translations/hi.json +170 -0
- package/translations/hi_IN.json +170 -0
- package/translations/id.json +170 -0
- package/translations/it.json +170 -0
- package/translations/km.json +53 -0
- package/translations/ku.json +170 -0
- package/translations/ky.json +170 -0
- package/translations/lg.json +170 -0
- package/translations/ne.json +170 -0
- package/translations/pl.json +170 -0
- package/translations/pt.json +170 -0
- package/translations/pt_BR.json +170 -0
- package/translations/qu.json +170 -0
- package/translations/ro_RO.json +170 -0
- package/translations/ru_RU.json +170 -0
- package/translations/si.json +170 -0
- package/translations/sw.json +170 -0
- package/translations/sw_KE.json +170 -0
- package/translations/tr.json +170 -0
- package/translations/tr_TR.json +170 -0
- package/translations/uk.json +170 -0
- package/translations/uz.json +170 -0
- package/translations/uz@Latn.json +170 -0
- package/translations/uz_UZ.json +170 -0
- package/translations/vi.json +170 -0
- package/translations/zh.json +170 -0
- package/translations/zh_CN.json +170 -0
- package/tsconfig.json +10 -8
- package/webpack.config.js +1 -1
- package/dist/146.js +0 -1
- package/dist/146.js.map +0 -1
- package/dist/294.js +0 -2
- package/dist/294.js.map +0 -1
- package/dist/319.js +0 -1
- package/dist/384.js +0 -1
- package/dist/384.js.map +0 -1
- package/dist/421.js +0 -1
- package/dist/421.js.map +0 -1
- package/dist/533.js +0 -1
- package/dist/533.js.map +0 -1
- package/dist/574.js +0 -1
- package/dist/591.js +0 -2
- package/dist/591.js.map +0 -1
- package/dist/614.js +0 -2
- package/dist/614.js.LICENSE.txt +0 -37
- package/dist/614.js.map +0 -1
- package/dist/753.js +0 -1
- package/dist/753.js.map +0 -1
- package/dist/757.js +0 -1
- package/dist/770.js +0 -1
- package/dist/770.js.map +0 -1
- package/dist/783.js +0 -1
- package/dist/783.js.map +0 -1
- package/dist/788.js +0 -1
- package/dist/800.js +0 -2
- package/dist/800.js.LICENSE.txt +0 -3
- package/dist/800.js.map +0 -1
- package/dist/807.js +0 -1
- package/dist/833.js +0 -1
- package/dist/992.js +0 -1
- package/dist/992.js.map +0 -1
- package/src/root.scss +0 -30
- /package/dist/{294.js.LICENSE.txt → 6540.js.LICENSE.txt} +0 -0
- /package/dist/{935.js.LICENSE.txt → 961.js.LICENSE.txt} +0 -0
- /package/{src → tools}/setup-tests.ts +0 -0
- /package/{test-helpers.tsx → tools/test-helpers.tsx} +0 -0
package/src/index.ts
CHANGED
|
@@ -1,23 +1,17 @@
|
|
|
1
1
|
import { configSchema } from './config-schema';
|
|
2
|
-
import { createDashboardLink
|
|
2
|
+
import { createDashboardLink } from '@openmrs/esm-patient-common-lib';
|
|
3
3
|
import { createLeftPanelLink } from './left-panel-link.component';
|
|
4
4
|
import { dashboardMeta } from './dashboard.meta';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
getAsyncLifecycle,
|
|
8
|
-
getSyncLifecycle,
|
|
9
|
-
registerFeatureFlag,
|
|
10
|
-
translateFrom,
|
|
11
|
-
} from '@openmrs/esm-framework';
|
|
5
|
+
import { defineConfigSchema, getAsyncLifecycle, getSyncLifecycle } from '@openmrs/esm-framework';
|
|
6
|
+
import appMenu from './billable-services/billable-services-menu-item/item.component';
|
|
12
7
|
import BillableServiceHome from './billable-services/billable-services-home.component';
|
|
13
8
|
import BillableServicesCardLink from './billable-services-admin-card-link.component';
|
|
14
9
|
import BillHistory from './bill-history/bill-history.component';
|
|
15
10
|
import BillingCheckInForm from './billing-form/billing-checkin-form.component';
|
|
16
11
|
import RequirePaymentModal from './modal/require-payment-modal.component';
|
|
17
12
|
import RootComponent from './root.component';
|
|
18
|
-
import VisitAttributeTags from './invoice/payments/visit-tags/visit-attribute.component';
|
|
19
|
-
import BillableServicesDashboard from './billable-services/dashboard/dashboard.component';
|
|
20
13
|
import ServiceMetrics from './billable-services/dashboard/service-metrics.component';
|
|
14
|
+
import VisitAttributeTags from './invoice/payments/visit-tags/visit-attribute.component';
|
|
21
15
|
|
|
22
16
|
const moduleName = '@openmrs/esm-billing-app';
|
|
23
17
|
|
|
@@ -26,12 +20,6 @@ const options = {
|
|
|
26
20
|
moduleName,
|
|
27
21
|
};
|
|
28
22
|
|
|
29
|
-
registerFeatureFlag(
|
|
30
|
-
'billing',
|
|
31
|
-
'Billing module',
|
|
32
|
-
'This feature introduces navigation links on the patient chart and home page to allow accessing the billing module features',
|
|
33
|
-
);
|
|
34
|
-
|
|
35
23
|
// t('billing', 'Billing')
|
|
36
24
|
export const billingDashboardLink = getSyncLifecycle(
|
|
37
25
|
createLeftPanelLink({
|
|
@@ -45,16 +33,6 @@ export const importTranslation = require.context('../translations', false, /.jso
|
|
|
45
33
|
|
|
46
34
|
export function startupApp() {
|
|
47
35
|
defineConfigSchema(moduleName, configSchema);
|
|
48
|
-
|
|
49
|
-
// t('billingForm', 'Billing form')
|
|
50
|
-
registerWorkspace({
|
|
51
|
-
name: 'billing-form-workspace',
|
|
52
|
-
title: translateFrom(moduleName, 'billingForm', 'Billing form'),
|
|
53
|
-
load: getAsyncLifecycle(() => import('./billing-form/billing-form.component'), options),
|
|
54
|
-
type: 'billing',
|
|
55
|
-
canHide: false,
|
|
56
|
-
width: 'wider',
|
|
57
|
-
});
|
|
58
36
|
}
|
|
59
37
|
|
|
60
38
|
export const billingSummaryDashboardLink = getSyncLifecycle(
|
|
@@ -62,11 +40,28 @@ export const billingSummaryDashboardLink = getSyncLifecycle(
|
|
|
62
40
|
options,
|
|
63
41
|
);
|
|
64
42
|
|
|
43
|
+
export const billableServicesAppMenuItem = getSyncLifecycle(appMenu, options);
|
|
44
|
+
|
|
65
45
|
export const billableServicesCardLink = getSyncLifecycle(BillableServicesCardLink, options);
|
|
46
|
+
|
|
66
47
|
export const billableServicesHome = getSyncLifecycle(BillableServiceHome, options);
|
|
48
|
+
|
|
67
49
|
export const billingCheckInForm = getSyncLifecycle(BillingCheckInForm, options);
|
|
68
|
-
|
|
50
|
+
|
|
69
51
|
export const billingPatientSummary = getSyncLifecycle(BillHistory, options);
|
|
52
|
+
|
|
70
53
|
export const requirePaymentModal = getSyncLifecycle(RequirePaymentModal, options);
|
|
54
|
+
|
|
71
55
|
export const root = getSyncLifecycle(RootComponent, options);
|
|
56
|
+
|
|
57
|
+
export const serviceMetrics = getSyncLifecycle(ServiceMetrics, options);
|
|
58
|
+
|
|
72
59
|
export const visitAttributeTags = getSyncLifecycle(VisitAttributeTags, options);
|
|
60
|
+
|
|
61
|
+
export const editBillLineItemDialog = getAsyncLifecycle(() => import('./bill-item-actions/edit-bill-item.component'), {
|
|
62
|
+
featureName: 'edit bill line item',
|
|
63
|
+
moduleName,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// t('billingForm', 'Billing form')
|
|
67
|
+
export const billingFormWorkspace = getAsyncLifecycle(() => import('./billing-form/billing-form.component'), options);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import React, { useMemo, useState } from 'react';
|
|
1
|
+
import React, { useMemo, useState, useEffect, useCallback } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import fuzzy from 'fuzzy';
|
|
4
4
|
import {
|
|
5
|
+
Button,
|
|
5
6
|
DataTable,
|
|
6
7
|
DataTableSkeleton,
|
|
7
8
|
Layer,
|
|
@@ -12,18 +13,16 @@ import {
|
|
|
12
13
|
TableHead,
|
|
13
14
|
TableHeader,
|
|
14
15
|
TableRow,
|
|
15
|
-
TableToolbar,
|
|
16
|
-
TableToolbarContent,
|
|
17
|
-
TableToolbarSearch,
|
|
18
16
|
TableSelectRow,
|
|
17
|
+
TableToolbarSearch,
|
|
19
18
|
Tile,
|
|
20
|
-
type DataTableHeader,
|
|
21
19
|
type DataTableRow,
|
|
22
20
|
} from '@carbon/react';
|
|
23
|
-
import {
|
|
21
|
+
import { Edit } from '@carbon/react/icons';
|
|
22
|
+
import { isDesktop, showModal, useConfig, useDebounce, useLayoutType } from '@openmrs/esm-framework';
|
|
24
23
|
import { type LineItem, type MappedBill } from '../types';
|
|
25
|
-
import styles from './invoice-table.scss';
|
|
26
24
|
import { convertToCurrency } from '../helpers';
|
|
25
|
+
import styles from './invoice-table.scss';
|
|
27
26
|
|
|
28
27
|
type InvoiceTableProps = {
|
|
29
28
|
bill: MappedBill;
|
|
@@ -34,14 +33,21 @@ type InvoiceTableProps = {
|
|
|
34
33
|
|
|
35
34
|
const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true, isLoadingBill, onSelectItem }) => {
|
|
36
35
|
const { t } = useTranslation();
|
|
37
|
-
const
|
|
36
|
+
const { defaultCurrency, showEditBillButton } = useConfig();
|
|
38
37
|
const layout = useLayoutType();
|
|
38
|
+
const lineItems = useMemo(() => bill?.lineItems ?? [], [bill?.lineItems]);
|
|
39
|
+
const paidLineItems = useMemo(() => lineItems?.filter((item) => item.paymentStatus === 'PAID') ?? [], [lineItems]);
|
|
39
40
|
const responsiveSize = isDesktop(layout) ? 'sm' : 'lg';
|
|
40
|
-
|
|
41
|
-
const [selectedLineItems, setSelectedLineItems] = useState(
|
|
41
|
+
|
|
42
|
+
const [selectedLineItems, setSelectedLineItems] = useState(paidLineItems ?? []);
|
|
42
43
|
const [searchTerm, setSearchTerm] = useState('');
|
|
43
44
|
const debouncedSearchTerm = useDebounce(searchTerm);
|
|
44
|
-
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (onSelectItem) {
|
|
48
|
+
onSelectItem(selectedLineItems);
|
|
49
|
+
}
|
|
50
|
+
}, [selectedLineItems, onSelectItem]);
|
|
45
51
|
|
|
46
52
|
const filteredLineItems = useMemo(() => {
|
|
47
53
|
if (!debouncedSearchTerm) {
|
|
@@ -58,16 +64,28 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
|
|
|
58
64
|
: lineItems;
|
|
59
65
|
}, [debouncedSearchTerm, lineItems]);
|
|
60
66
|
|
|
61
|
-
const tableHeaders
|
|
62
|
-
{ header: 'No', key: 'no' },
|
|
63
|
-
{ header: 'Bill item', key: 'billItem' },
|
|
64
|
-
{ header: 'Bill code', key: 'billCode' },
|
|
65
|
-
{ header: 'Status', key: 'status' },
|
|
66
|
-
{ header: 'Quantity', key: 'quantity' },
|
|
67
|
-
{ header: 'Price', key: 'price' },
|
|
68
|
-
{ header: 'Total', key: 'total' },
|
|
67
|
+
const tableHeaders = [
|
|
68
|
+
{ header: 'No', key: 'no', width: 7 }, // Width as a percentage
|
|
69
|
+
{ header: 'Bill item', key: 'billItem', width: 25 },
|
|
70
|
+
{ header: 'Bill code', key: 'billCode', width: 20 },
|
|
71
|
+
{ header: 'Status', key: 'status', width: 25 },
|
|
72
|
+
{ header: 'Quantity', key: 'quantity', width: 15 },
|
|
73
|
+
{ header: 'Price', key: 'price', width: 24 },
|
|
74
|
+
{ header: 'Total', key: 'total', width: 15 },
|
|
75
|
+
{ header: t('actions', 'Actions'), key: 'actionButton' },
|
|
69
76
|
];
|
|
70
77
|
|
|
78
|
+
const handleSelectBillItem = useCallback(
|
|
79
|
+
(row: LineItem) => {
|
|
80
|
+
const dispose = showModal('edit-bill-line-item-dialog', {
|
|
81
|
+
bill,
|
|
82
|
+
item: row,
|
|
83
|
+
closeModal: () => dispose(),
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
[bill],
|
|
87
|
+
);
|
|
88
|
+
|
|
71
89
|
const tableRows: Array<typeof DataTableRow> = useMemo(
|
|
72
90
|
() =>
|
|
73
91
|
filteredLineItems?.map((item, index) => {
|
|
@@ -75,20 +93,38 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
|
|
|
75
93
|
no: `${index + 1}`,
|
|
76
94
|
id: `${item.uuid}`,
|
|
77
95
|
billItem: item.billableService ? item.billableService : item?.item,
|
|
78
|
-
billCode: bill?.receiptNumber
|
|
79
|
-
status: item
|
|
96
|
+
billCode: <span data-testid={`receipt-number-${index}`}>{bill?.receiptNumber}</span>,
|
|
97
|
+
status: item.paymentStatus,
|
|
80
98
|
quantity: item.quantity,
|
|
81
99
|
price: convertToCurrency(item.price, defaultCurrency),
|
|
82
|
-
total:
|
|
100
|
+
total: item.price * item.quantity,
|
|
101
|
+
actionButton: (
|
|
102
|
+
<span>
|
|
103
|
+
{showEditBillButton ? (
|
|
104
|
+
<Button
|
|
105
|
+
data-testid={`edit-button-${item.uuid}`}
|
|
106
|
+
renderIcon={Edit}
|
|
107
|
+
hasIconOnly
|
|
108
|
+
kind="ghost"
|
|
109
|
+
iconDescription={t('editThisBillItem', 'Edit this bill item')}
|
|
110
|
+
tooltipPosition="left"
|
|
111
|
+
onClick={() => handleSelectBillItem(item)}
|
|
112
|
+
/>
|
|
113
|
+
) : (
|
|
114
|
+
'--'
|
|
115
|
+
)}
|
|
116
|
+
</span>
|
|
117
|
+
),
|
|
83
118
|
};
|
|
84
119
|
}) ?? [],
|
|
85
|
-
[bill?.receiptNumber,
|
|
120
|
+
[filteredLineItems, bill?.receiptNumber, defaultCurrency, showEditBillButton, t, handleSelectBillItem],
|
|
86
121
|
);
|
|
87
122
|
|
|
88
123
|
if (isLoadingBill) {
|
|
89
124
|
return (
|
|
90
125
|
<div className={styles.loaderContainer}>
|
|
91
126
|
<DataTableSkeleton
|
|
127
|
+
data-testid="loader"
|
|
92
128
|
columnCount={tableHeaders.length}
|
|
93
129
|
showHeader={false}
|
|
94
130
|
showToolbar={false}
|
|
@@ -123,20 +159,17 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
|
|
|
123
159
|
</span>
|
|
124
160
|
}
|
|
125
161
|
title={t('lineItems', 'Line items')}>
|
|
126
|
-
<
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
</TableToolbar>
|
|
138
|
-
</div>
|
|
139
|
-
<Table {...getTableProps()} aria-label="Invoice line items" className={styles.table}>
|
|
162
|
+
<TableToolbarSearch
|
|
163
|
+
className={styles.searchbox}
|
|
164
|
+
expanded
|
|
165
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchTerm(e.target.value)}
|
|
166
|
+
placeholder={t('searchThisTable', 'Search this table')}
|
|
167
|
+
size={responsiveSize}
|
|
168
|
+
/>
|
|
169
|
+
<Table
|
|
170
|
+
{...getTableProps()}
|
|
171
|
+
aria-label="Invoice line items"
|
|
172
|
+
className={`${styles.invoiceTable} billingTable`}>
|
|
140
173
|
<TableHead>
|
|
141
174
|
<TableRow>
|
|
142
175
|
{rows.length > 1 && isSelectable ? <TableHeader /> : null}
|
|
@@ -146,25 +179,31 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
|
|
|
146
179
|
</TableRow>
|
|
147
180
|
</TableHead>
|
|
148
181
|
<TableBody>
|
|
149
|
-
{rows.map((row, index) =>
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
182
|
+
{rows.map((row, index) => {
|
|
183
|
+
return (
|
|
184
|
+
<TableRow
|
|
185
|
+
key={row.id}
|
|
186
|
+
{...getRowProps({
|
|
187
|
+
row,
|
|
188
|
+
})}>
|
|
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
|
+
{row.cells.map((cell) => (
|
|
202
|
+
<TableCell key={cell.id}>{cell.value}</TableCell>
|
|
203
|
+
))}
|
|
204
|
+
</TableRow>
|
|
205
|
+
);
|
|
206
|
+
})}
|
|
168
207
|
</TableBody>
|
|
169
208
|
</Table>
|
|
170
209
|
</TableContainer>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
@use '@carbon/colors';
|
|
2
2
|
@use '@carbon/layout';
|
|
3
3
|
@use '@carbon/type';
|
|
4
|
-
@
|
|
4
|
+
@use '@openmrs/esm-styleguide/src/vars' as *;
|
|
5
5
|
|
|
6
6
|
.filterEmptyState {
|
|
7
7
|
align-items: center;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
.filterEmptyStateContent {
|
|
20
20
|
@include type.type-style('heading-compact-02');
|
|
21
21
|
color: $text-02;
|
|
22
|
-
margin-bottom:
|
|
22
|
+
margin-bottom: layout.$spacing-03;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
.filterEmptyStateHelper {
|
|
@@ -32,7 +32,6 @@
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
.invoiceContainer {
|
|
35
|
-
margin: layout.$spacing-09 layout.$spacing-05 0;
|
|
36
35
|
border: 1px solid $ui-03;
|
|
37
36
|
}
|
|
38
37
|
|
|
@@ -42,10 +41,10 @@
|
|
|
42
41
|
}
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
.invoiceTable {
|
|
45
|
+
width: 100%;
|
|
46
|
+
table-layout: fixed;
|
|
47
|
+
border-collapse: collapse;
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
.tableDescription {
|
|
@@ -57,7 +56,7 @@
|
|
|
57
56
|
::after {
|
|
58
57
|
content: '';
|
|
59
58
|
display: block;
|
|
60
|
-
width:
|
|
59
|
+
width: layout.$spacing-07;
|
|
61
60
|
padding-top: 0.188rem;
|
|
62
61
|
border-bottom: 0.375rem solid var(--brand-03);
|
|
63
62
|
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { render, screen, fireEvent, act } from '@testing-library/react';
|
|
4
|
+
import { showModal } from '@openmrs/esm-framework';
|
|
5
|
+
import InvoiceTable from './invoice-table.component';
|
|
6
|
+
import { type MappedBill } from '../types';
|
|
7
|
+
|
|
8
|
+
// Mocking dependencies
|
|
9
|
+
jest.mock('react-i18next', () => ({
|
|
10
|
+
useTranslation: jest.fn(() => ({
|
|
11
|
+
t: jest.fn((key, fallback) => fallback || key),
|
|
12
|
+
i18n: { language: 'en' },
|
|
13
|
+
})),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
jest.mock('@openmrs/esm-framework', () => ({
|
|
17
|
+
showModal: jest.fn(),
|
|
18
|
+
useConfig: jest.fn(() => ({
|
|
19
|
+
defaultCurrency: 'USD',
|
|
20
|
+
showEditBillButton: true,
|
|
21
|
+
})),
|
|
22
|
+
useDebounce: jest.fn((value) => value),
|
|
23
|
+
useLayoutType: jest.fn(() => 'desktop'),
|
|
24
|
+
isDesktop: jest.fn(() => true),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
jest.mock('../helpers', () => ({
|
|
28
|
+
convertToCurrency: jest.fn((price) => `USD ${price}`),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
describe('InvoiceTable', () => {
|
|
32
|
+
const mockT = jest.fn((key) => key);
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
(useTranslation as jest.Mock).mockReturnValue({ t: mockT, i18n: { language: 'en' } });
|
|
36
|
+
jest.useFakeTimers();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
jest.useRealTimers();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const bill: MappedBill = {
|
|
44
|
+
uuid: 'bill-uuid',
|
|
45
|
+
id: 123,
|
|
46
|
+
patientUuid: 'patient-uuid',
|
|
47
|
+
patientName: 'John Doe',
|
|
48
|
+
lineItems: [
|
|
49
|
+
{
|
|
50
|
+
uuid: '1',
|
|
51
|
+
item: 'Item 1',
|
|
52
|
+
paymentStatus: 'PAID',
|
|
53
|
+
quantity: 1,
|
|
54
|
+
price: 100,
|
|
55
|
+
display: '',
|
|
56
|
+
voided: false,
|
|
57
|
+
voidReason: '',
|
|
58
|
+
billableService: '',
|
|
59
|
+
priceName: '',
|
|
60
|
+
priceUuid: '',
|
|
61
|
+
lineItemOrder: 0,
|
|
62
|
+
resourceVersion: '',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
uuid: '2',
|
|
66
|
+
item: 'Item 2',
|
|
67
|
+
paymentStatus: 'PENDING',
|
|
68
|
+
quantity: 2,
|
|
69
|
+
price: 200,
|
|
70
|
+
display: '',
|
|
71
|
+
voided: false,
|
|
72
|
+
voidReason: '',
|
|
73
|
+
billableService: '',
|
|
74
|
+
priceName: '',
|
|
75
|
+
priceUuid: '',
|
|
76
|
+
lineItemOrder: 0,
|
|
77
|
+
resourceVersion: '',
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
receiptNumber: '12345',
|
|
81
|
+
cashPointUuid: 'cash-point-uuid',
|
|
82
|
+
cashPointName: 'Main Cash Point',
|
|
83
|
+
cashPointLocation: 'Front Desk',
|
|
84
|
+
cashier: {
|
|
85
|
+
uuid: 'cashier-uuid',
|
|
86
|
+
display: 'John Doe',
|
|
87
|
+
links: [],
|
|
88
|
+
},
|
|
89
|
+
status: 'PAID',
|
|
90
|
+
identifier: 'receipt-identifier',
|
|
91
|
+
dateCreated: new Date().toISOString(),
|
|
92
|
+
billingService: 'billing-service-uuid',
|
|
93
|
+
payments: [],
|
|
94
|
+
totalAmount: 300,
|
|
95
|
+
tenderedAmount: 300,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
it('renders the table and displays line items correctly', () => {
|
|
99
|
+
render(<InvoiceTable bill={bill} />);
|
|
100
|
+
|
|
101
|
+
expect(screen.getByText('Item 1')).toBeInTheDocument();
|
|
102
|
+
expect(screen.getByText('Item 2')).toBeInTheDocument();
|
|
103
|
+
expect(screen.getByTestId('receipt-number-0')).toHaveTextContent('12345');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('renders the edit button and calls showModal when clicked', () => {
|
|
107
|
+
render(<InvoiceTable bill={bill} />);
|
|
108
|
+
|
|
109
|
+
const editButton = screen.getByTestId('edit-button-1');
|
|
110
|
+
fireEvent.click(editButton);
|
|
111
|
+
expect(showModal).toHaveBeenCalledWith('edit-bill-line-item-dialog', expect.anything());
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('displays a skeleton loader when the bill is loading', () => {
|
|
115
|
+
render(<InvoiceTable bill={bill} isLoadingBill={true} />);
|
|
116
|
+
|
|
117
|
+
expect(screen.getByTestId('loader')).toBeInTheDocument();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('filters line items based on the search term', () => {
|
|
121
|
+
render(<InvoiceTable bill={bill} />);
|
|
122
|
+
const searchInput = screen.getByPlaceholderText('searchThisTable'); //
|
|
123
|
+
|
|
124
|
+
fireEvent.change(searchInput, { target: { value: 'Item 2' } });
|
|
125
|
+
|
|
126
|
+
expect(screen.queryByText('Item 1')).not.toBeInTheDocument();
|
|
127
|
+
expect(screen.getByText('Item 2')).toBeInTheDocument();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('correctly handles row selection', () => {
|
|
131
|
+
const onSelectItem = jest.fn();
|
|
132
|
+
render(<InvoiceTable bill={bill} onSelectItem={onSelectItem} />);
|
|
133
|
+
|
|
134
|
+
const checkboxes = screen.getAllByLabelText('Select row');
|
|
135
|
+
fireEvent.click(checkboxes[0]);
|
|
136
|
+
|
|
137
|
+
expect(onSelectItem).toHaveBeenCalledWith([bill.lineItems[0]]);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('resets isRedirecting to false after timeout', () => {
|
|
141
|
+
render(<InvoiceTable bill={bill} />);
|
|
142
|
+
|
|
143
|
+
const button = screen.getByTestId('edit-button-1');
|
|
144
|
+
fireEvent.click(button);
|
|
145
|
+
act(() => {
|
|
146
|
+
jest.advanceTimersByTime(1000);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(button).not.toBeDisabled();
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -24,15 +24,15 @@ const Invoice: React.FC = () => {
|
|
|
24
24
|
const { t } = useTranslation();
|
|
25
25
|
const { billUuid, patientUuid } = useParams();
|
|
26
26
|
const { patient, isLoading: isLoadingPatient } = usePatient(patientUuid);
|
|
27
|
-
const { bill, isLoading: isLoadingBill, error } = useBill(billUuid);
|
|
27
|
+
const { bill, isLoading: isLoadingBill, error, mutate } = useBill(billUuid);
|
|
28
28
|
const [isPrinting, setIsPrinting] = useState(false);
|
|
29
|
-
const [selectedLineItems, setSelectedLineItems] = useState([]);
|
|
29
|
+
const [selectedLineItems, setSelectedLineItems] = useState<LineItem[]>([]);
|
|
30
30
|
const componentRef = useRef<HTMLDivElement>(null);
|
|
31
31
|
const onBeforeGetContentResolve = useRef<(() => void) | null>(null);
|
|
32
|
-
const
|
|
32
|
+
const { defaultCurrency } = useConfig();
|
|
33
|
+
const handleSelectItem = (lineItems: LineItem[]) => {
|
|
33
34
|
setSelectedLineItems(lineItems);
|
|
34
35
|
};
|
|
35
|
-
const { defaultCurrency } = useConfig();
|
|
36
36
|
|
|
37
37
|
const handleAfterPrint = useCallback(() => {
|
|
38
38
|
onBeforeGetContentResolve.current = null;
|
|
@@ -52,9 +52,7 @@ const Invoice: React.FC = () => {
|
|
|
52
52
|
|
|
53
53
|
const handlePrint = useReactToPrint({
|
|
54
54
|
content: reactToPrintContent,
|
|
55
|
-
documentTitle: `Invoice ${bill?.receiptNumber} - ${patient?.name?.[0]?.given?.join(' ')} ${
|
|
56
|
-
patient?.name?.[0].family
|
|
57
|
-
}`,
|
|
55
|
+
documentTitle: `Invoice ${bill?.receiptNumber} - ${patient?.name?.[0]?.given?.join(' ')} ${patient?.name?.[0].family}`,
|
|
58
56
|
onBeforeGetContent: handleOnBeforeGetContent,
|
|
59
57
|
onAfterPrint: handleAfterPrint,
|
|
60
58
|
removeAfterPrint: true,
|
|
@@ -118,12 +116,12 @@ const Invoice: React.FC = () => {
|
|
|
118
116
|
size="md">
|
|
119
117
|
{t('printBill', 'Print bill')}
|
|
120
118
|
</Button>
|
|
121
|
-
{bill?.status === 'PAID'
|
|
119
|
+
{(bill?.status === 'PAID' || bill?.tenderedAmount > 0) && <PrintReceipt billId={bill?.id} />}
|
|
122
120
|
</div>
|
|
123
121
|
</div>
|
|
124
122
|
|
|
125
123
|
<InvoiceTable bill={bill} isLoadingBill={isLoadingBill} onSelectItem={handleSelectItem} />
|
|
126
|
-
<Payments bill={bill} selectedLineItems={selectedLineItems} />
|
|
124
|
+
<Payments bill={bill} mutate={mutate} selectedLineItems={selectedLineItems} />
|
|
127
125
|
|
|
128
126
|
<div className={styles.printContainer} ref={componentRef}>
|
|
129
127
|
{isPrinting && <PrintableInvoice bill={bill} patient={patient} isLoading={isLoadingPatient} />}
|
package/src/invoice/invoice.scss
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
.invoiceContainer {
|
|
6
6
|
background-color: colors.$gray-10;
|
|
7
|
-
height: calc(100vh -
|
|
7
|
+
height: calc(100vh - layout.$spacing-09);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
.errorContainer {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
align-items: center;
|
|
29
29
|
justify-content: space-between;
|
|
30
30
|
margin: layout.$spacing-05;
|
|
31
|
-
row-gap:
|
|
31
|
+
row-gap: layout.$spacing-06;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
.label {
|