@openmrs/esm-billing-app 1.0.2-pre.76 → 1.0.2-pre.761

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 (198) hide show
  1. package/README.md +55 -9
  2. package/__mocks__/bills.mock.ts +12 -0
  3. package/__mocks__/react-i18next.js +6 -5
  4. package/dist/1119.js +1 -1
  5. package/dist/1146.js +1 -2
  6. package/dist/1146.js.map +1 -1
  7. package/dist/1197.js +1 -1
  8. package/dist/1856.js +1 -0
  9. package/dist/1856.js.map +1 -0
  10. package/dist/2146.js +1 -1
  11. package/dist/2177.js +2 -0
  12. package/dist/2177.js.LICENSE.txt +9 -0
  13. package/dist/2177.js.map +1 -0
  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/3041.js +1 -0
  18. package/dist/3041.js.map +1 -0
  19. package/dist/3099.js +1 -1
  20. package/dist/3584.js +1 -1
  21. package/dist/4055.js +1 -1
  22. package/dist/4132.js +1 -1
  23. package/dist/4225.js +1 -0
  24. package/dist/4225.js.map +1 -0
  25. package/dist/4300.js +1 -1
  26. package/dist/4335.js +1 -1
  27. package/dist/4618.js +1 -1
  28. package/dist/4652.js +1 -1
  29. package/dist/4724.js +1 -0
  30. package/dist/4724.js.map +1 -0
  31. package/dist/4739.js +1 -1
  32. package/dist/4739.js.map +1 -1
  33. package/dist/4944.js +1 -1
  34. package/dist/5173.js +1 -1
  35. package/dist/5241.js +1 -1
  36. package/dist/5422.js +1 -0
  37. package/dist/5422.js.map +1 -0
  38. package/dist/5442.js +1 -1
  39. package/dist/5661.js +1 -1
  40. package/dist/6022.js +1 -1
  41. package/dist/6468.js +1 -1
  42. package/dist/6540.js +1 -1
  43. package/dist/6540.js.map +1 -1
  44. package/dist/6606.js +1 -0
  45. package/dist/6606.js.map +1 -0
  46. package/dist/6679.js +1 -1
  47. package/dist/6840.js +1 -1
  48. package/dist/6859.js +1 -1
  49. package/dist/7097.js +1 -1
  50. package/dist/7159.js +1 -1
  51. package/dist/723.js +1 -1
  52. package/dist/7452.js +2 -0
  53. package/dist/7452.js.map +1 -0
  54. package/dist/7617.js +1 -1
  55. package/dist/795.js +1 -1
  56. package/dist/8163.js +1 -1
  57. package/dist/8349.js +1 -1
  58. package/dist/8618.js +1 -1
  59. package/dist/890.js +1 -1
  60. package/dist/8930.js +2 -0
  61. package/dist/{6525.js.LICENSE.txt → 8930.js.LICENSE.txt} +16 -4
  62. package/dist/8930.js.map +1 -0
  63. package/dist/9214.js +1 -1
  64. package/dist/942.js +1 -0
  65. package/dist/942.js.map +1 -0
  66. package/dist/9538.js +1 -1
  67. package/dist/9569.js +1 -1
  68. package/dist/961.js +1 -1
  69. package/dist/961.js.map +1 -1
  70. package/dist/986.js +1 -1
  71. package/dist/9879.js +1 -1
  72. package/dist/9895.js +1 -1
  73. package/dist/9900.js +1 -1
  74. package/dist/9913.js +1 -1
  75. package/dist/main.js +1 -1
  76. package/dist/main.js.map +1 -1
  77. package/dist/openmrs-esm-billing-app.js +1 -1
  78. package/dist/openmrs-esm-billing-app.js.buildmanifest.json +368 -262
  79. package/dist/openmrs-esm-billing-app.js.map +1 -1
  80. package/dist/routes.json +1 -1
  81. package/e2e/README.md +19 -18
  82. package/e2e/specs/sample-test.spec.ts +0 -1
  83. package/package.json +10 -10
  84. package/src/bill-history/bill-history.component.tsx +17 -25
  85. package/src/bill-history/bill-history.scss +4 -94
  86. package/src/bill-history/bill-history.test.tsx +37 -78
  87. package/src/bill-item-actions/bill-item-actions.scss +0 -4
  88. package/src/bill-item-actions/{edit-bill-item.component.tsx → edit-bill-item.modal.tsx} +57 -56
  89. package/src/bill-item-actions/edit-bill-item.test.tsx +22 -25
  90. package/src/billable-services/bill-waiver/bill-selection.component.tsx +2 -2
  91. package/src/billable-services/bill-waiver/patient-bills.component.tsx +3 -3
  92. package/src/billable-services/billable-service.resource.ts +17 -9
  93. package/src/billable-services/billable-services-home.component.tsx +1 -1
  94. package/src/billable-services/billable-services.component.tsx +142 -145
  95. package/src/billable-services/billable-services.scss +3 -0
  96. package/src/billable-services/billable-services.test.tsx +2 -45
  97. package/src/billable-services/cash-point/add-cash-point.modal.tsx +168 -0
  98. package/src/billable-services/cash-point/cash-point-configuration.component.tsx +18 -192
  99. package/src/billable-services/cash-point/cash-point-configuration.scss +1 -5
  100. package/src/billable-services/create-edit/add-billable-service.component.tsx +345 -298
  101. package/src/billable-services/create-edit/add-billable-service.scss +5 -6
  102. package/src/billable-services/create-edit/add-billable-service.test.tsx +37 -36
  103. package/src/billable-services/create-edit/edit-billable-service.modal.tsx +51 -0
  104. package/src/billable-services/payment-modes/add-payment-mode.modal.tsx +121 -0
  105. package/src/billable-services/payment-modes/delete-payment-mode.modal.tsx +72 -0
  106. package/src/billable-services/payment-modes/payment-modes-config.component.tsx +125 -0
  107. package/src/billable-services/{payyment-modes → payment-modes}/payment-modes-config.scss +5 -4
  108. package/src/billing-form/billing-checkin-form.component.tsx +2 -3
  109. package/src/billing-form/billing-checkin-form.test.tsx +97 -24
  110. package/src/billing-form/billing-form.component.tsx +214 -269
  111. package/src/billing-form/billing-form.scss +143 -0
  112. package/src/billing.resource.ts +16 -19
  113. package/src/bills-table/bills-table.test.tsx +97 -53
  114. package/src/config-schema.ts +52 -18
  115. package/src/dashboard.meta.ts +4 -2
  116. package/src/helpers/functions.ts +5 -4
  117. package/src/index.ts +17 -6
  118. package/src/invoice/invoice-table.component.tsx +24 -54
  119. package/src/invoice/invoice-table.scss +1 -5
  120. package/src/invoice/invoice-table.test.tsx +21 -47
  121. package/src/invoice/invoice.component.tsx +36 -29
  122. package/src/invoice/invoice.scss +7 -4
  123. package/src/invoice/invoice.test.tsx +22 -48
  124. package/src/invoice/payments/payment-form/payment-form.component.tsx +29 -29
  125. package/src/invoice/payments/payment-form/payment-form.scss +5 -6
  126. package/src/invoice/payments/payment-form/payment-form.test.tsx +215 -67
  127. package/src/invoice/payments/payment-history/payment-history.component.tsx +6 -4
  128. package/src/invoice/payments/payment-history/payment-history.test.tsx +9 -14
  129. package/src/invoice/payments/payments.component.tsx +53 -65
  130. package/src/invoice/payments/payments.test.tsx +282 -0
  131. package/src/invoice/payments/utils.ts +5 -23
  132. package/src/invoice/printable-invoice/print-receipt.component.tsx +3 -2
  133. package/src/invoice/printable-invoice/print-receipt.test.tsx +14 -25
  134. package/src/invoice/printable-invoice/printable-footer.component.tsx +2 -2
  135. package/src/invoice/printable-invoice/printable-footer.test.tsx +4 -13
  136. package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +12 -11
  137. package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +16 -14
  138. package/src/invoice/printable-invoice/printable-invoice.component.tsx +19 -33
  139. package/src/metrics-cards/metrics-cards.test.tsx +18 -5
  140. package/src/modal/require-payment-modal.test.tsx +27 -22
  141. package/src/modal/{require-payment-modal.component.tsx → require-payment.modal.tsx} +17 -18
  142. package/src/routes.json +22 -2
  143. package/src/types/index.ts +26 -17
  144. package/translations/am.json +60 -18
  145. package/translations/ar.json +60 -18
  146. package/translations/ar_SY.json +60 -18
  147. package/translations/bn.json +65 -23
  148. package/translations/de.json +60 -18
  149. package/translations/en.json +60 -18
  150. package/translations/en_US.json +60 -18
  151. package/translations/es.json +60 -18
  152. package/translations/es_MX.json +60 -18
  153. package/translations/fr.json +73 -31
  154. package/translations/he.json +60 -18
  155. package/translations/hi.json +60 -18
  156. package/translations/hi_IN.json +60 -18
  157. package/translations/id.json +61 -19
  158. package/translations/it.json +96 -54
  159. package/translations/ka.json +61 -19
  160. package/translations/km.json +60 -18
  161. package/translations/ku.json +60 -18
  162. package/translations/ky.json +60 -18
  163. package/translations/lg.json +60 -18
  164. package/translations/ne.json +60 -18
  165. package/translations/pl.json +60 -18
  166. package/translations/pt.json +60 -18
  167. package/translations/pt_BR.json +60 -18
  168. package/translations/qu.json +60 -18
  169. package/translations/ro_RO.json +206 -164
  170. package/translations/ru_RU.json +60 -18
  171. package/translations/si.json +60 -18
  172. package/translations/sw.json +60 -18
  173. package/translations/sw_KE.json +60 -18
  174. package/translations/tr.json +60 -18
  175. package/translations/tr_TR.json +60 -18
  176. package/translations/uk.json +60 -18
  177. package/translations/uz.json +60 -18
  178. package/translations/uz@Latn.json +60 -18
  179. package/translations/uz_UZ.json +60 -18
  180. package/translations/vi.json +60 -18
  181. package/translations/zh.json +60 -18
  182. package/translations/zh_CN.json +117 -75
  183. package/dist/1146.js.LICENSE.txt +0 -21
  184. package/dist/2352.js +0 -1
  185. package/dist/2352.js.map +0 -1
  186. package/dist/246.js +0 -1
  187. package/dist/246.js.map +0 -1
  188. package/dist/6525.js +0 -2
  189. package/dist/6525.js.map +0 -1
  190. package/dist/8556.js +0 -2
  191. package/dist/8556.js.map +0 -1
  192. package/dist/8638.js +0 -1
  193. package/dist/8638.js.map +0 -1
  194. package/dist/9968.js +0 -1
  195. package/dist/9968.js.map +0 -1
  196. package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +0 -280
  197. package/src/invoice/payments/payments.component.test.tsx +0 -121
  198. /package/dist/{8556.js.LICENSE.txt → 7452.js.LICENSE.txt} +0 -0
