@openmrs/esm-billing-app 1.0.2-pre.78 → 1.0.2-pre.790

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 (203) hide show
  1. package/.eslintrc +16 -2
  2. package/README.md +54 -9
  3. package/__mocks__/bills.mock.ts +12 -0
  4. package/__mocks__/react-i18next.js +6 -5
  5. package/dist/1119.js +1 -1
  6. package/dist/1146.js +1 -2
  7. package/dist/1146.js.map +1 -1
  8. package/dist/1197.js +1 -1
  9. package/dist/1856.js +1 -0
  10. package/dist/1856.js.map +1 -0
  11. package/dist/2146.js +1 -1
  12. package/dist/2177.js +2 -0
  13. package/dist/2177.js.LICENSE.txt +9 -0
  14. package/dist/2177.js.map +1 -0
  15. package/dist/2524.js +1 -0
  16. package/dist/2524.js.map +1 -0
  17. package/dist/2690.js +1 -1
  18. package/dist/3041.js +1 -0
  19. package/dist/3041.js.map +1 -0
  20. package/dist/3099.js +1 -1
  21. package/dist/3584.js +1 -1
  22. package/dist/4055.js +1 -1
  23. package/dist/4132.js +1 -1
  24. package/dist/4225.js +1 -0
  25. package/dist/4225.js.map +1 -0
  26. package/dist/4300.js +1 -1
  27. package/dist/4335.js +1 -1
  28. package/dist/4618.js +1 -1
  29. package/dist/4652.js +1 -1
  30. package/dist/4724.js +1 -0
  31. package/dist/4724.js.map +1 -0
  32. package/dist/4739.js +1 -1
  33. package/dist/4739.js.map +1 -1
  34. package/dist/4944.js +1 -1
  35. package/dist/5173.js +1 -1
  36. package/dist/5241.js +1 -1
  37. package/dist/5422.js +1 -0
  38. package/dist/5422.js.map +1 -0
  39. package/dist/5442.js +1 -1
  40. package/dist/5661.js +1 -1
  41. package/dist/6022.js +1 -1
  42. package/dist/6468.js +1 -1
  43. package/dist/6540.js +1 -1
  44. package/dist/6540.js.map +1 -1
  45. package/dist/6606.js +1 -0
  46. package/dist/6606.js.map +1 -0
  47. package/dist/6679.js +1 -1
  48. package/dist/6840.js +1 -1
  49. package/dist/6859.js +1 -1
  50. package/dist/7097.js +1 -1
  51. package/dist/7159.js +1 -1
  52. package/dist/723.js +1 -1
  53. package/dist/7452.js +2 -0
  54. package/dist/7452.js.map +1 -0
  55. package/dist/7617.js +1 -1
  56. package/dist/795.js +1 -1
  57. package/dist/8163.js +1 -1
  58. package/dist/8349.js +1 -1
  59. package/dist/8618.js +1 -1
  60. package/dist/890.js +1 -1
  61. package/dist/8930.js +2 -0
  62. package/dist/{6525.js.LICENSE.txt → 8930.js.LICENSE.txt} +16 -4
  63. package/dist/8930.js.map +1 -0
  64. package/dist/9214.js +1 -1
  65. package/dist/942.js +1 -0
  66. package/dist/942.js.map +1 -0
  67. package/dist/9538.js +1 -1
  68. package/dist/9569.js +1 -1
  69. package/dist/961.js +1 -1
  70. package/dist/961.js.map +1 -1
  71. package/dist/986.js +1 -1
  72. package/dist/9879.js +1 -1
  73. package/dist/9895.js +1 -1
  74. package/dist/9900.js +1 -1
  75. package/dist/9913.js +1 -1
  76. package/dist/main.js +1 -1
  77. package/dist/main.js.map +1 -1
  78. package/dist/openmrs-esm-billing-app.js +1 -1
  79. package/dist/openmrs-esm-billing-app.js.buildmanifest.json +368 -262
  80. package/dist/openmrs-esm-billing-app.js.map +1 -1
  81. package/dist/routes.json +1 -1
  82. package/e2e/README.md +19 -18
  83. package/e2e/core/test.ts +1 -1
  84. package/e2e/fixtures/api.ts +1 -1
  85. package/e2e/specs/sample-test.spec.ts +0 -1
  86. package/e2e/support/github/Dockerfile +1 -1
  87. package/package.json +13 -10
  88. package/src/bill-history/bill-history.component.tsx +17 -25
  89. package/src/bill-history/bill-history.scss +4 -94
  90. package/src/bill-history/bill-history.test.tsx +37 -78
  91. package/src/bill-item-actions/bill-item-actions.scss +0 -4
  92. package/src/bill-item-actions/{edit-bill-item.component.tsx → edit-bill-item.modal.tsx} +100 -78
  93. package/src/bill-item-actions/edit-bill-item.test.tsx +116 -31
  94. package/src/billable-services/bill-waiver/bill-selection.component.tsx +2 -2
  95. package/src/billable-services/bill-waiver/patient-bills.component.tsx +3 -3
  96. package/src/billable-services/billable-service.resource.ts +17 -9
  97. package/src/billable-services/billable-services-home.component.tsx +1 -1
  98. package/src/billable-services/billable-services.component.tsx +142 -145
  99. package/src/billable-services/billable-services.scss +3 -0
  100. package/src/billable-services/billable-services.test.tsx +2 -45
  101. package/src/billable-services/cash-point/add-cash-point.modal.tsx +168 -0
  102. package/src/billable-services/cash-point/cash-point-configuration.component.tsx +18 -192
  103. package/src/billable-services/cash-point/cash-point-configuration.scss +1 -5
  104. package/src/billable-services/create-edit/add-billable-service.component.tsx +345 -298
  105. package/src/billable-services/create-edit/add-billable-service.scss +5 -6
  106. package/src/billable-services/create-edit/add-billable-service.test.tsx +37 -36
  107. package/src/billable-services/create-edit/edit-billable-service.modal.tsx +51 -0
  108. package/src/billable-services/payment-modes/add-payment-mode.modal.tsx +121 -0
  109. package/src/billable-services/payment-modes/delete-payment-mode.modal.tsx +72 -0
  110. package/src/billable-services/payment-modes/payment-modes-config.component.tsx +125 -0
  111. package/src/billable-services/{payyment-modes → payment-modes}/payment-modes-config.scss +5 -4
  112. package/src/billing-form/billing-checkin-form.component.tsx +2 -3
  113. package/src/billing-form/billing-checkin-form.test.tsx +97 -24
  114. package/src/billing-form/billing-form.component.tsx +216 -269
  115. package/src/billing-form/billing-form.scss +143 -0
  116. package/src/billing.resource.ts +16 -19
  117. package/src/bills-table/bills-table.test.tsx +98 -54
  118. package/src/config-schema.ts +52 -24
  119. package/src/dashboard.meta.ts +4 -2
  120. package/src/helpers/functions.ts +5 -4
  121. package/src/index.ts +17 -6
  122. package/src/invoice/invoice-table.component.tsx +35 -69
  123. package/src/invoice/invoice-table.scss +1 -5
  124. package/src/invoice/invoice-table.test.tsx +273 -62
  125. package/src/invoice/invoice.component.tsx +36 -29
  126. package/src/invoice/invoice.scss +7 -4
  127. package/src/invoice/invoice.test.tsx +324 -120
  128. package/src/invoice/payments/payment-form/payment-form.component.tsx +43 -34
  129. package/src/invoice/payments/payment-form/payment-form.scss +5 -6
  130. package/src/invoice/payments/payment-form/payment-form.test.tsx +216 -66
  131. package/src/invoice/payments/payment-history/payment-history.component.tsx +6 -4
  132. package/src/invoice/payments/payment-history/payment-history.test.tsx +9 -14
  133. package/src/invoice/payments/payments.component.tsx +53 -65
  134. package/src/invoice/payments/payments.test.tsx +282 -0
  135. package/src/invoice/payments/utils.ts +5 -23
  136. package/src/invoice/printable-invoice/print-receipt.component.tsx +3 -2
  137. package/src/invoice/printable-invoice/print-receipt.test.tsx +14 -25
  138. package/src/invoice/printable-invoice/printable-footer.component.tsx +2 -2
  139. package/src/invoice/printable-invoice/printable-footer.test.tsx +4 -13
  140. package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +12 -11
  141. package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +16 -14
  142. package/src/invoice/printable-invoice/printable-invoice.component.tsx +19 -33
  143. package/src/left-panel-link.test.tsx +1 -4
  144. package/src/metrics-cards/metrics-cards.test.tsx +18 -5
  145. package/src/modal/require-payment-modal.test.tsx +27 -22
  146. package/src/modal/{require-payment-modal.component.tsx → require-payment.modal.tsx} +17 -18
  147. package/src/routes.json +22 -2
  148. package/src/types/index.ts +26 -17
  149. package/translations/am.json +70 -21
  150. package/translations/ar.json +70 -21
  151. package/translations/ar_SY.json +70 -21
  152. package/translations/bn.json +75 -26
  153. package/translations/de.json +70 -21
  154. package/translations/en.json +70 -21
  155. package/translations/en_US.json +70 -21
  156. package/translations/es.json +70 -21
  157. package/translations/es_MX.json +70 -21
  158. package/translations/fr.json +83 -34
  159. package/translations/he.json +70 -21
  160. package/translations/hi.json +70 -21
  161. package/translations/hi_IN.json +70 -21
  162. package/translations/id.json +70 -21
  163. package/translations/it.json +105 -56
  164. package/translations/ka.json +70 -21
  165. package/translations/km.json +70 -21
  166. package/translations/ku.json +70 -21
  167. package/translations/ky.json +70 -21
  168. package/translations/lg.json +70 -21
  169. package/translations/ne.json +70 -21
  170. package/translations/pl.json +70 -21
  171. package/translations/pt.json +70 -21
  172. package/translations/pt_BR.json +70 -21
  173. package/translations/qu.json +70 -21
  174. package/translations/ro_RO.json +214 -165
  175. package/translations/ru_RU.json +70 -21
  176. package/translations/si.json +70 -21
  177. package/translations/sw.json +70 -21
  178. package/translations/sw_KE.json +70 -21
  179. package/translations/tr.json +70 -21
  180. package/translations/tr_TR.json +70 -21
  181. package/translations/uk.json +70 -21
  182. package/translations/uz.json +70 -21
  183. package/translations/uz@Latn.json +70 -21
  184. package/translations/uz_UZ.json +70 -21
  185. package/translations/vi.json +70 -21
  186. package/translations/zh.json +70 -21
  187. package/translations/zh_CN.json +125 -76
  188. package/dist/1146.js.LICENSE.txt +0 -21
  189. package/dist/2352.js +0 -1
  190. package/dist/2352.js.map +0 -1
  191. package/dist/246.js +0 -1
  192. package/dist/246.js.map +0 -1
  193. package/dist/6525.js +0 -2
  194. package/dist/6525.js.map +0 -1
  195. package/dist/8556.js +0 -2
  196. package/dist/8556.js.map +0 -1
  197. package/dist/8638.js +0 -1
  198. package/dist/8638.js.map +0 -1
  199. package/dist/9968.js +0 -1
  200. package/dist/9968.js.map +0 -1
  201. package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +0 -280
  202. package/src/invoice/payments/payments.component.test.tsx +0 -121
  203. /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);
