@openmrs/esm-billing-app 1.0.2-pre.96 → 1.0.2-pre.972

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.
Files changed (214) hide show
  1. package/.eslintrc +16 -2
  2. package/README.md +54 -9
  3. package/__mocks__/bills.mock.ts +12 -0
  4. package/__mocks__/react-i18next.js +6 -5
  5. package/dist/1119.js +1 -1
  6. package/dist/1146.js +1 -2
  7. package/dist/1146.js.map +1 -1
  8. package/dist/1197.js +1 -1
  9. package/dist/1537.js +1 -0
  10. package/dist/1537.js.map +1 -0
  11. package/dist/1856.js +1 -0
  12. package/dist/1856.js.map +1 -0
  13. package/dist/2146.js +1 -1
  14. package/dist/2524.js +1 -0
  15. package/dist/2524.js.map +1 -0
  16. package/dist/2690.js +1 -1
  17. package/dist/3099.js +1 -1
  18. package/dist/3584.js +1 -1
  19. package/dist/3717.js +2 -0
  20. package/dist/3717.js.map +1 -0
  21. package/dist/4055.js +1 -1
  22. package/dist/4132.js +1 -1
  23. package/dist/4300.js +1 -1
  24. package/dist/4335.js +1 -1
  25. package/dist/4618.js +1 -1
  26. package/dist/4652.js +1 -1
  27. package/dist/4724.js +1 -0
  28. package/dist/4724.js.map +1 -0
  29. package/dist/4739.js +1 -1
  30. package/dist/4739.js.map +1 -1
  31. package/dist/4944.js +1 -1
  32. package/dist/5173.js +1 -1
  33. package/dist/5241.js +1 -1
  34. package/dist/5442.js +1 -1
  35. package/dist/5661.js +1 -1
  36. package/dist/6022.js +1 -1
  37. package/dist/6468.js +1 -1
  38. package/dist/6540.js +1 -1
  39. package/dist/6540.js.map +1 -1
  40. package/dist/6679.js +1 -1
  41. package/dist/6840.js +1 -1
  42. package/dist/6859.js +1 -1
  43. package/dist/7097.js +1 -1
  44. package/dist/7159.js +1 -1
  45. package/dist/723.js +1 -1
  46. package/dist/7255.js +1 -1
  47. package/dist/7255.js.map +1 -1
  48. package/dist/7617.js +1 -1
  49. package/dist/795.js +1 -1
  50. package/dist/8163.js +1 -1
  51. package/dist/8349.js +1 -1
  52. package/dist/8572.js +1 -0
  53. package/dist/8572.js.map +1 -0
  54. package/dist/8618.js +1 -1
  55. package/dist/8708.js +2 -0
  56. package/dist/{6557.js.LICENSE.txt → 8708.js.LICENSE.txt} +22 -0
  57. package/dist/8708.js.map +1 -0
  58. package/dist/890.js +1 -1
  59. package/dist/9214.js +1 -1
  60. package/dist/9538.js +1 -1
  61. package/dist/9569.js +1 -1
  62. package/dist/961.js +1 -1
  63. package/dist/961.js.map +1 -1
  64. package/dist/986.js +1 -1
  65. package/dist/9879.js +1 -1
  66. package/dist/9895.js +1 -1
  67. package/dist/9900.js +1 -1
  68. package/dist/9913.js +1 -1
  69. package/dist/main.js +1 -1
  70. package/dist/main.js.map +1 -1
  71. package/dist/openmrs-esm-billing-app.js +1 -1
  72. package/dist/openmrs-esm-billing-app.js.buildmanifest.json +271 -285
  73. package/dist/openmrs-esm-billing-app.js.map +1 -1
  74. package/dist/routes.json +1 -1
  75. package/e2e/README.md +19 -18
  76. package/e2e/core/test.ts +1 -1
  77. package/e2e/fixtures/api.ts +1 -1
  78. package/e2e/specs/sample-test.spec.ts +0 -1
  79. package/e2e/support/github/Dockerfile +1 -1
  80. package/package.json +18 -15
  81. package/src/bill-history/bill-history.component.tsx +20 -28
  82. package/src/bill-history/bill-history.scss +4 -94
  83. package/src/bill-history/bill-history.test.tsx +37 -78
  84. package/src/bill-item-actions/bill-item-actions.scss +21 -5
  85. package/src/bill-item-actions/edit-bill-item.modal.tsx +226 -0
  86. package/src/bill-item-actions/edit-bill-item.test.tsx +233 -40
  87. package/src/billable-services/bill-waiver/bill-selection.component.tsx +5 -5
  88. package/src/billable-services/bill-waiver/bill-waiver-form.component.tsx +34 -37
  89. package/src/billable-services/bill-waiver/patient-bills.component.tsx +7 -7
  90. package/src/billable-services/bill-waiver/utils.ts +13 -3
  91. package/src/billable-services/{create-edit/add-billable-service.scss → billable-service-form/billable-service-form.scss} +32 -64
  92. package/src/billable-services/billable-service-form/billable-service-form.test.tsx +1048 -0
  93. package/src/billable-services/billable-service-form/billable-service-form.workspace.tsx +515 -0
  94. package/src/billable-services/billable-service.resource.ts +71 -27
  95. package/src/billable-services/billable-services-home.component.tsx +13 -42
  96. package/src/billable-services/billable-services-left-panel-link.component.tsx +48 -0
  97. package/src/billable-services/billable-services-left-panel-menu.component.tsx +46 -0
  98. package/src/billable-services/billable-services-menu-item/item.component.tsx +5 -4
  99. package/src/billable-services/billable-services.component.tsx +156 -152
  100. package/src/billable-services/billable-services.scss +29 -0
  101. package/src/billable-services/billable-services.test.tsx +6 -49
  102. package/src/billable-services/cash-point/add-cash-point.modal.tsx +170 -0
  103. package/src/billable-services/cash-point/cash-point-configuration.component.tsx +19 -193
  104. package/src/billable-services/cash-point/cash-point-configuration.scss +1 -5
  105. package/src/billable-services/dashboard/dashboard.component.tsx +0 -2
  106. package/src/billable-services/payment-modes/delete-payment-mode.modal.tsx +77 -0
  107. package/src/billable-services/payment-modes/payment-mode-form.modal.tsx +131 -0
  108. package/src/billable-services/payment-modes/payment-modes-config.component.tsx +139 -0
  109. package/src/billable-services/{payyment-modes → payment-modes}/payment-modes-config.scss +5 -4
  110. package/src/billable-services-admin-card-link.component.test.tsx +2 -2
  111. package/src/billable-services-admin-card-link.component.tsx +1 -1
  112. package/src/billing-dashboard/billing-dashboard.scss +1 -1
  113. package/src/billing-form/billing-checkin-form.component.tsx +21 -17
  114. package/src/billing-form/billing-checkin-form.test.tsx +99 -26
  115. package/src/billing-form/billing-form.component.tsx +226 -289
  116. package/src/billing-form/billing-form.scss +143 -0
  117. package/src/billing-form/visit-attributes/visit-attributes-form.component.tsx +1 -1
  118. package/src/billing.resource.ts +69 -74
  119. package/src/bills-table/bills-table.component.tsx +3 -3
  120. package/src/bills-table/bills-table.test.tsx +98 -54
  121. package/src/config-schema.ts +52 -24
  122. package/src/dashboard.meta.ts +4 -2
  123. package/src/helpers/functions.ts +5 -4
  124. package/src/index.ts +71 -9
  125. package/src/invoice/invoice-table.component.tsx +36 -70
  126. package/src/invoice/invoice-table.scss +8 -5
  127. package/src/invoice/invoice-table.test.tsx +273 -62
  128. package/src/invoice/invoice.component.tsx +39 -33
  129. package/src/invoice/invoice.scss +11 -4
  130. package/src/invoice/invoice.test.tsx +324 -120
  131. package/src/invoice/payments/invoice-breakdown/invoice-breakdown.scss +9 -9
  132. package/src/invoice/payments/payment-form/payment-form.component.tsx +43 -34
  133. package/src/invoice/payments/payment-form/payment-form.scss +5 -6
  134. package/src/invoice/payments/payment-form/payment-form.test.tsx +216 -66
  135. package/src/invoice/payments/payment-history/payment-history.component.tsx +6 -4
  136. package/src/invoice/payments/payment-history/payment-history.test.tsx +9 -14
  137. package/src/invoice/payments/payments.component.tsx +55 -67
  138. package/src/invoice/payments/payments.scss +4 -3
  139. package/src/invoice/payments/payments.test.tsx +282 -0
  140. package/src/invoice/payments/utils.ts +15 -27
  141. package/src/invoice/printable-invoice/print-receipt.component.tsx +3 -3
  142. package/src/invoice/printable-invoice/print-receipt.test.tsx +14 -25
  143. package/src/invoice/printable-invoice/printable-footer.component.tsx +2 -2
  144. package/src/invoice/printable-invoice/printable-footer.test.tsx +4 -13
  145. package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +20 -11
  146. package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +95 -16
  147. package/src/invoice/printable-invoice/printable-invoice.component.tsx +21 -35
  148. package/src/left-panel-link.test.tsx +1 -4
  149. package/src/metrics-cards/metrics-cards.component.tsx +16 -6
  150. package/src/metrics-cards/metrics-cards.scss +4 -0
  151. package/src/metrics-cards/metrics-cards.test.tsx +18 -5
  152. package/src/modal/require-payment-modal.test.tsx +27 -22
  153. package/src/modal/{require-payment-modal.component.tsx → require-payment.modal.tsx} +18 -19
  154. package/src/routes.json +44 -20
  155. package/src/types/index.ts +87 -24
  156. package/translations/am.json +133 -78
  157. package/translations/ar.json +134 -79
  158. package/translations/ar_SY.json +134 -79
  159. package/translations/bn.json +136 -81
  160. package/translations/de.json +134 -79
  161. package/translations/en.json +136 -79
  162. package/translations/en_US.json +134 -79
  163. package/translations/es.json +133 -78
  164. package/translations/es_MX.json +134 -79
  165. package/translations/fr.json +139 -84
  166. package/translations/he.json +133 -78
  167. package/translations/hi.json +134 -79
  168. package/translations/hi_IN.json +134 -79
  169. package/translations/id.json +134 -79
  170. package/translations/it.json +160 -105
  171. package/translations/ka.json +134 -79
  172. package/translations/km.json +133 -78
  173. package/translations/ku.json +134 -79
  174. package/translations/ky.json +134 -79
  175. package/translations/lg.json +134 -79
  176. package/translations/ne.json +134 -79
  177. package/translations/pl.json +134 -79
  178. package/translations/pt.json +134 -79
  179. package/translations/pt_BR.json +134 -79
  180. package/translations/qu.json +134 -79
  181. package/translations/ro_RO.json +220 -165
  182. package/translations/ru_RU.json +134 -79
  183. package/translations/si.json +134 -79
  184. package/translations/sw.json +134 -79
  185. package/translations/sw_KE.json +134 -79
  186. package/translations/tr.json +134 -79
  187. package/translations/tr_TR.json +134 -79
  188. package/translations/uk.json +134 -79
  189. package/translations/uz.json +134 -79
  190. package/translations/uz@Latn.json +134 -79
  191. package/translations/uz_UZ.json +134 -79
  192. package/translations/vi.json +134 -79
  193. package/translations/zh.json +134 -79
  194. package/translations/zh_CN.json +164 -109
  195. package/dist/1146.js.LICENSE.txt +0 -21
  196. package/dist/2352.js +0 -1
  197. package/dist/2352.js.map +0 -1
  198. package/dist/246.js +0 -1
  199. package/dist/246.js.map +0 -1
  200. package/dist/4689.js +0 -2
  201. package/dist/4689.js.map +0 -1
  202. package/dist/6557.js +0 -2
  203. package/dist/6557.js.map +0 -1
  204. package/dist/8638.js +0 -1
  205. package/dist/8638.js.map +0 -1
  206. package/dist/9968.js +0 -1
  207. package/dist/9968.js.map +0 -1
  208. package/src/bill-item-actions/edit-bill-item.component.tsx +0 -221
  209. package/src/billable-services/create-edit/add-billable-service.component.tsx +0 -401
  210. package/src/billable-services/create-edit/add-billable-service.test.tsx +0 -154
  211. package/src/billable-services/dashboard/service-metrics.component.tsx +0 -41
  212. package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +0 -280
  213. package/src/invoice/payments/payments.component.test.tsx +0 -121
  214. /package/dist/{4689.js.LICENSE.txt → 3717.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,45 +10,46 @@ 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: 0, referenceCode: '' };
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: control });
25
+ const { fields, remove, append } = useFieldArray({ name: 'payment', control });
33
26
  const [isFormVisible, setIsFormVisible] = useState(isSingleLineItem);
