@openmrs/esm-billing-app 1.0.1-pre.98 → 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 +5 -3
- 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 +21 -4
- 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
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useContext } from 'react';
|
|
2
|
+
import dayjs from 'dayjs';
|
|
3
|
+
import { DatePickerInput, DatePicker } from '@carbon/react';
|
|
2
4
|
import { useTranslation } from 'react-i18next';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
+
import { Location, UserFollow } from '@carbon/react/icons';
|
|
6
|
+
import { useSession } from '@openmrs/esm-framework';
|
|
7
|
+
import { omrsDateFormat } from '../constants';
|
|
5
8
|
import BillingIllustration from './billing-illustration.component';
|
|
9
|
+
import SelectedDateContext from '../hooks/selectedDateContext';
|
|
6
10
|
import styles from './billing-header.scss';
|
|
7
11
|
|
|
8
12
|
interface BillingHeaderProps {
|
|
@@ -13,6 +17,7 @@ const BillingHeader: React.FC<BillingHeaderProps> = ({ title }) => {
|
|
|
13
17
|
const { t } = useTranslation();
|
|
14
18
|
const session = useSession();
|
|
15
19
|
const location = session?.sessionLocation?.display;
|
|
20
|
+
const { selectedDate, setSelectedDate } = useContext(SelectedDateContext);
|
|
16
21
|
|
|
17
22
|
return (
|
|
18
23
|
<div className={styles.header} data-testid="billing-header">
|
|
@@ -32,8 +37,19 @@ const BillingHeader: React.FC<BillingHeaderProps> = ({ title }) => {
|
|
|
32
37
|
<Location size={16} />
|
|
33
38
|
<span className={styles.value}>{location}</span>
|
|
34
39
|
<span className={styles.middot}>·</span>
|
|
35
|
-
<
|
|
36
|
-
|
|
40
|
+
<DatePicker
|
|
41
|
+
onChange={([date]) => setSelectedDate(dayjs(date).startOf('day').format(omrsDateFormat))}
|
|
42
|
+
value={dayjs(selectedDate).format('DD MMM YYYY')}
|
|
43
|
+
dateFormat="d-M-Y"
|
|
44
|
+
datePickerType="single">
|
|
45
|
+
<DatePickerInput
|
|
46
|
+
style={{ cursor: 'pointer', backgroundColor: 'transparent', border: 'none', maxWidth: '10rem' }}
|
|
47
|
+
id="appointment-date-picker"
|
|
48
|
+
placeholder="DD-MMM-YYYY"
|
|
49
|
+
labelText=""
|
|
50
|
+
type="text"
|
|
51
|
+
/>
|
|
52
|
+
</DatePicker>
|
|
37
53
|
</div>
|
|
38
54
|
</div>
|
|
39
55
|
</div>
|
package/src/billing.resource.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
import dayjs from 'dayjs';
|
|
3
|
+
import isEmpty from 'lodash-es/isEmpty';
|
|
4
|
+
import sortBy from 'lodash-es/sortBy';
|
|
1
5
|
import useSWR from 'swr';
|
|
2
6
|
import { formatDate, parseDate, openmrsFetch, useSession, useVisit, restBaseUrl } from '@openmrs/esm-framework';
|
|
7
|
+
import { apiBasePath, omrsDateFormat } from './constants';
|
|
3
8
|
import type { FacilityDetail, MappedBill, PatientInvoice } from './types';
|
|
4
|
-
import
|
|
5
|
-
import sortBy from 'lodash-es/sortBy';
|
|
6
|
-
import { apiBasePath } from './constants';
|
|
9
|
+
import SelectedDateContext from './hooks/selectedDateContext';
|
|
7
10
|
|
|
8
11
|
export const useBills = (patientUuid: string = '', billStatus: string = '') => {
|
|
9
|
-
const
|
|
12
|
+
const { selectedDate } = useContext(SelectedDateContext);
|
|
13
|
+
const endDate = dayjs().endOf('day').format(omrsDateFormat);
|
|
14
|
+
const url = `${apiBasePath}bill?q=&v=full`;
|
|
15
|
+
|
|
10
16
|
const patientUrl = `${apiBasePath}bill?patientUuid=${patientUuid}&v=full`;
|
|
11
17
|
|
|
12
18
|
const { data, error, isLoading, isValidating, mutate } = useSWR<{ data: { results: Array<PatientInvoice> } }>(
|
|
@@ -146,3 +152,14 @@ export const processBillItems = (payload) => {
|
|
|
146
152
|
},
|
|
147
153
|
});
|
|
148
154
|
};
|
|
155
|
+
|
|
156
|
+
export const updateBillItems = (payload) => {
|
|
157
|
+
const url = `${apiBasePath}bill/${payload.uuid}`;
|
|
158
|
+
return openmrsFetch(url, {
|
|
159
|
+
method: 'POST',
|
|
160
|
+
body: payload,
|
|
161
|
+
headers: {
|
|
162
|
+
'Content-Type': 'application/json',
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
};
|
|
@@ -36,10 +36,10 @@ const BillsTable = () => {
|
|
|
36
36
|
const config = useConfig();
|
|
37
37
|
const layout = useLayoutType();
|
|
38
38
|
const responsiveSize = isDesktop(layout) ? 'sm' : 'lg';
|
|
39
|
-
const [billPaymentStatus, setBillPaymentStatus] = useState('');
|
|
39
|
+
const [billPaymentStatus, setBillPaymentStatus] = useState('PENDING');
|
|
40
40
|
const pageSizes = config?.bills?.pageSizes ?? [10, 20, 30, 40, 50];
|
|
41
41
|
const [pageSize, setPageSize] = useState(config?.bills?.pageSize ?? 10);
|
|
42
|
-
const { bills, isLoading, isValidating, error } = useBills('',
|
|
42
|
+
const { bills, isLoading, isValidating, error } = useBills('', '');
|
|
43
43
|
const [searchString, setSearchString] = useState('');
|
|
44
44
|
|
|
45
45
|
const headerData = [
|
|
@@ -62,22 +62,28 @@ const BillsTable = () => {
|
|
|
62
62
|
];
|
|
63
63
|
|
|
64
64
|
const searchResults = useMemo(() => {
|
|
65
|
-
if (bills
|
|
66
|
-
if (searchString && searchString.trim() !== '') {
|
|
67
|
-
const search = searchString.toLowerCase();
|
|
68
|
-
return bills?.filter((activeBillRow) =>
|
|
69
|
-
Object.entries(activeBillRow).some(([header, value]) => {
|
|
70
|
-
if (header === 'patientUuid') {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
return `${value}`.toLowerCase().includes(search);
|
|
74
|
-
}),
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
65
|
+
if (!bills?.length) return bills;
|
|
78
66
|
|
|
79
|
-
return bills
|
|
80
|
-
|
|
67
|
+
return bills
|
|
68
|
+
.map((bill) => {
|
|
69
|
+
if (bill.payments?.length > 0) {
|
|
70
|
+
const totalPaid = bill.payments.reduce((sum, payment) => sum + payment.amountTendered, 0);
|
|
71
|
+
if (totalPaid >= bill.totalAmount) {
|
|
72
|
+
bill.status = 'PAID';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return bill;
|
|
76
|
+
})
|
|
77
|
+
.filter((bill) => {
|
|
78
|
+
const statusMatch = billPaymentStatus === '' ? true : bill.status === billPaymentStatus;
|
|
79
|
+
const searchMatch = !searchString
|
|
80
|
+
? true
|
|
81
|
+
: bill.patientName.toLowerCase().includes(searchString.toLowerCase()) ||
|
|
82
|
+
bill.identifier.toLowerCase().includes(searchString.toLowerCase());
|
|
83
|
+
|
|
84
|
+
return statusMatch && searchMatch;
|
|
85
|
+
});
|
|
86
|
+
}, [bills, searchString, billPaymentStatus]);
|
|
81
87
|
|
|
82
88
|
const { paginated, goTo, results, currentPage } = usePagination(searchResults, pageSize);
|
|
83
89
|
|
|
@@ -86,23 +92,25 @@ const BillsTable = () => {
|
|
|
86
92
|
|
|
87
93
|
const billingUrl = '${openmrsSpaBase}/home/billing/patient/${patientUuid}/${uuid}';
|
|
88
94
|
|
|
89
|
-
const rowData = results?.map((bill, index) =>
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
95
|
+
const rowData = results?.map((bill, index) => {
|
|
96
|
+
return {
|
|
97
|
+
id: `${index}`,
|
|
98
|
+
uuid: bill.uuid,
|
|
99
|
+
patientName: (
|
|
100
|
+
<ConfigurableLink
|
|
101
|
+
style={{ textDecoration: 'none', maxWidth: '50%' }}
|
|
102
|
+
to={billingUrl}
|
|
103
|
+
templateParams={{ patientUuid: bill.patientUuid, uuid: bill.uuid }}>
|
|
104
|
+
{bill.patientName}
|
|
105
|
+
</ConfigurableLink>
|
|
106
|
+
),
|
|
107
|
+
visitTime: bill.dateCreated,
|
|
108
|
+
identifier: bill.identifier,
|
|
109
|
+
department: '--',
|
|
110
|
+
billedItems: setBilledItems(bill),
|
|
111
|
+
billingPrice: '--',
|
|
112
|
+
};
|
|
113
|
+
});
|
|
106
114
|
|
|
107
115
|
const handleSearch = useCallback(
|
|
108
116
|
(e) => {
|
|
@@ -118,7 +126,9 @@ const BillsTable = () => {
|
|
|
118
126
|
{ id: 'PAID', text: 'Paid bills' },
|
|
119
127
|
];
|
|
120
128
|
|
|
121
|
-
const handleFilterChange = ({ selectedItem }) =>
|
|
129
|
+
const handleFilterChange = ({ selectedItem }) => {
|
|
130
|
+
setBillPaymentStatus(selectedItem.id);
|
|
131
|
+
};
|
|
122
132
|
|
|
123
133
|
if (isLoading) {
|
|
124
134
|
return (
|
|
@@ -152,7 +162,7 @@ const BillsTable = () => {
|
|
|
152
162
|
className={styles.filterDropdown}
|
|
153
163
|
direction="bottom"
|
|
154
164
|
id={`filter-${id}`}
|
|
155
|
-
initialSelectedItem={filterItems[
|
|
165
|
+
initialSelectedItem={filterItems[1]}
|
|
156
166
|
items={filterItems}
|
|
157
167
|
itemToString={(item) => (item ? item.text : '')}
|
|
158
168
|
label=""
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
@use '@carbon/layout';
|
|
2
2
|
@use '@carbon/type';
|
|
3
|
-
@
|
|
3
|
+
@use '@openmrs/esm-styleguide/src/vars' as *;
|
|
4
4
|
|
|
5
5
|
.container {
|
|
6
|
-
margin:
|
|
6
|
+
margin: layout.$spacing-07 0;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
.emptyStateContainer,
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
justify-content: space-between;
|
|
64
64
|
|
|
65
65
|
&:global(.cds--inline-loading) {
|
|
66
|
-
max-height:
|
|
66
|
+
max-height: layout.$spacing-05;
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -104,11 +104,11 @@
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
.expandedActiveVisitRow td {
|
|
107
|
-
padding: 0
|
|
107
|
+
padding: 0 layout.$spacing-05;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
.expandedActiveVisitRow th[colspan] td[colspan] > div:first-child {
|
|
111
|
-
padding: 0
|
|
111
|
+
padding: 0 layout.$spacing-05;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
.action {
|
|
@@ -172,7 +172,7 @@
|
|
|
172
172
|
.filterEmptyStateContent {
|
|
173
173
|
@include type.type-style('heading-compact-02');
|
|
174
174
|
color: $text-02;
|
|
175
|
-
margin-bottom:
|
|
175
|
+
margin-bottom: layout.$spacing-03;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
.filterEmptyStateHelper {
|
|
@@ -1,18 +1,39 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
|
-
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
4
4
|
import { useBills } from '../billing.resource';
|
|
5
5
|
import BillsTable from './bills-table.component';
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
jest.mock('react-i18next', () => ({
|
|
8
|
+
useTranslation: () => ({
|
|
9
|
+
t: (key: string) => key,
|
|
10
|
+
}),
|
|
11
|
+
}));
|
|
8
12
|
|
|
9
13
|
const mockBillsData = [
|
|
10
|
-
{
|
|
11
|
-
|
|
14
|
+
{
|
|
15
|
+
uuid: '1',
|
|
16
|
+
patientName: 'John Doe',
|
|
17
|
+
identifier: '12345678',
|
|
18
|
+
visitType: 'Checkup',
|
|
19
|
+
patientUuid: 'uuid1',
|
|
20
|
+
dateCreated: '2024-01-01',
|
|
21
|
+
lineItems: [{ billableService: 'Service 1' }],
|
|
22
|
+
status: 'PENDING',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
uuid: '2',
|
|
26
|
+
patientName: 'Mary Smith',
|
|
27
|
+
identifier: '98765432',
|
|
28
|
+
visitType: 'Wake up',
|
|
29
|
+
patientUuid: 'uuid2',
|
|
30
|
+
dateCreated: '2024-01-02',
|
|
31
|
+
lineItems: [{ billableService: 'Service 2' }],
|
|
32
|
+
status: 'PENDING',
|
|
33
|
+
},
|
|
12
34
|
];
|
|
13
35
|
|
|
14
36
|
jest.mock('../billing.resource', () => ({
|
|
15
|
-
...jest.requireActual('../billing.resource'),
|
|
16
37
|
useBills: jest.fn(() => ({
|
|
17
38
|
bills: mockBillsData,
|
|
18
39
|
isLoading: false,
|
|
@@ -21,47 +42,59 @@ jest.mock('../billing.resource', () => ({
|
|
|
21
42
|
})),
|
|
22
43
|
}));
|
|
23
44
|
|
|
45
|
+
jest.mock('@openmrs/esm-patient-common-lib', () => ({
|
|
46
|
+
EmptyDataIllustration: jest.fn(() => <div>Empty state illustration</div>),
|
|
47
|
+
}));
|
|
48
|
+
|
|
24
49
|
jest.mock('@openmrs/esm-framework', () => ({
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
50
|
+
useLayoutType: jest.fn(() => 'desktop'),
|
|
51
|
+
isDesktop: jest.fn(() => true),
|
|
52
|
+
ErrorState: jest.fn(({ error }) => <div data-testid="error-state">{error?.message || error}</div>),
|
|
53
|
+
useConfig: jest.fn(() => ({
|
|
54
|
+
bills: {
|
|
55
|
+
pageSizes: [10, 20, 30, 40, 50],
|
|
56
|
+
pageSize: 10,
|
|
57
|
+
},
|
|
58
|
+
})),
|
|
33
59
|
usePagination: jest.fn().mockImplementation((data) => ({
|
|
34
60
|
currentPage: 1,
|
|
35
|
-
goTo: ()
|
|
61
|
+
goTo: jest.fn(),
|
|
36
62
|
results: data,
|
|
37
|
-
paginated:
|
|
63
|
+
paginated: true,
|
|
38
64
|
})),
|
|
65
|
+
ConfigurableLink: jest.fn(({ children, to, templateParams }) => {
|
|
66
|
+
const resolvedTo = '/home/billing/patient/' + templateParams.patientUuid + '/' + templateParams.uuid;
|
|
67
|
+
return <a href={resolvedTo}>{children}</a>;
|
|
68
|
+
}),
|
|
69
|
+
openmrsSpaBase: '',
|
|
39
70
|
}));
|
|
40
71
|
|
|
41
72
|
describe('BillsTable', () => {
|
|
73
|
+
const mockBills = useBills as jest.Mock;
|
|
42
74
|
let user;
|
|
43
75
|
|
|
44
76
|
beforeEach(() => {
|
|
45
77
|
user = userEvent.setup();
|
|
78
|
+
mockBills.mockImplementation(() => ({
|
|
79
|
+
bills: mockBillsData,
|
|
80
|
+
isLoading: false,
|
|
81
|
+
isValidating: false,
|
|
82
|
+
error: null,
|
|
83
|
+
}));
|
|
46
84
|
});
|
|
47
85
|
|
|
48
|
-
|
|
86
|
+
test('renders data table with pending bills', () => {
|
|
49
87
|
render(<BillsTable />);
|
|
50
88
|
|
|
51
|
-
expect(screen.getByText('
|
|
52
|
-
expect(screen.getByText('
|
|
53
|
-
const expectedColumnHeaders = [/Visit time/, /Identifier/, /Name/, /Billing service/];
|
|
54
|
-
expectedColumnHeaders.forEach((header) => {
|
|
55
|
-
expect(screen.getByRole('columnheader', { name: new RegExp(header, 'i') })).toBeInTheDocument();
|
|
56
|
-
});
|
|
89
|
+
expect(screen.getByText('visitTime')).toBeInTheDocument();
|
|
90
|
+
expect(screen.getByText('identifier')).toBeInTheDocument();
|
|
57
91
|
|
|
58
|
-
|
|
59
|
-
expect(
|
|
60
|
-
expect(patientNameLink.tagName).toBe('A');
|
|
92
|
+
expect(screen.getByText(/John Doe/)).toBeInTheDocument();
|
|
93
|
+
expect(screen.getByText('12345678')).toBeInTheDocument();
|
|
61
94
|
});
|
|
62
95
|
|
|
63
|
-
|
|
64
|
-
|
|
96
|
+
test('displays empty state when there are no bills', () => {
|
|
97
|
+
mockBills.mockImplementationOnce(() => ({
|
|
65
98
|
bills: [],
|
|
66
99
|
isLoading: false,
|
|
67
100
|
isValidating: false,
|
|
@@ -69,12 +102,11 @@ describe('BillsTable', () => {
|
|
|
69
102
|
}));
|
|
70
103
|
|
|
71
104
|
render(<BillsTable />);
|
|
72
|
-
|
|
73
|
-
expect(screen.getByText(/there are no bills to display/i)).toBeInTheDocument();
|
|
105
|
+
expect(screen.getByText('There are no bills to display.')).toBeInTheDocument();
|
|
74
106
|
});
|
|
75
107
|
|
|
76
|
-
|
|
77
|
-
|
|
108
|
+
test('should show the loading spinner while retrieving data', () => {
|
|
109
|
+
mockBills.mockImplementationOnce(() => ({
|
|
78
110
|
bills: undefined,
|
|
79
111
|
isLoading: true,
|
|
80
112
|
isValidating: false,
|
|
@@ -82,73 +114,81 @@ describe('BillsTable', () => {
|
|
|
82
114
|
}));
|
|
83
115
|
|
|
84
116
|
render(<BillsTable />);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
expect(screen.queryByRole('columnheader', { name: new RegExp(header, 'i') })).not.toBeInTheDocument();
|
|
89
|
-
});
|
|
117
|
+
const dataTableSkeleton = screen.getByRole('table');
|
|
118
|
+
expect(dataTableSkeleton).toBeInTheDocument();
|
|
119
|
+
expect(dataTableSkeleton).toHaveClass('cds--skeleton cds--data-table cds--data-table--zebra');
|
|
90
120
|
});
|
|
91
121
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
122
|
+
test('should display the error state when there is error', () => {
|
|
123
|
+
mockBills.mockImplementationOnce(() => ({
|
|
124
|
+
bills: undefined,
|
|
95
125
|
isLoading: false,
|
|
96
126
|
isValidating: false,
|
|
97
|
-
error: 'Error in fetching data',
|
|
127
|
+
error: new Error('Error in fetching data'),
|
|
98
128
|
}));
|
|
99
129
|
|
|
100
130
|
render(<BillsTable />);
|
|
101
|
-
|
|
102
|
-
expect(screen.getByText(/sorry, there was a problem displaying this information/i)).toBeInTheDocument();
|
|
131
|
+
expect(screen.getByTestId('error-state')).toBeInTheDocument();
|
|
103
132
|
expect(screen.queryByRole('table')).not.toBeInTheDocument();
|
|
104
133
|
});
|
|
105
134
|
|
|
106
|
-
test('should filter bills by search term
|
|
135
|
+
test('should filter bills by search term', async () => {
|
|
107
136
|
render(<BillsTable />);
|
|
108
137
|
|
|
109
138
|
const searchInput = screen.getByRole('searchbox');
|
|
110
|
-
|
|
139
|
+
expect(searchInput).toBeInTheDocument();
|
|
111
140
|
|
|
112
141
|
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
|
113
|
-
expect(screen.
|
|
142
|
+
expect(screen.getByText('Mary Smith')).toBeInTheDocument();
|
|
114
143
|
|
|
115
|
-
await user.
|
|
116
|
-
await user.type(searchInput, 'Mary Smith');
|
|
144
|
+
await user.type(searchInput, 'John Doe');
|
|
117
145
|
|
|
118
|
-
|
|
119
|
-
|
|
146
|
+
await waitFor(() => {
|
|
147
|
+
expect(screen.queryByText('Mary Smith')).not.toBeInTheDocument();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('should render patient name as a link', () => {
|
|
154
|
+
render(<BillsTable />);
|
|
120
155
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
expect(billCategorySelect).toBeInTheDocument();
|
|
124
|
-
await user.click(billCategorySelect, { name: 'All bills' });
|
|
125
|
-
expect(mockbills).toHaveBeenCalledWith('', '');
|
|
156
|
+
const patientNameLink = screen.getByRole('link', { name: 'John Doe' });
|
|
157
|
+
expect(patientNameLink).toBeInTheDocument();
|
|
126
158
|
|
|
127
|
-
|
|
128
|
-
expect(screen.getByText('Pending bills')).toBeInTheDocument();
|
|
129
|
-
expect(mockbills).toHaveBeenCalledWith('', 'PENDING');
|
|
159
|
+
expect(patientNameLink.getAttribute('href')).toEqual('/home/billing/patient/uuid1/1');
|
|
130
160
|
});
|
|
131
161
|
|
|
132
|
-
test('should
|
|
133
|
-
|
|
134
|
-
bills:
|
|
135
|
-
isLoading:
|
|
162
|
+
test('should filter bills by payment status', async () => {
|
|
163
|
+
mockBills.mockImplementationOnce(() => ({
|
|
164
|
+
bills: mockBillsData.map((bill) => ({ ...bill, status: 'PENDING' })),
|
|
165
|
+
isLoading: false,
|
|
136
166
|
isValidating: false,
|
|
137
167
|
error: null,
|
|
138
168
|
}));
|
|
139
169
|
|
|
140
170
|
render(<BillsTable />);
|
|
141
171
|
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
172
|
+
const filterDropdown = screen.getByText('Pending bills');
|
|
173
|
+
await user.click(filterDropdown);
|
|
174
|
+
|
|
175
|
+
const paidBillsOption = screen.getAllByText('Paid bills')[0];
|
|
176
|
+
await user.click(paidBillsOption);
|
|
177
|
+
|
|
178
|
+
expect(screen.getByText('noMatchingBillsToDisplay')).toBeInTheDocument();
|
|
145
179
|
});
|
|
146
180
|
|
|
147
|
-
test('should
|
|
181
|
+
test('should show loading state during background updates', () => {
|
|
182
|
+
mockBills.mockImplementationOnce(() => ({
|
|
183
|
+
bills: mockBillsData,
|
|
184
|
+
isLoading: false,
|
|
185
|
+
isValidating: true,
|
|
186
|
+
error: null,
|
|
187
|
+
}));
|
|
188
|
+
|
|
148
189
|
render(<BillsTable />);
|
|
149
190
|
|
|
150
|
-
const
|
|
151
|
-
expect(
|
|
152
|
-
expect(patientNameLink).toHaveAttribute('href', '/openmrs/spa/home/billing/patient/uuid1/1');
|
|
191
|
+
const loadingIndicator = screen.getByTitle('loading');
|
|
192
|
+
expect(loadingIndicator).toBeInTheDocument();
|
|
153
193
|
});
|
|
154
194
|
});
|
package/src/config-schema.ts
CHANGED
|
@@ -23,11 +23,36 @@ export const configSchema = {
|
|
|
23
23
|
payingDetails: '44b34972-6630-4e5a-a9f6-a6eb0f109650',
|
|
24
24
|
nonPayingDetails: 'f3fb2d88-cccd-422c-8766-be101ba7bd2e',
|
|
25
25
|
insuranceDetails: 'beac329b-f1dc-4a33-9e7c-d95821a137a6',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
nonPayingPatientCategories: {
|
|
30
|
+
_type: Type.Object,
|
|
31
|
+
_description: 'Concept UUIDs for non-paying patient categories',
|
|
32
|
+
_default: {
|
|
26
33
|
childUnder5: '1528AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
27
34
|
student: '159465AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
28
35
|
},
|
|
29
36
|
},
|
|
30
37
|
|
|
38
|
+
postBilledItems: {
|
|
39
|
+
_type: Type.Object,
|
|
40
|
+
_description: 'Post Bill Items such as cashPoints, cashier, priceUUid when submitting a bill',
|
|
41
|
+
_default: {
|
|
42
|
+
cashPoint: '54065383-b4d4-42d2-af4d-d250a1fd2590',
|
|
43
|
+
cashier: 'f9badd80-ab76-11e2-9e96-0800200c9a66',
|
|
44
|
+
priceUuid: '7b9171ac-d3c1-49b4-beff-c9902aee5245',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
serviceTypes: {
|
|
49
|
+
_type: Type.Object,
|
|
50
|
+
_description: 'Post Bill Items such as cashPoints, cashier, priceUUid when submitting a bill',
|
|
51
|
+
_default: {
|
|
52
|
+
billableService: '21b8cf43-9f9f-4d02-9f4a-d710ece54261',
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
|
|
31
56
|
defaultCurrency: {
|
|
32
57
|
_type: Type.String,
|
|
33
58
|
_description: 'The default currency for the application. Specify the currency code (e.g., KES, UGX, GBP).',
|
|
@@ -35,10 +60,16 @@ export const configSchema = {
|
|
|
35
60
|
},
|
|
36
61
|
|
|
37
62
|
pageSize: {
|
|
38
|
-
_type: Type.
|
|
63
|
+
_type: Type.Number,
|
|
39
64
|
_description: 'The default page size',
|
|
40
65
|
_default: 10,
|
|
41
66
|
},
|
|
67
|
+
|
|
68
|
+
showEditBillButton: {
|
|
69
|
+
_type: Type.Boolean,
|
|
70
|
+
_description: 'Whether to show the edit bill button or not.',
|
|
71
|
+
_default: false,
|
|
72
|
+
},
|
|
42
73
|
};
|
|
43
74
|
|
|
44
75
|
export interface ConfigObject {
|
|
@@ -47,4 +78,8 @@ export interface ConfigObject {
|
|
|
47
78
|
catergoryConcepts: Object;
|
|
48
79
|
pageSize;
|
|
49
80
|
object;
|
|
81
|
+
showEditBillButton: boolean;
|
|
82
|
+
postBilledItems: Object;
|
|
83
|
+
serviceTypes: Object;
|
|
84
|
+
nonPayingPatientCategories: Object;
|
|
50
85
|
}
|
package/src/constants.ts
CHANGED
package/src/dashboard.meta.ts
CHANGED
package/src/helpers/functions.ts
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
import dayjs from 'dayjs';
|
|
3
|
+
import { omrsDateFormat } from '../constants';
|
|
4
|
+
|
|
5
|
+
const SelectedDateContext = createContext({
|
|
6
|
+
selectedDate: dayjs().startOf('day').format(omrsDateFormat),
|
|
7
|
+
setSelectedDate: (date: string) => {},
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export default SelectedDateContext;
|