@@ -1,4 +1,4 @@
1
- import React, { useMemo, useState, useEffect, useCallback } from 'react';
1
+ import React, { useMemo, useState, useCallback } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import fuzzy from 'fuzzy';
4
4
  import {
@@ -13,42 +13,38 @@ import {
13
13
  TableHead,
14
14
  TableHeader,
15
15
  TableRow,
16
- TableSelectRow,
17
16
  TableToolbarSearch,
18
17
  Tile,
19
18
  type DataTableRow,
20
19
  } from '@carbon/react';
21
20
  import { Edit } from '@carbon/react/icons';
22
- import { isDesktop, showModal, useConfig, useDebounce, useLayoutType } from '@openmrs/esm-framework';
21
+ import {
22
+ isDesktop,
23
+ showModal,
24
+ useConfig,
25
+ useDebounce,
26
+ useLayoutType,
27
+ getCoreTranslation,
28
+ } from '@openmrs/esm-framework';
23
29
  import { type LineItem, type MappedBill } from '../types';
24
30
  import { convertToCurrency } from '../helpers';
31
+ import type { BillingConfig } from '../config-schema';
25
32
  import styles from './invoice-table.scss';
26
33
 
27
34
  type InvoiceTableProps = {
28
35
  bill: MappedBill;
29
- isSelectable?: boolean;
30
36
  isLoadingBill?: boolean;
31
- onSelectItem?: (selectedLineItems: LineItem[]) => void;
32
37
  };
33
38
 
34
- const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true, isLoadingBill, onSelectItem }) => {
39
+ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isLoadingBill }) => {
35
40
  const { t } = useTranslation();
36
- const { defaultCurrency, showEditBillButton } = useConfig();
41
+ const { defaultCurrency, showEditBillButton } = useConfig<BillingConfig>();
37
42
  const layout = useLayoutType();
38
43
  const lineItems = useMemo(() => bill?.lineItems ?? [], [bill?.lineItems]);
39
- const paidLineItems = useMemo(() => lineItems?.filter((item) => item.paymentStatus === 'PAID') ?? [], [lineItems]);
40
44
  const responsiveSize = isDesktop(layout) ? 'sm' : 'lg';
41
-
42
- const [selectedLineItems, setSelectedLineItems] = useState(paidLineItems ?? []);
43
45
  const [searchTerm, setSearchTerm] = useState('');
44
46
  const debouncedSearchTerm = useDebounce(searchTerm);
45
47
 
46
- useEffect(() => {
47
- if (onSelectItem) {
48
- onSelectItem(selectedLineItems);
49
- }
50
- }, [selectedLineItems, onSelectItem]);
51
-
52
48
  const filteredLineItems = useMemo(() => {
53
49
  if (!debouncedSearchTerm) {
54
50
  return lineItems;
@@ -65,14 +61,14 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
65
61
  }, [debouncedSearchTerm, lineItems]);
66
62
 
67
63
  const tableHeaders = [
68
- { header: 'No', key: 'no', width: 7 }, // Width as a percentage
69
- { header: 'Bill item', key: 'billItem', width: 25 },
70
- { header: 'Bill code', key: 'billCode', width: 20 },
71
- { header: 'Status', key: 'status', width: 25 },
72
- { header: 'Quantity', key: 'quantity', width: 15 },
73
- { header: 'Price', key: 'price', width: 24 },
74
- { header: 'Total', key: 'total', width: 15 },
75
- { header: t('actions', 'Actions'), key: 'actionButton' },
64
+ { header: t('number', 'No'), key: 'no', width: 7 }, // Width as a percentage
65
+ { header: t('billItem', 'Bill item'), key: 'billItem', width: 25 },
66
+ { header: t('billCode', 'Bill code'), key: 'billCode', width: 20 },
67
+ { header: t('status', 'Status'), key: 'status', width: 25 },
68
+ { header: t('quantity', 'Quantity'), key: 'quantity', width: 15 },
69
+ { header: t('price', 'Price'), key: 'price', width: 24 },
70
+ { header: t('total', 'Total'), key: 'total', width: 15 },
71
+ { header: getCoreTranslation('actions'), key: 'actionButton' },
76
72
  ];
77
73
 
78
74
  const handleSelectBillItem = useCallback(
@@ -135,23 +131,10 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
135
131
  );
136
132
  }
137
133
 
138
- const handleRowSelection = (row: typeof DataTableRow, checked: boolean) => {
139
- const matchingRow = filteredLineItems.find((item) => item.uuid === row.id);
140
- let newSelectedLineItems;
141
-
142
- if (checked) {
143
- newSelectedLineItems = [...selectedLineItems, matchingRow];
144
- } else {
145
- newSelectedLineItems = selectedLineItems.filter((item) => item.uuid !== row.id);
146
- }
147
- setSelectedLineItems(newSelectedLineItems);
148
- onSelectItem(newSelectedLineItems);
149
- };
150
-
151
134
  return (
152
- <div className={styles.invoiceContainer}>
153
- <DataTable headers={tableHeaders} isSortable rows={tableRows} size={responsiveSize} useZebraStyles>
154
- {({ rows, headers, getRowProps, getSelectionProps, getTableProps, getToolbarProps }) => (
135
+ <>
136
+ <DataTable headers={tableHeaders} rows={tableRows} size={responsiveSize} useZebraStyles>
137
+ {({ rows, headers, getRowProps, getTableProps }) => (
155
138
  <TableContainer
156
139
  description={
157
140
  <span className={styles.tableDescription}>
@@ -172,7 +155,6 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
172
155
  className={`${styles.invoiceTable} billingTable`}>
173
156
  <TableHead>
174
157
  <TableRow>
175
- {rows.length > 1 && isSelectable ? <TableHeader /> : null}
176
158
  {headers.map((header) => (
177
159
  <TableHeader key={header.key}>{header.header}</TableHeader>
178
160
  ))}
@@ -186,18 +168,6 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
186
168
  {...getRowProps({
187
169
  row,
188
170
  })}>
189
- {rows.length > 1 && isSelectable && (
190
- <TableSelectRow
191
- aria-label="Select row"
192
- {...getSelectionProps({ row })}
193
- disabled={tableRows[index].status === 'PAID'}
194
- onChange={(checked: boolean) => handleRowSelection(row, checked)}
195
- checked={
196
- tableRows[index].status === 'PAID' ||
197
- Boolean(selectedLineItems?.find((item) => item?.uuid === row?.id))
198
- }
199
- />
200
- )}
201
171
  {row.cells.map((cell) => (
202
172
  <TableCell key={cell.id}>{cell.value}</TableCell>
203
173
  ))}
@@ -221,7 +191,7 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
221
191
  </Layer>
222
192
  </div>
223
193
  )}
224
- </div>
194
+ </>
225
195
  );
226
196
  };
227
197
 
@@ -29,11 +29,7 @@
29
29
 
30
30
  .headerContainer {
31
31
  background-color: colors.$gray-10;
32
- }
33
-
34
- .invoiceContainer {
35
- border: 1px solid $ui-03;
36
- }
32
+ }c
37
33
 
38
34
  .searchbox {
39
35
  input:focus {
@@ -1,43 +1,24 @@
1
1
  import React from 'react';
2
- import { useTranslation } from 'react-i18next';
3
- import { render, screen, fireEvent, act } from '@testing-library/react';
4
- import { showModal } from '@openmrs/esm-framework';
5
- import InvoiceTable from './invoice-table.component';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { render, screen, act } from '@testing-library/react';
4
+ import { getDefaultsFromConfigSchema, showModal, useConfig } from '@openmrs/esm-framework';
6
5
  import { type MappedBill } from '../types';
6
+ import { configSchema, type BillingConfig } from '../config-schema';
7
+ import InvoiceTable from './invoice-table.component';
7
8
 
8
- // Mocking dependencies
9
- jest.mock('react-i18next', () => ({
10
- useTranslation: jest.fn(() => ({
11
- t: jest.fn((key, fallback) => fallback || key),
12
- i18n: { language: 'en' },
13
- })),
14
- }));
15
-
16
- jest.mock('@openmrs/esm-framework', () => ({
17
- showModal: jest.fn(),
18
- useConfig: jest.fn(() => ({
19
- defaultCurrency: 'USD',
20
- showEditBillButton: true,
21
- })),
22
- useDebounce: jest.fn((value) => value),
23
- useLayoutType: jest.fn(() => 'desktop'),
24
- isDesktop: jest.fn(() => true),
25
- }));
9
+ const mockUseConfig = jest.mocked(useConfig<BillingConfig>);
26
10
 
27
11
  jest.mock('../helpers', () => ({
28
12
  convertToCurrency: jest.fn((price) => `USD ${price}`),
29
13
  }));
30
14
 
31
15
  describe('InvoiceTable', () => {
32
- const mockT = jest.fn((key) => key);
33
-
34
16
  beforeEach(() => {
35
- (useTranslation as jest.Mock).mockReturnValue({ t: mockT, i18n: { language: 'en' } });
36
- jest.useFakeTimers();
37
- });
38
-
39
- afterEach(() => {
40
- jest.useRealTimers();
17
+ mockUseConfig.mockReturnValue({
18
+ ...getDefaultsFromConfigSchema(configSchema),
19
+ defaultCurrency: 'USD',
20
+ showEditBillButton: true,
21
+ });
41
22
  });
42
23
 
43
24
  const bill: MappedBill = {
@@ -103,11 +84,12 @@ describe('InvoiceTable', () => {
103
84
  expect(screen.getByTestId('receipt-number-0')).toHaveTextContent('12345');
104
85
  });
105
86
 
106
- it('renders the edit button and calls showModal when clicked', () => {
87
+ it('renders the edit button and calls showModal when clicked', async () => {
88
+ const user = userEvent.setup();
107
89
  render(<InvoiceTable bill={bill} />);
108
90
 
109
91
  const editButton = screen.getByTestId('edit-button-1');
110
- fireEvent.click(editButton);
92
+ await user.click(editButton);
111
93
  expect(showModal).toHaveBeenCalledWith('edit-bill-line-item-dialog', expect.anything());
112
94
  });
113
95
 
@@ -117,31 +99,23 @@ describe('InvoiceTable', () => {
117
99
  expect(screen.getByTestId('loader')).toBeInTheDocument();
118
100
  });
119
101
 
120
- it('filters line items based on the search term', () => {
102
+ it('filters line items based on the search term', async () => {
103
+ const user = userEvent.setup();
121
104
  render(<InvoiceTable bill={bill} />);
122
- const searchInput = screen.getByPlaceholderText('searchThisTable'); //
105
+ const searchInput = screen.getByPlaceholderText(/search this table/i);
123
106
 
124
- fireEvent.change(searchInput, { target: { value: 'Item 2' } });
107
+ await user.type(searchInput, 'Item 2');
125
108
 
126
109
  expect(screen.queryByText('Item 1')).not.toBeInTheDocument();
127
110
  expect(screen.getByText('Item 2')).toBeInTheDocument();
128
111
  });
129
112
 
130
- it('correctly handles row selection', () => {
131
- const onSelectItem = jest.fn();
132
- render(<InvoiceTable bill={bill} onSelectItem={onSelectItem} />);
133
-
134
- const checkboxes = screen.getAllByLabelText('Select row');
135
- fireEvent.click(checkboxes[0]);
136
-
137
- expect(onSelectItem).toHaveBeenCalledWith([bill.lineItems[0]]);
138
- });
139
-
140
- it('resets isRedirecting to false after timeout', () => {
113
+ it('resets isRedirecting to false after timeout', async () => {
114
+ const user = userEvent.setup();
141
115
  render(<InvoiceTable bill={bill} />);
142
116
 
143
117
  const button = screen.getByTestId('edit-button-1');
144
- fireEvent.click(button);
118
+ await user.click(button);
145
119
  act(() => {
146
120
  jest.advanceTimersByTime(1000);
147
121
  });
@@ -4,15 +4,15 @@ import { Printer } from '@carbon/react/icons';
4
4
  import { useParams } from 'react-router-dom';
5
5
  import { useReactToPrint } from 'react-to-print';
6
6
  import { useTranslation } from 'react-i18next';
7
- import { ExtensionSlot, useConfig, usePatient } from '@openmrs/esm-framework';
8
- import { ErrorState } from '@openmrs/esm-patient-common-lib';
9
- import { convertToCurrency } from '../helpers';
10
- import { type LineItem } from '../types';
11
- import { useBill } from '../billing.resource';
7
+ import { ExtensionSlot, showSnackbar, useConfig, usePatient } from '@openmrs/esm-framework';
12
8
  import InvoiceTable from './invoice-table.component';
13
9
  import Payments from './payments/payments.component';
14
10
  import PrintReceipt from './printable-invoice/print-receipt.component';
15
11
  import PrintableInvoice from './printable-invoice/printable-invoice.component';
12
+ import { ErrorState } from '@openmrs/esm-patient-common-lib';
13
+ import { convertToCurrency } from '../helpers';
14
+ import { useBill, useDefaultFacility } from '../billing.resource';
15
+ import type { BillingConfig } from '../config-schema';
16
16
  import styles from './invoice.scss';
17
17
 
18
18
  interface InvoiceDetailsProps {
@@ -22,25 +22,20 @@ interface InvoiceDetailsProps {
22
22
 
23
23
  const Invoice: React.FC = () => {
24
24
  const { t } = useTranslation();
25
+ const { data } = useDefaultFacility();
25
26
  const { billUuid, patientUuid } = useParams();
26
27
  const { patient, isLoading: isLoadingPatient } = usePatient(patientUuid);
27
28
  const { bill, isLoading: isLoadingBill, error, mutate } = useBill(billUuid);
28
29
  const [isPrinting, setIsPrinting] = useState(false);
29
- const [selectedLineItems, setSelectedLineItems] = useState<LineItem[]>([]);
30
30
  const componentRef = useRef<HTMLDivElement>(null);
31
31
  const onBeforeGetContentResolve = useRef<(() => void) | null>(null);
32
- const { defaultCurrency } = useConfig();
33
- const handleSelectItem = (lineItems: LineItem[]) => {
34
- setSelectedLineItems(lineItems);
35
- };
32
+ const { defaultCurrency } = useConfig<BillingConfig>();
36
33
 
37
34
  const handleAfterPrint = useCallback(() => {
38
35
  onBeforeGetContentResolve.current = null;
39
36
  setIsPrinting(false);
40
37
  }, []);
41
38
 
42
- const reactToPrintContent = useCallback(() => componentRef.current, []);
43
-
44
39
  const handleOnBeforeGetContent = useCallback(() => {
45
40
  return new Promise<void>((resolve) => {
46
41
  if (patient && bill) {
@@ -51,11 +46,17 @@ const Invoice: React.FC = () => {
51
46
  }, [bill, patient]);
52
47
 
53
48
  const handlePrint = useReactToPrint({
54
- content: reactToPrintContent,
49
+ contentRef: componentRef,
55
50
  documentTitle: `Invoice ${bill?.receiptNumber} - ${patient?.name?.[0]?.given?.join(' ')} ${patient?.name?.[0].family}`,
56
- onBeforeGetContent: handleOnBeforeGetContent,
51
+ onBeforePrint: handleOnBeforeGetContent,
57
52
  onAfterPrint: handleAfterPrint,
58
- removeAfterPrint: true,
53
+ preserveAfterPrint: false,
54
+ onPrintError: (_, error) =>
55
+ showSnackbar({
56
+ title: t('errorPrintingInvoice', 'Error printing invoice'),
57
+ kind: 'error',
58
+ subtitle: error.message,
59
+ }),
59
60
  });
60
61
 
61
62
  useEffect(() => {
@@ -64,11 +65,14 @@ const Invoice: React.FC = () => {
64
65
  }
65
66
  }, [isPrinting]);
66
67
 
67
- useEffect(() => {
68
- const unPaidLineItems = bill?.lineItems?.filter((item) => item.paymentStatus === 'PENDING') ?? [];
69
- setSelectedLineItems(unPaidLineItems);
70
- }, [bill?.lineItems]);
71
-
68
+ // Do not remove this comment. Adds the translation keys for the invoice details
69
+ /**
70
+ * t('totalAmount', 'Total Amount')
71
+ * t('amountTendered', 'Amount Tendered')
72
+ * t('invoiceNumber', 'Invoice Number')
73
+ * t('dateAndTime', 'Date And Time')
74
+ * t('invoiceStatus', 'Invoice Status')
75
+ */
72
76
  const invoiceDetails = {
73
77
  'Total Amount': convertToCurrency(bill?.totalAmount, defaultCurrency),
74
78
  'Amount Tendered': convertToCurrency(bill?.tenderedAmount, defaultCurrency),
@@ -77,14 +81,14 @@ const Invoice: React.FC = () => {
77
81
  'Invoice Status': bill?.status,
78
82
  };
79
83
 
80
- if (isLoadingPatient && isLoadingBill) {
84
+ if (isLoadingPatient || isLoadingBill) {
81
85
  return (
82
86
  <div className={styles.invoiceContainer}>
83
87
  <InlineLoading
84
88
  className={styles.loader}
85
89
  status="active"
86
90
  iconDescription="Loading"
87
- description="Loading patient header..."
91
+ description={t('loadingBillInfo', 'Loading bill information...')}
88
92
  />
89
93
  </div>
90
94
  );
@@ -109,7 +113,7 @@ const Invoice: React.FC = () => {
109
113
  </section>
110
114
  <div>
111
115
  <Button
112
- disabled={isPrinting}
116
+ disabled={isPrinting || isLoadingPatient || isLoadingBill}
113
117
  onClick={handlePrint}
114
118
  renderIcon={(props) => <Printer size={24} {...props} />}
115
119
  iconDescription="Print bill"
@@ -120,20 +124,23 @@ const Invoice: React.FC = () => {
120
124
  </div>
121
125
  </div>
122
126
 
123
- <InvoiceTable bill={bill} isLoadingBill={isLoadingBill} onSelectItem={handleSelectItem} />
124
- <Payments bill={bill} mutate={mutate} selectedLineItems={selectedLineItems} />
127
+ <InvoiceTable bill={bill} isLoadingBill={isLoadingBill} />
128
+ <Payments bill={bill} mutate={mutate} />
125
129
 
126
- <div className={styles.printContainer} ref={componentRef}>
127
- {isPrinting && <PrintableInvoice bill={bill} patient={patient} isLoading={isLoadingPatient} />}
128
- </div>
130
+ {bill && patient && (
131
+ <div className={styles.printContainer}>
132
+ <PrintableInvoice bill={bill} patient={patient} defaultFacility={data} componentRef={componentRef} />
133
+ </div>
134
+ )}
129
135
  </div>
130
136
  );
131
137
  };
132
138
 
133
139
  function InvoiceDetails({ label, value }: InvoiceDetailsProps) {
140
+ const { t } = useTranslation();
134
141
  return (
135
142
  <div>
136
- <h1 className={styles.label}>{label}</h1>
143
+ <h1 className={styles.label}>{t(label)}</h1>
137
144
  <span className={styles.value}>{value}</span>
138
145
  </div>
139
146
  );
@@ -78,16 +78,19 @@
78
78
  color: colors.$cool-gray-90;
79
79
  }
80
80
 
81
- @media screen {
82
- .printContainer {
83
- background-color: colors.$white;
84
- display: none;
81
+ .printContainer {
82
+ background-color: colors.$white;
83
+ display: none;
84
+
85
+ @media print {
86
+ display: block !important;
85
87
  }
86
88
  }
87
89
 
88
90
  @media print {
89
91
  html,
90
92
  body {
93
+ display: block !important;
91
94
  background-color: colors.$white !important;
92
95
  }
93
96
  }
@@ -1,24 +1,21 @@
1
1
  import React from 'react';
2
- import { screen, render } from '@testing-library/react';
3
2
  import userEvent from '@testing-library/user-event';
3
+ import { screen, render } from '@testing-library/react';
4
4
  import { useReactToPrint } from 'react-to-print';
5
- import { mockBill } from '../../__mocks__/bills.mock';
5
+ import { getDefaultsFromConfigSchema, useConfig, usePatient } from '@openmrs/esm-framework';
6
+ import { configSchema, type BillingConfig } from '../config-schema';
7
+ import { mockBill, mockPatient } from '../../__mocks__/bills.mock';
6
8
  import { useBill, processBillPayment } from '../billing.resource';
7
9
  import { usePaymentModes } from './payments/payment.resource';
8
10
  import Invoice from './invoice.component';
9
11
 
12
+ const mockUseConfig = jest.mocked(useConfig<BillingConfig>);
13
+
10
14
  // Mock convertToCurrency
11
15
  jest.mock('../helpers/functions', () => ({
12
16
  convertToCurrency: jest.fn((amount) => `USD ${amount}`),
13
17
  }));
14
18
 
15
- // Mock i18next
16
- jest.mock('react-i18next', () => ({
17
- useTranslation: () => ({
18
- t: (key: string) => key,
19
- }),
20
- }));
21
-
22
19
  // Set window.i18next
23
20
  window.i18next = {
24
21
  language: 'en',
@@ -78,40 +75,9 @@ jest.mock('react-to-print', () => ({
78
75
  useReactToPrint: jest.fn(),
79
76
  }));
80
77
 
81
- // Mock OpenMRS framework
82
- jest.mock('@openmrs/esm-framework', () => ({
83
- showSnackbar: jest.fn(),
84
- useLayoutType: jest.fn(() => 'desktop'),
85
- isDesktop: jest.fn(() => true),
86
- useConfig: jest.fn(() => ({
87
- defaultCurrency: 'USD',
88
- })),
89
- formatDate: jest.fn((date) => date?.toString() ?? ''),
90
- ExtensionSlot: jest.fn(({ children }) => <div data-testid="extension-slot">{children}</div>),
91
- usePatient: jest.fn().mockReturnValue({
92
- patient: {
93
- id: 'b2fcf02b-7ee3-4d16-a48f-576be2b103aa',
94
- name: [{ given: ['John'], family: 'Doe' }],
95
- },
96
- patientUuid: 'b2fcf02b-7ee3-4d16-a48f-576be2b103aa',
97
- isLoading: false,
98
- error: null,
99
- }),
100
- createGlobalStore: jest.fn(),
101
- getGlobalStore: jest.fn(() => ({
102
- subscribe: jest.fn(),
103
- getState: jest.fn(),
104
- setState: jest.fn(),
105
- })),
106
- }));
107
-
108
- // Mock patient common lib
109
- jest.mock('@openmrs/esm-patient-common-lib', () => ({
110
- ErrorState: jest.fn(({ error }) => <div data-testid="error-state">Error: {error?.message || error}</div>),
111
- }));
112
-
113
78
  describe('Invoice', () => {
114
79
  const mockedBill = useBill as jest.Mock;
80
+ const mockedPatient = usePatient as jest.Mock;
115
81
  const mockedProcessBillPayment = processBillPayment as jest.Mock;
116
82
  const mockedUsePaymentModes = usePaymentModes as jest.Mock;
117
83
  const mockedUseReactToPrint = useReactToPrint as jest.Mock;
@@ -144,6 +110,14 @@ describe('Invoice', () => {
144
110
  mutate: jest.fn(),
145
111
  });
146
112
 
113
+ mockedPatient.mockReturnValue({
114
+ patient: mockPatient,
115
+ isLoading: false,
116
+ error: null,
117
+ isValidating: false,
118
+ mutate: jest.fn(),
119
+ });
120
+
147
121
  mockedUsePaymentModes.mockReturnValue({
148
122
  paymentModes: [
149
123
  { uuid: 'cash-uuid', name: 'Cash', description: 'Cash Method', retired: false },
@@ -154,15 +128,13 @@ describe('Invoice', () => {
154
128
  mutate: jest.fn(),
155
129
  });
156
130
 
131
+ mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(configSchema), defaultCurrency: 'USD' });
132
+
157
133
  // Setup print handler mock
158
134
  const printHandler = jest.fn();
159
135
  mockedUseReactToPrint.mockReturnValue(printHandler);
160
136
  });
161
137
 
162
- afterEach(() => {
163
- jest.clearAllMocks();
164
- });
165
-
166
138
  it('should render error state correctly', () => {
167
139
  mockedBill.mockReturnValue({
168
140
  bill: null,
@@ -173,8 +145,8 @@ describe('Invoice', () => {
173
145
  });
174
146
 
175
147
  render(<Invoice />);
176
- expect(screen.getByTestId('error-state')).toBeInTheDocument();
177
- expect(screen.getByText(/Test error/i)).toBeInTheDocument();
148
+ expect(screen.getByText(/Invoice error/i)).toBeInTheDocument();
149
+ expect(screen.getByText(/Error/)).toBeInTheDocument();
178
150
  });
179
151
 
180
152
  it('should render invoice details correctly', () => {
@@ -265,7 +237,9 @@ describe('Invoice', () => {
265
237
 
266
238
  it('should show patient information correctly', () => {
267
239
  render(<Invoice />);
268
- expect(screen.getByTestId('extension-slot')).toBeInTheDocument();
240
+ // Check that the invoice details are rendered
241
+ expect(screen.getByText('Invoice Number')).toBeInTheDocument();
242
+ expect(screen.getByText('RCPT-001')).toBeInTheDocument();
269
243
  });
270
244
 
271
245
  // Add more test cases as needed for specific features or edge cases