@openmrs/esm-billing-app 1.0.2-pre.84 → 1.0.2-pre.849

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/1856.js +1 -0
  10. package/dist/1856.js.map +1 -0
  11. package/dist/2146.js +1 -1
  12. package/dist/2177.js +2 -0
  13. package/dist/2177.js.LICENSE.txt +9 -0
  14. package/dist/2177.js.map +1 -0
  15. package/dist/2524.js +1 -0
  16. package/dist/2524.js.map +1 -0
  17. package/dist/2690.js +1 -1
  18. package/dist/3041.js +1 -0
  19. package/dist/3041.js.map +1 -0
  20. package/dist/3099.js +1 -1
  21. package/dist/3584.js +1 -1
  22. package/dist/3717.js +2 -0
  23. package/dist/3717.js.map +1 -0
  24. package/dist/4055.js +1 -1
  25. package/dist/4132.js +1 -1
  26. package/dist/4225.js +1 -0
  27. package/dist/4225.js.map +1 -0
  28. package/dist/4300.js +1 -1
  29. package/dist/4335.js +1 -1
  30. package/dist/4344.js +1 -0
  31. package/dist/4344.js.map +1 -0
  32. package/dist/4618.js +1 -1
  33. package/dist/4652.js +1 -1
  34. package/dist/4724.js +1 -0
  35. package/dist/4724.js.map +1 -0
  36. package/dist/4739.js +1 -1
  37. package/dist/4739.js.map +1 -1
  38. package/dist/4944.js +1 -1
  39. package/dist/5173.js +1 -1
  40. package/dist/5241.js +1 -1
  41. package/dist/5422.js +1 -0
  42. package/dist/5422.js.map +1 -0
  43. package/dist/5442.js +1 -1
  44. package/dist/5661.js +1 -1
  45. package/dist/6022.js +1 -1
  46. package/dist/6295.js +2 -0
  47. package/dist/{6525.js.LICENSE.txt → 6295.js.LICENSE.txt} +16 -4
  48. package/dist/6295.js.map +1 -0
  49. package/dist/6468.js +1 -1
  50. package/dist/6540.js +1 -1
  51. package/dist/6540.js.map +1 -1
  52. package/dist/6606.js +1 -0
  53. package/dist/6606.js.map +1 -0
  54. package/dist/6679.js +1 -1
  55. package/dist/6840.js +1 -1
  56. package/dist/6859.js +1 -1
  57. package/dist/7097.js +1 -1
  58. package/dist/7159.js +1 -1
  59. package/dist/723.js +1 -1
  60. package/dist/7617.js +1 -1
  61. package/dist/795.js +1 -1
  62. package/dist/8163.js +1 -1
  63. package/dist/8349.js +1 -1
  64. package/dist/8618.js +1 -1
  65. package/dist/890.js +1 -1
  66. package/dist/9214.js +1 -1
  67. package/dist/9538.js +1 -1
  68. package/dist/9569.js +1 -1
  69. package/dist/961.js +1 -1
  70. package/dist/961.js.map +1 -1
  71. package/dist/986.js +1 -1
  72. package/dist/9879.js +1 -1
  73. package/dist/9895.js +1 -1
  74. package/dist/9900.js +1 -1
  75. package/dist/9913.js +1 -1
  76. package/dist/main.js +1 -1
  77. package/dist/main.js.map +1 -1
  78. package/dist/openmrs-esm-billing-app.js +1 -1
  79. package/dist/openmrs-esm-billing-app.js.buildmanifest.json +388 -282
  80. package/dist/openmrs-esm-billing-app.js.map +1 -1
  81. package/dist/routes.json +1 -1
  82. package/e2e/README.md +19 -18
  83. package/e2e/core/test.ts +1 -1
  84. package/e2e/fixtures/api.ts +1 -1
  85. package/e2e/specs/sample-test.spec.ts +0 -1
  86. package/e2e/support/github/Dockerfile +1 -1
  87. package/package.json +13 -10
  88. package/src/bill-history/bill-history.component.tsx +20 -28
  89. package/src/bill-history/bill-history.scss +4 -94
  90. package/src/bill-history/bill-history.test.tsx +37 -78
  91. package/src/bill-item-actions/bill-item-actions.scss +21 -5
  92. package/src/bill-item-actions/edit-bill-item.modal.tsx +225 -0
  93. package/src/bill-item-actions/edit-bill-item.test.tsx +214 -40
  94. package/src/billable-services/bill-waiver/bill-selection.component.tsx +5 -5
  95. package/src/billable-services/bill-waiver/bill-waiver-form.component.tsx +28 -32
  96. package/src/billable-services/bill-waiver/patient-bills.component.tsx +7 -7
  97. package/src/billable-services/bill-waiver/utils.ts +13 -3
  98. package/src/billable-services/billable-service.resource.ts +28 -12
  99. package/src/billable-services/billable-services-home.component.tsx +4 -4
  100. package/src/billable-services/billable-services.component.tsx +149 -148
  101. package/src/billable-services/billable-services.scss +3 -0
  102. package/src/billable-services/billable-services.test.tsx +6 -49
  103. package/src/billable-services/cash-point/add-cash-point.modal.tsx +168 -0
  104. package/src/billable-services/cash-point/cash-point-configuration.component.tsx +19 -193
  105. package/src/billable-services/cash-point/cash-point-configuration.scss +1 -5
  106. package/src/billable-services/create-edit/add-billable-service.component.tsx +356 -300
  107. package/src/billable-services/create-edit/add-billable-service.scss +6 -65
  108. package/src/billable-services/create-edit/add-billable-service.test.tsx +167 -81
  109. package/src/billable-services/create-edit/edit-billable-service.modal.tsx +51 -0
  110. package/src/billable-services/dashboard/service-metrics.component.tsx +11 -3
  111. package/src/billable-services/payment-modes/add-payment-mode.modal.tsx +121 -0
  112. package/src/billable-services/payment-modes/delete-payment-mode.modal.tsx +72 -0
  113. package/src/billable-services/payment-modes/payment-modes-config.component.tsx +125 -0
  114. package/src/billable-services/{payyment-modes → payment-modes}/payment-modes-config.scss +5 -4
  115. package/src/billing-dashboard/billing-dashboard.scss +1 -1
  116. package/src/billing-form/billing-checkin-form.component.tsx +21 -17
  117. package/src/billing-form/billing-checkin-form.test.tsx +99 -26
  118. package/src/billing-form/billing-form.component.tsx +222 -292
  119. package/src/billing-form/billing-form.scss +143 -0
  120. package/src/billing-form/visit-attributes/visit-attributes-form.component.tsx +1 -1
  121. package/src/billing.resource.ts +69 -74
  122. package/src/bills-table/bills-table.component.tsx +3 -3
  123. package/src/bills-table/bills-table.test.tsx +98 -54
  124. package/src/config-schema.ts +52 -24
  125. package/src/dashboard.meta.ts +4 -2
  126. package/src/helpers/functions.ts +5 -4
  127. package/src/index.ts +17 -6
  128. package/src/invoice/invoice-table.component.tsx +36 -70
  129. package/src/invoice/invoice-table.scss +8 -5
  130. package/src/invoice/invoice-table.test.tsx +273 -62
  131. package/src/invoice/invoice.component.tsx +39 -32
  132. package/src/invoice/invoice.scss +11 -4
  133. package/src/invoice/invoice.test.tsx +324 -120
  134. package/src/invoice/payments/invoice-breakdown/invoice-breakdown.scss +9 -9
  135. package/src/invoice/payments/payment-form/payment-form.component.tsx +43 -34
  136. package/src/invoice/payments/payment-form/payment-form.scss +5 -6
  137. package/src/invoice/payments/payment-form/payment-form.test.tsx +216 -66
  138. package/src/invoice/payments/payment-history/payment-history.component.tsx +6 -4
  139. package/src/invoice/payments/payment-history/payment-history.test.tsx +9 -14
  140. package/src/invoice/payments/payments.component.tsx +55 -67
  141. package/src/invoice/payments/payments.scss +4 -3
  142. package/src/invoice/payments/payments.test.tsx +282 -0
  143. package/src/invoice/payments/utils.ts +15 -27
  144. package/src/invoice/printable-invoice/print-receipt.component.tsx +3 -2
  145. package/src/invoice/printable-invoice/print-receipt.test.tsx +14 -25
  146. package/src/invoice/printable-invoice/printable-footer.component.tsx +2 -2
  147. package/src/invoice/printable-invoice/printable-footer.test.tsx +4 -13
  148. package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +12 -11
  149. package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +16 -14
  150. package/src/invoice/printable-invoice/printable-invoice.component.tsx +20 -34
  151. package/src/left-panel-link.test.tsx +1 -4
  152. package/src/metrics-cards/metrics-cards.component.tsx +12 -2
  153. package/src/metrics-cards/metrics-cards.scss +4 -0
  154. package/src/metrics-cards/metrics-cards.test.tsx +18 -5
  155. package/src/modal/require-payment-modal.test.tsx +27 -22
  156. package/src/modal/{require-payment-modal.component.tsx → require-payment.modal.tsx} +18 -19
  157. package/src/routes.json +25 -7
  158. package/src/types/index.ts +80 -18
  159. package/translations/am.json +125 -74
  160. package/translations/ar.json +126 -75
  161. package/translations/ar_SY.json +126 -75
  162. package/translations/bn.json +128 -77
  163. package/translations/de.json +126 -75
  164. package/translations/en.json +126 -75
  165. package/translations/en_US.json +126 -75
  166. package/translations/es.json +125 -74
  167. package/translations/es_MX.json +126 -75
  168. package/translations/fr.json +131 -80
  169. package/translations/he.json +125 -74
  170. package/translations/hi.json +126 -75
  171. package/translations/hi_IN.json +126 -75
  172. package/translations/id.json +126 -75
  173. package/translations/it.json +152 -101
  174. package/translations/ka.json +126 -75
  175. package/translations/km.json +125 -74
  176. package/translations/ku.json +126 -75
  177. package/translations/ky.json +126 -75
  178. package/translations/lg.json +126 -75
  179. package/translations/ne.json +126 -75
  180. package/translations/pl.json +126 -75
  181. package/translations/pt.json +126 -75
  182. package/translations/pt_BR.json +126 -75
  183. package/translations/qu.json +126 -75
  184. package/translations/ro_RO.json +216 -165
  185. package/translations/ru_RU.json +126 -75
  186. package/translations/si.json +126 -75
  187. package/translations/sw.json +126 -75
  188. package/translations/sw_KE.json +126 -75
  189. package/translations/tr.json +126 -75
  190. package/translations/tr_TR.json +126 -75
  191. package/translations/uk.json +126 -75
  192. package/translations/uz.json +126 -75
  193. package/translations/uz@Latn.json +126 -75
  194. package/translations/uz_UZ.json +126 -75
  195. package/translations/vi.json +126 -75
  196. package/translations/zh.json +126 -75
  197. package/translations/zh_CN.json +158 -107
  198. package/dist/1146.js.LICENSE.txt +0 -21
  199. package/dist/2352.js +0 -1
  200. package/dist/2352.js.map +0 -1
  201. package/dist/246.js +0 -1
  202. package/dist/246.js.map +0 -1
  203. package/dist/6525.js +0 -2
  204. package/dist/6525.js.map +0 -1
  205. package/dist/8556.js +0 -2
  206. package/dist/8556.js.map +0 -1
  207. package/dist/8638.js +0 -1
  208. package/dist/8638.js.map +0 -1
  209. package/dist/9968.js +0 -1
  210. package/dist/9968.js.map +0 -1
  211. package/src/bill-item-actions/edit-bill-item.component.tsx +0 -221
  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/{8556.js.LICENSE.txt → 3717.js.LICENSE.txt} +0 -0
