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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/README.md +55 -9
  2. package/__mocks__/bills.mock.ts +12 -0
  3. package/__mocks__/react-i18next.js +6 -5
  4. package/dist/1119.js +1 -1
  5. package/dist/1146.js +1 -2
  6. package/dist/1146.js.map +1 -1
  7. package/dist/1197.js +1 -1
  8. package/dist/1856.js +1 -0
  9. package/dist/1856.js.map +1 -0
  10. package/dist/2146.js +1 -1
  11. package/dist/2177.js +2 -0
  12. package/dist/2177.js.LICENSE.txt +9 -0
  13. package/dist/2177.js.map +1 -0
  14. package/dist/2524.js +1 -0
  15. package/dist/2524.js.map +1 -0
  16. package/dist/2690.js +1 -1
  17. package/dist/3041.js +1 -0
  18. package/dist/3041.js.map +1 -0
  19. package/dist/3099.js +1 -1
  20. package/dist/3584.js +1 -1
  21. package/dist/4055.js +1 -1
  22. package/dist/4132.js +1 -1
  23. package/dist/4225.js +1 -0
  24. package/dist/4225.js.map +1 -0
  25. package/dist/4300.js +1 -1
  26. package/dist/4335.js +1 -1
  27. package/dist/4618.js +1 -1
  28. package/dist/4652.js +1 -1
  29. package/dist/4724.js +1 -0
  30. package/dist/4724.js.map +1 -0
  31. package/dist/4739.js +1 -1
  32. package/dist/4739.js.map +1 -1
  33. package/dist/4944.js +1 -1
  34. package/dist/5173.js +1 -1
  35. package/dist/5241.js +1 -1
  36. package/dist/5422.js +1 -0
  37. package/dist/5422.js.map +1 -0
  38. package/dist/5442.js +1 -1
  39. package/dist/5661.js +1 -1
  40. package/dist/6022.js +1 -1
  41. package/dist/6468.js +1 -1
  42. package/dist/6540.js +1 -1
  43. package/dist/6540.js.map +1 -1
  44. package/dist/6606.js +1 -0
  45. package/dist/6606.js.map +1 -0
  46. package/dist/6679.js +1 -1
  47. package/dist/6840.js +1 -1
  48. package/dist/6859.js +1 -1
  49. package/dist/7097.js +1 -1
  50. package/dist/7159.js +1 -1
  51. package/dist/723.js +1 -1
  52. package/dist/7452.js +2 -0
  53. package/dist/7452.js.map +1 -0
  54. package/dist/7617.js +1 -1
  55. package/dist/795.js +1 -1
  56. package/dist/8163.js +1 -1
  57. package/dist/8349.js +1 -1
  58. package/dist/8618.js +1 -1
  59. package/dist/890.js +1 -1
  60. package/dist/8930.js +2 -0
  61. package/dist/{6525.js.LICENSE.txt → 8930.js.LICENSE.txt} +16 -4
  62. package/dist/8930.js.map +1 -0
  63. package/dist/9214.js +1 -1
  64. package/dist/942.js +1 -0
  65. package/dist/942.js.map +1 -0
  66. package/dist/9538.js +1 -1
  67. package/dist/9569.js +1 -1
  68. package/dist/961.js +1 -1
  69. package/dist/961.js.map +1 -1
  70. package/dist/986.js +1 -1
  71. package/dist/9879.js +1 -1
  72. package/dist/9895.js +1 -1
  73. package/dist/9900.js +1 -1
  74. package/dist/9913.js +1 -1
  75. package/dist/main.js +1 -1
  76. package/dist/main.js.map +1 -1
  77. package/dist/openmrs-esm-billing-app.js +1 -1
  78. package/dist/openmrs-esm-billing-app.js.buildmanifest.json +368 -262
  79. package/dist/openmrs-esm-billing-app.js.map +1 -1
  80. package/dist/routes.json +1 -1
  81. package/e2e/README.md +19 -18
  82. package/e2e/specs/sample-test.spec.ts +0 -1
  83. package/package.json +10 -10
  84. package/src/bill-history/bill-history.component.tsx +17 -25
  85. package/src/bill-history/bill-history.scss +4 -94
  86. package/src/bill-history/bill-history.test.tsx +37 -78
  87. package/src/bill-item-actions/bill-item-actions.scss +0 -4
  88. package/src/bill-item-actions/{edit-bill-item.component.tsx → edit-bill-item.modal.tsx} +57 -56
  89. package/src/bill-item-actions/edit-bill-item.test.tsx +22 -25
  90. package/src/billable-services/bill-waiver/bill-selection.component.tsx +2 -2
  91. package/src/billable-services/bill-waiver/patient-bills.component.tsx +3 -3
  92. package/src/billable-services/billable-service.resource.ts +17 -9
  93. package/src/billable-services/billable-services-home.component.tsx +1 -1
  94. package/src/billable-services/billable-services.component.tsx +142 -145
  95. package/src/billable-services/billable-services.scss +3 -0
  96. package/src/billable-services/billable-services.test.tsx +2 -45
  97. package/src/billable-services/cash-point/add-cash-point.modal.tsx +168 -0
  98. package/src/billable-services/cash-point/cash-point-configuration.component.tsx +18 -192
  99. package/src/billable-services/cash-point/cash-point-configuration.scss +1 -5
  100. package/src/billable-services/create-edit/add-billable-service.component.tsx +345 -298
  101. package/src/billable-services/create-edit/add-billable-service.scss +5 -6
  102. package/src/billable-services/create-edit/add-billable-service.test.tsx +37 -36
  103. package/src/billable-services/create-edit/edit-billable-service.modal.tsx +51 -0
  104. package/src/billable-services/payment-modes/add-payment-mode.modal.tsx +121 -0
  105. package/src/billable-services/payment-modes/delete-payment-mode.modal.tsx +72 -0
  106. package/src/billable-services/payment-modes/payment-modes-config.component.tsx +125 -0
  107. package/src/billable-services/{payyment-modes → payment-modes}/payment-modes-config.scss +5 -4
  108. package/src/billing-form/billing-checkin-form.component.tsx +2 -3
  109. package/src/billing-form/billing-checkin-form.test.tsx +97 -24
  110. package/src/billing-form/billing-form.component.tsx +214 -269
  111. package/src/billing-form/billing-form.scss +143 -0
  112. package/src/billing.resource.ts +16 -19
  113. package/src/bills-table/bills-table.test.tsx +97 -53
  114. package/src/config-schema.ts +52 -18
  115. package/src/dashboard.meta.ts +4 -2
  116. package/src/helpers/functions.ts +5 -4
  117. package/src/index.ts +17 -6
  118. package/src/invoice/invoice-table.component.tsx +24 -54
  119. package/src/invoice/invoice-table.scss +1 -5
  120. package/src/invoice/invoice-table.test.tsx +21 -47
  121. package/src/invoice/invoice.component.tsx +36 -29
  122. package/src/invoice/invoice.scss +7 -4
  123. package/src/invoice/invoice.test.tsx +22 -48
  124. package/src/invoice/payments/payment-form/payment-form.component.tsx +29 -29
  125. package/src/invoice/payments/payment-form/payment-form.scss +5 -6
  126. package/src/invoice/payments/payment-form/payment-form.test.tsx +215 -67
  127. package/src/invoice/payments/payment-history/payment-history.component.tsx +6 -4
  128. package/src/invoice/payments/payment-history/payment-history.test.tsx +9 -14
  129. package/src/invoice/payments/payments.component.tsx +53 -65
  130. package/src/invoice/payments/payments.test.tsx +282 -0
  131. package/src/invoice/payments/utils.ts +5 -23
  132. package/src/invoice/printable-invoice/print-receipt.component.tsx +3 -2
  133. package/src/invoice/printable-invoice/print-receipt.test.tsx +14 -25
  134. package/src/invoice/printable-invoice/printable-footer.component.tsx +2 -2
  135. package/src/invoice/printable-invoice/printable-footer.test.tsx +4 -13
  136. package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +12 -11
  137. package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +16 -14
  138. package/src/invoice/printable-invoice/printable-invoice.component.tsx +19 -33
  139. package/src/metrics-cards/metrics-cards.test.tsx +18 -5
  140. package/src/modal/require-payment-modal.test.tsx +27 -22
  141. package/src/modal/{require-payment-modal.component.tsx → require-payment.modal.tsx} +17 -18
  142. package/src/routes.json +22 -2
  143. package/src/types/index.ts +26 -17
  144. package/translations/am.json +60 -18
  145. package/translations/ar.json +60 -18
  146. package/translations/ar_SY.json +60 -18
  147. package/translations/bn.json +65 -23
  148. package/translations/de.json +60 -18
  149. package/translations/en.json +60 -18
  150. package/translations/en_US.json +60 -18
  151. package/translations/es.json +60 -18
  152. package/translations/es_MX.json +60 -18
  153. package/translations/fr.json +73 -31
  154. package/translations/he.json +60 -18
  155. package/translations/hi.json +60 -18
  156. package/translations/hi_IN.json +60 -18
  157. package/translations/id.json +61 -19
  158. package/translations/it.json +96 -54
  159. package/translations/ka.json +61 -19
  160. package/translations/km.json +60 -18
  161. package/translations/ku.json +60 -18
  162. package/translations/ky.json +60 -18
  163. package/translations/lg.json +60 -18
  164. package/translations/ne.json +60 -18
  165. package/translations/pl.json +60 -18
  166. package/translations/pt.json +60 -18
  167. package/translations/pt_BR.json +60 -18
  168. package/translations/qu.json +60 -18
  169. package/translations/ro_RO.json +206 -164
  170. package/translations/ru_RU.json +60 -18
  171. package/translations/si.json +60 -18
  172. package/translations/sw.json +60 -18
  173. package/translations/sw_KE.json +60 -18
  174. package/translations/tr.json +60 -18
  175. package/translations/tr_TR.json +60 -18
  176. package/translations/uk.json +60 -18
  177. package/translations/uz.json +60 -18
  178. package/translations/uz@Latn.json +60 -18
  179. package/translations/uz_UZ.json +60 -18
  180. package/translations/vi.json +60 -18
  181. package/translations/zh.json +60 -18
  182. package/translations/zh_CN.json +117 -75
  183. package/dist/1146.js.LICENSE.txt +0 -21
  184. package/dist/2352.js +0 -1
  185. package/dist/2352.js.map +0 -1
  186. package/dist/246.js +0 -1
  187. package/dist/246.js.map +0 -1
  188. package/dist/6525.js +0 -2
  189. package/dist/6525.js.map +0 -1
  190. package/dist/8556.js +0 -2
  191. package/dist/8556.js.map +0 -1
  192. package/dist/8638.js +0 -1
  193. package/dist/8638.js.map +0 -1
  194. package/dist/9968.js +0 -1
  195. package/dist/9968.js.map +0 -1
  196. package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +0 -280
  197. package/src/invoice/payments/payments.component.test.tsx +0 -121
  198. /package/dist/{8556.js.LICENSE.txt → 7452.js.LICENSE.txt} +0 -0