@@ -39,8 +40,23 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
39
40
  const schema = useMemo(
40
41
  () =>
41
42
  z.object({
42
- quantity: z.string({ required_error: t('quantityRequired', 'Quantity is required') }),
43
- price: z.string({ required_error: t('priceIsRequired', 'Price is required') }),
43
+ // NOTE: Frontend-only validation - quantities <1 or >100 can still be submitted via API.
44
+ // Backend (BillServiceImpl.java:100) has empty validate() method.
45
+ // TODO: Add server-side validation to enforce data integrity
46
+ quantity: z.coerce
47
+ .number({
48
+ required_error: t('quantityRequired', 'Quantity is required'),
49
+ invalid_type_error: t('quantityMustBeNumber', 'Quantity must be a valid number'),
50
+ })
51
+ .int(t('quantityMustBeInteger', 'Quantity must be a whole number'))
52
+ .min(1, t('quantityMustBeAtLeastOne', 'Quantity must be at least 1'))
53
+ .max(100, t('quantityCannotExceed100', 'Quantity cannot exceed 100')),
54
+ price: z.coerce
55
+ .number({
56
+ required_error: t('priceIsRequired', 'Price is required'),
57
+ invalid_type_error: t('priceMustBeNumber', 'Price must be a valid number'),
58
+ })
59
+ .positive(t('priceMustBePositive', 'Price must be greater than 0')),
44
60
  }),
45
61
  [t],
46
62
  );
