@openmrs/esm-billing-app 1.0.2-pre.74 → 1.0.2-pre.740

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 (196) 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 -0
  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 -0
  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 +381 -231
  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} +58 -56
  89. package/src/bill-item-actions/edit-bill-item.test.tsx +22 -24
  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 +4 -3
  93. package/src/billable-services/billable-services-home.component.tsx +1 -1
  94. package/src/billable-services/billable-services.component.tsx +115 -132
  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 +17 -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 +28 -24
  101. package/src/billable-services/create-edit/add-billable-service.scss +2 -5
  102. package/src/billable-services/create-edit/add-billable-service.test.tsx +6 -6
  103. package/src/billable-services/create-edit/edit-billable-service.modal.tsx +50 -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 -1
  108. package/src/billing-form/billing-checkin-form.component.tsx +2 -3
  109. package/src/billing-form/billing-checkin-form.test.tsx +0 -2
  110. package/src/billing-form/billing-form.component.tsx +210 -268
  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 +2 -9
  125. package/src/invoice/payments/payment-form/payment-form.test.tsx +14 -46
  126. package/src/invoice/payments/payment-history/payment-history.component.tsx +6 -4
  127. package/src/invoice/payments/payment-history/payment-history.test.tsx +9 -14
  128. package/src/invoice/payments/payments.component.tsx +16 -27
  129. package/src/invoice/payments/{payments.component.test.tsx → payments.test.tsx} +24 -10
  130. package/src/invoice/payments/utils.ts +4 -22
  131. package/src/invoice/printable-invoice/print-receipt.component.tsx +3 -2
  132. package/src/invoice/printable-invoice/print-receipt.test.tsx +14 -25
  133. package/src/invoice/printable-invoice/printable-footer.component.tsx +2 -2
  134. package/src/invoice/printable-invoice/printable-footer.test.tsx +4 -13
  135. package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +12 -11
  136. package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +16 -14
  137. package/src/invoice/printable-invoice/printable-invoice.component.tsx +19 -33
  138. package/src/metrics-cards/metrics-cards.test.tsx +18 -5
  139. package/src/modal/require-payment-modal.test.tsx +25 -20
  140. package/src/modal/{require-payment-modal.component.tsx → require-payment.modal.tsx} +17 -18
  141. package/src/routes.json +22 -2
  142. package/src/types/index.ts +13 -12
  143. package/translations/am.json +33 -16
  144. package/translations/ar.json +33 -16
  145. package/translations/ar_SY.json +33 -16
  146. package/translations/bn.json +38 -21
  147. package/translations/de.json +33 -16
  148. package/translations/en.json +33 -16
  149. package/translations/en_US.json +187 -0
  150. package/translations/es.json +48 -31
  151. package/translations/es_MX.json +33 -16
  152. package/translations/fr.json +47 -30
  153. package/translations/he.json +33 -16
  154. package/translations/hi.json +33 -16
  155. package/translations/hi_IN.json +33 -16
  156. package/translations/id.json +180 -163
  157. package/translations/it.json +70 -53
  158. package/translations/ka.json +187 -0
  159. package/translations/km.json +33 -16
  160. package/translations/ku.json +33 -16
  161. package/translations/ky.json +33 -16
  162. package/translations/lg.json +33 -16
  163. package/translations/ne.json +33 -16
  164. package/translations/pl.json +33 -16
  165. package/translations/pt.json +33 -16
  166. package/translations/pt_BR.json +33 -16
  167. package/translations/qu.json +33 -16
  168. package/translations/ro_RO.json +182 -165
  169. package/translations/ru_RU.json +33 -16
  170. package/translations/si.json +33 -16
  171. package/translations/sw.json +33 -16
  172. package/translations/sw_KE.json +33 -16
  173. package/translations/tr.json +33 -16
  174. package/translations/tr_TR.json +33 -16
  175. package/translations/uk.json +33 -16
  176. package/translations/uz.json +33 -16
  177. package/translations/uz@Latn.json +33 -16
  178. package/translations/uz_UZ.json +33 -16
  179. package/translations/vi.json +33 -16
  180. package/translations/zh.json +33 -16
  181. package/translations/zh_CN.json +91 -74
  182. package/dist/1146.js.LICENSE.txt +0 -21
  183. package/dist/2352.js +0 -1
  184. package/dist/2352.js.map +0 -1
  185. package/dist/246.js +0 -1
  186. package/dist/246.js.map +0 -1
  187. package/dist/6525.js +0 -2
  188. package/dist/6525.js.map +0 -1
  189. package/dist/8556.js +0 -2
  190. package/dist/8556.js.map +0 -1
  191. package/dist/8638.js +0 -1
  192. package/dist/8638.js.map +0 -1
  193. package/dist/9968.js +0 -1
  194. package/dist/9968.js.map +0 -1
  195. package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +0 -280
  196. /package/dist/{8556.js.LICENSE.txt → 7452.js.LICENSE.txt} +0 -0