@@ -1,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,41 @@ 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
+ });
114
+ closeModal();
115
+ } catch (error) {
116
+ showSnackbar({
117
+ title: t('billProcessingError', 'Bill processing error'),
118
+ kind: 'error',
119
+ subtitle: error?.message,
120
+ });
121
+ }
119
122
  };
120
123
 
121
124
  if (Object.keys(bill)?.length === 0) {
122
125
  return <ModalHeader closeModal={closeModal} title={t('billLineItemEmpty', 'This bill has no line items')} />;
123
126
  }
124
127
 
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>
128
+ return (
129
+ <>
130
+ <ModalHeader closeModal={closeModal} title={t('editBillLineItem', 'Edit bill line item?')} />
131
+ <Form onSubmit={handleSubmit(onSubmit, onError)}>
132
+ <ModalBody>
133
+ <Stack gap={5}>
131
134
  <div className={styles.modalBody}>
132
135
  <h5>
133
136
  {bill?.patientName} &nbsp; · &nbsp;{bill?.cashPointName} &nbsp; · &nbsp;{bill?.receiptNumber}&nbsp;
134
137
  </h5>
135
138
  </div>
136
- <section className={styles.section}>
139
+ <section>
137
140
  <p className={styles.label}>
138
141
  {t('item', 'Item')} : {item?.billableService ? item?.billableService : item?.item}
139
142
  </p>
@@ -146,7 +149,7 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
146
149
  <Controller
147
150
  name="quantity"
148
151
  control={control}
149
- render={({ field: { onChange, onBlur, value } }) => (
152
+ render={({ field: { onChange, value } }) => (
150
153
  <NumberInput
151
154
  label={t('quantity', 'Quantity')}
152
155
  id="quantityInput"
@@ -171,7 +174,7 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
171
174
  value={value}
172
175
  readOnly={true}
173
176
  className={styles.controlField}
174
- helperText="This is the unit Price for this item."
177
+ helperText={t('unitPriceHelperText', 'This is the unit price for this item.')}
175
178
  />
176
179
  )}
177
180
  />
@@ -191,31 +194,29 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
191
194
  </Column>
192
195
  )}
193
196
  </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
- }
197
+ </Stack>
198
+ </ModalBody>
199
+ <ModalFooter>
200
+ <Button kind="secondary" onClick={closeModal}>
201
+ {getCoreTranslation('cancel')}
202
+ </Button>
203
+ <Button type="submit" disabled={isSubmitting}>
204
+ {isSubmitting ? (
205
+ <div className={styles.inline}>
206
+ <InlineLoading
207
+ status="active"
208
+ iconDescription={t('submitting', 'Submitting')}
209
+ description={t('submitting', 'Submitting') + '...'}
210
+ />
211
+ </div>
212
+ ) : (
213
+ getCoreTranslation('save')
214
+ )}
215
+ </Button>
216
+ </ModalFooter>
217
+ </Form>
218
+ </>
219
+ );
219
220
  };