@@ -56,12 +72,12 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
56
72
  const {
57
73
  control,
58
74
  handleSubmit,
59
- formState: { isSubmitting, errors, isDirty },
75
+ formState: { isSubmitting, errors },
60
76
  watch,
61
77
  } = useForm<BillLineItemForm>({
62
78
  defaultValues: {
63
- quantity: item.quantity.toString(),
64
- price: item.price.toString(),
79
+ quantity: item.quantity,
80
+ price: item.price,
65
81
  },
66
82
  resolver: zodResolver(schema),
67
83
  });
@@ -70,17 +86,19 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
70
86
  const price = watch('price');
71
87
 
72
88
  useEffect(() => {
73
- const newTotal = parseInt(quantity) * parseInt(price);
74
- setTotal(newTotal);
89
+ const quantityNum = typeof quantity === 'number' ? quantity : parseFloat(quantity) || 0;
90
+ const priceNum = typeof price === 'number' ? price : parseFloat(price) || 0;
91
+ const newTotal = quantityNum * priceNum;
92
+ setTotal(isNaN(newTotal) ? 0 : newTotal);
75
93
  }, [quantity, price]);
76
94
 
77
- const onSubmit = (data: BillLineItemForm) => {
95
+ const onSubmit = async (data: BillLineItemForm) => {
78
96
  const url = `${apiBasePath}bill`;
79
97
 
80
98
  const newItem = {
81
99
  ...item,
82
- quantity: parseInt(data.quantity),
83
- price: parseInt(data?.price),
100
+ quantity: data.quantity,
101
+ price: data.price,
84
102
  billableService: getBillableServiceUuid(billableServices, item.billableService),
85
103
  item: item?.item,
86
104
  };
@@ -89,8 +107,9 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
89
107
  .filter((currItem) => currItem.uuid !== item?.uuid)
90
108
  .map((currItem) => ({
91
109
  ...currItem,
92
- billableService: getBillableServiceUuid(billableServices, item.billableService),
110
+ billableService: getBillableServiceUuid(billableServices, currItem.billableService),
93
111
  }));
112
+
94
113
  const updatedLineItems = previousLineitems.concat(newItem);
95
114
 
96
115
  const payload = {
@@ -101,62 +120,67 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
101
120
  status: bill.status,
102
121
  uuid: bill.uuid,
103
122
  };
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
- );
123
+
124
+ try {
125
+ await updateBillItems(payload);
126
+ mutate((key) => typeof key === 'string' && key.startsWith(url), undefined, { revalidate: true });
127
+ showSnackbar({
128
+ title: t('lineItemUpdated', 'Line item updated'),
129
+ subtitle: t('lineItemUpdateSuccess', 'The bill line item has been updated successfully'),
130
+ kind: 'success',
131
+ });
132
+ closeModal();
133
+ } catch (error) {
134
+ showSnackbar({
135
+ title: t('lineItemUpdateFailed', 'Failed to update line item'),
136
+ subtitle:
137
+ error?.message || t('lineItemUpdateErrorDefault', 'Unable to update the bill line item. Please try again.'),
138
+ kind: 'error',
139
+ });
140
+ }
119
141
  };
