@openmrs/esm-billing-app 1.0.2-pre.613 → 1.0.2-pre.619

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 (55) hide show
  1. package/README.md +12 -6
  2. package/__mocks__/react-i18next.js +6 -5
  3. package/dist/2352.js +1 -1
  4. package/dist/2352.js.map +1 -1
  5. package/dist/4300.js +1 -1
  6. package/dist/4739.js +1 -1
  7. package/dist/4739.js.map +1 -1
  8. package/dist/7239.js +1 -1
  9. package/dist/7239.js.map +1 -1
  10. package/dist/8638.js +1 -1
  11. package/dist/8638.js.map +1 -1
  12. package/dist/main.js +1 -1
  13. package/dist/main.js.map +1 -1
  14. package/dist/openmrs-esm-billing-app.js +1 -1
  15. package/dist/openmrs-esm-billing-app.js.buildmanifest.json +19 -19
  16. package/dist/routes.json +1 -1
  17. package/e2e/README.md +19 -18
  18. package/package.json +2 -2
  19. package/src/bill-history/bill-history.test.tsx +37 -77
  20. package/src/bill-item-actions/edit-bill-item.component.tsx +7 -3
  21. package/src/bill-item-actions/edit-bill-item.test.tsx +17 -19
  22. package/src/billable-services/bill-waiver/patient-bills.component.tsx +3 -3
  23. package/src/billable-services/billable-service.resource.ts +4 -3
  24. package/src/billable-services/billable-services.component.tsx +5 -4
  25. package/src/billable-services/billable-services.test.tsx +3 -44
  26. package/src/billable-services/cash-point/cash-point-configuration.component.tsx +2 -2
  27. package/src/billable-services/create-edit/add-billable-service.component.tsx +2 -2
  28. package/src/billable-services/create-edit/add-billable-service.test.tsx +6 -6
  29. package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +1 -1
  30. package/src/billing-form/billing-checkin-form.component.tsx +1 -2
  31. package/src/billing-form/billing-checkin-form.test.tsx +0 -2
  32. package/src/billing-form/billing-form.component.tsx +8 -4
  33. package/src/billing.resource.ts +13 -9
  34. package/src/bills-table/bills-table.test.tsx +97 -53
  35. package/src/config-schema.ts +52 -18
  36. package/src/invoice/invoice-table.component.tsx +9 -9
  37. package/src/invoice/invoice-table.scss +1 -5
  38. package/src/invoice/invoice-table.test.tsx +24 -39
  39. package/src/invoice/invoice.component.tsx +8 -6
  40. package/src/invoice/invoice.scss +7 -4
  41. package/src/invoice/invoice.test.tsx +12 -47
  42. package/src/invoice/payments/payment-form/payment-form.test.tsx +8 -10
  43. package/src/invoice/payments/payment-history/payment-history.component.tsx +6 -4
  44. package/src/invoice/payments/payment-history/payment-history.test.tsx +9 -14
  45. package/src/invoice/payments/payments.component.test.tsx +20 -6
  46. package/src/invoice/printable-invoice/print-receipt.component.tsx +1 -1
  47. package/src/invoice/printable-invoice/print-receipt.test.tsx +14 -25
  48. package/src/invoice/printable-invoice/printable-footer.component.tsx +2 -2
  49. package/src/invoice/printable-invoice/printable-footer.test.tsx +4 -13
  50. package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +5 -4
  51. package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +11 -11
  52. package/src/invoice/printable-invoice/printable-invoice.component.tsx +11 -11
  53. package/src/metrics-cards/metrics-cards.test.tsx +18 -5
  54. package/src/modal/require-payment-modal.test.tsx +24 -19
  55. package/translations/en.json +18 -0
@@ -26,17 +26,18 @@ import { EmptyState } from '@openmrs/esm-patient-common-lib';
26
26
  import { type BillableService } from '../types/index';
27
27
  import { useBillableServices } from './billable-service.resource';
28
28
  import AddBillableService from './create-edit/add-billable-service.component';
29
+ import type { BillingConfig } from '../config-schema';
29
30
  import styles from './billable-services.scss';
30
31
 