@@ -1,17 +1,23 @@
1
1
  import React from 'react';
2
2
  import userEvent from '@testing-library/user-event';
3
3
  import { render, screen } from '@testing-library/react';
4
+ import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
5
+ import { configSchema, type BillingConfig } from '../config-schema';
4
6
  import { useBills } from '../billing.resource';
5
7
  import BillHistory from './bill-history.component';
6
8
 
7
- // Mock i18next
8
- jest.mock('react-i18next', () => ({
9
- useTranslation: () => ({
10
- t: (key: string) => key,
11
- }),
9
+ const mockUseConfig = jest.mocked(useConfig<BillingConfig>);
10
+ const mockUseBills = jest.mocked(useBills);
11
+
12
+ jest.mock('../billing.resource', () => ({
13
+ useBills: jest.fn(() => ({
14
+ bills: mockBillData,
15
+ isLoading: false,
16
+ isValidating: false,
17
+ error: null,
18
+ })),
12
19
  }));
13
20
 
14
- // Mock window.i18next
15
21
  window.i18next = {
16
22
  language: 'en-US',
17
23
  } as any;
@@ -20,9 +26,7 @@ const testProps = {
20
26
  patientUuid: 'some-uuid',
21
27
  };
22
28
 
23
- const mockbills = useBills as jest.MockedFunction<typeof useBills>;
24
-
25
- const mockBillsData = [
29
+ const mockBillData = [
26
30
  { uuid: '1', patientName: 'John Doe', identifier: '12345678', billingService: 'Checkup', totalAmount: 500 },
27
31
  { uuid: '2', patientName: 'John Doe', identifier: '12345678', billingService: 'Consulatation', totalAmount: 600 },
28
32
  { uuid: '3', patientName: 'John Doe', identifier: '12345678', billingService: 'Child services', totalAmount: 700 },
@@ -37,70 +41,19 @@ const mockBillsData = [
37
41
  { uuid: '12', patientName: 'John Doe', identifier: '12345678', billingService: 'MCH', totalAmount: 1300 },
38
42
  ];
39
43
 
40
- // Mock the invoice table component
41
- jest.mock('../invoice/invoice-table.component', () => jest.fn(() => <div>Invoice table</div>));
42
-
43
- // Mock the billing resource
44
- jest.mock('../billing.resource', () => ({
45
- useBills: jest.fn(() => ({
46
- bills: mockBillsData,
47
- isLoading: false,
48
- isValidating: false,
49
- error: null,
50
- })),
51
- }));
52
-
53
- // Mock esm-patient-common-lib
54
- jest.mock('@openmrs/esm-patient-common-lib', () => ({
55
- CardHeader: jest.fn(({ children }) => <div>{children}</div>),
56
- EmptyDataIllustration: jest.fn(() => <div>Empty state illustration</div>),
57
- ErrorState: jest.fn(({ error }) => <div>Error: {error?.message}</div>),
58
- launchPatientWorkspace: jest.fn(),
59
- usePaginationInfo: jest.fn(() => ({
60
- pageSizes: [10, 20, 30],
61
- currentPage: 1,
62
- })),
63
- }));
64
-
65
- // Mock esm-framework
66
- jest.mock('@openmrs/esm-framework', () => ({
67
- useLayoutType: jest.fn(() => 'small-desktop'),
68
- isDesktop: jest.fn(() => true),
69
- usePagination: jest.fn().mockImplementation((data) => ({
70
- currentPage: 1,
71
- goTo: jest.fn(),
72
- results: data,
73
- paginated: true,
74
- })),
75
- showToast: jest.fn(),
76
- showNotification: jest.fn(),
77
- createErrorHandler: jest.fn(),
78
- createGlobalStore: jest.fn(),
79
- getGlobalStore: jest.fn(() => ({
80
- subscribe: jest.fn(),
81
- getState: jest.fn(),
82
- setState: jest.fn(),
83
- })),
84
- useConfig: jest.fn(() => ({
85
- pageSize: 10,
86
- defaultCurrency: 'USD',
87
- })),
88
- useSession: jest.fn(() => ({
89
- sessionLocation: { uuid: 'some-uuid', display: 'Location' },
90
- })),
91
- formatDate: jest.fn((date) => date?.toString() ?? ''),
92
- formatDatetime: jest.fn((date) => date?.toString() ?? ''),
93
- parseDate: jest.fn((dateString) => new Date(dateString)),
94
- ExtensionSlot: jest.fn(({ children }) => <>{children}</>),
95
- }));
96
-
97
44
  describe('BillHistory', () => {
98
- afterEach(() => {
99
- jest.clearAllMocks();
45
+ beforeEach(() => {
46
+ mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(configSchema), defaultCurrency: 'USD' });
100
47
  });
101
48
 
102
49
  test('should render loading datatable skeleton', () => {
103
- mockbills.mockReturnValueOnce({ isLoading: true, isValidating: false, error: null, bills: [], mutate: jest.fn() });
50
+ mockUseBills.mockReturnValueOnce({
51
+ isLoading: true,
52
+ isValidating: false,
53
+ error: null,
54
+ bills: [],
55
+ mutate: jest.fn(),
56
+ });
104
57
  render(<BillHistory {...testProps} />);
105
58
  const loadingSkeleton = screen.getByRole('table');
106
59
  expect(loadingSkeleton).toBeInTheDocument();
@@ -108,7 +61,7 @@ describe('BillHistory', () => {
108
61
  });
109
62
 
110
63
  test('should render error state when API call fails', () => {
111
- mockbills.mockReturnValueOnce({
64
+ mockUseBills.mockReturnValueOnce({
112
65
  isLoading: false,
113
66
  isValidating: false,
114
67
  error: new Error('some error'),
@@ -116,31 +69,31 @@ describe('BillHistory', () => {
116
69
  mutate: jest.fn(),
117
70
  });
118
71
  render(<BillHistory {...testProps} />);
119
- const errorState = screen.getByText('Error: some error');
72
+ const errorState = screen.getByText(/Error/);
120
73
  expect(errorState).toBeInTheDocument();
121
74
  });
122
75
 
123
76
  test('should render bills table', async () => {
124
77
  const user = userEvent.setup();
125
- mockbills.mockReturnValueOnce({
78
+ mockUseBills.mockReturnValueOnce({
126
79
  isLoading: false,
127
80
  isValidating: false,
128
81
  error: null,
129
- bills: mockBillsData as any,
82
+ bills: mockBillData as any,
130
83
  mutate: jest.fn(),
131
84
  });
132
85
  render(<BillHistory {...testProps} />);
133
86
 
134
87
  // Verify headers
135
- expect(screen.getByText('visitTime')).toBeInTheDocument();
136
- expect(screen.getByText('identifier')).toBeInTheDocument();
88
+ expect(screen.getByText('Visit time')).toBeInTheDocument();
89
+ expect(screen.getByText('Identifier')).toBeInTheDocument();
137
90
 
138
91
  const tableRowGroup = screen.getAllByRole('rowgroup');
139
92
  expect(tableRowGroup).toHaveLength(2);
140
93
 
141
94
  // Page navigation should work as expected
142
- const nextPageButton = screen.getByRole('button', { name: /nextPage/ });
143
- const prevPageButton = screen.getByRole('button', { name: /previousPage/ });
95
+ const nextPageButton = screen.getByRole('button', { name: /Next page/ });
96
+ const prevPageButton = screen.getByRole('button', { name: /Previous page/ });
144
97
 
145
98
  expect(nextPageButton).toBeInTheDocument();
146
99
  expect(prevPageButton).toBeInTheDocument();
@@ -156,7 +109,13 @@ describe('BillHistory', () => {
156
109
  });
157
110
 
158
111
  test('should render empty state view when there are no bills', () => {
159
- mockbills.mockReturnValueOnce({ isLoading: false, isValidating: false, error: null, bills: [], mutate: jest.fn() });
112
+ mockUseBills.mockReturnValueOnce({
113
+ isLoading: false,
114
+ isValidating: false,
115
+ error: null,
116
+ bills: [],
117
+ mutate: jest.fn(),
118
+ });
160
119
  render(<BillHistory {...testProps} />);
161
120
  const emptyState = screen.getByText(/There are no bills to display./);
162
121
  expect(emptyState).toBeInTheDocument();
@@ -2,10 +2,6 @@
2
2
  @use '@carbon/type';
3
3
  @use '@carbon/colors';
4
4
 
5
- .section {
6
- margin: layout.$spacing-03;
7
- }
8
-
9
5
  .sectionTitle {
10
6
  @include type.type-style('heading-compact-02');
11
7
  color: colors.$gray-70;
@@ -9,6 +9,7 @@ import {
9
9
  ModalFooter,
10
10
  ModalHeader,
11
11
  NumberInput,
12
+ Stack,
12
13
  TextInput,
13
14
  } from '@carbon/react';
14
15
  import { useTranslation } from 'react-i18next';
@@ -16,7 +17,7 @@ import { Controller, type FieldErrors, useForm } from 'react-hook-form';
16
17
  import { mutate } from 'swr';
17
18
  import { z } from 'zod';
18
19
  import { zodResolver } from '@hookform/resolvers/zod';
19
- import { showSnackbar } from '@openmrs/esm-framework';
20
+ import { getCoreTranslation, showSnackbar } from '@openmrs/esm-framework';
20
21
  import { apiBasePath } from '../constants';
21
22
  import { getBillableServiceUuid } from '../invoice/payments/utils';
22
23
  import { type LineItem, type MappedBill } from '../types';
@@ -24,13 +25,13 @@ import { updateBillItems } from '../billing.resource';
24
25
  import { useBillableServices } from '../billable-services/billable-service.resource';
25
26
  import styles from './bill-item-actions.scss';
26
27
 
27
- interface BillLineItemProps {
28
+ interface EditBillLineItemModalProps {
28
29
  bill: MappedBill;
29
- item: LineItem;
30
30
  closeModal: () => void;
31
+ item: LineItem;
31
32
  }
32
33
 
33
- const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) => {
34
+ const EditBillLineItemModal: React.FC<EditBillLineItemModalProps> = ({ bill, closeModal, item }) => {
34
35
  const { t } = useTranslation();
35
36
  const { billableServices } = useBillableServices();
36
37
  const [showErrorNotification, setShowErrorNotification] = useState(false);
@@ -56,7 +57,7 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
56
57
  const {
57
58
  control,
58
59
  handleSubmit,
59
- formState: { isSubmitting, errors, isDirty },
60
+ formState: { isSubmitting, errors },
60
61
  watch,
61
62
  } = useForm<BillLineItemForm>({
62
63
  defaultValues: {
@@ -74,7 +75,7 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
74
75
  setTotal(newTotal);
75
76
  }, [quantity, price]);
76
77
 
77
- const onSubmit = (data: BillLineItemForm) => {
78
+ const onSubmit = async (data: BillLineItemForm) => {
78
79
  const url = `${apiBasePath}bill`;
79
80
 
80
81
  const newItem = {
@@ -101,39 +102,42 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
101
102
  status: bill.status,
102
103
  uuid: bill.uuid,
103
104
  };
104
- updateBillItems(payload).then(
105
- (res) => {
106
- mutate((key) => typeof key === 'string' && key.startsWith(url), undefined, { revalidate: true });
107
- showSnackbar({
108
- title: t('billItems', 'Save Bill'),
109
- subtitle: 'Bill processing has been successful',
110
- kind: 'success',
111
- timeoutInMs: 3000,
112
- });
113
- closeModal();
114
- },
115
- (error) => {
116
- showSnackbar({ title: 'Bill processing error', kind: 'error', subtitle: error?.message });
117
- },
118
- );
105
+
106
+ try {
107
+ await updateBillItems(payload);
108
+ mutate((key) => typeof key === 'string' && key.startsWith(url), undefined, { revalidate: true });
109
+ showSnackbar({
110
+ title: t('saveBill', 'Save Bill'),
111
+ subtitle: t('billProcessingSuccess', 'Bill processing has been successful'),
112
+ kind: 'success',
113
+ timeoutInMs: 3000,
114
+ });
115
+ closeModal();
116
+ } catch (error) {
117
+ showSnackbar({
118
+ title: t('billProcessingError', 'Bill processing error'),
119
+ kind: 'error',
120
+ subtitle: error?.message,
121
+ });
122
+ }
119
123
  };
120
124
 
121
125
  if (Object.keys(bill)?.length === 0) {
122
126
  return <ModalHeader closeModal={closeModal} title={t('billLineItemEmpty', 'This bill has no line items')} />;
123
127
  }
124
128
 
125
- if (Object.keys(bill)?.length > 0) {
126
- return (
127
- <div>
128
- <Form onSubmit={handleSubmit(onSubmit, onError)}>
129
- <ModalHeader closeModal={closeModal} title={t('editBillLineItem', 'Edit bill line item?')} />
130
- <ModalBody>
129
+ return (
130
+ <>
131
+ <ModalHeader closeModal={closeModal} title={t('editBillLineItem', 'Edit bill line item?')} />
132
+ <Form onSubmit={handleSubmit(onSubmit, onError)}>
133
+ <ModalBody>
134
+ <Stack gap={5}>
131
135
  <div className={styles.modalBody}>
132
136
  <h5>
133
137
  {bill?.patientName} &nbsp; · &nbsp;{bill?.cashPointName} &nbsp; · &nbsp;{bill?.receiptNumber}&nbsp;
134
138
  </h5>
135
139
  </div>
136
- <section className={styles.section}>
140
+ <section>
137
141
  <p className={styles.label}>
138
142
  {t('item', 'Item')} : {item?.billableService ? item?.billableService : item?.item}
139
143
  </p>
@@ -146,7 +150,7 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
146
150
  <Controller
147
151
  name="quantity"
148
152
  control={control}
149
- render={({ field: { onChange, onBlur, value } }) => (
153
+ render={({ field: { onChange, value } }) => (
150
154
  <NumberInput
151
155
  label={t('quantity', 'Quantity')}
152
156
  id="quantityInput"
@@ -171,7 +175,7 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
171
175
  value={value}
172
176
  readOnly={true}
173
177
  className={styles.controlField}
174
- helperText="This is the unit Price for this item."
178
+ helperText={t('unitPriceHelperText', 'This is the unit price for this item.')}
175
179
  />
176
180
  )}
177
181
  />
@@ -191,31 +195,29 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
191
195
  </Column>
192
196
  )}
193
197
  </section>
194
- </ModalBody>
195
- <ModalFooter>
196
- <Button kind="secondary" onClick={closeModal}>
197
- {t('cancel', 'Cancel')}
198
- </Button>
199
- <Button disabled={isSubmitting} type="submit">
200
- <>
201
- {isSubmitting ? (
202
- <div className={styles.inline}>
203
- <InlineLoading
204
- status="active"
205
- iconDescription={t('submitting', 'Submitting')}
206
- description={t('submitting', 'Submitting...')}
207
- />
208
- </div>
209
- ) : (
210
- t('save', 'Save')
211
- )}
212
- </>
213
- </Button>
214
- </ModalFooter>
215
- </Form>
216
- </div>
217
- );
218
- }
198
+ </Stack>
199
+ </ModalBody>
200
+ <ModalFooter>
201
+ <Button kind="secondary" onClick={closeModal}>
202
+ {getCoreTranslation('cancel')}
203
+ </Button>
204
+ <Button type="submit" disabled={isSubmitting}>
205
+ {isSubmitting ? (
206
+ <div className={styles.inline}>
207
+ <InlineLoading
208
+ status="active"
209
+ iconDescription={t('submitting', 'Submitting')}
210
+ description={t('submitting', 'Submitting') + '...'}
211
+ />
212
+ </div>
213
+ ) : (
214
+ getCoreTranslation('save')
215
+ )}
216
+ </Button>
217
+ </ModalFooter>
218
+ </Form>
219
+ </>
220
+ );
219
221
  };
220
222
 
221
- export default ChangeStatus;
223
+ export default EditBillLineItemModal;
@@ -1,19 +1,18 @@
1
1
  import React from 'react';
2
- import { render, screen, fireEvent, waitFor } from '@testing-library/react';
3
- import { showSnackbar } from '@openmrs/esm-framework';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { render, screen, waitFor } from '@testing-library/react';
4
+ import { type FetchResponse, showSnackbar } from '@openmrs/esm-framework';
4
5
  import { type MappedBill } from '../types';
5
6
  import { updateBillItems } from '../billing.resource';
6
- import ChangeStatus from './edit-bill-item.component';
7
+ import EditBillLineItemModal from './edit-bill-item.modal';
8
+
9
+ const mockUpdateBillItems = jest.mocked(updateBillItems);
10
+ const mockShowSnackbar = jest.mocked(showSnackbar);
7
11
 
8
- // Mock external dependencies
9
12
  jest.mock('../billing.resource', () => ({
10
13
  updateBillItems: jest.fn(() => Promise.resolve({})),
11
14
  }));
12
15
 
13
- jest.mock('@openmrs/esm-framework', () => ({
14
- showSnackbar: jest.fn(),
15
- }));
16
-
17
16
  jest.mock('../billable-services/billable-service.resource', () => ({
18
17
  useBillableServices: jest.fn(() => ({
19
18
  billableServices: [],
@@ -77,12 +76,8 @@ const mockItem = {
77
76
  describe('ChangeStatus component', () => {
78
77
  const closeModalMock = jest.fn();
79
78
 
80
- beforeEach(() => {
81
- jest.clearAllMocks();
82
- });
83
-
84
79
  test('renders the form with correct fields and default values', () => {
85
- render(<ChangeStatus bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
80
+ render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
86
81
 
87
82
  expect(screen.getByText('Edit bill line item?')).toBeInTheDocument();
88
83
  expect(screen.getByText('John Doe · Main Cashpoint · 123456')).toBeInTheDocument();
@@ -91,24 +86,26 @@ describe('ChangeStatus component', () => {
91
86
  expect(screen.getByText(/Total/)).toHaveTextContent('200');
92
87
  });
93
88
 
94
- test('updates total when quantity is changed', () => {
95
- render(<ChangeStatus bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
89
+ test('updates total when quantity is changed', async () => {
90
+ const user = userEvent.setup();
91
+ render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
96
92
 
97
93
  const quantityInput = screen.getByRole('spinbutton', { name: /Quantity/ });
98
- fireEvent.change(quantityInput, { target: { value: 3 } });
94
+ await user.type(quantityInput, '3');
99
95
 
100
96
  expect(screen.getByText(/Total/)).toHaveTextContent('300');
101
97
  });
102
98
 
103
99
  test('submits the form and shows a success notification', async () => {
104
- (updateBillItems as jest.Mock).mockResolvedValueOnce({});
100
+ const user = userEvent.setup();
101
+ mockUpdateBillItems.mockResolvedValueOnce({} as FetchResponse<any>);
105
102
 
106
- render(<ChangeStatus bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
103
+ render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
107
104
 
108
- fireEvent.click(screen.getByText(/Save/));
105
+ await user.click(screen.getByText(/Save/));
109
106
 
110
107
  await waitFor(() => {
111
- expect(updateBillItems).toHaveBeenCalled();
108
+ expect(mockUpdateBillItems).toHaveBeenCalled();
112
109
  expect(showSnackbar).toHaveBeenCalledWith({
113
110
  title: 'Save Bill',
114
111
  subtitle: 'Bill processing has been successful',
@@ -120,14 +117,15 @@ describe('ChangeStatus component', () => {
120
117
  });
121
118
 
122
119
  test('shows error notification when submission fails', async () => {
123
- (updateBillItems as jest.Mock).mockRejectedValueOnce({ message: 'Error occurred' });
120
+ const user = userEvent.setup();
121
+ mockUpdateBillItems.mockRejectedValueOnce({ message: 'Error occurred' });
124
122
 
125
- render(<ChangeStatus bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
123
+ render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
126
124
 
127
- fireEvent.click(screen.getByText(/Save/));
125
+ await user.click(screen.getByText(/Save/));
128
126
 
129
127
  await waitFor(() => {
130
- expect(showSnackbar).toHaveBeenCalledWith({
128
+ expect(mockShowSnackbar).toHaveBeenCalledWith({
131
129
  title: 'Bill processing error',
132
130
  kind: 'error',
133
131
  subtitle: 'Error occurred',
@@ -9,7 +9,7 @@ import {
9
9
  StructuredListWrapper,
10
10
  } from '@carbon/react';
11
11
  import { useTranslation } from 'react-i18next';
12
- import { useConfig } from '@openmrs/esm-framework';
12
+ import { getCoreTranslation, useConfig } from '@openmrs/esm-framework';
13
13
  import { convertToCurrency } from '../../helpers';
14
14
  import { type MappedBill, type LineItem } from '../../types';
15
15
  import BillWaiverForm from './bill-waiver-form.component';
@@ -44,7 +44,7 @@ const PatientBillsSelections: React.FC<{ bills: MappedBill; setPatientUuid: (pat
44
44
  <StructuredListCell head>{t('quantity', 'Quantity')}</StructuredListCell>
45
45
  <StructuredListCell head>{t('unitPrice', 'Unit Price')}</StructuredListCell>
46
46
  <StructuredListCell head>{t('total', 'Total')}</StructuredListCell>
47
- <StructuredListCell head>{t('actions', 'Actions')}</StructuredListCell>
47
+ <StructuredListCell head>{getCoreTranslation('actions')}</StructuredListCell>
48
48
  </StructuredListRow>
49
49
  </StructuredListHead>
50
50
  <StructuredListBody>
@@ -37,9 +37,9 @@ const PatientBills: React.FC<PatientBillsProps> = ({ patientUuid, bills, setPati
37
37
  }
38
38
 
39
39
  const tableHeaders = [
40
- { header: 'Date', key: 'date' },
41
- { header: 'Billable Service', key: 'billableService' },
42
- { header: 'Total Amount', key: 'totalAmount' },
40
+ { header: t('date', 'Date'), key: 'date' },
41
+ { header: t('billableService', 'Billable Service'), key: 'billableService' },
42
+ { header: t('totalAmount', 'Total Amount'), key: 'totalAmount' },
43
43
  ];
44
44
 
45
45
  const tableRows = bills.map((bill) => ({
@@ -3,6 +3,7 @@ import { type OpenmrsResource, openmrsFetch, restBaseUrl, useOpenmrsFetchAll, us
3
3
  import { type ServiceConcept } from '../types';
4
4
  import { apiBasePath } from '../constants';
5
5
  import { type BillableService } from '../types/index';
6
+ import type { BillingConfig } from '../config-schema';
6
7
 
7
8
  type ResponseObject = {
8
9
  results: Array<OpenmrsResource>;
@@ -22,8 +23,8 @@ export const useBillableServices = () => {
22
23
  };
23
24
 
24
25
  export function useServiceTypes() {
25
- const config = useConfig();
26
- const serviceConceptUuid = config.serviceTypes.billableService;
26
+ const { serviceTypes } = useConfig<BillingConfig>();
27
+ const serviceConceptUuid = serviceTypes.billableService;
27
28
  const url = `${restBaseUrl}/concept/${serviceConceptUuid}?v=custom:(setMembers:(uuid,display))`;
28
29
 
29
30
  const { data, error, isLoading } = useSWR<{ data }>(url, openmrsFetch);
@@ -47,7 +48,7 @@ export const usePaymentModes = () => {
47
48
  };
48
49
  };
49
50
 
50
- export const createBillableSerice = (payload: any) => {
51
+ export const createBillableService = (payload: any) => {
51
52
  const url = `${apiBasePath}api/billable-service`;
52
53
  return openmrsFetch(url, {
53
54
  method: 'POST',
@@ -9,7 +9,7 @@ import BillWaiver from './bill-waiver/bill-waiver.component';
9
9
  import BillableServicesDashboard from './dashboard/dashboard.component';
10
10
  import BillingHeader from '../billing-header/billing-header.component';
11
11
  import CashPointConfiguration from './cash-point/cash-point-configuration.component';
12
- import PaymentModesConfig from './payyment-modes/payment-modes-config.component';
12
+ import PaymentModesConfig from './payment-modes/payment-modes-config.component';
13
13
  import styles from './billable-services.scss';
14
14
 
15
15
  const BillableServiceHome: React.FC = () => {