@openmrs/esm-billing-app 1.0.1-pre.98 → 1.0.2-pre.56

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 (224) hide show
  1. package/.eslintignore +0 -1
  2. package/.eslintrc +33 -24
  3. package/.husky/pre-commit +1 -1
  4. package/.turbo.json +1 -1
  5. package/.tx/config +11 -0
  6. package/README.md +111 -1
  7. package/dist/1119.js +1 -0
  8. package/dist/1197.js +1 -0
  9. package/dist/1362.js +1 -0
  10. package/dist/1362.js.map +1 -0
  11. package/dist/2146.js +1 -0
  12. package/dist/2690.js +1 -0
  13. package/dist/3029.js +2 -0
  14. package/dist/3029.js.LICENSE.txt +7 -0
  15. package/dist/3029.js.map +1 -0
  16. package/dist/3099.js +1 -0
  17. package/dist/3511.js +1 -0
  18. package/dist/3511.js.map +1 -0
  19. package/dist/3584.js +1 -0
  20. package/dist/4055.js +1 -0
  21. package/dist/4132.js +1 -0
  22. package/dist/4225.js +1 -0
  23. package/dist/4225.js.map +1 -0
  24. package/dist/4300.js +1 -0
  25. package/dist/4335.js +1 -0
  26. package/dist/4618.js +1 -0
  27. package/dist/4652.js +1 -0
  28. package/dist/4817.js +2 -0
  29. package/dist/4817.js.LICENSE.txt +77 -0
  30. package/dist/4817.js.map +1 -0
  31. package/dist/4944.js +1 -0
  32. package/dist/4993.js +1 -0
  33. package/dist/4993.js.map +1 -0
  34. package/dist/5173.js +1 -0
  35. package/dist/5241.js +1 -0
  36. package/dist/5442.js +1 -0
  37. package/dist/5661.js +1 -0
  38. package/dist/6022.js +1 -0
  39. package/dist/6468.js +1 -0
  40. package/dist/6540.js +2 -0
  41. package/dist/6540.js.map +1 -0
  42. package/dist/6606.js +2 -0
  43. package/dist/{591.js.LICENSE.txt → 6606.js.LICENSE.txt} +2 -2
  44. package/dist/6606.js.map +1 -0
  45. package/dist/6679.js +1 -0
  46. package/dist/6840.js +1 -0
  47. package/dist/6859.js +1 -0
  48. package/dist/6941.js +1 -0
  49. package/dist/6941.js.map +1 -0
  50. package/dist/7097.js +1 -0
  51. package/dist/7159.js +1 -0
  52. package/dist/723.js +1 -0
  53. package/dist/7255.js +1 -0
  54. package/dist/7255.js.map +1 -0
  55. package/dist/7617.js +1 -0
  56. package/dist/763.js +1 -0
  57. package/dist/763.js.map +1 -0
  58. package/dist/8163.js +1 -0
  59. package/dist/8349.js +1 -0
  60. package/dist/8618.js +1 -0
  61. package/dist/890.js +1 -0
  62. package/dist/9055.js +1 -0
  63. package/dist/9055.js.map +1 -0
  64. package/dist/9214.js +1 -0
  65. package/dist/9538.js +1 -0
  66. package/dist/{935.js → 961.js} +2 -2
  67. package/dist/{935.js.map → 961.js.map} +1 -1
  68. package/dist/986.js +1 -0
  69. package/dist/9879.js +1 -0
  70. package/dist/9895.js +1 -0
  71. package/dist/9900.js +1 -0
  72. package/dist/9913.js +1 -0
  73. package/dist/main.js +1 -1
  74. package/dist/main.js.LICENSE.txt +31 -1
  75. package/dist/main.js.map +1 -1
  76. package/dist/openmrs-esm-billing-app.js +1 -1
  77. package/dist/openmrs-esm-billing-app.js.buildmanifest.json +844 -165
  78. package/dist/openmrs-esm-billing-app.js.map +1 -1
  79. package/dist/routes.json +1 -1
  80. package/jest.config.js +4 -1
  81. package/package.json +19 -21
  82. package/src/bill-history/bill-history.component.tsx +5 -3
  83. package/src/bill-history/bill-history.scss +24 -9
  84. package/src/bill-history/bill-history.test.tsx +58 -16
  85. package/src/bill-item-actions/bill-item-actions.scss +26 -0
  86. package/src/bill-item-actions/edit-bill-item.component.tsx +221 -0
  87. package/src/bill-item-actions/edit-bill-item.test.tsx +137 -0
  88. package/src/billable-services/bill-waiver/bill-selection.component.tsx +1 -1
  89. package/src/billable-services/bill-waiver/bill-waiver-form.component.tsx +2 -2
  90. package/src/billable-services/bill-waiver/bill-waiver-form.scss +4 -4
  91. package/src/billable-services/bill-waiver/bill-waiver.component.tsx +4 -4
  92. package/src/billable-services/bill-waiver/patient-bills.component.tsx +1 -1
  93. package/src/billable-services/billable-service.resource.ts +19 -6
  94. package/src/billable-services/billable-services-home.component.tsx +19 -3
  95. package/src/billable-services/billable-services-menu-item/item.component.tsx +17 -0
  96. package/src/billable-services/billable-services-menu-item/item.scss +14 -0
  97. package/src/billable-services/billable-services.component.tsx +48 -9
  98. package/src/billable-services/billable-services.scss +10 -9
  99. package/src/billable-services/billable-services.test.tsx +172 -8
  100. package/src/billable-services/cash-point/cash-point-configuration.component.tsx +276 -0
  101. package/src/billable-services/cash-point/cash-point-configuration.scss +23 -0
  102. package/src/billable-services/create-edit/add-billable-service.component.tsx +126 -47
  103. package/src/billable-services/create-edit/add-billable-service.scss +14 -8
  104. package/src/billable-services/create-edit/add-billable-service.test.tsx +12 -10
  105. package/src/billable-services/dashboard/dashboard.scss +3 -3
  106. package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +280 -0
  107. package/src/billable-services/payyment-modes/payment-modes-config.scss +23 -0
  108. package/src/billing-dashboard/billing-dashboard.component.tsx +17 -4
  109. package/src/billing-dashboard/billing-dashboard.scss +3 -3
  110. package/src/billing-form/billing-form.component.tsx +31 -25
  111. package/src/billing-form/billing-form.scss +9 -10
  112. package/src/billing-form/visit-attributes/visit-attributes-form.component.tsx +38 -14
  113. package/src/billing-header/billing-header.component.tsx +21 -5
  114. package/src/billing-header/billing-header.scss +1 -1
  115. package/src/billing.resource.ts +21 -4
  116. package/src/bills-table/bills-table.component.tsx +46 -36
  117. package/src/bills-table/bills-table.scss +6 -6
  118. package/src/bills-table/bills-table.test.tsx +108 -68
  119. package/src/config-schema.ts +36 -1
  120. package/src/constants.ts +2 -0
  121. package/src/dashboard.meta.ts +2 -1
  122. package/src/helpers/functions.ts +0 -2
  123. package/src/hooks/selectedDateContext.ts +10 -0
  124. package/src/index.ts +22 -27
  125. package/src/invoice/invoice-table.component.tsx +95 -56
  126. package/src/invoice/invoice-table.scss +7 -8
  127. package/src/invoice/invoice-table.test.tsx +151 -0
  128. package/src/invoice/invoice.component.tsx +7 -9
  129. package/src/invoice/invoice.scss +2 -2
  130. package/src/invoice/invoice.test.tsx +199 -169
  131. package/src/invoice/payments/payment-form/payment-form.component.tsx +84 -55
  132. package/src/invoice/payments/payment-form/payment-form.test.tsx +174 -0
  133. package/src/invoice/payments/payment-history/payment-history.component.tsx +9 -7
  134. package/src/invoice/payments/payment-history/payment-history.test.tsx +160 -0
  135. package/src/invoice/payments/payments.component.test.tsx +121 -0
  136. package/src/invoice/payments/payments.component.tsx +57 -48
  137. package/src/invoice/payments/utils.ts +17 -13
  138. package/src/invoice/printable-invoice/print-receipt.component.tsx +23 -8
  139. package/src/invoice/printable-invoice/print-receipt.test.tsx +50 -0
  140. package/src/metrics-cards/card.component.tsx +4 -2
  141. package/src/metrics-cards/metrics-cards.test.tsx +1 -1
  142. package/src/modal/require-payment-modal.component.tsx +2 -2
  143. package/src/modal/require-payment-modal.test.tsx +66 -0
  144. package/src/modal/require-payment.scss +2 -1
  145. package/src/routes.json +40 -8
  146. package/src/types/index.ts +15 -0
  147. package/{i18next-parser.config.js → tools/i18next-parser.config.js} +19 -19
  148. package/tools/update-openmrs-deps.mjs +42 -0
  149. package/translations/am.json +53 -0
  150. package/translations/ar.json +170 -0
  151. package/translations/ar_SY.json +170 -0
  152. package/translations/bn.json +170 -0
  153. package/translations/de.json +170 -0
  154. package/translations/en.json +53 -0
  155. package/translations/es.json +53 -0
  156. package/translations/es_MX.json +170 -0
  157. package/translations/fr.json +53 -0
  158. package/translations/he.json +53 -0
  159. package/translations/hi.json +170 -0
  160. package/translations/hi_IN.json +170 -0
  161. package/translations/id.json +170 -0
  162. package/translations/it.json +170 -0
  163. package/translations/km.json +53 -0
  164. package/translations/ku.json +170 -0
  165. package/translations/ky.json +170 -0
  166. package/translations/lg.json +170 -0
  167. package/translations/ne.json +170 -0
  168. package/translations/pl.json +170 -0
  169. package/translations/pt.json +170 -0
  170. package/translations/pt_BR.json +170 -0
  171. package/translations/qu.json +170 -0
  172. package/translations/ro_RO.json +170 -0
  173. package/translations/ru_RU.json +170 -0
  174. package/translations/si.json +170 -0
  175. package/translations/sw.json +170 -0
  176. package/translations/sw_KE.json +170 -0
  177. package/translations/tr.json +170 -0
  178. package/translations/tr_TR.json +170 -0
  179. package/translations/uk.json +170 -0
  180. package/translations/uz.json +170 -0
  181. package/translations/uz@Latn.json +170 -0
  182. package/translations/uz_UZ.json +170 -0
  183. package/translations/vi.json +170 -0
  184. package/translations/zh.json +170 -0
  185. package/translations/zh_CN.json +170 -0
  186. package/tsconfig.json +10 -8
  187. package/webpack.config.js +1 -1
  188. package/dist/146.js +0 -1
  189. package/dist/146.js.map +0 -1
  190. package/dist/294.js +0 -2
  191. package/dist/294.js.map +0 -1
  192. package/dist/319.js +0 -1
  193. package/dist/384.js +0 -1
  194. package/dist/384.js.map +0 -1
  195. package/dist/421.js +0 -1
  196. package/dist/421.js.map +0 -1
  197. package/dist/533.js +0 -1
  198. package/dist/533.js.map +0 -1
  199. package/dist/574.js +0 -1
  200. package/dist/591.js +0 -2
  201. package/dist/591.js.map +0 -1
  202. package/dist/614.js +0 -2
  203. package/dist/614.js.LICENSE.txt +0 -37
  204. package/dist/614.js.map +0 -1
  205. package/dist/753.js +0 -1
  206. package/dist/753.js.map +0 -1
  207. package/dist/757.js +0 -1
  208. package/dist/770.js +0 -1
  209. package/dist/770.js.map +0 -1
  210. package/dist/783.js +0 -1
  211. package/dist/783.js.map +0 -1
  212. package/dist/788.js +0 -1
  213. package/dist/800.js +0 -2
  214. package/dist/800.js.LICENSE.txt +0 -3
  215. package/dist/800.js.map +0 -1
  216. package/dist/807.js +0 -1
  217. package/dist/833.js +0 -1
  218. package/dist/992.js +0 -1
  219. package/dist/992.js.map +0 -1
  220. package/src/root.scss +0 -30
  221. /package/dist/{294.js.LICENSE.txt → 6540.js.LICENSE.txt} +0 -0
  222. /package/dist/{935.js.LICENSE.txt → 961.js.LICENSE.txt} +0 -0
  223. /package/{src → tools}/setup-tests.ts +0 -0
  224. /package/{test-helpers.tsx → tools/test-helpers.tsx} +0 -0