31
32
  const BillableServices = () => {
32
33
  const { t } = useTranslation();
33
34
  const { billableServices, isLoading, isValidating, error, mutate } = useBillableServices();
34
35
  const layout = useLayoutType();
35
- const config = useConfig();
36
+ const { pageSize: configuredPageSize } = useConfig<BillingConfig>();
36
37
  const [searchString, setSearchString] = useState('');
37
38
  const responsiveSize = isDesktop(layout) ? 'lg' : 'sm';
38
- const pageSizes = config?.billableServices?.pageSizes ?? [10, 20, 30, 40, 50];
39
- const [pageSize, setPageSize] = useState(config?.billableServices?.pageSize ?? 10);
39
+ const pageSizes = [10, 20, 30, 40, 50];
40
+ const [pageSize, setPageSize] = useState(configuredPageSize ?? 10);
40
41
 
41
42
  const [showOverlay, setShowOverlay] = useState(false);
42
43
  const [editingService, setEditingService] = useState(null);
@@ -55,7 +56,7 @@ const BillableServices = () => {
55
56
  key: 'serviceType',
56
57
  },
57
58
  {
58
- header: t('status', 'Service Status'),
59
+ header: t('serviceStatus', 'Service Status'),
59
60
  key: 'status',
60
61
  },
61
62
  {
@@ -4,55 +4,13 @@ import userEvent from '@testing-library/user-event';
4
4
  import BillableServices from './billable-services.component';
5
5
  import { useBillableServices } from './billable-service.resource';
6
6
 
7
- // Mock the resource
8
7
  jest.mock('./billable-service.resource', () => ({
9
8
  useBillableServices: jest.fn(),
10
9
  }));
11
10
 
12
- // Mock the empty state component
13
- jest.mock('@openmrs/esm-patient-common-lib', () => ({
14
- EmptyState: jest.fn(({ displayText, headerTitle }) => (
15
- <div data-testid="empty-state">
16
- <h1>{headerTitle}</h1>
17
- <p>{displayText}</p>
18
- </div>
19
- )),
20
- }));
21
-
22
- // Mock navigation
23
- jest.mock('@openmrs/esm-framework', () => ({
24
- useLayoutType: jest.fn(() => 'desktop'),
25
- isDesktop: jest.fn(() => true),
26
- useConfig: jest.fn(() => ({
27
- billableServices: {
28
- pageSizes: [10, 20, 30, 40, 50],
29
- pageSize: 10,
30
- },
31
- })),
32
- usePagination: jest.fn().mockImplementation((data) => ({
33
- currentPage: 1,
34
- goTo: jest.fn(),
35
- results: data,
36
- paginated: true,
37
- })),
38
- navigate: jest.fn(),
39
- ErrorState: jest.fn(({ error }) => <div>Error: {error?.message || error}</div>),
40
- }));
41
-
42
- // Mock i18next
43
- jest.mock('react-i18next', () => ({
44
- useTranslation: () => ({
45
- t: (key: string, fallback: string) => fallback || key,
46
- }),
47
- }));
48
-
49
11
  describe('BillableService', () => {
50
12
  const mockedUseBillableServices = useBillableServices as jest.Mock;
51
13
 
52
- beforeEach(() => {
53
- jest.clearAllMocks();
54
- });
55
-
56
14
  it('renders an empty state when there are no billable services', () => {
57
15
  mockedUseBillableServices.mockReturnValue({
58
16
  billableServices: [],
@@ -64,9 +22,10 @@ describe('BillableService', () => {
64
22
 
65
23
  render(<BillableServices />);
66
24
 
67
- expect(screen.getByTestId('empty-state')).toBeInTheDocument();
25
+ // Check that empty state is rendered without looking for specific test ID
68
26
  expect(screen.getByText('Billable service')).toBeInTheDocument();
69
- expect(screen.getByText('There are no services to display')).toBeInTheDocument();
27
+ // Check that empty state is visible with some indication
28
+ expect(screen.queryByRole('table')).not.toBeInTheDocument();
70
29
  });
71
30
 
72
31
  it('renders billable services table correctly', () => {
@@ -172,7 +172,7 @@ const CashPointConfiguration: React.FC = () => {
172
172
  <div className={styles.card}>
173
173
  <CardHeader title={t('cashPointHistory', 'Cash Point History')}>
174
174
  <Button renderIcon={Add} onClick={() => setIsModalOpen(true)} kind="ghost">
175
- {t('addCashPoint', 'Add New Cash Point')}
175
+ {t('addNewCashPoint', 'Add New Cash Point')}
176
176
  </Button>
177
177
  </CardHeader>
178
178
  <div className={styles.billHistoryContainer}>
@@ -257,7 +257,7 @@ const CashPointConfiguration: React.FC = () => {
257
257
  render={({ field }) => (
258
258
  <Dropdown
259
259
  id="cash-point-location"
260
- label={t('location', 'Select Location')}
260
+ label={t('selectLocation', 'Select Location')}
261
261
  titleText={t('cashPointLocation', 'Cash Point Location')}
262
262
  items={locations}
263
263
  selectedItem={locations.find((loc) => loc.id === field.value)}
@@ -18,7 +18,7 @@ import { z } from 'zod';
18
18
  import { zodResolver } from '@hookform/resolvers/zod';
19
19
  import { navigate, showSnackbar, useDebounce, useLayoutType } from '@openmrs/esm-framework';
20
20
  import {
21
- createBillableSerice,
21
+ createBillableService,
22
22
  updateBillableService,
23
23
  useConceptsSearch,
24
24
  usePaymentModes,
@@ -142,7 +142,7 @@ const AddBillableService: React.FC<{ editingService?: any; onClose: () => void;
142
142
 
143
143
  const saveAction = editingService
144
144
  ? updateBillableService(editingService.uuid, payload)
145
- : createBillableSerice(payload);
145
+ : createBillableService(payload);
146
146
 
147
147
  saveAction.then(
148
148
  (resp) => {
@@ -6,21 +6,21 @@ import {
6
6
  useBillableServices,
7
7
  usePaymentModes,
8
8
  useServiceTypes,
9
- createBillableSerice,
9
+ createBillableService,
10
10
  } from '../billable-service.resource';
11
11
  import AddBillableService from './add-billable-service.component';
12
12
 
13
13
  const mockUseBillableServices = useBillableServices as jest.MockedFunction<typeof useBillableServices>;
14
14
  const mockUsePaymentModes = usePaymentModes as jest.MockedFunction<typeof usePaymentModes>;
15
15
  const mockUseServiceTypes = useServiceTypes as jest.MockedFunction<typeof useServiceTypes>;
16
- const mockCreateBillableSerice = createBillableSerice as jest.MockedFunction<typeof createBillableSerice>;
16
+ const mockcreateBillableService = createBillableService as jest.MockedFunction<typeof createBillableService>;
17
17
  const mockNavigate = navigate as jest.MockedFunction<typeof navigate>;
18
18
 
19
19
  jest.mock('../billable-service.resource', () => ({
20
20
  useBillableServices: jest.fn(),
21
21
  usePaymentModes: jest.fn(),
22
22
  useServiceTypes: jest.fn(),
23
- createBillableSerice: jest.fn(),
23
+ createBillableService: jest.fn(),
24
24
  }));
25
25
 
26
26
  const mockPaymentModes = [
@@ -107,13 +107,13 @@ xdescribe('AddBillableService', () => {
107
107
  expect(priceTextInp).toBeInTheDocument();
108
108
  await user.type(priceTextInp, '1000');
109
109
 
110
- mockCreateBillableSerice.mockReturnValue(Promise.resolve({} as FetchResponse<any>));
110
+ mockcreateBillableService.mockReturnValue(Promise.resolve({} as FetchResponse<any>));
111
111
  const saveBtn = screen.getByRole('button', { name: /Save/i });
112
112
  expect(saveBtn).toBeInTheDocument();
113
113
  await user.click(saveBtn);
114
114
 
115
- expect(mockCreateBillableSerice).toHaveBeenCalledTimes(1);
116
- expect(mockCreateBillableSerice).toHaveBeenCalledWith({
115
+ expect(mockcreateBillableService).toHaveBeenCalledTimes(1);
116
+ expect(mockcreateBillableService).toHaveBeenCalledWith({
117
117
  name: 'Test Service Name',
118
118
  shortName: 'Test Short Name',
119
119
  serviceType: undefined,
@@ -172,7 +172,7 @@ const PaymentModesConfig: React.FC = () => {
172
172
  <div className={styles.card}>
173
173
  <CardHeader title={t('paymentModeHistory', 'Payment Mode History')}>
174
174
  <Button renderIcon={Add} onClick={() => setIsModalOpen(true)} kind="ghost">
175
- {t('addPaymentMode', 'Add New Payment Mode')}
175
+ {t('addNewPaymentMode', 'Add New Payment Mode')}
176
176
  </Button>
177
177
  </CardHeader>
178
178
  <div className={styles.historyContainer}>
@@ -1,12 +1,11 @@
1
1
  import React, { useCallback, useState } from 'react';
2
2
  import { Dropdown, InlineLoading, InlineNotification } from '@carbon/react';
3
3
  import { useTranslation } from 'react-i18next';
4
- import { showSnackbar, useConfig } from '@openmrs/esm-framework';
4
+ import { showSnackbar } from '@openmrs/esm-framework';
5
5
  import { useCashPoint, useBillableItems, createPatientBill } from './billing-form.resource';
6
6
  import VisitAttributesForm from './visit-attributes/visit-attributes-form.component';
7
7
  import styles from './billing-checkin-form.scss';
8
8
 
9
- const DEFAULT_PRICE = 500.00001;
10
9
  const PENDING_PAYMENT_STATUS = 'PENDING';
11
10
 
12
11
  type BillingCheckInFormProps = {
@@ -42,8 +42,6 @@ const mockBillableItems = [
42
42
 
43
43
  const mockUseCashPoint = useCashPoint as jest.MockedFunction<typeof useCashPoint>;
44
44
  const mockUseBillableItems = useBillableItems as jest.MockedFunction<typeof useBillableItems>;
45
- const mockCreatePatientBill = createPatientBill as jest.MockedFunction<typeof createPatientBill>;
46
- const mockusePaymentMethods = usePaymentMethods as jest.MockedFunction<typeof usePaymentMethods>;
47
45
 
48
46
  jest.mock('./billing-form.resource', () => ({
49
47
  useBillableItems: jest.fn(),
@@ -70,7 +70,7 @@ const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }
70
70
  isValid = false;
71
71
  const parsedErrorMessage = JSON.parse(error.message);
72
72
  showToast({
73
- title: t('billItems', 'Save Bill'),
73
+ title: t('saveBill', 'Save Bill'),
74
74
  kind: 'error',
75
75
  description: parsedErrorMessage[0].message,
76
76
  });
@@ -214,15 +214,19 @@ const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }
214
214
  closeWorkspace();
215
215
  mutate((key) => typeof key === 'string' && key.startsWith(url), undefined, { revalidate: true });
216
216
  showSnackbar({
217
- title: t('billItems', 'Save Bill'),
218
- subtitle: 'Bill processing has been successful',
217
+ title: t('saveBill', 'Save Bill'),
218
+ subtitle: t('billProcessingSuccess', 'Bill processing has been successful'),
219
219
  kind: 'success',
220
220
  timeoutInMs: 3000,
221
221
  });
222
222
  },
223
223
  (error) => {
224
224
  setIsSubmitting(false);
225
- showSnackbar({ title: 'Bill processing error', kind: 'error', subtitle: error?.message });
225
+ showSnackbar({
226
+ title: t('billProcessingError', 'Bill processing error'),
227
+ kind: 'error',
228
+ subtitle: error?.message,
229
+ });
226
230
  },
227
231
  );
228
232
  };
@@ -3,9 +3,17 @@ import dayjs from 'dayjs';
3
3
  import isEmpty from 'lodash-es/isEmpty';
4
4
  import sortBy from 'lodash-es/sortBy';
5
5
  import useSWR from 'swr';
6
- import { formatDate, parseDate, openmrsFetch, useSession, useVisit, restBaseUrl } from '@openmrs/esm-framework';
6
+ import {
7
+ formatDate,
8
+ parseDate,
9
+ openmrsFetch,
10
+ useSession,
11
+ useVisit,
12
+ restBaseUrl,
13
+ type SessionLocation,
14
+ } from '@openmrs/esm-framework';
7
15
  import { apiBasePath, omrsDateFormat } from './constants';
8
- import type { FacilityDetail, MappedBill, PatientInvoice } from './types';
16
+ import type { MappedBill, PatientInvoice } from './types';
9
17
  import SelectedDateContext from './hooks/selectedDateContext';
10
18
 
11
19
  export const useBills = (patientUuid: string = '', billStatus: string = '') => {
@@ -108,13 +116,9 @@ export const processBillPayment = (payload, billUuid: string) => {
108
116
  });
109
117
  };
110
118
 
111
- export function useDefaultFacility() {
112
- const url = `${restBaseUrl}/kenyaemr/default-facility`;
113
- const { authenticated } = useSession();
114
-
115
- const { data, isLoading } = useSWR<{ data: FacilityDetail }>(authenticated ? url : null, openmrsFetch);
116
-
117
- return { data: data?.data, isLoading: isLoading };
119
+ export function useDefaultFacility(): { data: SessionLocation | null } {
120
+ const { sessionLocation } = useSession();
121
+ return { data: sessionLocation };
118
122
  }
119
123
 
120
124
  export const usePatientPaymentInfo = (patientUuid: string) => {
@@ -4,91 +4,126 @@ import { render, screen, waitFor } from '@testing-library/react';
4
4
  import { useBills } from '../billing.resource';
5
5
  import BillsTable from './bills-table.component';
6
6
 
7
- jest.mock('react-i18next', () => ({
8
- useTranslation: () => ({
9
- t: (key: string) => key,
10
- }),
7
+ jest.mock('@openmrs/esm-framework', () => {
8
+ const actual = jest.requireActual('@openmrs/esm-framework');
9
+ return {
10
+ ...actual,
11
+ ConfigurableLink: ({ children, to, templateParams }: any) => {
12
+ let resolvedTo = to as string;
13
+ if (templateParams) {
14
+ resolvedTo = resolvedTo
15
+ .replace('${patientUuid}', templateParams.patientUuid)
16
+ .replace('${uuid}', templateParams.uuid)
17
+ .replace('${openmrsSpaBase}', '/openmrs/spa');
18
+ }
19
+ resolvedTo = resolvedTo.replace(/^\/openmrs\/spa/, '');
20
+ return <a href={resolvedTo}>{children}</a>;
21
+ },
22
+ };
23
+ });
24
+
25
+ jest.mock('../billing.resource', () => ({
26
+ useBills: jest.fn(() => ({
27
+ bills: mockBillsData,
28
+ isLoading: false,
29
+ isValidating: false,
30
+ error: null,
31
+ })),
11
32
  }));
12
33
 
34
+ const mockBills = jest.mocked(useBills);
35
+
13
36
  const mockBillsData = [
14
37
  {
15
38
  uuid: '1',
39
+ id: 1,
16
40
  patientName: 'John Doe',
17
41
  identifier: '12345678',
18
42
  visitType: 'Checkup',
19
43
  patientUuid: 'uuid1',
20
44
  dateCreated: '2024-01-01',
21
- lineItems: [{ billableService: 'Service 1' }],
45
+ lineItems: [
46
+ {
47
+ uuid: 'line-item-1',
48
+ display: 'Service 1 Line Item',
49
+ voided: false,
50
+ voidReason: null,
51
+ item: 'Service 1',
52
+ billableService: 'Service 1',
53
+ quantity: 1,
54
+ price: 100,
55
+ priceName: 'Standard Price',
56
+ priceUuid: 'price-1',
57
+ lineItemOrder: 1,
58
+ resourceVersion: '1.0',
59
+ paymentStatus: 'PENDING',
60
+ },
61
+ ],
22
62
  status: 'PENDING',
63
+ cashPointUuid: 'cash-point-1',
64
+ cashPointName: 'Main Cash Point',
65
+ cashPointLocation: 'Main Hospital',
66
+ cashier: { uuid: 'cashier-1', display: 'Jane Cashier', links: [] },
67
+ receiptNumber: 'RCP-001',
68
+ billingService: 'Service 1',
69
+ payments: [],
70
+ totalAmount: 100,
71
+ tenderedAmount: 0,
23
72
  },
24
73
  {
25
74
  uuid: '2',
75
+ id: 2,
26
76
  patientName: 'Mary Smith',
27
77
  identifier: '98765432',
28
78
  visitType: 'Wake up',
29
79
  patientUuid: 'uuid2',
30
80
  dateCreated: '2024-01-02',
31
- lineItems: [{ billableService: 'Service 2' }],
81
+ lineItems: [
82
+ {
83
+ uuid: 'line-item-2',
84
+ display: 'Service 2 Line Item',
85
+ voided: false,
86
+ voidReason: null,
87
+ item: 'Service 2',
88
+ billableService: 'Service 2',
89
+ quantity: 1,
90
+ price: 200,
91
+ priceName: 'Standard Price',
92
+ priceUuid: 'price-1',
93
+ lineItemOrder: 1,
94
+ resourceVersion: '1.0',
95
+ paymentStatus: 'PENDING',
96
+ },
97
+ ],
32
98
  status: 'PENDING',
99
+ cashPointUuid: 'cash-point-1',
100
+ cashPointName: 'Main Cash Point',
101
+ cashPointLocation: 'Main Hospital',
102
+ cashier: { uuid: 'cashier-1', display: 'Jane Cashier', links: [] },
103
+ receiptNumber: 'RCP-002',
104
+ billingService: 'Service 2',
105
+ payments: [],
106
+ totalAmount: 200,
107
+ tenderedAmount: 200,
33
108
  },
34
109
  ];
35
110
 
36
- jest.mock('../billing.resource', () => ({
37
- useBills: jest.fn(() => ({
38
- bills: mockBillsData,
39
- isLoading: false,
40
- isValidating: false,
41
- error: null,
42
- })),
43
- }));
44
-
45
- jest.mock('@openmrs/esm-patient-common-lib', () => ({
46
- EmptyDataIllustration: jest.fn(() => <div>Empty state illustration</div>),
47
- }));
48
-
49
- jest.mock('@openmrs/esm-framework', () => ({
50
- useLayoutType: jest.fn(() => 'desktop'),
51
- isDesktop: jest.fn(() => true),
52
- ErrorState: jest.fn(({ error }) => <div data-testid="error-state">{error?.message || error}</div>),
53
- useConfig: jest.fn(() => ({
54
- bills: {
55
- pageSizes: [10, 20, 30, 40, 50],
56
- pageSize: 10,
57
- },
58
- })),
59
- usePagination: jest.fn().mockImplementation((data) => ({
60
- currentPage: 1,
61
- goTo: jest.fn(),
62
- results: data,
63
- paginated: true,
64
- })),
65
- ConfigurableLink: jest.fn(({ children, to, templateParams }) => {
66
- const resolvedTo = '/home/billing/patient/' + templateParams.patientUuid + '/' + templateParams.uuid;
67
- return <a href={resolvedTo}>{children}</a>;
68
- }),
69
- openmrsSpaBase: '',
70
- }));
71
-
72
111
  describe('BillsTable', () => {
73
- const mockBills = useBills as jest.Mock;
74
- let user;
75
-
76
112
  beforeEach(() => {
77
- user = userEvent.setup();
78
113
  mockBills.mockImplementation(() => ({
79
114
  bills: mockBillsData,
80
115
  isLoading: false,
81
116
  isValidating: false,
82
117
  error: null,
118
+ mutate: jest.fn(),
83
119
  }));
84
120
  });
85
121
 
86
122
  test('renders data table with pending bills', () => {
87
123
  render(<BillsTable />);
88
124
 
89
- expect(screen.getByText('visitTime')).toBeInTheDocument();
90
- expect(screen.getByText('identifier')).toBeInTheDocument();
91
-
125
+ expect(screen.getByText('Visit time')).toBeInTheDocument();
126
+ expect(screen.getByText('Identifier')).toBeInTheDocument();
92
127
  expect(screen.getByText(/John Doe/)).toBeInTheDocument();
93
128
  expect(screen.getByText('12345678')).toBeInTheDocument();
94
129
  });
@@ -99,6 +134,7 @@ describe('BillsTable', () => {
99
134
  isLoading: false,
100
135
  isValidating: false,
101
136
  error: null,
137
+ mutate: jest.fn(),
102
138
  }));
103
139
 
104
140
  render(<BillsTable />);
@@ -111,28 +147,33 @@ describe('BillsTable', () => {
111
147
  isLoading: true,
112
148
  isValidating: false,
113
149
  error: null,
150
+ mutate: jest.fn(),
114
151
  }));
115
152
 
116
153
  render(<BillsTable />);
154
+
117
155
  const dataTableSkeleton = screen.getByRole('table');
118
156
  expect(dataTableSkeleton).toBeInTheDocument();
119
157
  expect(dataTableSkeleton).toHaveClass('cds--skeleton cds--data-table cds--data-table--zebra');
120
158
  });
121
159
 
122
- test('should display the error state when there is error', () => {
160
+ test('should display an error state if there is a problem loading bill data', () => {
123
161
  mockBills.mockImplementationOnce(() => ({
124
162
  bills: undefined,
125
163
  isLoading: false,
126
164
  isValidating: false,
127
165
  error: new Error('Error in fetching data'),
166
+ mutate: jest.fn(),
128
167
  }));
129
168
 
130
169
  render(<BillsTable />);
131
- expect(screen.getByTestId('error-state')).toBeInTheDocument();
170
+
171
+ expect(screen.getByText(/error state/i)).toBeInTheDocument();
132
172
  expect(screen.queryByRole('table')).not.toBeInTheDocument();
133
173
  });
134
174
 
135
175
  test('should filter bills by search term', async () => {
176
+ const user = userEvent.setup();
136
177
  render(<BillsTable />);
137
178
 
138
179
  const searchInput = screen.getByRole('searchbox');
@@ -142,7 +183,6 @@ describe('BillsTable', () => {
142
183
  expect(screen.getByText('Mary Smith')).toBeInTheDocument();
143
184
 
144
185
  await user.type(searchInput, 'John Doe');
145
-
146
186
  await waitFor(() => {
147
187
  expect(screen.queryByText('Mary Smith')).not.toBeInTheDocument();
148
188
  });
@@ -160,11 +200,14 @@ describe('BillsTable', () => {
160
200
  });
161
201
 
162
202
  test('should filter bills by payment status', async () => {
203
+ const user = userEvent.setup();
204
+
163
205
  mockBills.mockImplementationOnce(() => ({
164
206
  bills: mockBillsData.map((bill) => ({ ...bill, status: 'PENDING' })),
165
207
  isLoading: false,
166
208
  isValidating: false,
167
209
  error: null,
210
+ mutate: jest.fn(),
168
211
  }));
169
212
 
170
213
  render(<BillsTable />);
@@ -175,7 +218,7 @@ describe('BillsTable', () => {
175
218
  const paidBillsOption = screen.getAllByText('Paid bills')[0];
176
219
  await user.click(paidBillsOption);
177
220
 
178
- expect(screen.getByText('noMatchingBillsToDisplay')).toBeInTheDocument();
221
+ expect(screen.getByText('No matching bills to display')).toBeInTheDocument();
179
222
  });
180
223
 
181
224
  test('should show loading state during background updates', () => {
@@ -184,6 +227,7 @@ describe('BillsTable', () => {
184
227
  isLoading: false,
185
228
  isValidating: true,
186
229
  error: null,
230
+ mutate: jest.fn(),
187
231
  }));
188
232
 
189
233
  render(<BillsTable />);
@@ -1,8 +1,25 @@
1
- import { Type } from '@openmrs/esm-framework';
2
-
3
- export interface BillingConfig {}
1
+ import { Type, validators } from '@openmrs/esm-framework';
4
2
 
5
3
  export const configSchema = {
4
+ logo: {
5
+ src: {
6
+ _type: Type.String,
7
+ _default: '',
8
+ _description:
9
+ 'The path or URL to the logo image. If set to an empty string, the default OpenMRS SVG sprite will be used.',
10
+ _validators: [validators.isUrl],
11
+ },
12
+ alt: {
13
+ _type: Type.String,
14
+ _default: 'Logo',
15
+ _description: 'The alternative text for the logo image, displayed when the image cannot be loaded or on hover.',
16
+ },
17
+ },
18
+ country: {
19
+ _type: Type.String,
20
+ _description: 'The text that gets printed on the top right of the invoice, typically the name of the country',
21
+ _default: 'Kenya',
22
+ },
6
23
  patientCatergory: {
7
24
  _type: Type.Object,
8
25
  _description: 'Patient Category Custom UUIDs',
@@ -15,7 +32,6 @@ export const configSchema = {
15
32
  formPayloadPending: '919b51c9-8e2e-468f-8354-181bf3e55786',
16
33
  },
17
34
  },
18
-
19
35
  catergoryConcepts: {
20
36
  _type: Type.Object,
21
37
  _description: 'Patient Category Concept UUIDs',
@@ -25,7 +41,6 @@ export const configSchema = {
25
41
  insuranceDetails: 'beac329b-f1dc-4a33-9e7c-d95821a137a6',
26
42
  },
27
43
  },
28
-
29
44
  nonPayingPatientCategories: {
30
45
  _type: Type.Object,
31
46
  _description: 'Concept UUIDs for non-paying patient categories',
@@ -34,7 +49,6 @@ export const configSchema = {
34
49
  student: '159465AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
35
50
  },
36
51
  },
37
-
38
52
  postBilledItems: {
39
53
  _type: Type.Object,
40
54
  _description: 'Post Bill Items such as cashPoints, cashier, priceUUid when submitting a bill',
@@ -44,7 +58,6 @@ export const configSchema = {
44
58
  priceUuid: '7b9171ac-d3c1-49b4-beff-c9902aee5245',
45
59
  },
46
60
  },
47
-
48
61
  serviceTypes: {
49
62
  _type: Type.Object,
50
63
  _description: 'Post Bill Items such as cashPoints, cashier, priceUUid when submitting a bill',
@@ -52,19 +65,16 @@ export const configSchema = {
52
65
  billableService: '21b8cf43-9f9f-4d02-9f4a-d710ece54261',
53
66
  },
54
67
  },
55
-
56
68
  defaultCurrency: {
57
69
  _type: Type.String,
58
70
  _description: 'The default currency for the application. Specify the currency code (e.g., KES, UGX, GBP).',
59
71
  _default: 'KES',
60
72
  },
61
-
62
73
  pageSize: {
63
74
  _type: Type.Number,
64
75
  _description: 'The default page size',
65
76
  _default: 10,
66
77
  },
67
-
68
78
  showEditBillButton: {
69
79
  _type: Type.Boolean,
70
80
  _description: 'Whether to show the edit bill button or not.',
@@ -72,14 +82,38 @@ export const configSchema = {
72
82
  },
73
83
  };
74
84
 
75
- export interface ConfigObject {
76
- patientCatergory: Object;
85
+ export interface BillingConfig {
86
+ logo: {
87
+ src: string;
88
+ alt: string;
89
+ };
90
+ country: string;
91
+ patientCatergory: {
92
+ paymentDetails: string;
93
+ paymentMethods: string;
94
+ policyNumber: string;
95
+ insuranceScheme: string;
96
+ patientCategory: string;
97
+ formPayloadPending: string;
98
+ };
99
+ catergoryConcepts: {
100
+ payingDetails: string;
101
+ nonPayingDetails: string;
102
+ insuranceDetails: string;
103
+ };
104
+ nonPayingPatientCategories: {
105
+ childUnder5: string;
106
+ student: string;
107
+ };
108
+ postBilledItems: {
109
+ cashPoint: string;
110
+ cashier: string;
111
+ priceUuid: string;
112
+ };
113
+ serviceTypes: {
114
+ billableService: string;
115
+ };
77
116
  defaultCurrency: string;
78
- catergoryConcepts: Object;
79
- pageSize;
80
- object;
117
+ pageSize: number;
81
118
  showEditBillButton: boolean;
82
- postBilledItems: Object;
83
- serviceTypes: Object;
84
- nonPayingPatientCategories: Object;
85
119
  }