@openmrs/esm-billing-app 1.0.2-pre.92 → 1.0.2-pre.929
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc +16 -2
- package/README.md +54 -9
- package/__mocks__/bills.mock.ts +12 -0
- package/__mocks__/react-i18next.js +6 -5
- package/dist/1119.js +1 -1
- package/dist/1146.js +1 -2
- package/dist/1146.js.map +1 -1
- package/dist/1197.js +1 -1
- package/dist/1537.js +1 -0
- package/dist/1537.js.map +1 -0
- package/dist/1856.js +1 -0
- package/dist/1856.js.map +1 -0
- package/dist/2146.js +1 -1
- package/dist/2524.js +1 -0
- package/dist/2524.js.map +1 -0
- package/dist/2690.js +1 -1
- package/dist/3099.js +1 -1
- package/dist/3584.js +1 -1
- package/dist/3717.js +2 -0
- package/dist/3717.js.map +1 -0
- package/dist/4055.js +1 -1
- package/dist/4132.js +1 -1
- package/dist/4300.js +1 -1
- package/dist/4335.js +1 -1
- package/dist/4618.js +1 -1
- package/dist/4652.js +1 -1
- package/dist/4692.js +1 -0
- package/dist/4692.js.map +1 -0
- package/dist/4724.js +1 -0
- package/dist/4724.js.map +1 -0
- package/dist/4739.js +1 -1
- package/dist/4739.js.map +1 -1
- package/dist/4944.js +1 -1
- package/dist/5173.js +1 -1
- package/dist/5241.js +1 -1
- package/dist/5442.js +1 -1
- package/dist/5661.js +1 -1
- package/dist/6022.js +1 -1
- package/dist/6468.js +1 -1
- package/dist/6540.js +1 -1
- package/dist/6540.js.map +1 -1
- package/dist/6679.js +1 -1
- package/dist/6840.js +1 -1
- package/dist/6859.js +1 -1
- package/dist/7097.js +1 -1
- package/dist/7159.js +1 -1
- package/dist/723.js +1 -1
- package/dist/7255.js +1 -1
- package/dist/7255.js.map +1 -1
- package/dist/7617.js +1 -1
- package/dist/795.js +1 -1
- package/dist/8163.js +1 -1
- package/dist/8349.js +1 -1
- package/dist/8618.js +1 -1
- package/dist/8708.js +2 -0
- package/dist/{6557.js.LICENSE.txt → 8708.js.LICENSE.txt} +22 -0
- package/dist/8708.js.map +1 -0
- package/dist/890.js +1 -1
- package/dist/9214.js +1 -1
- package/dist/9538.js +1 -1
- package/dist/9569.js +1 -1
- package/dist/961.js +1 -1
- package/dist/961.js.map +1 -1
- package/dist/986.js +1 -1
- package/dist/9879.js +1 -1
- package/dist/9895.js +1 -1
- package/dist/9900.js +1 -1
- package/dist/9913.js +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-billing-app.js +1 -1
- package/dist/openmrs-esm-billing-app.js.buildmanifest.json +282 -296
- package/dist/openmrs-esm-billing-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/e2e/README.md +19 -18
- package/e2e/core/test.ts +1 -1
- package/e2e/fixtures/api.ts +1 -1
- package/e2e/specs/sample-test.spec.ts +0 -1
- package/e2e/support/github/Dockerfile +1 -1
- package/package.json +18 -15
- package/src/bill-history/bill-history.component.tsx +20 -28
- package/src/bill-history/bill-history.scss +4 -94
- package/src/bill-history/bill-history.test.tsx +37 -78
- package/src/bill-item-actions/bill-item-actions.scss +21 -5
- package/src/bill-item-actions/edit-bill-item.modal.tsx +225 -0
- package/src/bill-item-actions/edit-bill-item.test.tsx +214 -40
- package/src/billable-services/bill-waiver/bill-selection.component.tsx +5 -5
- package/src/billable-services/bill-waiver/bill-waiver-form.component.tsx +28 -32
- package/src/billable-services/bill-waiver/patient-bills.component.tsx +7 -7
- package/src/billable-services/bill-waiver/utils.ts +13 -3
- package/src/billable-services/{create-edit/add-billable-service.scss → billable-service-form/billable-service-form.scss} +32 -64
- package/src/billable-services/billable-service-form/billable-service-form.test.tsx +893 -0
- package/src/billable-services/billable-service-form/billable-service-form.workspace.tsx +504 -0
- package/src/billable-services/billable-service.resource.ts +42 -26
- package/src/billable-services/billable-services-home.component.tsx +13 -42
- package/src/billable-services/billable-services-left-panel-link.component.tsx +48 -0
- package/src/billable-services/billable-services-left-panel-menu.component.tsx +46 -0
- package/src/billable-services/billable-services-menu-item/item.component.tsx +5 -4
- package/src/billable-services/billable-services.component.tsx +156 -152
- package/src/billable-services/billable-services.scss +29 -0
- package/src/billable-services/billable-services.test.tsx +6 -49
- package/src/billable-services/cash-point/add-cash-point.modal.tsx +170 -0
- package/src/billable-services/cash-point/cash-point-configuration.component.tsx +19 -193
- package/src/billable-services/cash-point/cash-point-configuration.scss +1 -5
- package/src/billable-services/dashboard/dashboard.component.tsx +0 -2
- package/src/billable-services/payment-modes/add-payment-mode.modal.tsx +121 -0
- package/src/billable-services/payment-modes/delete-payment-mode.modal.tsx +74 -0
- package/src/billable-services/payment-modes/payment-modes-config.component.tsx +125 -0
- package/src/billable-services/{payyment-modes → payment-modes}/payment-modes-config.scss +5 -4
- package/src/billable-services-admin-card-link.component.test.tsx +2 -2
- package/src/billable-services-admin-card-link.component.tsx +1 -1
- package/src/billing-dashboard/billing-dashboard.scss +1 -1
- package/src/billing-form/billing-checkin-form.component.tsx +21 -17
- package/src/billing-form/billing-checkin-form.test.tsx +99 -26
- package/src/billing-form/billing-form.component.tsx +222 -292
- package/src/billing-form/billing-form.scss +143 -0
- package/src/billing-form/visit-attributes/visit-attributes-form.component.tsx +1 -1
- package/src/billing.resource.ts +69 -74
- package/src/bills-table/bills-table.component.tsx +3 -3
- package/src/bills-table/bills-table.test.tsx +98 -54
- package/src/config-schema.ts +52 -24
- package/src/dashboard.meta.ts +4 -2
- package/src/helpers/functions.ts +5 -4
- package/src/index.ts +71 -9
- package/src/invoice/invoice-table.component.tsx +36 -70
- package/src/invoice/invoice-table.scss +8 -5
- package/src/invoice/invoice-table.test.tsx +273 -62
- package/src/invoice/invoice.component.tsx +39 -32
- package/src/invoice/invoice.scss +11 -4
- package/src/invoice/invoice.test.tsx +324 -120
- package/src/invoice/payments/invoice-breakdown/invoice-breakdown.scss +9 -9
- package/src/invoice/payments/payment-form/payment-form.component.tsx +43 -34
- package/src/invoice/payments/payment-form/payment-form.scss +5 -6
- package/src/invoice/payments/payment-form/payment-form.test.tsx +216 -66
- package/src/invoice/payments/payment-history/payment-history.component.tsx +6 -4
- package/src/invoice/payments/payment-history/payment-history.test.tsx +9 -14
- package/src/invoice/payments/payments.component.tsx +55 -67
- package/src/invoice/payments/payments.scss +4 -3
- package/src/invoice/payments/payments.test.tsx +282 -0
- package/src/invoice/payments/utils.ts +15 -27
- package/src/invoice/printable-invoice/print-receipt.component.tsx +3 -2
- package/src/invoice/printable-invoice/print-receipt.test.tsx +14 -25
- package/src/invoice/printable-invoice/printable-footer.component.tsx +2 -2
- package/src/invoice/printable-invoice/printable-footer.test.tsx +4 -13
- package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +12 -11
- package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +16 -14
- package/src/invoice/printable-invoice/printable-invoice.component.tsx +20 -34
- package/src/left-panel-link.test.tsx +1 -4
- package/src/metrics-cards/metrics-cards.component.tsx +16 -6
- package/src/metrics-cards/metrics-cards.scss +4 -0
- package/src/metrics-cards/metrics-cards.test.tsx +18 -5
- package/src/modal/require-payment-modal.test.tsx +27 -22
- package/src/modal/{require-payment-modal.component.tsx → require-payment.modal.tsx} +18 -19
- package/src/routes.json +44 -20
- package/src/types/index.ts +81 -23
- package/translations/am.json +132 -77
- package/translations/ar.json +133 -78
- package/translations/ar_SY.json +133 -78
- package/translations/bn.json +135 -80
- package/translations/de.json +133 -78
- package/translations/en.json +133 -78
- package/translations/en_US.json +133 -78
- package/translations/es.json +132 -77
- package/translations/es_MX.json +133 -78
- package/translations/fr.json +138 -83
- package/translations/he.json +132 -77
- package/translations/hi.json +133 -78
- package/translations/hi_IN.json +133 -78
- package/translations/id.json +133 -78
- package/translations/it.json +159 -104
- package/translations/ka.json +133 -78
- package/translations/km.json +132 -77
- package/translations/ku.json +133 -78
- package/translations/ky.json +133 -78
- package/translations/lg.json +133 -78
- package/translations/ne.json +133 -78
- package/translations/pl.json +133 -78
- package/translations/pt.json +133 -78
- package/translations/pt_BR.json +133 -78
- package/translations/qu.json +133 -78
- package/translations/ro_RO.json +220 -165
- package/translations/ru_RU.json +133 -78
- package/translations/si.json +133 -78
- package/translations/sw.json +133 -78
- package/translations/sw_KE.json +133 -78
- package/translations/tr.json +133 -78
- package/translations/tr_TR.json +133 -78
- package/translations/uk.json +133 -78
- package/translations/uz.json +133 -78
- package/translations/uz@Latn.json +133 -78
- package/translations/uz_UZ.json +133 -78
- package/translations/vi.json +133 -78
- package/translations/zh.json +133 -78
- package/translations/zh_CN.json +163 -108
- package/dist/1146.js.LICENSE.txt +0 -21
- package/dist/2352.js +0 -1
- package/dist/2352.js.map +0 -1
- package/dist/246.js +0 -1
- package/dist/246.js.map +0 -1
- package/dist/4689.js +0 -2
- package/dist/4689.js.map +0 -1
- package/dist/6557.js +0 -2
- package/dist/6557.js.map +0 -1
- package/dist/8638.js +0 -1
- package/dist/8638.js.map +0 -1
- package/dist/9968.js +0 -1
- package/dist/9968.js.map +0 -1
- package/src/bill-item-actions/edit-bill-item.component.tsx +0 -221
- package/src/billable-services/create-edit/add-billable-service.component.tsx +0 -401
- package/src/billable-services/create-edit/add-billable-service.test.tsx +0 -154
- package/src/billable-services/dashboard/service-metrics.component.tsx +0 -41
- package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +0 -280
- package/src/invoice/payments/payments.component.test.tsx +0 -121
- /package/dist/{4689.js.LICENSE.txt → 3717.js.LICENSE.txt} +0 -0
|
@@ -3,28 +3,19 @@ import { screen, render } from '@testing-library/react';
|
|
|
3
3
|
import { useDefaultFacility } from '../../billing.resource';
|
|
4
4
|
import PrintableFooter from './printable-footer.component';
|
|
5
5
|
|
|
6
|
-
const mockUseDefaultFacility =
|
|
6
|
+
const mockUseDefaultFacility = jest.mocked<typeof useDefaultFacility>(useDefaultFacility);
|
|
7
7
|
|
|
8
8
|
jest.mock('../../billing.resource', () => ({
|
|
9
9
|
useDefaultFacility: jest.fn(),
|
|
10
10
|
}));
|
|
11
11
|
|
|
12
12
|
describe('PrintableFooter', () => {
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
jest.clearAllMocks();
|
|
15
|
-
});
|
|
16
|
-
|
|
17
13
|
test('should render PrintableFooter component', () => {
|
|
18
|
-
mockUseDefaultFacility.mockReturnValue({
|
|
14
|
+
mockUseDefaultFacility.mockReturnValue({
|
|
15
|
+
data: { display: 'MTRH', uuid: 'mtrh-uuid', links: [] },
|
|
16
|
+
});
|
|
19
17
|
render(<PrintableFooter />);
|
|
20
18
|
const footer = screen.getByText('MTRH');
|
|
21
19
|
expect(footer).toBeInTheDocument();
|
|
22
20
|
});
|
|
23
|
-
|
|
24
|
-
test('should show placeholder text when facility isLoading', () => {
|
|
25
|
-
mockUseDefaultFacility.mockReturnValue({ data: { display: 'MTRH', uuid: 'mtrh-uuid' }, isLoading: true });
|
|
26
|
-
render(<PrintableFooter />);
|
|
27
|
-
const footer = screen.getByText('--');
|
|
28
|
-
expect(footer).toBeInTheDocument();
|
|
29
|
-
});
|
|
30
21
|
});
|
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { type PatientDetails } from '../../types';
|
|
3
|
-
import { useConfig } from '@openmrs/esm-framework';
|
|
3
|
+
import { type SessionLocation, useConfig, interpolateUrl } from '@openmrs/esm-framework';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
|
-
import {
|
|
5
|
+
import type { BillingConfig } from '../../config-schema';
|
|
6
6
|
import styles from './printable-invoice-header.scss';
|
|
7
|
+
import isEmpty from 'lodash/isEmpty';
|
|
7
8
|
|
|
8
9
|
interface PrintableInvoiceHeaderProps {
|
|
9
10
|
patientDetails: PatientDetails;
|
|
11
|
+
defaultFacility: SessionLocation | null;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
const PrintableInvoiceHeader: React.FC<PrintableInvoiceHeaderProps> = ({ patientDetails }) => {
|
|
14
|
+
const PrintableInvoiceHeader: React.FC<PrintableInvoiceHeaderProps> = ({ patientDetails, defaultFacility }) => {
|
|
13
15
|
const { t } = useTranslation();
|
|
14
|
-
const { logo } = useConfig();
|
|
15
|
-
const { data } = useDefaultFacility();
|
|
16
|
+
const { logo, country } = useConfig<BillingConfig>();
|
|
16
17
|
|
|
17
18
|
return (
|
|
18
19
|
<div className={styles.container}>
|
|
19
20
|
<div className={styles.printableHeader}>
|
|
20
21
|
<p className={styles.heading}>{t('invoice', 'Invoice')}</p>
|
|
21
|
-
{logo?.src ? (
|
|
22
|
-
<img className={styles.img} src={logo.src} alt={logo.alt} />
|
|
23
|
-
) : logo?.
|
|
24
|
-
logo.
|
|
22
|
+
{logo?.src && !isEmpty(logo.src) ? (
|
|
23
|
+
<img className={styles.img} src={interpolateUrl(logo.src)} alt={logo.alt} />
|
|
24
|
+
) : logo?.alt && !isEmpty(logo.alt) ? (
|
|
25
|
+
logo.alt
|
|
25
26
|
) : (
|
|
26
27
|
// OpenMRS Logo
|
|
27
28
|
<svg
|
|
@@ -52,8 +53,8 @@ const PrintableInvoiceHeader: React.FC<PrintableInvoiceHeaderProps> = ({ patient
|
|
|
52
53
|
</div>
|
|
53
54
|
|
|
54
55
|
<div className={styles.facilityDetails}>
|
|
55
|
-
<p className={styles.facilityName}>{
|
|
56
|
-
<p className={styles.itemLabel}>
|
|
56
|
+
<p className={styles.facilityName}>{defaultFacility?.display}</p>
|
|
57
|
+
<p className={styles.itemLabel}>{country}</p>
|
|
57
58
|
</div>
|
|
58
59
|
</div>
|
|
59
60
|
</div>
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { screen, render } from '@testing-library/react';
|
|
3
3
|
import { useConfig } from '@openmrs/esm-framework';
|
|
4
|
+
import { type BillingConfig } from '../../config-schema';
|
|
4
5
|
import { useDefaultFacility } from '../../billing.resource';
|
|
5
6
|
import PrintableInvoiceHeader from './printable-invoice-header.component';
|
|
6
7
|
|
|
7
|
-
const mockUseDefaultFacility =
|
|
8
|
-
const mockUseConfig =
|
|
8
|
+
const mockUseDefaultFacility = jest.mocked(useDefaultFacility);
|
|
9
|
+
const mockUseConfig = jest.mocked(useConfig<BillingConfig>);
|
|
9
10
|
|
|
10
11
|
jest.mock('../../billing.resource', () => ({
|
|
11
12
|
useDefaultFacility: jest.fn(),
|
|
12
13
|
}));
|
|
13
14
|
|
|
14
|
-
jest.mock('@openmrs/esm-framework', () => ({
|
|
15
|
-
useConfig: jest.fn(),
|
|
16
|
-
}));
|
|
17
15
|
const testProps = {
|
|
18
16
|
patientDetails: {
|
|
19
17
|
name: 'John Doe',
|
|
@@ -25,11 +23,19 @@ const testProps = {
|
|
|
25
23
|
},
|
|
26
24
|
};
|
|
27
25
|
|
|
26
|
+
const defaultFacility = { display: 'MTRH', uuid: 'mtrh-uuid', links: [] };
|
|
27
|
+
|
|
28
28
|
describe('PrintableInvoiceHeader', () => {
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
mockUseConfig.mockReturnValue({
|
|
31
|
+
logo: { src: 'logo.png', alt: 'logo' },
|
|
32
|
+
country: 'Kenya',
|
|
33
|
+
} as unknown as BillingConfig);
|
|
34
|
+
mockUseDefaultFacility.mockReturnValue({ data: { display: 'MTRH', uuid: 'mtrh-uuid', links: [] } });
|
|
35
|
+
});
|
|
36
|
+
|
|
29
37
|
test('should render PrintableInvoiceHeader component', () => {
|
|
30
|
-
|
|
31
|
-
mockUseDefaultFacility.mockReturnValue({ data: { display: 'MTRH', uuid: 'mtrh-uuid' }, isLoading: false });
|
|
32
|
-
render(<PrintableInvoiceHeader {...testProps} />);
|
|
38
|
+
render(<PrintableInvoiceHeader {...testProps} defaultFacility={defaultFacility} />);
|
|
33
39
|
const header = screen.getByText('Invoice');
|
|
34
40
|
expect(header).toBeInTheDocument();
|
|
35
41
|
|
|
@@ -41,17 +47,13 @@ describe('PrintableInvoiceHeader', () => {
|
|
|
41
47
|
});
|
|
42
48
|
|
|
43
49
|
test('should display the logo when logo is provided', () => {
|
|
44
|
-
|
|
45
|
-
mockUseDefaultFacility.mockReturnValue({ data: { display: 'MTRH', uuid: 'mtrh-uuid' }, isLoading: false });
|
|
46
|
-
render(<PrintableInvoiceHeader {...testProps} />);
|
|
50
|
+
render(<PrintableInvoiceHeader {...testProps} defaultFacility={defaultFacility} />);
|
|
47
51
|
const logo = screen.getByAltText('logo');
|
|
48
52
|
expect(logo).toBeInTheDocument();
|
|
49
53
|
});
|
|
50
54
|
|
|
51
55
|
test('should display the default logo when logo is not provided', () => {
|
|
52
|
-
|
|
53
|
-
mockUseDefaultFacility.mockReturnValue({ data: { display: 'MTRH', uuid: 'mtrh-uuid' }, isLoading: false });
|
|
54
|
-
render(<PrintableInvoiceHeader {...testProps} />);
|
|
56
|
+
render(<PrintableInvoiceHeader {...testProps} defaultFacility={defaultFacility} />);
|
|
55
57
|
const logo = screen.getByRole('img');
|
|
56
58
|
expect(logo).toBeInTheDocument();
|
|
57
59
|
});
|
|
@@ -8,9 +8,8 @@ import {
|
|
|
8
8
|
TableBody,
|
|
9
9
|
TableHeader,
|
|
10
10
|
TableCell,
|
|
11
|
-
DataTableSkeleton,
|
|
12
11
|
} from '@carbon/react';
|
|
13
|
-
import { age, isDesktop, useLayoutType } from '@openmrs/esm-framework';
|
|
12
|
+
import { age, isDesktop, type SessionLocation, useLayoutType } from '@openmrs/esm-framework';
|
|
14
13
|
import { getGender } from '../../helpers';
|
|
15
14
|
import { type MappedBill } from '../../types';
|
|
16
15
|
import { useTranslation } from 'react-i18next';
|
|
@@ -21,25 +20,26 @@ import styles from './printable-invoice.scss';
|
|
|
21
20
|
type PrintableInvoiceProps = {
|
|
22
21
|
bill: MappedBill;
|
|
23
22
|
patient: fhir.Patient;
|
|
24
|
-
|
|
23
|
+
componentRef: React.RefObject<HTMLDivElement>;
|
|
24
|
+
defaultFacility: SessionLocation;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
const PrintableInvoice: React.FC<PrintableInvoiceProps> = ({ bill, patient,
|
|
27
|
+
const PrintableInvoice: React.FC<PrintableInvoiceProps> = ({ bill, patient, componentRef, defaultFacility }) => {
|
|
28
28
|
const { t } = useTranslation();
|
|
29
29
|
const layout = useLayoutType();
|
|
30
30
|
const responsiveSize = isDesktop(layout) ? 'sm' : 'lg';
|
|
31
31
|
const headerData = [
|
|
32
|
-
{ header: 'Inventory item', key: 'billItem' },
|
|
33
|
-
{ header: 'Quantity', key: 'quantity' },
|
|
34
|
-
{ header: 'Unit price', key: 'price' },
|
|
35
|
-
{ header: 'Total', key: 'total' },
|
|
32
|
+
{ header: t('inventoryItem', 'Inventory item'), key: 'billItem' },
|
|
33
|
+
{ header: t('quantity', 'Quantity'), key: 'quantity' },
|
|
34
|
+
{ header: t('unitPrice', 'Unit price'), key: 'price' },
|
|
35
|
+
{ header: t('total', 'Total'), key: 'total' },
|
|
36
36
|
];
|
|
37
37
|
|
|
38
38
|
const rowData =
|
|
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,
|
|
@@ -47,10 +47,10 @@ const PrintableInvoice: React.FC<PrintableInvoiceProps> = ({ bill, patient, isLo
|
|
|
47
47
|
}) ?? [];
|
|
48
48
|
|
|
49
49
|
const invoiceTotal = {
|
|
50
|
-
'Total
|
|
51
|
-
'Amount
|
|
52
|
-
'Discount
|
|
53
|
-
'Amount due': bill?.totalAmount - bill?.tenderedAmount,
|
|
50
|
+
[t('totalAmount', 'Total amount')]: bill?.totalAmount,
|
|
51
|
+
[t('amountTendered', 'Amount tendered')]: bill?.tenderedAmount,
|
|
52
|
+
[t('discountAmount', 'Discount amount')]: 0,
|
|
53
|
+
[t('amountDue', 'Amount due')]: bill?.totalAmount - bill?.tenderedAmount,
|
|
54
54
|
};
|
|
55
55
|
|
|
56
56
|
const patientDetails = useMemo(() => {
|
|
@@ -65,28 +65,14 @@ const PrintableInvoice: React.FC<PrintableInvoiceProps> = ({ bill, patient, isLo
|
|
|
65
65
|
}, [patient, t]);
|
|
66
66
|
|
|
67
67
|
const invoiceDetails = {
|
|
68
|
-
'Invoice #': bill?.receiptNumber,
|
|
69
|
-
'Invoice date': bill
|
|
70
|
-
Status: bill?.status,
|
|
68
|
+
[t('invoiceNumber', 'Invoice #')]: bill?.receiptNumber,
|
|
69
|
+
[t('invoiceDate', 'Invoice date')]: bill?.dateCreated,
|
|
70
|
+
[t('status', 'Status')]: bill?.status,
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
-
if (isLoading) {
|
|
74
|
-
return (
|
|
75
|
-
<div className={styles.loaderContainer}>
|
|
76
|
-
<DataTableSkeleton
|
|
77
|
-
columnCount={headerData?.length ?? 0}
|
|
78
|
-
showHeader={false}
|
|
79
|
-
showToolbar={false}
|
|
80
|
-
size={responsiveSize}
|
|
81
|
-
zebra
|
|
82
|
-
/>
|
|
83
|
-
</div>
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
73
|
return (
|
|
88
|
-
<div className={styles.container}>
|
|
89
|
-
<PrintableInvoiceHeader patientDetails={patientDetails} />
|
|
74
|
+
<div className={styles.container} ref={componentRef}>
|
|
75
|
+
<PrintableInvoiceHeader patientDetails={patientDetails} defaultFacility={defaultFacility} />
|
|
90
76
|
<div className={styles.printableInvoiceContainer}>
|
|
91
77
|
<div className={styles.detailsContainer}>
|
|
92
78
|
{Object.entries(invoiceDetails).map(([key, val]) => (
|
|
@@ -99,10 +85,10 @@ const PrintableInvoice: React.FC<PrintableInvoiceProps> = ({ bill, patient, isLo
|
|
|
99
85
|
|
|
100
86
|
<div className={styles.itemsContainer}>
|
|
101
87
|
<div className={styles.tableContainer}>
|
|
102
|
-
<DataTable
|
|
88
|
+
<DataTable rows={rowData} headers={headerData} size={responsiveSize} useZebraStyles={false}>
|
|
103
89
|
{({ rows, headers, getRowProps, getTableProps }) => (
|
|
104
90
|
<TableContainer>
|
|
105
|
-
<Table {...getTableProps()} aria-label=
|
|
91
|
+
<Table {...getTableProps()} aria-label={t('invoiceLineItems', 'Invoice line items')}>
|
|
106
92
|
<TableHead>
|
|
107
93
|
<TableRow>
|
|
108
94
|
{headers.map((header) => (
|
|
@@ -23,7 +23,6 @@ describe('LinkExtension Component', () => {
|
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
describe('createLeftPanelLink Function', () => {
|
|
26
|
-
const user = userEvent.setup();
|
|
27
26
|
test('returns a component that renders LinkExtension', () => {
|
|
28
27
|
const config = { name: 'billing', title: 'Billing' };
|
|
29
28
|
const TestComponent = createLeftPanelLink(config);
|
|
@@ -31,8 +30,6 @@ describe('createLeftPanelLink Function', () => {
|
|
|
31
30
|
render(<TestComponent />);
|
|
32
31
|
expect(screen.getByText('Billing')).toBeInTheDocument();
|
|
33
32
|
const testLink = screen.getByRole('link', { name: 'Billing' });
|
|
34
|
-
|
|
35
|
-
expect(window.location.pathname).toBe('/billing/6eb8d678-514d-46ad-9554-51e48d96d567');
|
|
36
|
-
// expect(testLink).toHaveClass('active-left-nav-link');
|
|
33
|
+
expect(testLink).toHaveAttribute('href', '/openmrs/spa/home/billing');
|
|
37
34
|
});
|
|
38
35
|
});
|
|
@@ -2,6 +2,7 @@ import React, { useMemo } from 'react';
|
|
|
2
2
|
import { InlineLoading } from '@carbon/react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { ErrorState } from '@openmrs/esm-patient-common-lib';
|
|
5
|
+
import { getCoreTranslation } from '@openmrs/esm-framework';
|
|
5
6
|
import { useBills } from '../billing.resource';
|
|
6
7
|
import { useBillMetrics } from './metrics.resource';
|
|
7
8
|
import Card from './card.component';
|
|
@@ -14,24 +15,33 @@ export default function MetricsCards() {
|
|
|
14
15
|
|
|
15
16
|
const cards = useMemo(
|
|
16
17
|
() => [
|
|
17
|
-
{ title: 'Cumulative
|
|
18
|
-
{ title: 'Pending
|
|
19
|
-
{ title: 'Paid
|
|
18
|
+
{ title: t('cumulativeBills', 'Cumulative bills'), count: cumulativeBills },
|
|
19
|
+
{ title: t('pendingBills', 'Pending bills'), count: pendingBills },
|
|
20
|
+
{ title: t('paidBills', 'Paid bills'), count: paidBills },
|
|
20
21
|
],
|
|
21
|
-
[cumulativeBills, pendingBills, paidBills],
|
|
22
|
+
[cumulativeBills, pendingBills, paidBills, t],
|
|
22
23
|
);
|
|
23
24
|
|
|
24
25
|
if (isLoading) {
|
|
25
26
|
return (
|
|
26
27
|
<section className={styles.container}>
|
|
27
|
-
<InlineLoading
|
|
28
|
+
<InlineLoading
|
|
29
|
+
status="active"
|
|
30
|
+
iconDescription={getCoreTranslation('loading')}
|
|
31
|
+
description={t('loadingBillMetrics', 'Loading bill metrics') + '...'}
|
|
32
|
+
/>
|
|
28
33
|
</section>
|
|
29
34
|
);
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
if (error) {
|
|
33
|
-
return
|
|
38
|
+
return (
|
|
39
|
+
<div className={styles.errorContainer}>
|
|
40
|
+
<ErrorState headerTitle={t('billMetrics', 'Bill metrics')} error={error} />
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
34
43
|
}
|
|
44
|
+
|
|
35
45
|
return (
|
|
36
46
|
<section className={styles.container}>
|
|
37
47
|
{cards.map((card) => (
|
|
@@ -3,10 +3,11 @@ import { render, screen } from '@testing-library/react';
|
|
|
3
3
|
import { useConfig } from '@openmrs/esm-framework';
|
|
4
4
|
import { billsSummary } from '../../__mocks__/bills.mock';
|
|
5
5
|
import { useBills } from '../billing.resource';
|
|
6
|
+
import { type MappedBill } from '../types';
|
|
6
7
|
import MetricsCards from './metrics-cards.component';
|
|
7
8
|
|
|
8
|
-
const mockUseBills =
|
|
9
|
-
const mockUseConfig =
|
|
9
|
+
const mockUseBills = jest.mocked(useBills);
|
|
10
|
+
const mockUseConfig = jest.mocked(useConfig);
|
|
10
11
|
|
|
11
12
|
jest.mock('../billing.resource', () => ({
|
|
12
13
|
useBills: jest.fn(),
|
|
@@ -14,13 +15,19 @@ jest.mock('../billing.resource', () => ({
|
|
|
14
15
|
|
|
15
16
|
describe('MetricsCards', () => {
|
|
16
17
|
test('renders loading state', () => {
|
|
17
|
-
mockUseBills.mockReturnValue({ isLoading: true, bills: [], error: null });
|
|
18
|
+
mockUseBills.mockReturnValue({ isLoading: true, bills: [], error: null, isValidating: false, mutate: jest.fn() });
|
|
18
19
|
renderMetricsCards();
|
|
19
20
|
expect(screen.getByText(/Loading bill metrics.../i)).toBeInTheDocument();
|
|
20
21
|
});
|
|
21
22
|
|
|
22
23
|
test('renders error state', () => {
|
|
23
|
-
mockUseBills.mockReturnValue({
|
|
24
|
+
mockUseBills.mockReturnValue({
|
|
25
|
+
isLoading: false,
|
|
26
|
+
bills: [],
|
|
27
|
+
error: new Error('Internal server error'),
|
|
28
|
+
isValidating: false,
|
|
29
|
+
mutate: jest.fn(),
|
|
30
|
+
});
|
|
24
31
|
renderMetricsCards();
|
|
25
32
|
expect(
|
|
26
33
|
screen.getByText(
|
|
@@ -30,7 +37,13 @@ describe('MetricsCards', () => {
|
|
|
30
37
|
});
|
|
31
38
|
|
|
32
39
|
test('renders metrics cards', () => {
|
|
33
|
-
mockUseBills.mockReturnValue({
|
|
40
|
+
mockUseBills.mockReturnValue({
|
|
41
|
+
isLoading: false,
|
|
42
|
+
bills: billsSummary as unknown as MappedBill[],
|
|
43
|
+
error: null,
|
|
44
|
+
isValidating: false,
|
|
45
|
+
mutate: jest.fn(),
|
|
46
|
+
});
|
|
34
47
|
mockUseConfig.mockImplementation(() => ({ defaultCurrency: 'USD' }));
|
|
35
48
|
renderMetricsCards();
|
|
36
49
|
expect(screen.getByRole('heading', { name: /cumulative bills/i })).toBeInTheDocument();
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import
|
|
3
|
-
import '@testing-library/
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
|
|
4
5
|
import { useBills } from '../billing.resource';
|
|
5
|
-
import
|
|
6
|
+
import { type MappedBill } from '../types';
|
|
7
|
+
import { configSchema, type BillingConfig } from '../config-schema';
|
|
8
|
+
import RequirePaymentModal from './require-payment.modal';
|
|
6
9
|
|
|
7
|
-
jest.
|
|
8
|
-
|
|
9
|
-
}));
|
|
10
|
-
|
|
11
|
-
jest.mock('@openmrs/esm-framework', () => ({
|
|
12
|
-
useConfig: () => ({ defaultCurrency: 'USD' }),
|
|
13
|
-
}));
|
|
10
|
+
const mockUseConfig = jest.mocked(useConfig<BillingConfig>);
|
|
11
|
+
const mockUseBills = jest.mocked<typeof useBills>(useBills);
|
|
14
12
|
|
|
15
13
|
jest.mock('../billing.resource', () => ({
|
|
16
14
|
useBills: jest.fn(),
|
|
@@ -25,19 +23,19 @@ describe('RequirePaymentModal', () => {
|
|
|
25
23
|
const patientUuid = '12345';
|
|
26
24
|
|
|
27
25
|
beforeEach(() => {
|
|
28
|
-
|
|
26
|
+
mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(configSchema), defaultCurrency: 'USD' });
|
|
29
27
|
});
|
|
30
28
|
|
|
31
29
|
it('renders correctly', () => {
|
|
32
|
-
|
|
30
|
+
mockUseBills.mockReturnValue({ bills: [], isLoading: false, error: null, isValidating: false, mutate: jest.fn() });
|
|
33
31
|
render(<RequirePaymentModal closeModal={closeModal} patientUuid={patientUuid} />);
|
|
34
|
-
expect(screen.getByText('
|
|
32
|
+
expect(screen.getByText('Patient Billing Alert')).toBeInTheDocument();
|
|
35
33
|
});
|
|
36
34
|
|
|
37
35
|
it('displays loading state', () => {
|
|
38
|
-
|
|
36
|
+
mockUseBills.mockReturnValue({ bills: [], isLoading: true, error: null, isValidating: false, mutate: jest.fn() });
|
|
39
37
|
render(<RequirePaymentModal closeModal={closeModal} patientUuid={patientUuid} />);
|
|
40
|
-
expect(screen.getByText('
|
|
38
|
+
expect(screen.getByText('Loading bill items...')).toBeInTheDocument();
|
|
41
39
|
});
|
|
42
40
|
|
|
43
41
|
it('displays line items', () => {
|
|
@@ -45,22 +43,29 @@ describe('RequirePaymentModal', () => {
|
|
|
45
43
|
{
|
|
46
44
|
status: 'UNPAID',
|
|
47
45
|
lineItems: [
|
|
48
|
-
{ billableService: 'Service 1', quantity: 1, price: 100 },
|
|
49
|
-
{ item: 'Item 1', quantity: 2, price: 50 },
|
|
46
|
+
{ billableService: 'Service 1', quantity: 1, price: 100, uuid: 'billable-service-1' },
|
|
47
|
+
{ item: 'Item 1', quantity: 2, price: 50, uuid: 'billable-item-1' },
|
|
50
48
|
],
|
|
51
49
|
},
|
|
52
50
|
];
|
|
53
|
-
|
|
51
|
+
mockUseBills.mockReturnValue({
|
|
52
|
+
bills: bills as unknown as MappedBill[],
|
|
53
|
+
isLoading: false,
|
|
54
|
+
error: null,
|
|
55
|
+
isValidating: false,
|
|
56
|
+
mutate: jest.fn(),
|
|
57
|
+
});
|
|
54
58
|
render(<RequirePaymentModal closeModal={closeModal} patientUuid={patientUuid} />);
|
|
55
59
|
expect(screen.getByText('Service 1')).toBeInTheDocument();
|
|
56
60
|
expect(screen.getByText('Item 1')).toBeInTheDocument();
|
|
57
61
|
});
|
|
58
62
|
|
|
59
|
-
it('handles closeModal', () => {
|
|
60
|
-
|
|
63
|
+
it('handles closeModal', async () => {
|
|
64
|
+
const user = userEvent.setup();
|
|
65
|
+
mockUseBills.mockReturnValue({ bills: [], isLoading: false, error: null, isValidating: false, mutate: jest.fn() });
|
|
61
66
|
render(<RequirePaymentModal closeModal={closeModal} patientUuid={patientUuid} />);
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
await user.click(screen.getByText('Cancel'));
|
|
68
|
+
await user.click(screen.getByText('OK'));
|
|
64
69
|
expect(closeModal).toHaveBeenCalledTimes(2);
|
|
65
70
|
});
|
|
66
71
|
});
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
StructuredListRow,
|
|
13
13
|
StructuredListWrapper,
|
|
14
14
|
} from '@carbon/react';
|
|
15
|
-
import { useConfig } from '@openmrs/esm-framework';
|
|
15
|
+
import { getCoreTranslation, useConfig } from '@openmrs/esm-framework';
|
|
16
16
|
import { useBills } from '../billing.resource';
|
|
17
17
|
import { convertToCurrency } from '../helpers';
|
|
18
18
|
import styles from './require-payment.scss';
|
|
@@ -29,22 +29,23 @@ const RequirePaymentModal: React.FC<RequirePaymentModalProps> = ({ closeModal, p
|
|
|
29
29
|
const lineItems = bills.filter((bill) => bill?.status !== 'PAID').flatMap((bill) => bill?.lineItems);
|
|
30
30
|
|
|
31
31
|
return (
|
|
32
|
-
|
|
32
|
+
<>
|
|
33
33
|
<ModalHeader closeModal={closeModal} title={t('patientBillingAlert', 'Patient Billing Alert')} />
|
|
34
34
|
<ModalBody>
|
|
35
35
|
<p className={styles.bodyShort02}>
|
|
36
36
|
{t(
|
|
37
37
|
'billPaymentRequiredMessage',
|
|
38
|
-
'The current patient has pending bill.
|
|
38
|
+
'The current patient has a pending bill. Advise the patient to settle the bill before receiving services',
|
|
39
39
|
)}
|
|
40
40
|
</p>
|
|
41
41
|
{isLoading && (
|
|
42
42
|
<InlineLoading
|
|
43
43
|
status="active"
|
|
44
|
-
iconDescription=
|
|
45
|
-
description={t('
|
|
44
|
+
iconDescription={getCoreTranslation('loading')}
|
|
45
|
+
description={t('loadingBillItems', 'Loading bill items') + '...'}
|
|
46
46
|
/>
|
|
47
47
|
)}
|
|
48
|
+
|
|
48
49
|
<StructuredListWrapper isCondensed>
|
|
49
50
|
<StructuredListHead>
|
|
50
51
|
<StructuredListRow head>
|
|
@@ -55,30 +56,28 @@ const RequirePaymentModal: React.FC<RequirePaymentModalProps> = ({ closeModal, p
|
|
|
55
56
|
</StructuredListRow>
|
|
56
57
|
</StructuredListHead>
|
|
57
58
|
<StructuredListBody>
|
|
58
|
-
{lineItems.map((lineItem) =>
|
|
59
|
-
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
);
|
|
69
|
-
})}
|
|
59
|
+
{lineItems.map((lineItem) => (
|
|
60
|
+
<StructuredListRow key={lineItem.uuid}>
|
|
61
|
+
<StructuredListCell>{lineItem.billableService || lineItem.item}</StructuredListCell>
|
|
62
|
+
<StructuredListCell>{lineItem.quantity}</StructuredListCell>
|
|
63
|
+
<StructuredListCell>{convertToCurrency(lineItem.price, defaultCurrency)}</StructuredListCell>
|
|
64
|
+
<StructuredListCell>
|
|
65
|
+
{convertToCurrency(lineItem.quantity * lineItem.price, defaultCurrency)}
|
|
66
|
+
</StructuredListCell>
|
|
67
|
+
</StructuredListRow>
|
|
68
|
+
))}
|
|
70
69
|
</StructuredListBody>
|
|
71
70
|
</StructuredListWrapper>
|
|
72
71
|
</ModalBody>
|
|
73
72
|
<ModalFooter>
|
|
74
73
|
<Button kind="secondary" onClick={closeModal}>
|
|
75
|
-
{
|
|
74
|
+
{getCoreTranslation('cancel')}
|
|
76
75
|
</Button>
|
|
77
76
|
<Button kind="primary" onClick={closeModal}>
|
|
78
77
|
{t('ok', 'OK')}
|
|
79
78
|
</Button>
|
|
80
79
|
</ModalFooter>
|
|
81
|
-
|
|
80
|
+
</>
|
|
82
81
|
);
|
|
83
82
|
};
|
|
84
83
|
|
package/src/routes.json
CHANGED
|
@@ -19,8 +19,7 @@
|
|
|
19
19
|
"name": "billing",
|
|
20
20
|
"title": "billing",
|
|
21
21
|
"slot": "billing-dashboard-slot"
|
|
22
|
-
}
|
|
23
|
-
"featureFlag": "billing"
|
|
22
|
+
}
|
|
24
23
|
},
|
|
25
24
|
{
|
|
26
25
|
"component": "root",
|
|
@@ -46,8 +45,7 @@
|
|
|
46
45
|
"columnSpan": 1,
|
|
47
46
|
"slot": "patient-chart-billing-dashboard-slot",
|
|
48
47
|
"path": "Billing history"
|
|
49
|
-
}
|
|
50
|
-
"featureFlag": "billing"
|
|
48
|
+
}
|
|
51
49
|
},
|
|
52
50
|
{
|
|
53
51
|
"name": "billable-services-app-menu-item",
|
|
@@ -60,8 +58,7 @@
|
|
|
60
58
|
{
|
|
61
59
|
"name": "billing-checkin-form",
|
|
62
60
|
"slot": "extra-visit-attribute-slot",
|
|
63
|
-
"component": "billingCheckInForm"
|
|
64
|
-
"featureFlag": "billing"
|
|
61
|
+
"component": "billingCheckInForm"
|
|
65
62
|
},
|
|
66
63
|
{
|
|
67
64
|
"slot": "system-admin-page-card-link-slot",
|
|
@@ -75,18 +72,45 @@
|
|
|
75
72
|
"order": 2
|
|
76
73
|
},
|
|
77
74
|
{
|
|
78
|
-
"name": "
|
|
79
|
-
"
|
|
80
|
-
"
|
|
75
|
+
"name": "billable-services-left-panel-link",
|
|
76
|
+
"component": "billableServicesLeftPanelLink",
|
|
77
|
+
"slot": "billable-services-left-panel-slot",
|
|
78
|
+
"order": 0
|
|
81
79
|
},
|
|
82
80
|
{
|
|
83
|
-
"name": "
|
|
84
|
-
"component": "
|
|
85
|
-
"
|
|
86
|
-
"
|
|
81
|
+
"name": "bill-waiver-left-panel-link",
|
|
82
|
+
"component": "billWaiverLeftPanelLink",
|
|
83
|
+
"slot": "billable-services-left-panel-slot",
|
|
84
|
+
"order": 1
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "billing-settings-left-panel-menu",
|
|
88
|
+
"component": "billingSettingsLeftPanelMenu",
|
|
89
|
+
"slot": "billable-services-left-panel-slot",
|
|
90
|
+
"order": 2
|
|
87
91
|
}
|
|
88
92
|
],
|
|
89
93
|
"modals": [
|
|
94
|
+
{
|
|
95
|
+
"name": "add-cash-point-modal",
|
|
96
|
+
"component": "addCashPointModal"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"name": "add-payment-mode-modal",
|
|
100
|
+
"component": "addPaymentModeModal"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "delete-payment-mode-modal",
|
|
104
|
+
"component": "deletePaymentModeModal"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"name": "edit-bill-item-modal",
|
|
108
|
+
"component": "editBillLineItemModal"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "edit-bill-line-item-modal",
|
|
112
|
+
"component": "editBillLineItemModal"
|
|
113
|
+
},
|
|
90
114
|
{
|
|
91
115
|
"name": "require-billing-modal",
|
|
92
116
|
"component": "requirePaymentModal"
|
|
@@ -98,13 +122,13 @@
|
|
|
98
122
|
"title": "billingForm",
|
|
99
123
|
"component": "billingFormWorkspace",
|
|
100
124
|
"type": "form"
|
|
101
|
-
}
|
|
102
|
-
],
|
|
103
|
-
"featureFlags": [
|
|
125
|
+
},
|
|
104
126
|
{
|
|
105
|
-
"
|
|
106
|
-
"
|
|
107
|
-
"
|
|
127
|
+
"name": "billable-service-form",
|
|
128
|
+
"title": "billableServiceForm",
|
|
129
|
+
"component": "billableServiceFormWorkspace",
|
|
130
|
+
"type": "form",
|
|
131
|
+
"width": "wider"
|
|
108
132
|
}
|
|
109
133
|
]
|
|
110
|
-
}
|
|
134
|
+
}
|