@@ -3,28 +3,19 @@ import { screen, render } from '@testing-library/react';
3
3
  import { useDefaultFacility } from '../../billing.resource';
4
4
  import PrintableFooter from './printable-footer.component';
5
5
 
6
- const mockUseDefaultFacility = useDefaultFacility as jest.MockedFunction<typeof useDefaultFacility>;
6
+ const mockUseDefaultFacility = jest.mocked<typeof useDefaultFacility>(useDefaultFacility);
7
7
 
8
8
  jest.mock('../../billing.resource', () => ({
9
9
  useDefaultFacility: jest.fn(),
10
10
  }));
11
11
 
12
12
  describe('PrintableFooter', () => {
13
- beforeEach(() => {
14
- jest.clearAllMocks();
15
- });
16
-
17
13
  test('should render PrintableFooter component', () => {
18
- mockUseDefaultFacility.mockReturnValue({ data: { display: 'MTRH', uuid: 'mtrh-uuid' }, isLoading: false });
14
+ mockUseDefaultFacility.mockReturnValue({
15
+ data: { display: 'MTRH', uuid: 'mtrh-uuid', links: [] },
16
+ });
19
17
  render(<PrintableFooter />);
20
18
  const footer = screen.getByText('MTRH');
21
19
  expect(footer).toBeInTheDocument();
22
20
  });
23
-
24
- test('should show placeholder text when facility isLoading', () => {
25
- mockUseDefaultFacility.mockReturnValue({ data: { display: 'MTRH', uuid: 'mtrh-uuid' }, isLoading: true });
26
- render(<PrintableFooter />);
27
- const footer = screen.getByText('--');
28
- expect(footer).toBeInTheDocument();
29
- });
30
21
  });