34
27
 
35
28
  useEffect(() => {
36
- if (isSingleLineItem) {
29
+ if (isSingleLineItem && !disablePayment) {
37
30
  setIsFormVisible(true);
38
31
  if (fields.length === 0) {
39
32
  append(DEFAULT_PAYMENT);
40
33
  }
41
34
  }
42
- }, [isSingleLineItem, append, fields.length]);
35
+ }, [isSingleLineItem, append, fields.length, disablePayment]);
36
+
37
+ useEffect(() => {
38
+ if (disablePayment) {
39
+ setIsFormVisible(false);
40
+ remove();
41
+ }
42
+ }, [disablePayment, remove]);
43
43
 
44
44
  const handleAppendPaymentMode = useCallback(() => {
45
45
  setIsFormVisible(true);
46
46
  append(DEFAULT_PAYMENT);
47
47
  }, [append]);
48
- const handleRemovePaymentMode = useCallback((index) => remove(index), [remove]);
48
+
49
+ const handleRemovePaymentMode = useCallback((index: number) => remove(index), [remove]);
49
50
 
50
51
  if (isLoading) {
51
- return <NumberInputSkeleton data-testid="number-input-skeleton" />;
52
+ return <NumberInputSkeleton />;
52
53
  }
53
54
 
54
55
  if (error) {
@@ -62,14 +63,14 @@ const PaymentForm: React.FC<PaymentFormProps> = ({
62
63
  return (
63
64
  <div className={styles.container}>
64
65
  {isFormVisible &&
65
- fields.map((field, index) => (
66
- <div key={field.id} className={styles.paymentMethodContainer}>
66
+ fields.map((fieldItem, index) => (
67
+ <div key={fieldItem.id} className={styles.paymentMethodContainer}>
67
68
  <Controller
68
69
  control={control}
69
70
  name={`payment.${index}.method`}
70
71
  render={({ field }) => (
71
72
  <Dropdown
72
- id="paymentMethod"
73
+ id={`paymentMethod-${fieldItem.id}`}
73
74
  onChange={({ selectedItem }) => field.onChange(selectedItem?.uuid)}
74
75
  titleText={t('paymentMethod', 'Payment method')}
75
76
  label={t('selectPaymentMethod', 'Select payment method')}
@@ -85,13 +86,19 @@ const PaymentForm: React.FC<PaymentFormProps> = ({
85
86
  name={`payment.${index}.amount`}
86
87
  render={({ field }) => (
87
88
  <NumberInput
88
- id="paymentAmount"
89
- {...field}
90
- onChange={(e) => field.onChange(Number(e.target.value))}
89
+ allowEmpty
90
+ disableWheel
91
+ hideSteppers
92
+ id={`paymentAmount-${fieldItem.id}`}
91
93
  invalid={!!errors?.payment?.[index]?.amount}
92
94
  invalidText={errors?.payment?.[index]?.amount?.message}
93
95
  label={t('amount', 'Amount')}
96
+ onChange={(_, { value }) => {
97
+ const numValue = value === '' || value === undefined ? undefined : Number(value);
98
+ field.onChange(numValue);
99
+ }}
94
100
  placeholder={t('enterAmount', 'Enter amount')}
101
+ value={field.value ?? ''}
95
102
  />
96
103
  )}
97
104
  />
@@ -100,32 +107,34 @@ const PaymentForm: React.FC<PaymentFormProps> = ({
100
107
  control={control}
101
108
  render={({ field }) => (
102
109
  <TextInput
103
- id="paymentReferenceCode"
104
- {...field}
110
+ id={`paymentReferenceCode-${fieldItem.id}`}
105
111
  labelText={t('referenceNumber', 'Reference number')}
106
- placeholder={t('enterReferenceNumber', 'Enter ref. number')}
112
+ name={field.name}
113
+ onBlur={field.onBlur}
114
+ onChange={field.onChange}
115
+ placeholder={t('enterReferenceNumber', 'Enter reference number')}
107
116
  type="text"
117
+ value={field.value ?? ''}
108
118
  />
109
119
  )}
110
120
  />
111
121
  <div className={styles.removeButtonContainer}>
112
- <TrashCan
113
- onClick={() => handleRemovePaymentMode(index)}
114
- className={styles.removeButton}
115
- size={20}
116
- data-testid="trash-can-icon"
117
- />
122
+ <IconButton
123
+ kind="danger--tertiary"
124
+ label={t('removePaymentMethod', 'Remove payment method')}
125
+ onClick={() => handleRemovePaymentMode(index)}>
126
+ <TrashCan />
127
+ </IconButton>
118
128
  </div>
119
129
  </div>
120
130
  ))}
121
131
  <Button
122
- disabled={disablePayment || (!isSingleLineItem && !isSingleLineItemSelected)}
123
- size="md"
132
+ disabled={disablePayment}
124
133
  onClick={handleAppendPaymentMode}
125
134
  className={styles.paymentButtons}
126
135
  renderIcon={(props) => <Add size={24} {...props} />}
127
- iconDescription="Add">
128
- {t('addPaymentOptions', 'Add payment option')}
136
+ iconDescription={t('add', 'Add')}>
137
+ {t('addPaymentMethod', 'Add payment method')}
129
138
  </Button>
130
139
  </div>
131
140
  );
@@ -3,7 +3,8 @@
3
3
  @use '@carbon/type';
4
4
 
5
5
  .container {
6
- margin: 1rem;
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: repeat(4, minmax(auto, 1fr));
23
+ grid-template-columns: 1fr 1fr 1fr auto;
23
24
  align-items: flex-start;
24
- column-gap: 1rem;
25
- margin: 0.625rem 0;
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 { render, screen, fireEvent, waitFor } from '@testing-library/react';
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 { usePaymentModes } = jest.requireMock('../payment.resource');
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
- usePaymentModes.mockReturnValue({
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
- expect(screen.getByTestId('number-input-skeleton')).toBeInTheDocument();
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
- usePaymentModes.mockReturnValue({
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
- usePaymentModes.mockReturnValue({
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 option button is clicked', () => {
100
- usePaymentModes.mockReturnValue({
101
- paymentModes: [{ uuid: '1', name: 'Credit Card' }],
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
- const addButton = screen.getByText(/add payment option/i);
119
- fireEvent.click(addButton);
120
- const paymentMethodElements = screen.getAllByLabelText(/payment method/i);
121
- expect(paymentMethodElements).toHaveLength(2);
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
- usePaymentModes.mockReturnValue({
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 option/i)).toBeDisabled();
128
+ expect(screen.getByText(/add payment method/i)).toBeDisabled();
144
129
  });
145
130
 
146
- test('should remove payment field when trash can icon is clicked', async () => {
147
- usePaymentModes.mockReturnValue({
148
- paymentModes: [{ uuid: '1', name: 'Credit Card' }],
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
- fireEvent.click(screen.getByText(/add payment option/i));
146
+ await user.click(screen.getByText(/add payment method/i));
166
147
 
167
- const trashCanIcon = screen.getByTestId('trash-can-icon');
168
- fireEvent.click(trashCanIcon);
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 PaymentHistory from './payment-history.component';
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
- (useConfig as jest.Mock).mockReturnValue({
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('2023-09-01')).toBeInTheDocument();
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('2023-09-05')).toBeInTheDocument();
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('2023-09-01')).toBeInTheDocument();
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
  });