120
142
 
121
143
  if (Object.keys(bill)?.length === 0) {
122
144
  return <ModalHeader closeModal={closeModal} title={t('billLineItemEmpty', 'This bill has no line items')} />;
123
145
  }
124
146
 
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>
147
+ return (
148
+ <>
149
+ <ModalHeader closeModal={closeModal} title={t('editBillLineItem', 'Edit bill line item')} />
150
+ <Form onSubmit={handleSubmit(onSubmit, onError)}>
151
+ <ModalBody>
152
+ <Stack gap={5}>
131
153
  <div className={styles.modalBody}>
132
154
  <h5>
133
155
  {bill?.patientName} &nbsp; · &nbsp;{bill?.cashPointName} &nbsp; · &nbsp;{bill?.receiptNumber}&nbsp;
134
156
  </h5>
135
157
  </div>
136
- <section className={styles.section}>
158
+ <section>
137
159
  <p className={styles.label}>
138
- {t('item', 'Item')} : {item?.billableService ? item?.billableService : item?.item}
160
+ {t('item', 'Item')}: {item?.billableService ? item?.billableService : item?.item}
139
161
  </p>
140
162
  <p className={styles.label}>
141
- {t('currentPrice', 'Current price')} : {item?.price}
163
+ {t('currentPrice', 'Current price')}: {item?.price}
142
164
  </p>
143
165
  <p className={styles.label}>
144
- {t('status', 'status')} : {item?.paymentStatus}
166
+ {t('status', 'status')}: {item?.paymentStatus}
145
167
  </p>
146
168
  <Controller
147
169
  name="quantity"
148
170
  control={control}
149
- render={({ field: { onChange, onBlur, value } }) => (
171
+ render={({ field: { onChange, value } }) => (
150
172
  <NumberInput
151
- label={t('quantity', 'Quantity')}
152
- id="quantityInput"
153
- min={0}
154
- max={100}
155
- value={value}
156
- onChange={onChange}
173
+ disableWheel
157
174
  className={styles.controlField}
158
- invalid={errors.quantity?.message}
175
+ hideSteppers
176
+ id="quantityInput"
177
+ invalid={!!errors.quantity}
159
178
  invalidText={errors.quantity?.message}
179
+ label={t('quantity', 'Quantity')}
180
+ onChange={(_event, state: { value: number | string; direction: string }) => {
181
+ onChange(state.value);
182
+ }}
183
+ value={value}
160
184
  />
161
185
  )}
162
186
  />
@@ -166,18 +190,18 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
166
190
  control={control}
167
191
  render={({ field: { value } }) => (
168
192
  <TextInput
193
+ className={styles.controlField}
194
+ helperText={t('unitPriceHelperText', 'This is the unit price for this item.')}
169
195
  id="priceInput"
170
196
  labelText={t('price', 'Unit Price')}
197
+ readOnly
171
198
  value={value}
172
- readOnly={true}
173
- className={styles.controlField}
174
- helperText="This is the unit Price for this item."
175
199
  />
176
200
  )}
177
201
  />
178
202
 
179
203
  <p className={styles.label}>
180
- {t('total', 'Total')} : {total}{' '}
204
+ {t('total', 'Total')}: {total}{' '}
181
205
  </p>
182
206
 
183
207
  {showErrorNotification && (
@@ -191,31 +215,29 @@ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) =
191
215
  </Column>
192
216
  )}
193
217
  </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
- }
218
+ </Stack>
219
+ </ModalBody>
220
+ <ModalFooter>
221
+ <Button kind="secondary" onClick={closeModal}>
222
+ {getCoreTranslation('cancel')}
223
+ </Button>
224
+ <Button type="submit" disabled={isSubmitting}>
225
+ {isSubmitting ? (
226
+ <div className={styles.inline}>
227
+ <InlineLoading
228
+ status="active"
229
+ iconDescription={t('submitting', 'Submitting')}
230
+ description={t('submitting', 'Submitting') + '...'}
231
+ />
232
+ </div>
233
+ ) : (
234
+ getCoreTranslation('save')
235
+ )}
236
+ </Button>
237
+ </ModalFooter>
238
+ </Form>
239
+ </>
240
+ );
219
241
  };
220
242
 
221
- export default ChangeStatus;
243
+ export default EditBillLineItemModal;