@@ -1,27 +1,28 @@
1
1
  import React from 'react';
2
2
  import { type PatientDetails } from '../../types';
3
- import { useConfig } from '@openmrs/esm-framework';
3
+ import { type SessionLocation, useConfig, interpolateUrl } from '@openmrs/esm-framework';
4
4
  import { useTranslation } from 'react-i18next';
5
- import { useDefaultFacility } from '../../billing.resource';
5
+ import type { BillingConfig } from '../../config-schema';
6
6
  import styles from './printable-invoice-header.scss';
7
+ import isEmpty from 'lodash/isEmpty';
7
8
 
8
9
  interface PrintableInvoiceHeaderProps {
9
10
  patientDetails: PatientDetails;
11
+ defaultFacility: SessionLocation | null;
10
12
  }
11
13
 
12
- const PrintableInvoiceHeader: React.FC<PrintableInvoiceHeaderProps> = ({ patientDetails }) => {
14
+ const PrintableInvoiceHeader: React.FC<PrintableInvoiceHeaderProps> = ({ patientDetails, defaultFacility }) => {
13
15
  const { t } = useTranslation();
14
- const { logo } = useConfig();
15
- const { data } = useDefaultFacility();
16
+ const { logo, country } = useConfig<BillingConfig>();
16
17
 
17
18
  return (
18
19
  <div className={styles.container}>
19
20
  <div className={styles.printableHeader}>
20
21
  <p className={styles.heading}>{t('invoice', 'Invoice')}</p>
21
- {logo?.src ? (
22
- <img className={styles.img} src={logo.src} alt={logo.alt} />
23
- ) : logo?.name ? (
24
- logo.name
22
+ {logo?.src && !isEmpty(logo.src) ? (
23
+ <img className={styles.img} src={interpolateUrl(logo.src)} alt={logo.alt} />
24
+ ) : logo?.alt && !isEmpty(logo.alt) ? (
25
+ logo.alt
25
26
  ) : (
26
27
  // OpenMRS Logo
27
28
  <svg
@@ -52,8 +53,8 @@ const PrintableInvoiceHeader: React.FC<PrintableInvoiceHeaderProps> = ({ patient
52
53
  </div>
53
54
 
54
55
  <div className={styles.facilityDetails}>
55
- <p className={styles.facilityName}>{data?.display}</p>
56
- <p className={styles.itemLabel}>Kenya</p>
56
+ <p className={styles.facilityName}>{defaultFacility?.display}</p>
57
+ <p className={styles.itemLabel}>{country}</p>
57
58
  </div>
58
59
  </div>
59
60
  </div>
@@ -1,19 +1,17 @@
1
1
  import React from 'react';
2
2
  import { screen, render } from '@testing-library/react';
3
3
  import { useConfig } from '@openmrs/esm-framework';
4
+ import { type BillingConfig } from '../../config-schema';
4
5
  import { useDefaultFacility } from '../../billing.resource';
5
6
  import PrintableInvoiceHeader from './printable-invoice-header.component';
6
7
 
7
- const mockUseDefaultFacility = useDefaultFacility as jest.MockedFunction<typeof useDefaultFacility>;
8
- const mockUseConfig = useConfig as jest.MockedFunction<typeof useConfig>;
8
+ const mockUseDefaultFacility = jest.mocked(useDefaultFacility);
9
+ const mockUseConfig = jest.mocked(useConfig<BillingConfig>);
9
10
 
10
11
  jest.mock('../../billing.resource', () => ({
11
12
  useDefaultFacility: jest.fn(),
12
13
  }));
13
14
 
14
- jest.mock('@openmrs/esm-framework', () => ({
15
- useConfig: jest.fn(),
16
- }));
17
15
  const testProps = {
18
16
  patientDetails: {
19
17
  name: 'John Doe',
@@ -25,11 +23,19 @@ const testProps = {
25
23
  },
26
24
  };
27
25
 
26
+ const defaultFacility = { display: 'MTRH', uuid: 'mtrh-uuid', links: [] };
27
+
28
28
  describe('PrintableInvoiceHeader', () => {
29
+ beforeEach(() => {
30
+ mockUseConfig.mockReturnValue({
31
+ logo: { src: 'logo.png', alt: 'logo' },
32
+ country: 'Kenya',
33
+ } as unknown as BillingConfig);
34
+ mockUseDefaultFacility.mockReturnValue({ data: { display: 'MTRH', uuid: 'mtrh-uuid', links: [] } });
35
+ });
36
+
29
37
  test('should render PrintableInvoiceHeader component', () => {
30
- mockUseConfig.mockReturnValue({ logo: { src: 'logo.png', alt: 'logo' } });
31
- mockUseDefaultFacility.mockReturnValue({ data: { display: 'MTRH', uuid: 'mtrh-uuid' }, isLoading: false });
32
- render(<PrintableInvoiceHeader {...testProps} />);
38
+ render(<PrintableInvoiceHeader {...testProps} defaultFacility={defaultFacility} />);
33
39
  const header = screen.getByText('Invoice');
34
40
  expect(header).toBeInTheDocument();
35
41
 
@@ -41,17 +47,13 @@ describe('PrintableInvoiceHeader', () => {
41
47
  });
42
48
 
43
49
  test('should display the logo when logo is provided', () => {
44
- mockUseConfig.mockReturnValue({ logo: { src: 'logo.png', alt: 'logo' } });
45
- mockUseDefaultFacility.mockReturnValue({ data: { display: 'MTRH', uuid: 'mtrh-uuid' }, isLoading: false });
46
- render(<PrintableInvoiceHeader {...testProps} />);
50
+ render(<PrintableInvoiceHeader {...testProps} defaultFacility={defaultFacility} />);
47
51
  const logo = screen.getByAltText('logo');
48
52
  expect(logo).toBeInTheDocument();
49
53
  });
50
54
 
51
55
  test('should display the default logo when logo is not provided', () => {
52
- mockUseConfig.mockReturnValue({ logo: {} });
53
- mockUseDefaultFacility.mockReturnValue({ data: { display: 'MTRH', uuid: 'mtrh-uuid' }, isLoading: false });
54
- render(<PrintableInvoiceHeader {...testProps} />);
56
+ render(<PrintableInvoiceHeader {...testProps} defaultFacility={defaultFacility} />);
55
57
  const logo = screen.getByRole('img');
56
58
  expect(logo).toBeInTheDocument();
57
59
  });
@@ -8,9 +8,8 @@ import {
8
8
  TableBody,
9
9
  TableHeader,
10
10
  TableCell,
11
- DataTableSkeleton,
12
11
  } from '@carbon/react';
13
- import { age, isDesktop, useLayoutType } from '@openmrs/esm-framework';
12
+ import { age, isDesktop, type SessionLocation, useLayoutType } from '@openmrs/esm-framework';
14
13
  import { getGender } from '../../helpers';
15
14
  import { type MappedBill } from '../../types';
16
15
  import { useTranslation } from 'react-i18next';
@@ -21,25 +20,26 @@ import styles from './printable-invoice.scss';
21
20
  type PrintableInvoiceProps = {
22
21
  bill: MappedBill;
23
22
  patient: fhir.Patient;
24
- isLoading: boolean;
23
+ componentRef: React.RefObject<HTMLDivElement>;
24
+ defaultFacility: SessionLocation;
25
25
  };
26
26
 
27
- const PrintableInvoice: React.FC<PrintableInvoiceProps> = ({ bill, patient, isLoading }) => {
27
+ const PrintableInvoice: React.FC<PrintableInvoiceProps> = ({ bill, patient, componentRef, defaultFacility }) => {
28
28
  const { t } = useTranslation();
29
29
  const layout = useLayoutType();
30
30
  const responsiveSize = isDesktop(layout) ? 'sm' : 'lg';
31
31
  const headerData = [
32
- { header: 'Inventory item', key: 'billItem' },
33
- { header: 'Quantity', key: 'quantity' },
34
- { header: 'Unit price', key: 'price' },
35
- { header: 'Total', key: 'total' },
32
+ { header: t('inventoryItem', 'Inventory item'), key: 'billItem' },
33
+ { header: t('quantity', 'Quantity'), key: 'quantity' },
34
+ { header: t('unitPrice', 'Unit price'), key: 'price' },
35
+ { header: t('total', 'Total'), key: 'total' },
36
36
  ];
37
37
 
38
38
  const rowData =
39
39
  bill?.lineItems?.map((item) => {
40
40
  return {
41
41
  id: `${item.uuid}`,
42
- billItem: item.item,
42
+ billItem: item.billableService ?? item.item,
43
43
  quantity: item.quantity,
44
44
  price: item.price,
45
45
  total: item.price * item.quantity,
@@ -47,10 +47,10 @@ const PrintableInvoice: React.FC<PrintableInvoiceProps> = ({ bill, patient, isLo
47
47
  }) ?? [];
48
48
 
49
49
  const invoiceTotal = {
50
- 'Total Amount': bill?.totalAmount,
51
- 'Amount Tendered': bill?.tenderedAmount,
52
- 'Discount Amount': 0,
53
- 'Amount due': bill?.totalAmount - bill?.tenderedAmount,
50
+ [t('totalAmount', 'Total amount')]: bill?.totalAmount,
51
+ [t('amountTendered', 'Amount tendered')]: bill?.tenderedAmount,
52
+ [t('discountAmount', 'Discount amount')]: 0,
53
+ [t('amountDue', 'Amount due')]: bill?.totalAmount - bill?.tenderedAmount,
54
54
  };
55
55
 
56
56
  const patientDetails = useMemo(() => {
@@ -65,28 +65,14 @@ const PrintableInvoice: React.FC<PrintableInvoiceProps> = ({ bill, patient, isLo
65
65
  }, [patient, t]);
66
66
 
67
67
  const invoiceDetails = {
68
- 'Invoice #': bill?.receiptNumber,
69
- 'Invoice date': bill.dateCreated,
70
- Status: bill?.status,
68
+ [t('invoiceNumber', 'Invoice #')]: bill?.receiptNumber,
69
+ [t('invoiceDate', 'Invoice date')]: bill?.dateCreated,
70
+ [t('status', 'Status')]: bill?.status,
71
71
  };
72
72
 
73
- if (isLoading) {
74
- return (
75
- <div className={styles.loaderContainer}>
76
- <DataTableSkeleton
77
- columnCount={headerData?.length ?? 0}
78
- showHeader={false}
79
- showToolbar={false}
80
- size={responsiveSize}
81
- zebra
82
- />
83
- </div>
84
- );
85
- }
86
-
87
73
  return (
88
- <div className={styles.container}>
89
- <PrintableInvoiceHeader patientDetails={patientDetails} />
74
+ <div className={styles.container} ref={componentRef}>
75
+ <PrintableInvoiceHeader patientDetails={patientDetails} defaultFacility={defaultFacility} />
90
76
  <div className={styles.printableInvoiceContainer}>
91
77
  <div className={styles.detailsContainer}>
92
78
  {Object.entries(invoiceDetails).map(([key, val]) => (
@@ -99,10 +85,10 @@ const PrintableInvoice: React.FC<PrintableInvoiceProps> = ({ bill, patient, isLo
99
85
 
100
86
  <div className={styles.itemsContainer}>
101
87
  <div className={styles.tableContainer}>
102
- <DataTable isSortable rows={rowData} headers={headerData} size={responsiveSize} useZebraStyles={false}>
88
+ <DataTable rows={rowData} headers={headerData} size={responsiveSize} useZebraStyles={false}>
103
89
  {({ rows, headers, getRowProps, getTableProps }) => (
104
90
  <TableContainer>
105
- <Table {...getTableProps()} aria-label="Invoice line items">
91
+ <Table {...getTableProps()} aria-label={t('invoiceLineItems', 'Invoice line items')}>
106
92
  <TableHead>
107
93
  <TableRow>
108
94
  {headers.map((header) => (
@@ -23,7 +23,6 @@ describe('LinkExtension Component', () => {
23
23
  });
24
24
 
25
25
  describe('createLeftPanelLink Function', () => {
26
- const user = userEvent.setup();
27
26
  test('returns a component that renders LinkExtension', () => {
28
27
  const config = { name: 'billing', title: 'Billing' };
29
28
  const TestComponent = createLeftPanelLink(config);
@@ -31,8 +30,6 @@ describe('createLeftPanelLink Function', () => {
31
30
  render(<TestComponent />);
32
31
  expect(screen.getByText('Billing')).toBeInTheDocument();
33
32
  const testLink = screen.getByRole('link', { name: 'Billing' });
34
- user.click(testLink);
35
- expect(window.location.pathname).toBe('/billing/6eb8d678-514d-46ad-9554-51e48d96d567');
36
- // expect(testLink).toHaveClass('active-left-nav-link');
33
+ expect(testLink).toHaveAttribute('href', '/openmrs/spa/home/billing');
37
34
  });
38
35
  });
@@ -2,6 +2,7 @@ import React, { useMemo } from 'react';
2
2
  import { InlineLoading } from '@carbon/react';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import { ErrorState } from '@openmrs/esm-patient-common-lib';
5
+ import { getCoreTranslation } from '@openmrs/esm-framework';
5
6
  import { useBills } from '../billing.resource';
6
7
  import { useBillMetrics } from './metrics.resource';
7
8
  import Card from './card.component';
@@ -24,14 +25,23 @@ export default function MetricsCards() {
24
25
  if (isLoading) {
25
26
  return (
26
27
  <section className={styles.container}>
27
- <InlineLoading status="active" iconDescription="Loading" description="Loading bill metrics..." />
28
+ <InlineLoading
29
+ status="active"
30
+ iconDescription={getCoreTranslation('loading')}
31
+ description={t('loadingBillMetrics', 'Loading bill metrics') + '...'}
32
+ />
28
33
  </section>
29
34
  );
30
35
  }
31
36
 
32
37
  if (error) {
33
- return <ErrorState headerTitle={t('billMetrics', 'Bill metrics')} error={error} />;
38
+ return (
39
+ <div className={styles.errorContainer}>
40
+ <ErrorState headerTitle={t('billMetrics', 'Bill metrics')} error={error} />
41
+ </div>
42
+ );
34
43
  }
44
+
35
45
  return (
36
46
  <section className={styles.container}>
37
47
  {cards.map((card) => (
@@ -10,3 +10,7 @@
10
10
  row-gap: layout.$spacing-05;
11
11
  flex-flow: row wrap;
12
12
  }
13
+
14
+ .errorContainer {
15
+ margin: layout.$spacing-05;
16
+ }
@@ -3,10 +3,11 @@ import { render, screen } from '@testing-library/react';
3
3
  import { useConfig } from '@openmrs/esm-framework';
4
4
  import { billsSummary } from '../../__mocks__/bills.mock';
5
5
  import { useBills } from '../billing.resource';
6
+ import { type MappedBill } from '../types';
6
7
  import MetricsCards from './metrics-cards.component';
7
8
 
8
- const mockUseBills = useBills as jest.Mock;
9
- const mockUseConfig = useConfig as jest.Mock;
9
+ const mockUseBills = jest.mocked(useBills);
10
+ const mockUseConfig = jest.mocked(useConfig);
10
11
 
11
12
  jest.mock('../billing.resource', () => ({
12
13
  useBills: jest.fn(),
@@ -14,13 +15,19 @@ jest.mock('../billing.resource', () => ({
14
15
 
15
16
  describe('MetricsCards', () => {
16
17
  test('renders loading state', () => {
17
- mockUseBills.mockReturnValue({ isLoading: true, bills: [], error: null });
18
+ mockUseBills.mockReturnValue({ isLoading: true, bills: [], error: null, isValidating: false, mutate: jest.fn() });
18
19
  renderMetricsCards();
19
20
  expect(screen.getByText(/Loading bill metrics.../i)).toBeInTheDocument();
20
21
  });
21
22
 
22
23
  test('renders error state', () => {
23
- mockUseBills.mockReturnValue({ isLoading: false, bills: [], error: new Error('Internal server error') });
24
+ mockUseBills.mockReturnValue({
25
+ isLoading: false,
26
+ bills: [],
27
+ error: new Error('Internal server error'),
28
+ isValidating: false,
29
+ mutate: jest.fn(),
30
+ });
24
31
  renderMetricsCards();
25
32
  expect(
26
33
  screen.getByText(
@@ -30,7 +37,13 @@ describe('MetricsCards', () => {
30
37
  });
31
38
 
32
39
  test('renders metrics cards', () => {
33
- mockUseBills.mockReturnValue({ isLoading: false, bills: billsSummary, error: null });
40
+ mockUseBills.mockReturnValue({
41
+ isLoading: false,
42
+ bills: billsSummary as unknown as MappedBill[],
43
+ error: null,
44
+ isValidating: false,
45
+ mutate: jest.fn(),
46
+ });
34
47
  mockUseConfig.mockImplementation(() => ({ defaultCurrency: 'USD' }));
35
48
  renderMetricsCards();
36
49
  expect(screen.getByRole('heading', { name: /cumulative bills/i })).toBeInTheDocument();
@@ -1,16 +1,14 @@
1
1
  import React from 'react';
2
- import { render, screen, fireEvent } from '@testing-library/react';
3
- import '@testing-library/jest-dom';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { render, screen } from '@testing-library/react';
4
+ import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
4
5
  import { useBills } from '../billing.resource';
5
- import RequirePaymentModal from './require-payment-modal.component';
6
+ import { type MappedBill } from '../types';
7
+ import { configSchema, type BillingConfig } from '../config-schema';
8
+ import RequirePaymentModal from './require-payment.modal';
6
9
 
7
- jest.mock('react-i18next', () => ({
8
- useTranslation: () => ({ t: (key: string) => key }),
9
- }));
10
-
11
- jest.mock('@openmrs/esm-framework', () => ({
12
- useConfig: () => ({ defaultCurrency: 'USD' }),
13
- }));
10
+ const mockUseConfig = jest.mocked(useConfig<BillingConfig>);
11
+ const mockUseBills = jest.mocked<typeof useBills>(useBills);
14
12
 
15
13
  jest.mock('../billing.resource', () => ({
16
14
  useBills: jest.fn(),
@@ -25,19 +23,19 @@ describe('RequirePaymentModal', () => {
25
23
  const patientUuid = '12345';
26
24
 
27
25
  beforeEach(() => {
28
- jest.clearAllMocks();
26
+ mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(configSchema), defaultCurrency: 'USD' });
29
27
  });
30
28
 
31
29
  it('renders correctly', () => {
32
- (useBills as jest.Mock).mockReturnValue({ bills: [], isLoading: false, error: null });
30
+ mockUseBills.mockReturnValue({ bills: [], isLoading: false, error: null, isValidating: false, mutate: jest.fn() });
33
31
  render(<RequirePaymentModal closeModal={closeModal} patientUuid={patientUuid} />);
34
- expect(screen.getByText('patientBillingAlert')).toBeInTheDocument();
32
+ expect(screen.getByText('Patient Billing Alert')).toBeInTheDocument();
35
33
  });
36
34
 
37
35
  it('displays loading state', () => {
38
- (useBills as jest.Mock).mockReturnValue({ bills: [], isLoading: true, error: null });
36
+ mockUseBills.mockReturnValue({ bills: [], isLoading: true, error: null, isValidating: false, mutate: jest.fn() });
39
37
  render(<RequirePaymentModal closeModal={closeModal} patientUuid={patientUuid} />);
40
- expect(screen.getByText('inlineLoading')).toBeInTheDocument();
38
+ expect(screen.getByText('Loading bill items...')).toBeInTheDocument();
41
39
  });
42
40
 
43
41
  it('displays line items', () => {
@@ -45,22 +43,29 @@ describe('RequirePaymentModal', () => {
45
43
  {
46
44
  status: 'UNPAID',
47
45
  lineItems: [
48
- { billableService: 'Service 1', quantity: 1, price: 100 },
49
- { item: 'Item 1', quantity: 2, price: 50 },
46
+ { billableService: 'Service 1', quantity: 1, price: 100, uuid: 'billable-service-1' },
47
+ { item: 'Item 1', quantity: 2, price: 50, uuid: 'billable-item-1' },
50
48
  ],
51
49
  },
52
50
  ];
53
- (useBills as jest.Mock).mockReturnValue({ bills, isLoading: false, error: null });
51
+ mockUseBills.mockReturnValue({
52
+ bills: bills as unknown as MappedBill[],
53
+ isLoading: false,
54
+ error: null,
55
+ isValidating: false,
56
+ mutate: jest.fn(),
57
+ });
54
58
  render(<RequirePaymentModal closeModal={closeModal} patientUuid={patientUuid} />);
55
59
  expect(screen.getByText('Service 1')).toBeInTheDocument();
56
60
  expect(screen.getByText('Item 1')).toBeInTheDocument();
57
61
  });
58
62
 
59
- it('handles closeModal', () => {
60
- (useBills as jest.Mock).mockReturnValue({ bills: [], isLoading: false, error: null });
63
+ it('handles closeModal', async () => {
64
+ const user = userEvent.setup();
65
+ mockUseBills.mockReturnValue({ bills: [], isLoading: false, error: null, isValidating: false, mutate: jest.fn() });
61
66
  render(<RequirePaymentModal closeModal={closeModal} patientUuid={patientUuid} />);
62
- fireEvent.click(screen.getByText('cancel'));
63
- fireEvent.click(screen.getByText('ok'));
67
+ await user.click(screen.getByText('Cancel'));
68
+ await user.click(screen.getByText('OK'));
64
69
  expect(closeModal).toHaveBeenCalledTimes(2);
65
70
  });
66
71
  });
@@ -12,7 +12,7 @@ import {
12
12
  StructuredListRow,
13
13
  StructuredListWrapper,
14
14
  } from '@carbon/react';
15
- import { useConfig } from '@openmrs/esm-framework';
15
+ import { getCoreTranslation, useConfig } from '@openmrs/esm-framework';
16
16
  import { useBills } from '../billing.resource';
17
17
  import { convertToCurrency } from '../helpers';
18
18
  import styles from './require-payment.scss';
@@ -29,22 +29,23 @@ const RequirePaymentModal: React.FC<RequirePaymentModalProps> = ({ closeModal, p
29
29
  const lineItems = bills.filter((bill) => bill?.status !== 'PAID').flatMap((bill) => bill?.lineItems);
30
30
 
31
31
  return (
32
- <div>
32
+ <>
33
33
  <ModalHeader closeModal={closeModal} title={t('patientBillingAlert', 'Patient Billing Alert')} />
34
34
  <ModalBody>
35
35
  <p className={styles.bodyShort02}>
36
36
  {t(
37
37
  'billPaymentRequiredMessage',
38
- 'The current patient has pending bill. Advice patient to settle bill before receiving services',
38
+ 'The current patient has a pending bill. Advise the patient to settle the bill before receiving services',
39
39
  )}
40
40
  </p>
41
41
  {isLoading && (
42
42
  <InlineLoading
43
43
  status="active"
44
- iconDescription="Loading"
45
- description={t('inlineLoading', 'Loading bill items...')}
44
+ iconDescription={getCoreTranslation('loading')}
45
+ description={t('loadingBillItems', 'Loading bill items') + '...'}
46
46
  />
47
47
  )}
48
+
48
49
  <StructuredListWrapper isCondensed>
49
50
  <StructuredListHead>
50
51
  <StructuredListRow head>
@@ -55,30 +56,28 @@ const RequirePaymentModal: React.FC<RequirePaymentModalProps> = ({ closeModal, p
55
56
  </StructuredListRow>
56
57
  </StructuredListHead>
57
58
  <StructuredListBody>
58
- {lineItems.map((lineItem) => {
59
- return (
60
- <StructuredListRow>
61
- <StructuredListCell>{lineItem.billableService || lineItem.item}</StructuredListCell>
62
- <StructuredListCell>{lineItem.quantity}</StructuredListCell>
63
- <StructuredListCell>{convertToCurrency(lineItem.price, defaultCurrency)}</StructuredListCell>
64
- <StructuredListCell>
65
- {convertToCurrency(lineItem.quantity * lineItem.price, defaultCurrency)}
66
- </StructuredListCell>
67
- </StructuredListRow>
68
- );
69
- })}
59
+ {lineItems.map((lineItem) => (
60
+ <StructuredListRow key={lineItem.uuid}>
61
+ <StructuredListCell>{lineItem.billableService || lineItem.item}</StructuredListCell>
62
+ <StructuredListCell>{lineItem.quantity}</StructuredListCell>
63
+ <StructuredListCell>{convertToCurrency(lineItem.price, defaultCurrency)}</StructuredListCell>
64
+ <StructuredListCell>
65
+ {convertToCurrency(lineItem.quantity * lineItem.price, defaultCurrency)}
66
+ </StructuredListCell>
67
+ </StructuredListRow>
68
+ ))}
70
69
  </StructuredListBody>
71
70
  </StructuredListWrapper>
72
71
  </ModalBody>
73
72
  <ModalFooter>
74
73
  <Button kind="secondary" onClick={closeModal}>
75
- {t('cancel', 'Cancel')}
74
+ {getCoreTranslation('cancel')}
76
75
  </Button>
77
76
  <Button kind="primary" onClick={closeModal}>
78
77
  {t('ok', 'OK')}
79
78
  </Button>
80
79
  </ModalFooter>
81
- </div>
80
+ </>
82
81
  );
83
82
  };
84
83
 
package/src/routes.json CHANGED
@@ -78,15 +78,33 @@
78
78
  "name": "billing-home-tiles-ext",
79
79
  "slot": "billing-home-tiles-slot",
80
80
  "component": "serviceMetrics"
81
- },
82
- {
83
- "name": "edit-bill-line-item-dialog",
84
- "component": "editBillLineItemDialog",
85
- "online": true,
86
- "offline": true
87
81
  }
88
82
  ],
89
83
  "modals": [
84
+ {
85
+ "name": "add-cash-point-modal",
86
+ "component": "addCashPointModal"
87
+ },
88
+ {
89
+ "name": "add-payment-mode-modal",
90
+ "component": "addPaymentModeModal"
91
+ },
92
+ {
93
+ "name": "delete-payment-mode-modal",
94
+ "component": "deletePaymentModeModal"
95
+ },
96
+ {
97
+ "name": "edit-bill-item-modal",
98
+ "component": "editBillLineItemModal"
99
+ },
100
+ {
101
+ "name": "edit-bill-line-item-modal",
102
+ "component": "editBillLineItemModal"
103
+ },
104
+ {
105
+ "name": "edit-billable-service-modal",
106
+ "component": "editBillableServiceModal"
107
+ },
90
108
  {
91
109
  "name": "require-billing-modal",
92
110
  "component": "requirePaymentModal"
@@ -107,4 +125,4 @@
107
125
  "description": "This feature introduces navigation links on the patient chart and home page to allow accessing the billing module features"
108
126
  }
109
127
  ]
110
- }
128
+ }