@@ -1,4 +1,3 @@
1
- import { type OpenmrsResource } from '@openmrs/esm-framework';
2
1
  import { type LineItem, type MappedBill } from '../../types';
3
2
  import { type Payment } from './payments.component';
4
3
 
@@ -20,12 +19,13 @@ export const createPaymentPayload = (
20
19
  ) => {
21
20
  const { cashier } = bill;
22
21
  const totalAmount = bill?.totalAmount;
23
- const totalPaymentStatus = amountDue <= 0 ? 'PAID' : 'PENDING';
24
- const previousPayments = bill.payments.map((payment) => ({
22
+ const paymentStatus = amountDue <= 0 ? 'PAID' : 'PENDING';
23
+ const previousPayments = bill?.payments.map((payment) => ({
25
24
  amount: payment.amount,
26
25
  amountTendered: payment.amountTendered,
27
26
  attributes: [],
28
27
  instanceType: payment.instanceType.uuid,
28
+ dateCreated: payment.dateCreated,
29
29
  }));
30
30
 
31
31
  const newPayments = formValues.map((formValue) => ({
@@ -33,36 +33,40 @@ export const createPaymentPayload = (
33
33
  amountTendered: parseFloat(Number(formValue.amount).toFixed(2)),
34
34
  attributes: [],
35
35
  instanceType: formValue.method,
36
+ dateCreated: new Date(),
36
37
  }));
37
38
 
38
- const updatedPayments = newPayments.concat(previousPayments);
39
+ const updatedPayments = [...newPayments, ...previousPayments];
39
40
  const totalAmountRendered = updatedPayments.reduce((acc, payment) => acc + payment.amountTendered, 0);
41
+
40
42
  const updatedLineItems = bill?.lineItems.map((lineItem) => ({
41
43
  ...lineItem,
42
44
  billableService: getBillableServiceUuid(billableServices, lineItem.billableService),
43
- item: lineItem?.item,
44
- paymentStatus: hasLineItem(selectedLineItems ?? [], lineItem)
45
- ? totalAmountRendered >= lineItem.price * lineItem.quantity
46
- ? 'PAID'
47
- : 'PENDING'
48
- : lineItem.paymentStatus,
45
+ item: processBillItem?.(lineItem),
46
+ paymentStatus:
47
+ bill?.lineItems.length > 1
48
+ ? hasLineItem(selectedLineItems ?? [], lineItem) && totalAmountRendered >= lineItem.price * lineItem.quantity
49
+ ? 'PAID'
50
+ : 'PENDING'
51
+ : paymentStatus,
49
52
  }));
50
53
 
51
54
  const allItemsBillPaymentStatus =
52
55
  updatedLineItems.filter((item) => item.paymentStatus === 'PENDING').length === 0 ? 'PAID' : 'PENDING';
53
56
 
54
57
  const processedPayment = {
55
- cashPoint: bill.cashPointUuid,
58
+ cashPoint: bill?.cashPointUuid,
56
59
  cashier: cashier.uuid,
57
60
  lineItems: updatedLineItems,
58
61
  payments: [...updatedPayments],
59
62
  patient: patientUuid,
60
- status: selectedLineItems?.length > 0 ? allItemsBillPaymentStatus : totalPaymentStatus,
63
+ status: selectedLineItems?.length > 0 ? allItemsBillPaymentStatus : paymentStatus,
61
64
  };
62
65
 
63
66
  return processedPayment;
64
67
  };
65
68
 
66
- const getBillableServiceUuid = (billableServices: Array<any>, serviceName: string) => {
69
+ export const getBillableServiceUuid = (billableServices: Array<any>, serviceName: string) => {
67
70
  return billableServices.length ? billableServices.find((service) => service.name === serviceName).uuid : null;
68
71
  };
72
+ const processBillItem = (item) => (item.item || item.billableService)?.split(':')[0];
@@ -1,27 +1,42 @@
1
- import React from 'react';
1
+ import React, { useState } from 'react';
2
2
  import { Button } from '@carbon/react';
3
3
  import { Printer } from '@carbon/react/icons';
4
4
  import { useTranslation } from 'react-i18next';
5
- import { ConfigurableLink } from '@openmrs/esm-framework';
6
5
  import styles from './print-receipt.scss';
7
6
  import { apiBasePath } from '../../constants';
8
7
 
9
8
  interface PrintReceiptProps {
10
9
  billId: number;
11
10
  }
11
+
12
12
  const PrintReceipt: React.FC<PrintReceiptProps> = ({ billId }) => {
13
13
  const { t } = useTranslation();
14
+ const [isRedirecting, setIsRedirecting] = useState(false);
15
+ const baseUrl = new URL(window.location.href);
16
+
17
+ const handlePrintReceiptClick = () => {
18
+ setIsRedirecting(true);
19
+ setTimeout(() => {
20
+ const pdfUrl = `${baseUrl.origin}/openmrs${apiBasePath}receipt?billId=${billId}`;
21
+ const link = document.createElement('a');
22
+ link.href = pdfUrl;
23
+ link.download = `receipt_${billId}.pdf`;
24
+ document.body.appendChild(link);
25
+ link.click();
26
+ document.body.removeChild(link);
27
+ setIsRedirecting(false);
28
+ }, 1000);
29
+ };
30
+
14
31
  return (
15
32
  <Button
16
33
  kind="secondary"
17
34
  className={styles.button}
18
35
  size="md"
19
- renderIcon={(props) => <Printer size={24} {...props} />}>
20
- <ConfigurableLink
21
- className={styles.configurableLink}
22
- to={`\${openmrsBase}${apiBasePath}receipt?billId=${billId}`}>
23
- {t('printReceipt', 'Print receipt')}
24
- </ConfigurableLink>{' '}
36
+ renderIcon={(props) => <Printer size={24} {...props} />}
37
+ onClick={handlePrintReceiptClick}
38
+ disabled={isRedirecting}>
39
+ {isRedirecting ? t('loading', 'Loading') : t('printReceipt', 'Print receipt')}
25
40
  </Button>
26
41
  );
27
42
  };
@@ -0,0 +1,50 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import PrintReceipt from './print-receipt.component';
5
+
6
+ jest.mock('react-i18next', () => ({
7
+ useTranslation: jest.fn(),
8
+ }));
9
+
10
+ jest.mock('@carbon/react/icons', () => ({
11
+ Printer: jest.fn(() => <div data-testid="printer-icon" />),
12
+ }));
13
+
14
+ describe('PrintReceipt', () => {
15
+ const mockT = jest.fn((key) => key);
16
+
17
+ beforeEach(() => {
18
+ (useTranslation as jest.Mock).mockReturnValue({ t: mockT });
19
+ jest.useFakeTimers();
20
+ window.URL.createObjectURL = jest.fn();
21
+ });
22
+
23
+ afterEach(() => {
24
+ jest.useRealTimers();
25
+ });
26
+
27
+ it('renders button with correct text and icon', () => {
28
+ render(<PrintReceipt billId={123} />);
29
+ expect(screen.getByText('printReceipt')).toBeInTheDocument();
30
+ expect(screen.getByTestId('printer-icon')).toBeInTheDocument();
31
+ });
32
+
33
+ it('displays "Loading" and disables button when isRedirecting is true', () => {
34
+ render(<PrintReceipt billId={123} />);
35
+ const button = screen.getByRole('button');
36
+ fireEvent.click(button);
37
+ expect(screen.getByText('loading')).toBeInTheDocument();
38
+ expect(button).toBeDisabled();
39
+ });
40
+
41
+ it('applies correct CSS class to button', () => {
42
+ render(<PrintReceipt billId={123} />);
43
+ expect(screen.getByRole('button')).toHaveClass('button');
44
+ });
45
+
46
+ it('translates button text correctly', () => {
47
+ render(<PrintReceipt billId={123} />);
48
+ expect(mockT).toHaveBeenCalledWith('printReceipt', 'Print receipt');
49
+ });
50
+ });
@@ -1,14 +1,16 @@
1
1
  import React from 'react';
2
- import styles from './card.scss';
3
2
  import { useConfig } from '@openmrs/esm-framework';
4
3
  import { convertToCurrency } from '../helpers';
4
+ import styles from './card.scss';
5
5
 
6
6
  export default function Card({ count, title }) {
7
7
  const { defaultCurrency } = useConfig();
8
8
  return (
9
9
  <div className={styles.container}>
10
10
  <h1 className={styles.title}>{title}</h1>
11
- <span className={styles.count}>{convertToCurrency(count, defaultCurrency)}</span>
11
+ <span className={styles.count}>
12
+ {typeof count === 'number' ? convertToCurrency(count, defaultCurrency) : count}
13
+ </span>
12
14
  </div>
13
15
  );
14
16
  }
@@ -1,9 +1,9 @@
1
1
  import React from 'react';
2
2
  import { render, screen } from '@testing-library/react';
3
+ import { useConfig } from '@openmrs/esm-framework';
3
4
  import { billsSummary } from '../../__mocks__/bills.mock';
4
5
  import { useBills } from '../billing.resource';
5
6
  import MetricsCards from './metrics-cards.component';
6
- import { useConfig } from '@openmrs/esm-framework';
7
7
 
8
8
  const mockUseBills = useBills as jest.Mock;
9
9
  const mockUseConfig = useConfig as jest.Mock;
@@ -12,10 +12,10 @@ import {
12
12
  StructuredListRow,
13
13
  StructuredListWrapper,
14
14
  } from '@carbon/react';
15
+ import { useConfig } from '@openmrs/esm-framework';
15
16
  import { useBills } from '../billing.resource';
16
17
  import { convertToCurrency } from '../helpers';
17
18
  import styles from './require-payment.scss';
18
- import { useConfig } from '@openmrs/esm-framework';
19
19
 
20
20
  type RequirePaymentModalProps = {
21
21
  closeModal: () => void;
@@ -25,7 +25,7 @@ type RequirePaymentModalProps = {
25
25
  const RequirePaymentModal: React.FC<RequirePaymentModalProps> = ({ closeModal, patientUuid }) => {
26
26
  const { t } = useTranslation();
27
27
  const { defaultCurrency } = useConfig();
28
- const { bills, isLoading, error } = useBills(patientUuid);
28
+ const { bills, isLoading } = useBills(patientUuid);
29
29
  const lineItems = bills.filter((bill) => bill?.status !== 'PAID').flatMap((bill) => bill?.lineItems);
30
30
 
31
31
  return (
@@ -0,0 +1,66 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import { useBills } from '../billing.resource';
5
+ import RequirePaymentModal from './require-payment-modal.component';
6
+
7
+ jest.mock('react-i18next', () => ({
8
+ useTranslation: () => ({ t: (key: string) => key }),
9
+ }));
10
+
11
+ jest.mock('@openmrs/esm-framework', () => ({
12
+ useConfig: () => ({ defaultCurrency: 'USD' }),
13
+ }));
14
+
15
+ jest.mock('../billing.resource', () => ({
16
+ useBills: jest.fn(),
17
+ }));
18
+
19
+ jest.mock('../helpers', () => ({
20
+ convertToCurrency: (value, currency) => `${currency} ${value.toFixed(2)}`,
21
+ }));
22
+
23
+ describe('RequirePaymentModal', () => {
24
+ const closeModal = jest.fn();
25
+ const patientUuid = '12345';
26
+
27
+ beforeEach(() => {
28
+ jest.clearAllMocks();
29
+ });
30
+
31
+ it('renders correctly', () => {
32
+ (useBills as jest.Mock).mockReturnValue({ bills: [], isLoading: false, error: null });
33
+ render(<RequirePaymentModal closeModal={closeModal} patientUuid={patientUuid} />);
34
+ expect(screen.getByText('patientBillingAlert')).toBeInTheDocument();
35
+ });
36
+
37
+ it('displays loading state', () => {
38
+ (useBills as jest.Mock).mockReturnValue({ bills: [], isLoading: true, error: null });
39
+ render(<RequirePaymentModal closeModal={closeModal} patientUuid={patientUuid} />);
40
+ expect(screen.getByText('inlineLoading')).toBeInTheDocument();
41
+ });
42
+
43
+ it('displays line items', () => {
44
+ const bills = [
45
+ {
46
+ status: 'UNPAID',
47
+ lineItems: [
48
+ { billableService: 'Service 1', quantity: 1, price: 100 },
49
+ { item: 'Item 1', quantity: 2, price: 50 },
50
+ ],
51
+ },
52
+ ];
53
+ (useBills as jest.Mock).mockReturnValue({ bills, isLoading: false, error: null });
54
+ render(<RequirePaymentModal closeModal={closeModal} patientUuid={patientUuid} />);
55
+ expect(screen.getByText('Service 1')).toBeInTheDocument();
56
+ expect(screen.getByText('Item 1')).toBeInTheDocument();
57
+ });
58
+
59
+ it('handles closeModal', () => {
60
+ (useBills as jest.Mock).mockReturnValue({ bills: [], isLoading: false, error: null });
61
+ render(<RequirePaymentModal closeModal={closeModal} patientUuid={patientUuid} />);
62
+ fireEvent.click(screen.getByText('cancel'));
63
+ fireEvent.click(screen.getByText('ok'));
64
+ expect(closeModal).toHaveBeenCalledTimes(2);
65
+ });
66
+ });
@@ -1,6 +1,7 @@
1
1
  @use '@carbon/type';
2
+ @use '@carbon/layout';
2
3
 
3
4
  .bodyShort02 {
4
- margin-bottom: 0.5rem;
5
+ margin-bottom: layout.$spacing-03;
5
6
  @include type.type-style('body-compact-02');
6
7
  }
package/src/routes.json CHANGED
@@ -2,12 +2,12 @@
2
2
  "$schema": "https://json.openmrs.org/routes.schema.json",
3
3
  "backendDependencies": {
4
4
  "webservices.rest": ">=2.24.0",
5
- "fhir2": "^1.2.0"
5
+ "fhir2": ">=1.2"
6
6
  },
7
7
  "pages": [
8
8
  {
9
9
  "component": "billableServicesHome",
10
- "route":"billable-services"
10
+ "route": "billable-services"
11
11
  }
12
12
  ],
13
13
  "extensions": [
@@ -49,20 +49,25 @@
49
49
  },
50
50
  "featureFlag": "billing"
51
51
  },
52
+ {
53
+ "name": "billable-services-app-menu-item",
54
+ "component": "billableServicesAppMenuItem",
55
+ "slot": "app-menu-item-slot",
56
+ "meta": {
57
+ "name": "Billable Services"
58
+ }
59
+ },
52
60
  {
53
61
  "name": "billing-checkin-form",
54
62
  "slot": "extra-visit-attribute-slot",
55
- "component": "billingCheckInForm"
63
+ "component": "billingCheckInForm",
64
+ "featureFlag": "billing"
56
65
  },
57
66
  {
58
67
  "slot": "system-admin-page-card-link-slot",
59
68
  "component": "billableServicesCardLink",
60
69
  "name": "billable-services-admin-card-link"
61
70
  },
62
- {
63
- "name": "require-billing-modal",
64
- "component": "requirePaymentModal"
65
- },
66
71
  {
67
72
  "name": "patient-banner-billing-tags",
68
73
  "component": "visitAttributeTags",
@@ -73,6 +78,33 @@
73
78
  "name": "billing-home-tiles-ext",
74
79
  "slot": "billing-home-tiles-slot",
75
80
  "component": "serviceMetrics"
81
+ },
82
+ {
83
+ "name": "edit-bill-line-item-dialog",
84
+ "component": "editBillLineItemDialog",
85
+ "online": true,
86
+ "offline": true
87
+ }
88
+ ],
89
+ "modals": [
90
+ {
91
+ "name": "require-billing-modal",
92
+ "component": "requirePaymentModal"
93
+ }
94
+ ],
95
+ "workspaces": [
96
+ {
97
+ "name": "billing-form-workspace",
98
+ "title": "billingForm",
99
+ "component": "billingFormWorkspace",
100
+ "type": "form"
101
+ }
102
+ ],
103
+ "featureFlags": [
104
+ {
105
+ "flagName": "billing",
106
+ "label": "Billing module",
107
+ "description": "This feature introduces navigation links on the patient chart and home page to allow accessing the billing module features"
76
108
  }
77
109
  ]
78
- }
110
+ }
@@ -156,6 +156,7 @@ export interface FacilityDetail {
156
156
  }
157
157
 
158
158
  export type ServiceConcept = {
159
+ uuid: any;
159
160
  concept: {
160
161
  uuid: string;
161
162
  display: string;
@@ -179,3 +180,17 @@ export type ServicePrice = {
179
180
  price: string;
180
181
  uuid: string;
181
182
  };
183
+
184
+ export interface BillableService {
185
+ uuid: string;
186
+ name: string;
187
+ shortName: string;
188
+ serviceStatus: string;
189
+ serviceType?: {
190
+ display: string;
191
+ };
192
+ servicePrices: Array<{
193
+ name: string;
194
+ price: number;
195
+ }>;
196
+ }
@@ -1,14 +1,14 @@
1
1
  module.exports = {
2
- contextSeparator: "_",
2
+ contextSeparator: '_',
3
3
  // Key separator used in your translation keys
4
4
 
5
5
  createOldCatalogs: false,
6
6
  // Save the \_old files
7
7
 
8
- defaultNamespace: "translations",
8
+ defaultNamespace: 'translations',
9
9
  // Default namespace used in your i18next config
10
10
 
11
- defaultValue: "",
11
+ defaultValue: '',
12
12
  // Default value to give to empty keys
13
13
  // You may also specify a function accepting the locale, namespace, and key as arguments
14
14
 
@@ -18,43 +18,43 @@ module.exports = {
18
18
  keepRemoved: false,
19
19
  // Keep keys from the catalog that are no longer in code
20
20
 
21
- keySeparator: ".",
21
+ keySeparator: '.',
22
22
  // Key separator used in your translation keys
23
23
  // If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance.
24
24
 
25
25
  // see below for more details
26
26
  lexers: {
27
- hbs: ["HandlebarsLexer"],
28
- handlebars: ["HandlebarsLexer"],
27
+ hbs: ['HandlebarsLexer'],
28
+ handlebars: ['HandlebarsLexer'],
29
29
 
30
- htm: ["HTMLLexer"],
31
- html: ["HTMLLexer"],
30
+ htm: ['HTMLLexer'],
31
+ html: ['HTMLLexer'],
32
32
 
33
- mjs: ["JavascriptLexer"],
34
- js: ["JavascriptLexer"], // if you're writing jsx inside .js files, change this to JsxLexer
35
- ts: ["JavascriptLexer"],
36
- jsx: ["JsxLexer"],
37
- tsx: ["JsxLexer"],
33
+ mjs: ['JavascriptLexer'],
34
+ js: ['JavascriptLexer'], // if you're writing jsx inside .js files, change this to JsxLexer
35
+ ts: ['JavascriptLexer'],
36
+ jsx: ['JsxLexer'],
37
+ tsx: ['JsxLexer'],
38
38
 
39
- default: ["JavascriptLexer"],
39
+ default: ['JavascriptLexer'],
40
40
  },
41
41
 
42
- lineEnding: "auto",
42
+ lineEnding: 'auto',
43
43
  // Control the line ending. See options at https://github.com/ryanve/eol
44
44
 
45
- locales: ["en", "am", "es", "fr", "km", "he"],
45
+ locales: ['en'],
46
46
  // An array of the locales in your applications
47
47
 
48
- namespaceSeparator: ":",
48
+ namespaceSeparator: ':',
49
49
  // Namespace separator used in your translation keys
50
50
  // If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance.
51
51
 
52
- output: "$NAMESPACE/$LOCALE.json",
52
+ output: '$NAMESPACE/$LOCALE.json',
53
53
  // Supports $LOCALE and $NAMESPACE injection
54
54
  // Supports JSON (.json) and YAML (.yml) file formats
55
55
  // Where to write the locale files relative to process.cwd()
56
56
 
57
- pluralSeparator: "_",
57
+ pluralSeparator: '_',
58
58
  // Plural separator used in your translation keys
59
59
  // If you want to use plain english keys, separators such as `_` might conflict. You might want to set `pluralSeparator` to a different string that does not occur in your keys.
60
60
 
@@ -0,0 +1,42 @@
1
+ import { execSync } from 'node:child_process';
2
+
3
+ try {
4
+ execSync(`yarn up --fixed '@openmrs/*@next' 'openmrs@next'`, {
5
+ stdio: ['ignore', 'inherit', 'inherit'],
6
+ windowsHide: true,
7
+ });
8
+ } catch (error) {
9
+ console.error(`Error while updating dependencies: ${error.message ?? error}`);
10
+ process.exit(1);
11
+ }
12
+
13
+ try {
14
+ execSync(`yarn dedupe`, {
15
+ stdio: ['ignore', 'inherit', 'inherit'],
16
+ windowsHide: true,
17
+ });
18
+ } catch (error) {
19
+ console.error(`Error while deduplicating dependencies: ${error.message ?? error}`);
20
+ process.exit(1);
21
+ }
22
+
23
+ try {
24
+ execSync(`git diff-index --quiet HEAD --`, {
25
+ stdio: 'ignore',
26
+ windowsHide: true,
27
+ });
28
+ process.exit(0);
29
+ } catch (error) {
30
+ // git diff-index --quite HEAD --
31
+ // exits with status 1 if there are changes; we only need to run yarn verify if there are changes
32
+ }
33
+
34
+ try {
35
+ execSync(`yarn verify`, {
36
+ stdio: ['ignore', 'inherit', 'inherit'],
37
+ windowsHide: true,
38
+ });
39
+ } catch (error) {
40
+ console.error(`Error while running yarn verify: ${error.message ?? error}. Updates require manual intervention.`);
41
+ process.exit(1);
42
+ }
@@ -2,10 +2,13 @@
2
2
  "actions": "Actions",
3
3
  "addBill": "Add bill item(s)",
4
4
  "addBillableServices": "Add Billable Services",
5
+ "addCashPoint": "Add Cash Point",
5
6
  "addNewBillableService": "Add new billable service",
6
7
  "addNewService": "Add new service",
8
+ "addPaymentMode": "Add Payment Mode",
7
9
  "addPaymentOptions": "Add payment option",
8
10
  "amount": "Amount",
11
+ "amountDue": "Amount Due",
9
12
  "amountToWaiveAriaLabel": "Enter amount to waive",
10
13
  "amountToWaiveHelper": "Specify the amount to be deducted from the bill",
11
14
  "amountToWaiveLabel": "Amount to Waive",
@@ -17,12 +20,15 @@
17
20
  "billing": "Billing",
18
21
  "billingForm": "Billing form",
19
22
  "billingHistory": "Billing History",
23
+ "billingSettings": "Billing Settings",
20
24
  "billItem": "Bill item",
21
25
  "billItems": "Bill Items",
26
+ "billLineItemEmpty": "This bill has no line items",
22
27
  "billList": "Bill list",
23
28
  "billMetrics": "Bill metrics",
24
29
  "billName": " {{billName}} ",
25
30
  "billPayment": "Bill payment",
31
+ "billPaymentError": "Bill payment error",
26
32
  "billPaymentRequiredMessage": "The current patient has pending bill. Advice patient to settle bill before receiving services",
27
33
  "billServicesManagement": "Bill services management",
28
34
  "billsList": "Bill list",
@@ -31,15 +37,44 @@
31
37
  "billWaiverError": "Bill waiver failed {{error}}",
32
38
  "billWaiverSuccess": "Bill waiver successful",
33
39
  "cancel": "Cancel",
40
+ "cashPointConfig": "Cash Point Config",
41
+ "cashPointHistory": "Cash Point History",
42
+ "cashPointLocation": "Cash Point Location",
43
+ "cashPointName": "Cash Point Name",
44
+ "cashPointNamePlaceholder": "e.g., Pharmacy Cash Point",
45
+ "cashPointSaved": "Cash point was successfully saved.",
46
+ "cashPointUuid": "Cash Point UUID",
47
+ "cashPointUuidPlaceholder": "Enter UUID",
34
48
  "checkFilters": "Check the filters above",
35
49
  "clearSearchInput": "Clear search input",
50
+ "clientBalance": "Client Balance",
51
+ "confirmDeleteMessage": "Are you sure you want to delete this payment mode? Proceed cautiously.",
52
+ "createdSuccessfully": "Billable service created successfully",
53
+ "currentPrice": "Current price",
54
+ "delete": "Delete",
55
+ "deletePaymentMode": "Delete Payment Mode",
56
+ "description": "Description",
57
+ "descriptionPlaceholder": "e.g., Used for all cash transactions",
36
58
  "discard": "Discard",
37
59
  "discount": "Discount",
60
+ "duplicateCashPointError": "A cash point with the same name or UUID already exists. Please use a unique name and UUID.",
61
+ "duplicatePaymentModeError": "A payment mode with the same name already exists. Please create another payment mode",
62
+ "editBillableService": "Edit billable service",
63
+ "editBillableServices": "Edit Billable Services",
64
+ "editBillLineItem": "Edit bill line item?",
65
+ "editThisBillItem": "Edit this bill item",
38
66
  "enterAmount": "Enter amount",
39
67
  "enterConcept": "Associated concept",
40
68
  "enterReferenceNumber": "Enter ref. number",
69
+ "error": "Error",
70
+ "errorDeletingPaymentMode": "An error occurred while deleting the payment mode.",
71
+ "errorFetchingCashPoints": "An error occurred while fetching cash points.",
72
+ "errorFetchingLocations": "An error occurred while fetching locations.",
73
+ "errorFetchingPaymentModes": "An error occurred while fetching payment modes.",
41
74
  "errorLoadingBillServices": "Error loading bill services",
42
75
  "errorLoadingPaymentModes": "Payment modes error",
76
+ "errorSavingCashPoint": "An error occurred while saving the cash point.",
77
+ "errorSavingPaymentMode": "An error occurred while saving the payment mode.",
43
78
  "filterBy": "Filter by",
44
79
  "filterTable": "Filter table",
45
80
  "grandTotal": "Grand total",
@@ -57,6 +92,7 @@
57
92
  "loading": "Loading data...",
58
93
  "loadingBillingServices": "Loading billing services...",
59
94
  "loadingDescription": "Loading",
95
+ "location": "Select Location",
60
96
  "manageBillableServices": "Manage billable services",
61
97
  "name": "Name",
62
98
  "nextPage": "Next page",
@@ -76,16 +112,26 @@
76
112
  "paymentMethod": "Payment method",
77
113
  "paymentMethods": "Payment methods",
78
114
  "paymentMode": "Payment Mode",
115
+ "paymentModeDeleted": "Payment mode was successfully deleted.",
116
+ "paymentModeHistory": "Payment Mode History",
117
+ "paymentModeName": "Payment Mode Name",
118
+ "paymentModeNamePlaceholder": "e.g., Cash, Credit Card",
119
+ "paymentModeSaved": "Payment mode was successfully saved.",
120
+ "paymentModesConfig": "Payment Modes Config",
79
121
  "payments": "Payments",
122
+ "pleaseRequiredFields": "Please fill all required fields",
80
123
  "policyNumber": "Policy number",
81
124
  "postWaiver": "Post waiver",
82
125
  "previousPage": "Previous page",
126
+ "price": "Price",
127
+ "priceIsRequired": "Price is required",
83
128
  "prices": "Prices",
84
129
  "printBill": "Print bill",
85
130
  "printReceipt": "Print receipt",
86
131
  "processPayment": "Process Payment",
87
132
  "quantity": "Quantity",
88
133
  "quantityGreaterThanZero": "Quantity must be greater than zero",
134
+ "quantityRequired": "Quantity is required",
89
135
  "referenceNumber": "Reference number",
90
136
  "save": "Save",
91
137
  "saveAndClose": "Save and close",
@@ -96,22 +142,29 @@
96
142
  "searchThisTable": "Search this table",
97
143
  "selectBillableService": "Select a billable service...",
98
144
  "selectCategory": "Select category",
145
+ "selectPatientCategory": "Select patient category",
99
146
  "selectPaymentMethod": "Select payment method",
100
147
  "sellingAmount": "Enter selling price",
101
148
  "sellingPrice": "Selling Price",
102
149
  "service": "Service",
103
150
  "serviceMetrics": "Service Metrics",
104
151
  "serviceName": "Service Name",
152
+ "serviceNameExceedsLimit": "Service Name exceeds the character limit of {{MAX_NAME_LENGTH}}.",
105
153
  "serviceShortName": "Short Name",
106
154
  "servicesList": "Services list",
107
155
  "serviceType": "Service Type",
108
156
  "shortName": "Short Name",
157
+ "shortNameExceedsLimit": "Short Name exceeds the character limit of {{MAX_NAME_LENGTH}}.",
109
158
  "status": "Service Status",
110
159
  "stockItem": "Stock Item",
160
+ "submitting": "Submitting...",
161
+ "success": "Success",
111
162
  "total": "Total",
112
163
  "totalAmount": "Total Amount",
113
164
  "totalTendered": "Total Tendered",
114
165
  "unitPrice": "Unit price",
166
+ "updatedSuccessfully": "Billable service updated successfully",
167
+ "uuid": "UUID",
115
168
  "visitTime": "Visit time",
116
169
  "waiverForm": "Waiver form"
117
170
  }