220
221
 
221
- export default ChangeStatus;
222
+ 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,43 +86,45 @@ 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',
115
112
  kind: 'success',
116
- timeoutInMs: 3000,
117
113
  });
118
114
  expect(closeModalMock).toHaveBeenCalled();
119
115
  });
120
116
  });
121
117
 
122
118
  test('shows error notification when submission fails', async () => {
123
- (updateBillItems as jest.Mock).mockRejectedValueOnce({ message: 'Error occurred' });
119
+ const user = userEvent.setup();
120
+ mockUpdateBillItems.mockRejectedValueOnce({ message: 'Error occurred' });
124
121
 
125
- render(<ChangeStatus bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
122
+ render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
126
123
 
127
- fireEvent.click(screen.getByText(/Save/));
124
+ await user.click(screen.getByText(/Save/));
128
125
 
129
126
  await waitFor(() => {
130
- expect(showSnackbar).toHaveBeenCalledWith({
127
+ expect(mockShowSnackbar).toHaveBeenCalledWith({
131
128
  title: 'Bill processing error',
132
129
  kind: 'error',
133
130
  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) => ({
@@ -1,8 +1,8 @@
1
1
  import useSWR from 'swr';
2
2
  import { type OpenmrsResource, openmrsFetch, restBaseUrl, useOpenmrsFetchAll, useConfig } from '@openmrs/esm-framework';
3
- import { type ServiceConcept } from '../types';
4
3
  import { apiBasePath } from '../constants';
5
- import { type BillableService } from '../types/index';
4
+ import { type BillableService, type ServiceConcept } from '../types';
5
+ import type { BillingConfig } from '../config-schema';
6
6
 
7
7
  type ResponseObject = {
8
8
  results: Array<OpenmrsResource>;
@@ -22,16 +22,20 @@ export const useBillableServices = () => {
22
22
  };
23
23
 
24
24
  export function useServiceTypes() {
25
- const config = useConfig();
26
- const serviceConceptUuid = config.serviceTypes.billableService;
25
+ const { serviceTypes } = useConfig<BillingConfig>();
26
+ const serviceConceptUuid = serviceTypes.billableService;
27
27
  const url = `${restBaseUrl}/concept/${serviceConceptUuid}?v=custom:(setMembers:(uuid,display))`;
28
28
 
29
29
  const { data, error, isLoading } = useSWR<{ data }>(url, openmrsFetch);
30
30
 
31
+ const sortedServiceTypes = data?.data.setMembers
32
+ ? [...data.data.setMembers].sort((a, b) => a.display.localeCompare(b.display))
33
+ : [];
34
+
31
35
  return {
32
- serviceTypes: data?.data.setMembers ?? [],
36
+ serviceTypes: sortedServiceTypes,
33
37
  error,
34
- isLoading,
38
+ isLoadingServiceTypes: isLoading,
35
39
  };
36
40
  }
37
41
 
@@ -40,14 +44,18 @@ export const usePaymentModes = () => {
40
44
 
41
45
  const { data, error, isLoading } = useSWR<{ data: ResponseObject }>(url, openmrsFetch);
42
46
 
47
+ const sortedPaymentModes = data?.data.results
48
+ ? [...data.data.results].sort((a, b) => a.name.localeCompare(b.name))
49
+ : [];
50
+
43
51
  return {
44
- paymentModes: data?.data.results ?? [],
52
+ paymentModes: sortedPaymentModes,
45
53
  error,
46
- isLoading,
54
+ isLoadingPaymentModes: isLoading,
47
55
  };
48
56
  };
49
57
 
50
- export const createBillableSerice = (payload: any) => {
58
+ export const createBillableService = (payload: any) => {
51
59
  const url = `${apiBasePath}api/billable-service`;
52
60
  return openmrsFetch(url, {
53
61
  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 = () => {