@openmrs/esm-billing-app 1.0.2-pre.78 → 1.0.2-pre.786
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/1856.js +1 -0
- package/dist/1856.js.map +1 -0
- package/dist/2146.js +1 -1
- package/dist/2177.js +2 -0
- package/dist/2177.js.LICENSE.txt +9 -0
- package/dist/2177.js.map +1 -0
- package/dist/2524.js +1 -0
- package/dist/2524.js.map +1 -0
- package/dist/2690.js +1 -1
- package/dist/3041.js +1 -0
- package/dist/3041.js.map +1 -0
- package/dist/3099.js +1 -1
- package/dist/3584.js +1 -1
- package/dist/4055.js +1 -1
- package/dist/4132.js +1 -1
- package/dist/4225.js +1 -0
- package/dist/4225.js.map +1 -0
- package/dist/4300.js +1 -1
- package/dist/4335.js +1 -1
- package/dist/4618.js +1 -1
- package/dist/4652.js +1 -1
- package/dist/4724.js +1 -0
- package/dist/4724.js.map +1 -0
- package/dist/4739.js +1 -1
- package/dist/4739.js.map +1 -1
- package/dist/4944.js +1 -1
- package/dist/5173.js +1 -1
- package/dist/5241.js +1 -1
- package/dist/5422.js +1 -0
- package/dist/5422.js.map +1 -0
- package/dist/5442.js +1 -1
- package/dist/5661.js +1 -1
- package/dist/6022.js +1 -1
- package/dist/6468.js +1 -1
- package/dist/6540.js +1 -1
- package/dist/6540.js.map +1 -1
- package/dist/6606.js +1 -0
- package/dist/6606.js.map +1 -0
- package/dist/6679.js +1 -1
- package/dist/6840.js +1 -1
- package/dist/6859.js +1 -1
- package/dist/7097.js +1 -1
- package/dist/7159.js +1 -1
- package/dist/723.js +1 -1
- package/dist/7452.js +2 -0
- package/dist/7452.js.map +1 -0
- package/dist/7617.js +1 -1
- package/dist/795.js +1 -1
- package/dist/8163.js +1 -1
- package/dist/8349.js +1 -1
- package/dist/8618.js +1 -1
- package/dist/890.js +1 -1
- package/dist/8930.js +2 -0
- package/dist/{6525.js.LICENSE.txt → 8930.js.LICENSE.txt} +16 -4
- package/dist/8930.js.map +1 -0
- package/dist/9214.js +1 -1
- package/dist/942.js +1 -0
- package/dist/942.js.map +1 -0
- package/dist/9538.js +1 -1
- package/dist/9569.js +1 -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 +368 -262
- 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 +13 -10
- package/src/bill-history/bill-history.component.tsx +17 -25
- package/src/bill-history/bill-history.scss +4 -94
- package/src/bill-history/bill-history.test.tsx +37 -78
- package/src/bill-item-actions/bill-item-actions.scss +0 -4
- package/src/bill-item-actions/{edit-bill-item.component.tsx → edit-bill-item.modal.tsx} +100 -78
- package/src/bill-item-actions/edit-bill-item.test.tsx +116 -31
- package/src/billable-services/bill-waiver/bill-selection.component.tsx +2 -2
- package/src/billable-services/bill-waiver/patient-bills.component.tsx +3 -3
- package/src/billable-services/billable-service.resource.ts +17 -9
- package/src/billable-services/billable-services-home.component.tsx +1 -1
- package/src/billable-services/billable-services.component.tsx +142 -145
- package/src/billable-services/billable-services.scss +3 -0
- package/src/billable-services/billable-services.test.tsx +2 -45
- package/src/billable-services/cash-point/add-cash-point.modal.tsx +168 -0
- package/src/billable-services/cash-point/cash-point-configuration.component.tsx +18 -192
- package/src/billable-services/cash-point/cash-point-configuration.scss +1 -5
- package/src/billable-services/create-edit/add-billable-service.component.tsx +345 -298
- package/src/billable-services/create-edit/add-billable-service.scss +5 -6
- package/src/billable-services/create-edit/add-billable-service.test.tsx +37 -36
- package/src/billable-services/create-edit/edit-billable-service.modal.tsx +51 -0
- package/src/billable-services/payment-modes/add-payment-mode.modal.tsx +121 -0
- package/src/billable-services/payment-modes/delete-payment-mode.modal.tsx +72 -0
- package/src/billable-services/payment-modes/payment-modes-config.component.tsx +125 -0
- package/src/billable-services/{payyment-modes → payment-modes}/payment-modes-config.scss +5 -4
- package/src/billing-form/billing-checkin-form.component.tsx +2 -3
- package/src/billing-form/billing-checkin-form.test.tsx +97 -24
- package/src/billing-form/billing-form.component.tsx +216 -269
- package/src/billing-form/billing-form.scss +143 -0
- package/src/billing.resource.ts +16 -19
- 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 +17 -6
- package/src/invoice/invoice-table.component.tsx +35 -69
- package/src/invoice/invoice-table.scss +1 -5
- package/src/invoice/invoice-table.test.tsx +273 -62
- package/src/invoice/invoice.component.tsx +36 -29
- package/src/invoice/invoice.scss +7 -4
- package/src/invoice/invoice.test.tsx +324 -120
- package/src/invoice/payments/payment-form/payment-form.component.tsx +31 -29
- 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 +53 -65
- package/src/invoice/payments/payments.test.tsx +282 -0
- package/src/invoice/payments/utils.ts +5 -23
- package/src/invoice/printable-invoice/print-receipt.component.tsx +3 -2
- package/src/invoice/printable-invoice/print-receipt.test.tsx +14 -25
- package/src/invoice/printable-invoice/printable-footer.component.tsx +2 -2
- package/src/invoice/printable-invoice/printable-footer.test.tsx +4 -13
- package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +12 -11
- package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +16 -14
- package/src/invoice/printable-invoice/printable-invoice.component.tsx +19 -33
- package/src/left-panel-link.test.tsx +1 -4
- 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} +17 -18
- package/src/routes.json +22 -2
- package/src/types/index.ts +26 -17
- package/translations/am.json +70 -21
- package/translations/ar.json +70 -21
- package/translations/ar_SY.json +70 -21
- package/translations/bn.json +75 -26
- package/translations/de.json +70 -21
- package/translations/en.json +70 -21
- package/translations/en_US.json +70 -21
- package/translations/es.json +70 -21
- package/translations/es_MX.json +70 -21
- package/translations/fr.json +83 -34
- package/translations/he.json +70 -21
- package/translations/hi.json +70 -21
- package/translations/hi_IN.json +70 -21
- package/translations/id.json +70 -21
- package/translations/it.json +105 -56
- package/translations/ka.json +70 -21
- package/translations/km.json +70 -21
- package/translations/ku.json +70 -21
- package/translations/ky.json +70 -21
- package/translations/lg.json +70 -21
- package/translations/ne.json +70 -21
- package/translations/pl.json +70 -21
- package/translations/pt.json +70 -21
- package/translations/pt_BR.json +70 -21
- package/translations/qu.json +70 -21
- package/translations/ro_RO.json +214 -165
- package/translations/ru_RU.json +70 -21
- package/translations/si.json +70 -21
- package/translations/sw.json +70 -21
- package/translations/sw_KE.json +70 -21
- package/translations/tr.json +70 -21
- package/translations/tr_TR.json +70 -21
- package/translations/uk.json +70 -21
- package/translations/uz.json +70 -21
- package/translations/uz@Latn.json +70 -21
- package/translations/uz_UZ.json +70 -21
- package/translations/vi.json +70 -21
- package/translations/zh.json +70 -21
- package/translations/zh_CN.json +125 -76
- package/dist/1146.js.LICENSE.txt +0 -21
- package/dist/2352.js +0 -1
- package/dist/2352.js.map +0 -1
- package/dist/246.js +0 -1
- package/dist/246.js.map +0 -1
- package/dist/6525.js +0 -2
- package/dist/6525.js.map +0 -1
- package/dist/8556.js +0 -2
- package/dist/8556.js.map +0 -1
- package/dist/8638.js +0 -1
- package/dist/8638.js.map +0 -1
- package/dist/9968.js +0 -1
- package/dist/9968.js.map +0 -1
- package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +0 -280
- package/src/invoice/payments/payments.component.test.tsx +0 -121
- /package/dist/{8556.js.LICENSE.txt → 7452.js.LICENSE.txt} +0 -0
|
@@ -2,7 +2,7 @@ import React, { useCallback, useState, useEffect } from 'react';
|
|
|
2
2
|
import { Controller, useFieldArray, useFormContext } from 'react-hook-form';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { TrashCan, Add } from '@carbon/react/icons';
|
|
5
|
-
import { Button, Dropdown, NumberInputSkeleton, TextInput, NumberInput } from '@carbon/react';
|
|
5
|
+
import { Button, IconButton, Dropdown, NumberInputSkeleton, TextInput, NumberInput } from '@carbon/react';
|
|
6
6
|
import { ErrorState } from '@openmrs/esm-patient-common-lib';
|
|
7
7
|
import { type PaymentFormValue } from '../payments.component';
|
|
8
8
|
import { usePaymentModes } from '../payment.resource';
|
|
@@ -10,26 +10,19 @@ import styles from './payment-form.scss';
|
|
|
10
10
|
|
|
11
11
|
type PaymentFormProps = {
|
|
12
12
|
disablePayment: boolean;
|
|
13
|
-
clientBalance: number;
|
|
14
|
-
isSingleLineItemSelected: boolean;
|
|
15
13
|
isSingleLineItem: boolean;
|
|
16
14
|
};
|
|
17
15
|
|
|
18
|
-
const DEFAULT_PAYMENT = { method: '', amount:
|
|
16
|
+
const DEFAULT_PAYMENT = { method: '', amount: undefined, referenceCode: '' };
|
|
19
17
|
|
|
20
|
-
const PaymentForm: React.FC<PaymentFormProps> = ({
|
|
21
|
-
disablePayment,
|
|
22
|
-
clientBalance,
|
|
23
|
-
isSingleLineItemSelected,
|
|
24
|
-
isSingleLineItem,
|
|
25
|
-
}) => {
|
|
18
|
+
const PaymentForm: React.FC<PaymentFormProps> = ({ disablePayment, isSingleLineItem }) => {
|
|
26
19
|
const { t } = useTranslation();
|
|
27
20
|
const {
|
|
28
21
|
control,
|
|
29
22
|
formState: { errors },
|
|
30
23
|
} = useFormContext<PaymentFormValue>();
|
|
31
24
|
const { paymentModes, isLoading, error } = usePaymentModes();
|
|
32
|
-
const { fields, remove, append } = useFieldArray({ name: 'payment', control
|
|
25
|
+
const { fields, remove, append } = useFieldArray({ name: 'payment', control });
|
|
33
26
|
const [isFormVisible, setIsFormVisible] = useState(isSingleLineItem);
|
|
34
27
|
|
|
35
28
|
useEffect(() => {
|
|
@@ -45,10 +38,11 @@ const PaymentForm: React.FC<PaymentFormProps> = ({
|
|
|
45
38
|
setIsFormVisible(true);
|
|
46
39
|
append(DEFAULT_PAYMENT);
|
|
47
40
|
}, [append]);
|
|
48
|
-
|
|
41
|
+
|
|
42
|
+
const handleRemovePaymentMode = useCallback((index: number) => remove(index), [remove]);
|
|
49
43
|
|
|
50
44
|
if (isLoading) {
|
|
51
|
-
return <NumberInputSkeleton
|
|
45
|
+
return <NumberInputSkeleton />;
|
|
52
46
|
}
|
|
53
47
|
|
|
54
48
|
if (error) {
|
|
@@ -62,8 +56,8 @@ const PaymentForm: React.FC<PaymentFormProps> = ({
|
|
|
62
56
|
return (
|
|
63
57
|
<div className={styles.container}>
|
|
64
58
|
{isFormVisible &&
|
|
65
|
-
fields.map((
|
|
66
|
-
<div key={
|
|
59
|
+
fields.map((fieldItem, index) => (
|
|
60
|
+
<div key={fieldItem.id} className={styles.paymentMethodContainer}>
|
|
67
61
|
<Controller
|
|
68
62
|
control={control}
|
|
69
63
|
name={`payment.${index}.method`}
|
|
@@ -85,13 +79,19 @@ const PaymentForm: React.FC<PaymentFormProps> = ({
|
|
|
85
79
|
name={`payment.${index}.amount`}
|
|
86
80
|
render={({ field }) => (
|
|
87
81
|
<NumberInput
|
|
82
|
+
allowEmpty
|
|
83
|
+
disableWheel
|
|
84
|
+
hideSteppers
|
|
88
85
|
id="paymentAmount"
|
|
89
|
-
{...field}
|
|
90
|
-
onChange={(e) => field.onChange(Number(e.target.value))}
|
|
91
86
|
invalid={!!errors?.payment?.[index]?.amount}
|
|
92
87
|
invalidText={errors?.payment?.[index]?.amount?.message}
|
|
93
88
|
label={t('amount', 'Amount')}
|
|
89
|
+
onChange={(_, { value }) => {
|
|
90
|
+
const numValue = value === '' || value === undefined ? undefined : Number(value);
|
|
91
|
+
field.onChange(numValue);
|
|
92
|
+
}}
|
|
94
93
|
placeholder={t('enterAmount', 'Enter amount')}
|
|
94
|
+
value={field.value ?? ''}
|
|
95
95
|
/>
|
|
96
96
|
)}
|
|
97
97
|
/>
|
|
@@ -101,31 +101,33 @@ const PaymentForm: React.FC<PaymentFormProps> = ({
|
|
|
101
101
|
render={({ field }) => (
|
|
102
102
|
<TextInput
|
|
103
103
|
id="paymentReferenceCode"
|
|
104
|
-
{...field}
|
|
105
104
|
labelText={t('referenceNumber', 'Reference number')}
|
|
106
|
-
|
|
105
|
+
name={field.name}
|
|
106
|
+
onBlur={field.onBlur}
|
|
107
|
+
onChange={field.onChange}
|
|
108
|
+
placeholder={t('enterReferenceNumber', 'Enter reference number')}
|
|
107
109
|
type="text"
|
|
110
|
+
value={field.value ?? ''}
|
|
108
111
|
/>
|
|
109
112
|
)}
|
|
110
113
|
/>
|
|
111
114
|
<div className={styles.removeButtonContainer}>
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
<IconButton
|
|
116
|
+
kind="danger--tertiary"
|
|
117
|
+
label={t('removePaymentMethod', 'Remove payment method')}
|
|
118
|
+
onClick={() => handleRemovePaymentMode(index)}>
|
|
119
|
+
<TrashCan />
|
|
120
|
+
</IconButton>
|
|
118
121
|
</div>
|
|
119
122
|
</div>
|
|
120
123
|
))}
|
|
121
124
|
<Button
|
|
122
|
-
disabled={disablePayment
|
|
123
|
-
size="md"
|
|
125
|
+
disabled={disablePayment}
|
|
124
126
|
onClick={handleAppendPaymentMode}
|
|
125
127
|
className={styles.paymentButtons}
|
|
126
128
|
renderIcon={(props) => <Add size={24} {...props} />}
|
|
127
|
-
iconDescription=
|
|
128
|
-
{t('
|
|
129
|
+
iconDescription={t('add', 'Add')}>
|
|
130
|
+
{t('addPaymentMethod', 'Add payment method')}
|
|
129
131
|
</Button>
|
|
130
132
|
</div>
|
|
131
133
|
);
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
@use '@carbon/type';
|
|
4
4
|
|
|
5
5
|
.container {
|
|
6
|
-
margin:
|
|
6
|
+
margin: layout.$spacing-05;
|
|
7
|
+
gap: layout.$spacing-06;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
.paymentContainer {
|
|
@@ -19,10 +20,10 @@
|
|
|
19
20
|
|
|
20
21
|
.paymentMethodContainer {
|
|
21
22
|
display: grid;
|
|
22
|
-
grid-template-columns:
|
|
23
|
+
grid-template-columns: 1fr 1fr 1fr auto;
|
|
23
24
|
align-items: flex-start;
|
|
24
|
-
|
|
25
|
-
margin:
|
|
25
|
+
gap: layout.$spacing-05;
|
|
26
|
+
margin: layout.$spacing-05 0;
|
|
26
27
|
width: 100%;
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -45,8 +46,6 @@
|
|
|
45
46
|
.removeButtonContainer {
|
|
46
47
|
display: flex;
|
|
47
48
|
align-self: center;
|
|
48
|
-
cursor: pointer;
|
|
49
|
-
margin-left: layout.$spacing-07;
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
.removeButton {
|
|
@@ -1,32 +1,33 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
3
4
|
import { FormProvider, useForm } from 'react-hook-form';
|
|
4
5
|
import type { PaymentFormValue } from '../payments.component';
|
|
6
|
+
import { usePaymentModes } from '../payment.resource';
|
|
5
7
|
import PaymentForm from './payment-form.component';
|
|
6
8
|
|
|
7
|
-
// Mock the payment resource
|
|
8
9
|
jest.mock('../payment.resource', () => ({
|
|
9
10
|
usePaymentModes: jest.fn(),
|
|
10
11
|
}));
|
|
11
12
|
|
|
12
|
-
const
|
|
13
|
+
const mockUsePaymentModes = jest.mocked(usePaymentModes);
|
|
13
14
|
|
|
14
15
|
type WrapperProps = {
|
|
15
16
|
children: React.ReactNode;
|
|
17
|
+
defaultValues?: Partial<PaymentFormValue>;
|
|
16
18
|
};
|
|
17
19
|
|
|
18
|
-
const Wrapper: React.FC<WrapperProps> = ({ children }) => {
|
|
19
|
-
const methods = useForm<PaymentFormValue>(
|
|
20
|
+
const Wrapper: React.FC<WrapperProps> = ({ children, defaultValues }) => {
|
|
21
|
+
const methods = useForm<PaymentFormValue>({
|
|
22
|
+
mode: 'all',
|
|
23
|
+
defaultValues: defaultValues || { payment: [] },
|
|
24
|
+
});
|
|
20
25
|
return <FormProvider {...methods}>{children}</FormProvider>;
|
|
21
26
|
};
|
|
22
27
|
|
|
23
28
|
describe('PaymentForm Component', () => {
|
|
24
|
-
beforeEach(() => {
|
|
25
|
-
jest.clearAllMocks();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
29
|
test('should render skeleton while loading payment modes', () => {
|
|
29
|
-
|
|
30
|
+
mockUsePaymentModes.mockReturnValue({
|
|
30
31
|
paymentModes: [],
|
|
31
32
|
isLoading: true,
|
|
32
33
|
error: null,
|
|
@@ -35,20 +36,17 @@ describe('PaymentForm Component', () => {
|
|
|
35
36
|
|
|
36
37
|
render(
|
|
37
38
|
<Wrapper>
|
|
38
|
-
<PaymentForm
|
|
39
|
-
disablePayment={false}
|
|
40
|
-
clientBalance={100}
|
|
41
|
-
isSingleLineItemSelected={false}
|
|
42
|
-
isSingleLineItem={false}
|
|
43
|
-
/>
|
|
39
|
+
<PaymentForm disablePayment={false} isSingleLineItem={false} />
|
|
44
40
|
</Wrapper>,
|
|
45
41
|
);
|
|
46
42
|
|
|
47
|
-
|
|
43
|
+
// When loading, payment method elements should not be present
|
|
44
|
+
expect(screen.queryByText(/select payment method/i)).not.toBeInTheDocument();
|
|
45
|
+
expect(screen.queryByPlaceholderText(/enter amount/i)).not.toBeInTheDocument();
|
|
48
46
|
});
|
|
49
47
|
|
|
50
48
|
test('should render error message when payment modes fail to load', () => {
|
|
51
|
-
|
|
49
|
+
mockUsePaymentModes.mockReturnValue({
|
|
52
50
|
paymentModes: [],
|
|
53
51
|
isLoading: false,
|
|
54
52
|
error: new Error('Failed to load payment modes'),
|
|
@@ -57,12 +55,7 @@ describe('PaymentForm Component', () => {
|
|
|
57
55
|
|
|
58
56
|
render(
|
|
59
57
|
<Wrapper>
|
|
60
|
-
<PaymentForm
|
|
61
|
-
disablePayment={false}
|
|
62
|
-
clientBalance={100}
|
|
63
|
-
isSingleLineItemSelected={false}
|
|
64
|
-
isSingleLineItem={false}
|
|
65
|
-
/>
|
|
58
|
+
<PaymentForm disablePayment={false} isSingleLineItem={false} />
|
|
66
59
|
</Wrapper>,
|
|
67
60
|
);
|
|
68
61
|
|
|
@@ -70,8 +63,8 @@ describe('PaymentForm Component', () => {
|
|
|
70
63
|
});
|
|
71
64
|
|
|
72
65
|
test('should append default payment when isSingleLineItem is true', () => {
|
|
73
|
-
|
|
74
|
-
paymentModes: [{ uuid: '1', name: 'Credit Card' }],
|
|
66
|
+
mockUsePaymentModes.mockReturnValue({
|
|
67
|
+
paymentModes: [{ uuid: '1', name: 'Credit Card', description: 'Credit Card', retired: false }],
|
|
75
68
|
isLoading: false,
|
|
76
69
|
error: null,
|
|
77
70
|
mutate: jest.fn(),
|
|
@@ -79,12 +72,7 @@ describe('PaymentForm Component', () => {
|
|
|
79
72
|
|
|
80
73
|
render(
|
|
81
74
|
<Wrapper>
|
|
82
|
-
<PaymentForm
|
|
83
|
-
disablePayment={false}
|
|
84
|
-
clientBalance={100}
|
|
85
|
-
isSingleLineItemSelected={false}
|
|
86
|
-
isSingleLineItem={true}
|
|
87
|
-
/>
|
|
75
|
+
<PaymentForm disablePayment={false} isSingleLineItem={true} />
|
|
88
76
|
</Wrapper>,
|
|
89
77
|
);
|
|
90
78
|
|
|
@@ -96,9 +84,10 @@ describe('PaymentForm Component', () => {
|
|
|
96
84
|
expect(screen.getByPlaceholderText(/enter amount/i)).toBeInTheDocument();
|
|
97
85
|
});
|
|
98
86
|
|
|
99
|
-
test('should append a payment field when add payment
|
|
100
|
-
|
|
101
|
-
|
|
87
|
+
test('should append a payment field when add payment method button is clicked', async () => {
|
|
88
|
+
const user = userEvent.setup();
|
|
89
|
+
mockUsePaymentModes.mockReturnValue({
|
|
90
|
+
paymentModes: [{ uuid: '1', name: 'Credit Card', description: 'Credit Card', retired: false }],
|
|
102
91
|
isLoading: false,
|
|
103
92
|
error: null,
|
|
104
93
|
mutate: jest.fn(),
|
|
@@ -106,24 +95,25 @@ describe('PaymentForm Component', () => {
|
|
|
106
95
|
|
|
107
96
|
render(
|
|
108
97
|
<Wrapper>
|
|
109
|
-
<PaymentForm
|
|
110
|
-
disablePayment={false}
|
|
111
|
-
clientBalance={100}
|
|
112
|
-
isSingleLineItemSelected={true}
|
|
113
|
-
isSingleLineItem={false}
|
|
114
|
-
/>
|
|
98
|
+
<PaymentForm disablePayment={false} isSingleLineItem={false} />
|
|
115
99
|
</Wrapper>,
|
|
116
100
|
);
|
|
117
101
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
102
|
+
// Initially no payment fields are shown
|
|
103
|
+
expect(screen.queryByPlaceholderText(/enter amount/i)).not.toBeInTheDocument();
|
|
104
|
+
|
|
105
|
+
const addButton = screen.getByText(/add payment method/i);
|
|
106
|
+
await user.click(addButton);
|
|
107
|
+
|
|
108
|
+
// After clicking, payment fields should be visible
|
|
109
|
+
expect(screen.getByPlaceholderText(/enter amount/i)).toBeInTheDocument();
|
|
110
|
+
expect(screen.getByPlaceholderText(/enter reference number/i)).toBeInTheDocument();
|
|
111
|
+
expect(screen.getByText(/select payment method/i)).toBeInTheDocument();
|
|
122
112
|
});
|
|
123
113
|
|
|
124
114
|
test('should disable add payment button when disablePayment is true', () => {
|
|
125
|
-
|
|
126
|
-
paymentModes: [{ uuid: '1', name: 'Credit Card' }],
|
|
115
|
+
mockUsePaymentModes.mockReturnValue({
|
|
116
|
+
paymentModes: [{ uuid: '1', name: 'Credit Card', description: 'Credit Card', retired: false }],
|
|
127
117
|
isLoading: false,
|
|
128
118
|
error: null,
|
|
129
119
|
mutate: jest.fn(),
|
|
@@ -131,21 +121,17 @@ describe('PaymentForm Component', () => {
|
|
|
131
121
|
|
|
132
122
|
render(
|
|
133
123
|
<Wrapper>
|
|
134
|
-
<PaymentForm
|
|
135
|
-
disablePayment={true}
|
|
136
|
-
clientBalance={100}
|
|
137
|
-
isSingleLineItemSelected={true}
|
|
138
|
-
isSingleLineItem={false}
|
|
139
|
-
/>
|
|
124
|
+
<PaymentForm disablePayment={true} isSingleLineItem={false} />
|
|
140
125
|
</Wrapper>,
|
|
141
126
|
);
|
|
142
127
|
|
|
143
|
-
expect(screen.getByText(/add payment
|
|
128
|
+
expect(screen.getByText(/add payment method/i)).toBeDisabled();
|
|
144
129
|
});
|
|
145
130
|
|
|
146
|
-
test('should remove payment field when
|
|
147
|
-
|
|
148
|
-
|
|
131
|
+
test('should remove payment field when remove button is clicked', async () => {
|
|
132
|
+
const user = userEvent.setup();
|
|
133
|
+
mockUsePaymentModes.mockReturnValue({
|
|
134
|
+
paymentModes: [{ uuid: '1', name: 'Credit Card', description: 'Credit Card', retired: false }],
|
|
149
135
|
isLoading: false,
|
|
150
136
|
error: null,
|
|
151
137
|
mutate: jest.fn(),
|
|
@@ -153,22 +139,186 @@ describe('PaymentForm Component', () => {
|
|
|
153
139
|
|
|
154
140
|
render(
|
|
155
141
|
<Wrapper>
|
|
156
|
-
<PaymentForm
|
|
157
|
-
disablePayment={false}
|
|
158
|
-
clientBalance={100}
|
|
159
|
-
isSingleLineItemSelected={true}
|
|
160
|
-
isSingleLineItem={false}
|
|
161
|
-
/>
|
|
142
|
+
<PaymentForm disablePayment={false} isSingleLineItem={false} />
|
|
162
143
|
</Wrapper>,
|
|
163
144
|
);
|
|
164
145
|
|
|
165
|
-
|
|
146
|
+
await user.click(screen.getByText(/add payment method/i));
|
|
166
147
|
|
|
167
|
-
const
|
|
168
|
-
|
|
148
|
+
const removeButton = screen.getByRole('button', { name: /remove payment method/i });
|
|
149
|
+
await user.click(removeButton);
|
|
169
150
|
|
|
170
151
|
await waitFor(() => {
|
|
171
152
|
expect(screen.queryByPlaceholderText(/enter amount/i)).not.toBeInTheDocument();
|
|
172
153
|
});
|
|
173
154
|
});
|
|
155
|
+
|
|
156
|
+
test('should render amount input without leading zero', () => {
|
|
157
|
+
mockUsePaymentModes.mockReturnValue({
|
|
158
|
+
paymentModes: [{ uuid: '1', name: 'Credit Card', description: 'Credit Card', retired: false }],
|
|
159
|
+
isLoading: false,
|
|
160
|
+
error: null,
|
|
161
|
+
mutate: jest.fn(),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
render(
|
|
165
|
+
<Wrapper>
|
|
166
|
+
<PaymentForm disablePayment={false} isSingleLineItem={true} />
|
|
167
|
+
</Wrapper>,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const amountInput = screen.getByPlaceholderText(/enter amount/i) as HTMLInputElement;
|
|
171
|
+
expect(amountInput.value).toBe('');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('should allow user to clear amount input without reverting to zero', async () => {
|
|
175
|
+
const user = userEvent.setup();
|
|
176
|
+
mockUsePaymentModes.mockReturnValue({
|
|
177
|
+
paymentModes: [{ uuid: '1', name: 'Credit Card', description: 'Credit Card', retired: false }],
|
|
178
|
+
isLoading: false,
|
|
179
|
+
error: null,
|
|
180
|
+
mutate: jest.fn(),
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
render(
|
|
184
|
+
<Wrapper>
|
|
185
|
+
<PaymentForm disablePayment={false} isSingleLineItem={true} />
|
|
186
|
+
</Wrapper>,
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const amountInput = screen.getByPlaceholderText(/enter amount/i) as HTMLInputElement;
|
|
190
|
+
|
|
191
|
+
await user.type(amountInput, '100');
|
|
192
|
+
expect(amountInput.value).toBe('100');
|
|
193
|
+
|
|
194
|
+
await user.clear(amountInput);
|
|
195
|
+
expect(amountInput.value).toBe('');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('should handle amount input with decimal values', async () => {
|
|
199
|
+
const user = userEvent.setup();
|
|
200
|
+
mockUsePaymentModes.mockReturnValue({
|
|
201
|
+
paymentModes: [{ uuid: '1', name: 'Credit Card', description: 'Credit Card', retired: false }],
|
|
202
|
+
isLoading: false,
|
|
203
|
+
error: null,
|
|
204
|
+
mutate: jest.fn(),
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
render(
|
|
208
|
+
<Wrapper>
|
|
209
|
+
<PaymentForm disablePayment={false} isSingleLineItem={true} />
|
|
210
|
+
</Wrapper>,
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const amountInput = screen.getByPlaceholderText(/enter amount/i) as HTMLInputElement;
|
|
214
|
+
|
|
215
|
+
await user.type(amountInput, '10.50');
|
|
216
|
+
expect(amountInput.value).toBe('10.5');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test('should not auto-focus reference number input on mount', () => {
|
|
220
|
+
mockUsePaymentModes.mockReturnValue({
|
|
221
|
+
paymentModes: [{ uuid: '1', name: 'Credit Card', description: 'Credit Card', retired: false }],
|
|
222
|
+
isLoading: false,
|
|
223
|
+
error: null,
|
|
224
|
+
mutate: jest.fn(),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
render(
|
|
228
|
+
<Wrapper>
|
|
229
|
+
<PaymentForm disablePayment={false} isSingleLineItem={true} />
|
|
230
|
+
</Wrapper>,
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
const referenceInput = screen.getByPlaceholderText(/enter reference number/i);
|
|
234
|
+
expect(referenceInput).not.toHaveFocus();
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test('should allow adding multiple payment methods', async () => {
|
|
238
|
+
const user = userEvent.setup();
|
|
239
|
+
mockUsePaymentModes.mockReturnValue({
|
|
240
|
+
paymentModes: [
|
|
241
|
+
{ uuid: '1', name: 'Cash', description: 'Cash', retired: false },
|
|
242
|
+
{ uuid: '2', name: 'Credit Card', description: 'Credit Card', retired: false },
|
|
243
|
+
],
|
|
244
|
+
isLoading: false,
|
|
245
|
+
error: null,
|
|
246
|
+
mutate: jest.fn(),
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
render(
|
|
250
|
+
<Wrapper>
|
|
251
|
+
<PaymentForm disablePayment={false} isSingleLineItem={false} />
|
|
252
|
+
</Wrapper>,
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
const addButton = screen.getByText(/add payment method/i);
|
|
256
|
+
|
|
257
|
+
await user.click(addButton);
|
|
258
|
+
await user.click(addButton);
|
|
259
|
+
|
|
260
|
+
const amountInputs = screen.getAllByPlaceholderText(/enter amount/i);
|
|
261
|
+
expect(amountInputs).toHaveLength(2);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test('should preserve entered values when adding new payment method', async () => {
|
|
265
|
+
const user = userEvent.setup();
|
|
266
|
+
mockUsePaymentModes.mockReturnValue({
|
|
267
|
+
paymentModes: [{ uuid: '1', name: 'Cash', description: 'Cash', retired: false }],
|
|
268
|
+
isLoading: false,
|
|
269
|
+
error: null,
|
|
270
|
+
mutate: jest.fn(),
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
render(
|
|
274
|
+
<Wrapper>
|
|
275
|
+
<PaymentForm disablePayment={false} isSingleLineItem={false} />
|
|
276
|
+
</Wrapper>,
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
const addButton = screen.getByText(/add payment method/i);
|
|
280
|
+
await user.click(addButton);
|
|
281
|
+
|
|
282
|
+
const firstAmountInput = screen.getByPlaceholderText(/enter amount/i) as HTMLInputElement;
|
|
283
|
+
await user.type(firstAmountInput, '50');
|
|
284
|
+
|
|
285
|
+
await user.click(addButton);
|
|
286
|
+
|
|
287
|
+
const amountInputs = screen.getAllByPlaceholderText(/enter amount/i) as HTMLInputElement[];
|
|
288
|
+
expect(amountInputs[0].value).toBe('50');
|
|
289
|
+
expect(amountInputs[1].value).toBe('');
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test('should handle removing payment method without affecting other fields', async () => {
|
|
293
|
+
const user = userEvent.setup();
|
|
294
|
+
mockUsePaymentModes.mockReturnValue({
|
|
295
|
+
paymentModes: [{ uuid: '1', name: 'Cash', description: 'Cash', retired: false }],
|
|
296
|
+
isLoading: false,
|
|
297
|
+
error: null,
|
|
298
|
+
mutate: jest.fn(),
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
render(
|
|
302
|
+
<Wrapper>
|
|
303
|
+
<PaymentForm disablePayment={false} isSingleLineItem={false} />
|
|
304
|
+
</Wrapper>,
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
const addButton = screen.getByText(/add payment method/i);
|
|
308
|
+
await user.click(addButton);
|
|
309
|
+
await user.click(addButton);
|
|
310
|
+
|
|
311
|
+
const amountInputs = screen.getAllByPlaceholderText(/enter amount/i) as HTMLInputElement[];
|
|
312
|
+
await user.type(amountInputs[0], '50');
|
|
313
|
+
await user.type(amountInputs[1], '75');
|
|
314
|
+
|
|
315
|
+
const removeButtons = screen.getAllByRole('button', { name: /remove payment method/i });
|
|
316
|
+
await user.click(removeButtons[0]);
|
|
317
|
+
|
|
318
|
+
await waitFor(() => {
|
|
319
|
+
const remainingInputs = screen.getAllByPlaceholderText(/enter amount/i) as HTMLInputElement[];
|
|
320
|
+
expect(remainingInputs).toHaveLength(1);
|
|
321
|
+
expect(remainingInputs[0].value).toBe('75');
|
|
322
|
+
});
|
|
323
|
+
});
|
|
174
324
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { DataTable, Table, TableHead, TableRow, TableHeader, TableBody, TableCell } from '@carbon/react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
3
4
|
import { type MappedBill } from '../../../types';
|
|
4
5
|
import { formatDate, useConfig } from '@openmrs/esm-framework';
|
|
5
6
|
import { convertToCurrency } from '../../../helpers';
|
|
@@ -9,23 +10,24 @@ type PaymentHistoryProps = {
|
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
const PaymentHistory: React.FC<PaymentHistoryProps> = ({ bill }) => {
|
|
13
|
+
const { t } = useTranslation();
|
|
12
14
|
const { defaultCurrency } = useConfig();
|
|
13
15
|
const headers = [
|
|
14
16
|
{
|
|
15
17
|
key: 'dateCreated',
|
|
16
|
-
header: 'Date of payment',
|
|
18
|
+
header: t('dateOfPayment', 'Date of payment'),
|
|
17
19
|
},
|
|
18
20
|
{
|
|
19
21
|
key: 'amount',
|
|
20
|
-
header: 'Bill amount',
|
|
22
|
+
header: t('billAmount', 'Bill amount'),
|
|
21
23
|
},
|
|
22
24
|
{
|
|
23
25
|
key: 'amountTendered',
|
|
24
|
-
header: 'Amount tendered',
|
|
26
|
+
header: t('amountTendered', 'Amount tendered'),
|
|
25
27
|
},
|
|
26
28
|
{
|
|
27
29
|
key: 'paymentMethod',
|
|
28
|
-
header: 'Payment method',
|
|
30
|
+
header: t('paymentMethod', 'Payment method'),
|
|
29
31
|
},
|
|
30
32
|
];
|
|
31
33
|
const rows = bill?.payments?.map((payment, index) => {
|
|
@@ -1,24 +1,19 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, screen } from '@testing-library/react';
|
|
3
|
-
import { useConfig } from '@openmrs/esm-framework';
|
|
4
|
-
import
|
|
3
|
+
import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
|
|
4
|
+
import { type BillingConfig, configSchema } from '../../../config-schema';
|
|
5
5
|
import { type MappedBill } from '../../../types';
|
|
6
|
-
|
|
7
|
-
// Mocking useConfig to return a default currency
|
|
8
|
-
jest.mock('@openmrs/esm-framework', () => ({
|
|
9
|
-
useConfig: jest.fn(),
|
|
10
|
-
formatDate: jest.fn((date) => date.toISOString().split('T')[0]),
|
|
11
|
-
}));
|
|
6
|
+
import PaymentHistory from './payment-history.component';
|
|
12
7
|
|
|
13
8
|
jest.mock('../../../helpers', () => ({
|
|
14
9
|
convertToCurrency: jest.fn((amount, currency) => `${currency} ${amount.toFixed(2)}`),
|
|
15
10
|
}));
|
|
16
11
|
|
|
12
|
+
const mockUseConfig = jest.mocked(useConfig<BillingConfig>);
|
|
13
|
+
|
|
17
14
|
describe('PaymentHistory Component', () => {
|
|
18
15
|
beforeEach(() => {
|
|
19
|
-
(
|
|
20
|
-
defaultCurrency: 'USD',
|
|
21
|
-
});
|
|
16
|
+
mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(configSchema), defaultCurrency: 'USD' });
|
|
22
17
|
});
|
|
23
18
|
|
|
24
19
|
const mockBill: MappedBill = {
|
|
@@ -129,12 +124,12 @@ describe('PaymentHistory Component', () => {
|
|
|
129
124
|
test('renders correct data in the rows', () => {
|
|
130
125
|
render(<PaymentHistory bill={mockBill} />);
|
|
131
126
|
|
|
132
|
-
expect(screen.getByText('
|
|
127
|
+
expect(screen.getByText('01-Sept-2023')).toBeInTheDocument();
|
|
133
128
|
expect(screen.getByText('USD 80.00')).toBeInTheDocument();
|
|
134
129
|
expect(screen.getByText('USD 100.00')).toBeInTheDocument();
|
|
135
130
|
expect(screen.getByText('Credit Card')).toBeInTheDocument();
|
|
136
131
|
|
|
137
|
-
expect(screen.getByText('
|
|
132
|
+
expect(screen.getByText('05-Sept-2023')).toBeInTheDocument();
|
|
138
133
|
expect(screen.getByText('USD 180.00')).toBeInTheDocument();
|
|
139
134
|
expect(screen.getByText('USD 200.00')).toBeInTheDocument();
|
|
140
135
|
expect(screen.getByText('Cash')).toBeInTheDocument();
|
|
@@ -153,7 +148,7 @@ describe('PaymentHistory Component', () => {
|
|
|
153
148
|
test('formats dates and converts amounts correctly', () => {
|
|
154
149
|
render(<PaymentHistory bill={mockBill} />);
|
|
155
150
|
|
|
156
|
-
expect(screen.getByText('
|
|
151
|
+
expect(screen.getByText('01-Sept-2023')).toBeInTheDocument();
|
|
157
152
|
expect(screen.getByText('USD 80.00')).toBeInTheDocument();
|
|
158
153
|
expect(screen.getByText('USD 100.00')).toBeInTheDocument();
|
|
159
154
|
});
|