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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/.eslintignore +0 -1
  2. package/.eslintrc +33 -24
  3. package/.husky/pre-commit +1 -1
  4. package/.turbo.json +1 -1
  5. package/.tx/config +11 -0
  6. package/README.md +111 -1
  7. package/dist/1119.js +1 -0
  8. package/dist/1197.js +1 -0
  9. package/dist/1362.js +1 -0
  10. package/dist/1362.js.map +1 -0
  11. package/dist/2146.js +1 -0
  12. package/dist/2690.js +1 -0
  13. package/dist/3029.js +2 -0
  14. package/dist/3029.js.LICENSE.txt +7 -0
  15. package/dist/3029.js.map +1 -0
  16. package/dist/3099.js +1 -0
  17. package/dist/3511.js +1 -0
  18. package/dist/3511.js.map +1 -0
  19. package/dist/3584.js +1 -0
  20. package/dist/4055.js +1 -0
  21. package/dist/4132.js +1 -0
  22. package/dist/4225.js +1 -0
  23. package/dist/4225.js.map +1 -0
  24. package/dist/4300.js +1 -0
  25. package/dist/4335.js +1 -0
  26. package/dist/4618.js +1 -0
  27. package/dist/4652.js +1 -0
  28. package/dist/4817.js +2 -0
  29. package/dist/4817.js.LICENSE.txt +77 -0
  30. package/dist/4817.js.map +1 -0
  31. package/dist/4944.js +1 -0
  32. package/dist/4993.js +1 -0
  33. package/dist/4993.js.map +1 -0
  34. package/dist/5173.js +1 -0
  35. package/dist/5241.js +1 -0
  36. package/dist/5442.js +1 -0
  37. package/dist/5661.js +1 -0
  38. package/dist/6022.js +1 -0
  39. package/dist/6468.js +1 -0
  40. package/dist/6540.js +2 -0
  41. package/dist/6540.js.map +1 -0
  42. package/dist/6606.js +2 -0
  43. package/dist/{591.js.LICENSE.txt → 6606.js.LICENSE.txt} +2 -2
  44. package/dist/6606.js.map +1 -0
  45. package/dist/6679.js +1 -0
  46. package/dist/6840.js +1 -0
  47. package/dist/6859.js +1 -0
  48. package/dist/6941.js +1 -0
  49. package/dist/6941.js.map +1 -0
  50. package/dist/7097.js +1 -0
  51. package/dist/7159.js +1 -0
  52. package/dist/723.js +1 -0
  53. package/dist/7255.js +1 -0
  54. package/dist/7255.js.map +1 -0
  55. package/dist/7617.js +1 -0
  56. package/dist/763.js +1 -0
  57. package/dist/763.js.map +1 -0
  58. package/dist/8163.js +1 -0
  59. package/dist/8349.js +1 -0
  60. package/dist/8618.js +1 -0
  61. package/dist/890.js +1 -0
  62. package/dist/9055.js +1 -0
  63. package/dist/9055.js.map +1 -0
  64. package/dist/9214.js +1 -0
  65. package/dist/9538.js +1 -0
  66. package/dist/{935.js → 961.js} +2 -2
  67. package/dist/{935.js.map → 961.js.map} +1 -1
  68. package/dist/986.js +1 -0
  69. package/dist/9879.js +1 -0
  70. package/dist/9895.js +1 -0
  71. package/dist/9900.js +1 -0
  72. package/dist/9913.js +1 -0
  73. package/dist/main.js +1 -1
  74. package/dist/main.js.LICENSE.txt +31 -1
  75. package/dist/main.js.map +1 -1
  76. package/dist/openmrs-esm-billing-app.js +1 -1
  77. package/dist/openmrs-esm-billing-app.js.buildmanifest.json +844 -165
  78. package/dist/openmrs-esm-billing-app.js.map +1 -1
  79. package/dist/routes.json +1 -1
  80. package/jest.config.js +4 -1
  81. package/package.json +19 -21
  82. package/src/bill-history/bill-history.component.tsx +19 -7
  83. package/src/bill-history/bill-history.scss +24 -9
  84. package/src/bill-history/bill-history.test.tsx +58 -16
  85. package/src/bill-item-actions/bill-item-actions.scss +26 -0
  86. package/src/bill-item-actions/edit-bill-item.component.tsx +221 -0
  87. package/src/bill-item-actions/edit-bill-item.test.tsx +137 -0
  88. package/src/billable-services/bill-waiver/bill-selection.component.tsx +1 -1
  89. package/src/billable-services/bill-waiver/bill-waiver-form.component.tsx +2 -2
  90. package/src/billable-services/bill-waiver/bill-waiver-form.scss +4 -4
  91. package/src/billable-services/bill-waiver/bill-waiver.component.tsx +4 -4
  92. package/src/billable-services/bill-waiver/patient-bills.component.tsx +1 -1
  93. package/src/billable-services/billable-service.resource.ts +19 -6
  94. package/src/billable-services/billable-services-home.component.tsx +19 -3
  95. package/src/billable-services/billable-services-menu-item/item.component.tsx +17 -0
  96. package/src/billable-services/billable-services-menu-item/item.scss +14 -0
  97. package/src/billable-services/billable-services.component.tsx +48 -9
  98. package/src/billable-services/billable-services.scss +10 -9
  99. package/src/billable-services/billable-services.test.tsx +172 -8
  100. package/src/billable-services/cash-point/cash-point-configuration.component.tsx +276 -0
  101. package/src/billable-services/cash-point/cash-point-configuration.scss +23 -0
  102. package/src/billable-services/create-edit/add-billable-service.component.tsx +126 -47
  103. package/src/billable-services/create-edit/add-billable-service.scss +14 -8
  104. package/src/billable-services/create-edit/add-billable-service.test.tsx +12 -10
  105. package/src/billable-services/dashboard/dashboard.scss +3 -3
  106. package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +280 -0
  107. package/src/billable-services/payyment-modes/payment-modes-config.scss +23 -0
  108. package/src/billing-dashboard/billing-dashboard.component.tsx +17 -4
  109. package/src/billing-dashboard/billing-dashboard.scss +3 -3
  110. package/src/billing-form/billing-form.component.tsx +31 -25
  111. package/src/billing-form/billing-form.scss +9 -10
  112. package/src/billing-form/visit-attributes/visit-attributes-form.component.tsx +38 -14
  113. package/src/billing-header/billing-header.component.tsx +21 -5
  114. package/src/billing-header/billing-header.scss +1 -1
  115. package/src/billing.resource.ts +24 -8
  116. package/src/bills-table/bills-table.component.tsx +46 -36
  117. package/src/bills-table/bills-table.scss +6 -6
  118. package/src/bills-table/bills-table.test.tsx +108 -68
  119. package/src/config-schema.ts +36 -1
  120. package/src/constants.ts +2 -0
  121. package/src/dashboard.meta.ts +2 -1
  122. package/src/helpers/functions.ts +0 -2
  123. package/src/hooks/selectedDateContext.ts +10 -0
  124. package/src/index.ts +22 -27
  125. package/src/invoice/invoice-table.component.tsx +95 -56
  126. package/src/invoice/invoice-table.scss +7 -8
  127. package/src/invoice/invoice-table.test.tsx +151 -0
  128. package/src/invoice/invoice.component.tsx +7 -9
  129. package/src/invoice/invoice.scss +2 -2
  130. package/src/invoice/invoice.test.tsx +199 -169
  131. package/src/invoice/payments/payment-form/payment-form.component.tsx +84 -55
  132. package/src/invoice/payments/payment-form/payment-form.test.tsx +174 -0
  133. package/src/invoice/payments/payment-history/payment-history.component.tsx +9 -7
  134. package/src/invoice/payments/payment-history/payment-history.test.tsx +160 -0
  135. package/src/invoice/payments/payments.component.test.tsx +121 -0
  136. package/src/invoice/payments/payments.component.tsx +57 -48
  137. package/src/invoice/payments/utils.ts +17 -13
  138. package/src/invoice/printable-invoice/print-receipt.component.tsx +23 -8
  139. package/src/invoice/printable-invoice/print-receipt.test.tsx +50 -0
  140. package/src/metrics-cards/card.component.tsx +4 -2
  141. package/src/metrics-cards/metrics-cards.test.tsx +1 -1
  142. package/src/modal/require-payment-modal.component.tsx +2 -2
  143. package/src/modal/require-payment-modal.test.tsx +66 -0
  144. package/src/modal/require-payment.scss +2 -1
  145. package/src/routes.json +40 -8
  146. package/src/types/index.ts +15 -0
  147. package/{i18next-parser.config.js → tools/i18next-parser.config.js} +19 -19
  148. package/tools/update-openmrs-deps.mjs +42 -0
  149. package/translations/am.json +53 -0
  150. package/translations/ar.json +170 -0
  151. package/translations/ar_SY.json +170 -0
  152. package/translations/bn.json +170 -0
  153. package/translations/de.json +170 -0
  154. package/translations/en.json +53 -0
  155. package/translations/es.json +53 -0
  156. package/translations/es_MX.json +170 -0
  157. package/translations/fr.json +53 -0
  158. package/translations/he.json +53 -0
  159. package/translations/hi.json +170 -0
  160. package/translations/hi_IN.json +170 -0
  161. package/translations/id.json +170 -0
  162. package/translations/it.json +170 -0
  163. package/translations/km.json +53 -0
  164. package/translations/ku.json +170 -0
  165. package/translations/ky.json +170 -0
  166. package/translations/lg.json +170 -0
  167. package/translations/ne.json +170 -0
  168. package/translations/pl.json +170 -0
  169. package/translations/pt.json +170 -0
  170. package/translations/pt_BR.json +170 -0
  171. package/translations/qu.json +170 -0
  172. package/translations/ro_RO.json +170 -0
  173. package/translations/ru_RU.json +170 -0
  174. package/translations/si.json +170 -0
  175. package/translations/sw.json +170 -0
  176. package/translations/sw_KE.json +170 -0
  177. package/translations/tr.json +170 -0
  178. package/translations/tr_TR.json +170 -0
  179. package/translations/uk.json +170 -0
  180. package/translations/uz.json +170 -0
  181. package/translations/uz@Latn.json +170 -0
  182. package/translations/uz_UZ.json +170 -0
  183. package/translations/vi.json +170 -0
  184. package/translations/zh.json +170 -0
  185. package/translations/zh_CN.json +170 -0
  186. package/tsconfig.json +10 -8
  187. package/webpack.config.js +1 -1
  188. package/dist/146.js +0 -1
  189. package/dist/146.js.map +0 -1
  190. package/dist/294.js +0 -2
  191. package/dist/294.js.map +0 -1
  192. package/dist/319.js +0 -1
  193. package/dist/384.js +0 -1
  194. package/dist/384.js.map +0 -1
  195. package/dist/421.js +0 -1
  196. package/dist/421.js.map +0 -1
  197. package/dist/533.js +0 -1
  198. package/dist/533.js.map +0 -1
  199. package/dist/574.js +0 -1
  200. package/dist/591.js +0 -2
  201. package/dist/591.js.map +0 -1
  202. package/dist/614.js +0 -2
  203. package/dist/614.js.LICENSE.txt +0 -37
  204. package/dist/614.js.map +0 -1
  205. package/dist/753.js +0 -1
  206. package/dist/753.js.map +0 -1
  207. package/dist/757.js +0 -1
  208. package/dist/770.js +0 -1
  209. package/dist/770.js.map +0 -1
  210. package/dist/783.js +0 -1
  211. package/dist/783.js.map +0 -1
  212. package/dist/788.js +0 -1
  213. package/dist/800.js +0 -2
  214. package/dist/800.js.LICENSE.txt +0 -3
  215. package/dist/800.js.map +0 -1
  216. package/dist/807.js +0 -1
  217. package/dist/833.js +0 -1
  218. package/dist/992.js +0 -1
  219. package/dist/992.js.map +0 -1
  220. package/src/root.scss +0 -30
  221. /package/dist/{294.js.LICENSE.txt → 6540.js.LICENSE.txt} +0 -0
  222. /package/dist/{935.js.LICENSE.txt → 961.js.LICENSE.txt} +0 -0
  223. /package/{src → tools}/setup-tests.ts +0 -0
  224. /package/{test-helpers.tsx → tools/test-helpers.tsx} +0 -0
@@ -0,0 +1,221 @@
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
+ import {
3
+ Button,
4
+ Column,
5
+ Form,
6
+ InlineLoading,
7
+ InlineNotification,
8
+ ModalBody,
9
+ ModalFooter,
10
+ ModalHeader,
11
+ NumberInput,
12
+ TextInput,
13
+ } from '@carbon/react';
14
+ import { useTranslation } from 'react-i18next';
15
+ import { Controller, type FieldErrors, useForm } from 'react-hook-form';
16
+ import { mutate } from 'swr';
17
+ import { z } from 'zod';
18
+ import { zodResolver } from '@hookform/resolvers/zod';
19
+ import { showSnackbar } from '@openmrs/esm-framework';
20
+ import { apiBasePath } from '../constants';
21
+ import { getBillableServiceUuid } from '../invoice/payments/utils';
22
+ import { type LineItem, type MappedBill } from '../types';
23
+ import { updateBillItems } from '../billing.resource';
24
+ import { useBillableServices } from '../billable-services/billable-service.resource';
25
+ import styles from './bill-item-actions.scss';
26
+
27
+ interface BillLineItemProps {
28
+ bill: MappedBill;
29
+ item: LineItem;
30
+ closeModal: () => void;
31
+ }
32
+
33
+ const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) => {
34
+ const { t } = useTranslation();
35
+ const { billableServices } = useBillableServices();
36
+ const [showErrorNotification, setShowErrorNotification] = useState(false);
37
+ const [total, setTotal] = useState(0);
38
+
39
+ const schema = useMemo(
40
+ () =>
41
+ z.object({
42
+ quantity: z.string({ required_error: t('quantityRequired', 'Quantity is required') }),
43
+ price: z.string({ required_error: t('priceIsRequired', 'Price is required') }),
44
+ }),
45
+ [t],
46
+ );
47
+
48
+ type BillLineItemForm = z.infer<typeof schema>;
49
+
50
+ const onError = (errors: FieldErrors<LineItem>) => {
51
+ if (errors) {
52
+ setShowErrorNotification(true);
53
+ }
54
+ };
55
+
56
+ const {
57
+ control,
58
+ handleSubmit,
59
+ formState: { isSubmitting, errors, isDirty },
60
+ watch,
61
+ } = useForm<BillLineItemForm>({
62
+ defaultValues: {
63
+ quantity: item.quantity.toString(),
64
+ price: item.price.toString(),
65
+ },
66
+ resolver: zodResolver(schema),
67
+ });
68
+
69
+ const quantity = watch('quantity');
70
+ const price = watch('price');
71
+
72
+ useEffect(() => {
73
+ const newTotal = parseInt(quantity) * parseInt(price);
74
+ setTotal(newTotal);
75
+ }, [quantity, price]);
76
+
77
+ const onSubmit = (data: BillLineItemForm) => {
78
+ const url = `${apiBasePath}bill`;
79
+
80
+ const newItem = {
81
+ ...item,
82
+ quantity: parseInt(data.quantity),
83
+ price: parseInt(data?.price),
84
+ billableService: getBillableServiceUuid(billableServices, item.billableService),
85
+ item: item?.item,
86
+ };
87
+
88
+ const previousLineitems = bill?.lineItems
89
+ .filter((currItem) => currItem.uuid !== item?.uuid)
90
+ .map((currItem) => ({
91
+ ...currItem,
92
+ billableService: getBillableServiceUuid(billableServices, item.billableService),
93
+ }));
94
+ const updatedLineItems = previousLineitems.concat(newItem);
95
+
96
+ const payload = {
97
+ cashPoint: bill.cashPointUuid,
98
+ cashier: bill.cashier.uuid,
99
+ lineItems: updatedLineItems,
100
+ patient: bill.patientUuid,
101
+ status: bill.status,
102
+ uuid: bill.uuid,
103
+ };
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
+ );
119
+ };
120
+
121
+ if (Object.keys(bill)?.length === 0) {
122
+ return <ModalHeader closeModal={closeModal} title={t('billLineItemEmpty', 'This bill has no line items')} />;
123
+ }
124
+
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>
131
+ <div className={styles.modalBody}>
132
+ <h5>
133
+ {bill?.patientName} &nbsp; · &nbsp;{bill?.cashPointName} &nbsp; · &nbsp;{bill?.receiptNumber}&nbsp;
134
+ </h5>
135
+ </div>
136
+ <section className={styles.section}>
137
+ <p className={styles.label}>
138
+ {t('item', 'Item')} : {item?.billableService ? item?.billableService : item?.item}
139
+ </p>
140
+ <p className={styles.label}>
141
+ {t('currentPrice', 'Current price')} : {item?.price}
142
+ </p>
143
+ <p className={styles.label}>
144
+ {t('status', 'status')} : {item?.paymentStatus}
145
+ </p>
146
+ <Controller
147
+ name="quantity"
148
+ control={control}
149
+ render={({ field: { onChange, onBlur, value } }) => (
150
+ <NumberInput
151
+ label={t('quantity', 'Quantity')}
152
+ id="quantityInput"
153
+ min={0}
154
+ max={100}
155
+ value={value}
156
+ onChange={onChange}
157
+ className={styles.controlField}
158
+ invalid={errors.quantity?.message}
159
+ invalidText={errors.quantity?.message}
160
+ />
161
+ )}
162
+ />
163
+
164
+ <Controller
165
+ name="price"
166
+ control={control}
167
+ render={({ field: { value } }) => (
168
+ <TextInput
169
+ id="priceInput"
170
+ labelText={t('price', 'Unit Price')}
171
+ value={value}
172
+ readOnly={true}
173
+ className={styles.controlField}
174
+ helperText="This is the unit Price for this item."
175
+ />
176
+ )}
177
+ />
178
+
179
+ <p className={styles.label}>
180
+ {t('total', 'Total')} : {total}{' '}
181
+ </p>
182
+
183
+ {showErrorNotification && (
184
+ <Column className={styles.errorContainer}>
185
+ <InlineNotification
186
+ lowContrast
187
+ title={t('error', 'Error')}
188
+ subtitle={t('pleaseRequiredFields', 'Please fill all required fields') + '.'}
189
+ onClose={() => setShowErrorNotification(false)}
190
+ />
191
+ </Column>
192
+ )}
193
+ </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
+ }
219
+ };
220
+
221
+ export default ChangeStatus;
@@ -0,0 +1,137 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
3
+ import { showSnackbar } from '@openmrs/esm-framework';
4
+ import { type MappedBill } from '../types';
5
+ import { updateBillItems } from '../billing.resource';
6
+ import ChangeStatus from './edit-bill-item.component';
7
+
8
+ // Mock external dependencies
9
+ jest.mock('../billing.resource', () => ({
10
+ updateBillItems: jest.fn(() => Promise.resolve({})),
11
+ }));
12
+
13
+ jest.mock('@openmrs/esm-framework', () => ({
14
+ showSnackbar: jest.fn(),
15
+ }));
16
+
17
+ jest.mock('../billable-services/billable-service.resource', () => ({
18
+ useBillableServices: jest.fn(() => ({
19
+ billableServices: [],
20
+ })),
21
+ }));
22
+
23
+ const mockBill: MappedBill = {
24
+ id: 1,
25
+ uuid: 'bill-uuid',
26
+ patientUuid: 'patient-uuid',
27
+ cashier: {
28
+ uuid: 'cashier-uuid',
29
+ display: 'John Doe',
30
+ links: [],
31
+ },
32
+ cashPointUuid: 'cashpoint-uuid',
33
+ cashPointLocation: 'Main Location',
34
+ status: 'PENDING',
35
+ lineItems: [
36
+ {
37
+ uuid: 'item-uuid',
38
+ quantity: 2,
39
+ price: 100,
40
+ display: 'Test Item',
41
+ voided: false,
42
+ voidReason: null,
43
+ priceName: 'Service Price',
44
+ billableService: 'service-uuid',
45
+ priceUuid: 'price-uuid',
46
+ lineItemOrder: 1,
47
+ resourceVersion: '1.0',
48
+ item: 'Test Item',
49
+ paymentStatus: 'PENDING',
50
+ },
51
+ ],
52
+ dateCreated: new Date().toISOString(),
53
+ billingService: 'billing-service-uuid',
54
+ payments: [],
55
+ patientName: 'John Doe',
56
+ cashPointName: 'Main Cashpoint',
57
+ receiptNumber: '123456',
58
+ identifier: 'receipt-identifier',
59
+ };
60
+
61
+ const mockItem = {
62
+ uuid: 'item-uuid',
63
+ quantity: 2,
64
+ price: 100,
65
+ billableService: 'service-uuid',
66
+ paymentStatus: 'UNPAID',
67
+ item: 'Test Service',
68
+ display: 'Test Service',
69
+ voided: false,
70
+ voidReason: null,
71
+ priceName: 'Service Price',
72
+ priceUuid: 'price-uuid',
73
+ lineItemOrder: 1,
74
+ resourceVersion: '1.0',
75
+ };
76
+
77
+ describe('ChangeStatus component', () => {
78
+ const closeModalMock = jest.fn();
79
+
80
+ beforeEach(() => {
81
+ jest.clearAllMocks();
82
+ });
83
+
84
+ test('renders the form with correct fields and default values', () => {
85
+ render(<ChangeStatus bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
86
+
87
+ expect(screen.getByText('Edit bill line item?')).toBeInTheDocument();
88
+ expect(screen.getByText('John Doe · Main Cashpoint · 123456')).toBeInTheDocument();
89
+ expect(screen.getByRole('spinbutton', { name: /Quantity/ })).toHaveValue(2);
90
+ expect(screen.getByLabelText(/Unit Price/)).toHaveValue('100');
91
+ expect(screen.getByText(/Total/)).toHaveTextContent('200');
92
+ });
93
+
94
+ test('updates total when quantity is changed', () => {
95
+ render(<ChangeStatus bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
96
+
97
+ const quantityInput = screen.getByRole('spinbutton', { name: /Quantity/ });
98
+ fireEvent.change(quantityInput, { target: { value: 3 } });
99
+
100
+ expect(screen.getByText(/Total/)).toHaveTextContent('300');
101
+ });
102
+
103
+ test('submits the form and shows a success notification', async () => {
104
+ (updateBillItems as jest.Mock).mockResolvedValueOnce({});
105
+
106
+ render(<ChangeStatus bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
107
+
108
+ fireEvent.click(screen.getByText(/Save/));
109
+
110
+ await waitFor(() => {
111
+ expect(updateBillItems).toHaveBeenCalled();
112
+ expect(showSnackbar).toHaveBeenCalledWith({
113
+ title: 'Save Bill',
114
+ subtitle: 'Bill processing has been successful',
115
+ kind: 'success',
116
+ timeoutInMs: 3000,
117
+ });
118
+ expect(closeModalMock).toHaveBeenCalled();
119
+ });
120
+ });
121
+
122
+ test('shows error notification when submission fails', async () => {
123
+ (updateBillItems as jest.Mock).mockRejectedValueOnce({ message: 'Error occurred' });
124
+
125
+ render(<ChangeStatus bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
126
+
127
+ fireEvent.click(screen.getByText(/Save/));
128
+
129
+ await waitFor(() => {
130
+ expect(showSnackbar).toHaveBeenCalledWith({
131
+ title: 'Bill processing error',
132
+ kind: 'error',
133
+ subtitle: 'Error occurred',
134
+ });
135
+ });
136
+ });
137
+ });
@@ -9,11 +9,11 @@ import {
9
9
  StructuredListWrapper,
10
10
  } from '@carbon/react';
11
11
  import { useTranslation } from 'react-i18next';
12
+ import { useConfig } from '@openmrs/esm-framework';
12
13
  import { convertToCurrency } from '../../helpers';
13
14
  import { type MappedBill, type LineItem } from '../../types';
14
15
  import BillWaiverForm from './bill-waiver-form.component';
15
16
  import styles from './bill-waiver.scss';
16
- import { useConfig } from '@openmrs/esm-framework';
17
17
 
18
18
  const PatientBillsSelections: React.FC<{ bills: MappedBill; setPatientUuid: (patientUuid) => void }> = ({
19
19
  bills,
@@ -3,14 +3,14 @@ import { Form, Stack, FormGroup, Layer, Button, NumberInput } from '@carbon/reac
3
3
  import { TaskAdd } from '@carbon/react/icons';
4
4
  import { mutate } from 'swr';
5
5
  import { useTranslation } from 'react-i18next';
6
- import { restBaseUrl, showSnackbar, useConfig } from '@openmrs/esm-framework';
6
+ import { showSnackbar, useConfig } from '@openmrs/esm-framework';
7
7
  import { createBillWaiverPayload } from './utils';
8
8
  import { convertToCurrency } from '../../helpers';
9
9
  import { processBillPayment } from '../../billing.resource';
10
10
  import { useBillableItems } from '../../billing-form/billing-form.resource';
11
11
  import type { LineItem, MappedBill } from '../../types';
12
- import styles from './bill-waiver-form.scss';
13
12
  import { apiBasePath } from '../../constants';
13
+ import styles from './bill-waiver-form.scss';
14
14
 
15
15
  type BillWaiverFormProps = {
16
16
  bill: MappedBill;
@@ -18,11 +18,11 @@
18
18
 
19
19
  .billWaiverDescription {
20
20
  display: grid;
21
- grid-template-columns: 5rem 1fr;
22
- column-gap: 1rem;
21
+ grid-template-columns: layout.$spacing-11 1fr;
22
+ column-gap: layout.$spacing-05;
23
23
  align-items: center;
24
- margin-top: 0.125rem;
25
- margin-bottom: 0.5rem;
24
+ margin-top: layout.$spacing-01;
25
+ margin-bottom: layout.$spacing-03;
26
26
  }
27
27
 
28
28
  .label {
@@ -1,15 +1,15 @@
1
1
  import React, { useState } from 'react';
2
2
  import { ExtensionSlot, UserHasAccess } from '@openmrs/esm-framework';
3
- import PatientBills from './patient-bills.component';
4
3
  import { useBills } from '../../billing.resource';
4
+ import PatientBills from './patient-bills.component';
5
5
  import styles from './bill-waiver.scss';
6
6
 
7
- type BillWaiverProps = {};
8
-
9
- const BillWaiver: React.FC<BillWaiverProps> = () => {
7
+ const BillWaiver: React.FC = () => {
10
8
  const [patientUuid, setPatientUuid] = useState<string>('');
11
9
  const { bills } = useBills(patientUuid);
10
+
12
11
  const filterBills = bills.filter((bill) => bill?.status !== 'PAID' && patientUuid === bill.patientUuid) ?? [];
12
+
13
13
  return (
14
14
  <UserHasAccess privilege="coreapps.systemAdministration">
15
15
  <div className={styles.billWaiverContainer}>
@@ -15,12 +15,12 @@ import {
15
15
  Tile,
16
16
  } from '@carbon/react';
17
17
  import { useTranslation } from 'react-i18next';
18
+ import { useConfig } from '@openmrs/esm-framework';
18
19
  import { EmptyDataIllustration } from '@openmrs/esm-patient-common-lib';
19
20
  import { type MappedBill } from '../../types';
20
21
  import { convertToCurrency } from '../../helpers';
21
22
  import PatientBillsSelections from './bill-selection.component';
22
23
  import styles from '../../bills-table/bills-table.scss';
23
- import { useConfig } from '@openmrs/esm-framework';
24
24
 
25
25
  type PatientBillsProps = {
26
26
  patientUuid: string;
@@ -1,19 +1,19 @@
1
1
  import useSWR from 'swr';
2
- import { type OpenmrsResource, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import { type OpenmrsResource, openmrsFetch, restBaseUrl, useOpenmrsFetchAll, useConfig } from '@openmrs/esm-framework';
3
3
  import { type ServiceConcept } from '../types';
4
4
  import { apiBasePath } from '../constants';
5
+ import { type BillableService } from '../types/index';
5
6
 
6
7
  type ResponseObject = {
7
8
  results: Array<OpenmrsResource>;
8
9
  };
9
10
 
10
11
  export const useBillableServices = () => {
11
- const url = `${apiBasePath}billableService?v=custom:(uuid,name,shortName,serviceStatus,serviceType:(display),servicePrices:(uuid,name,price))`;
12
-
13
- const { data, isLoading, isValidating, error, mutate } = useSWR<{ data: ResponseObject }>(url, openmrsFetch);
12
+ const url = `${apiBasePath}billableService?v=custom:(uuid,name,shortName,serviceStatus,concept:(uuid,display,name:(name)),serviceType:(display),servicePrices:(uuid,name,price,paymentMode:(uuid,name)))`;
13
+ const { data, isLoading, isValidating, error, mutate } = useOpenmrsFetchAll<BillableService[]>(url);
14
14
 
15
15
  return {
16
- billableServices: data?.data.results ?? [],
16
+ billableServices: data ?? [],
17
17
  isLoading,
18
18
  isValidating,
19
19
  error,
@@ -22,7 +22,9 @@ export const useBillableServices = () => {
22
22
  };
23
23
 
24
24
  export function useServiceTypes() {
25
- const url = `${restBaseUrl}/concept/21b8cf43-9f9f-4d02-9f4a-d710ece54261?v=custom:(setMembers:(uuid,display))`;
25
+ const config = useConfig();
26
+ const serviceConceptUuid = config.serviceTypes.billableService;
27
+ const url = `${restBaseUrl}/concept/${serviceConceptUuid}?v=custom:(setMembers:(uuid,display))`;
26
28
 
27
29
  const { data, error, isLoading } = useSWR<{ data }>(url, openmrsFetch);
28
30
 
@@ -70,3 +72,14 @@ export function useConceptsSearch(conceptToLookup: string) {
70
72
  isSearching: isLoading,
71
73
  };
72
74
  }
75
+
76
+ export const updateBillableService = (uuid: string, payload: any) => {
77
+ const url = `${apiBasePath}/billableService/${uuid}`;
78
+ return openmrsFetch(url, {
79
+ method: 'POST',
80
+ body: JSON.stringify(payload),
81
+ headers: {
82
+ 'Content-Type': 'application/json',
83
+ },
84
+ });
85
+ };
@@ -1,13 +1,15 @@
1
1
  import React from 'react';
2
2
  import { BrowserRouter, Routes, Route } from 'react-router-dom';
3
- import { SideNav, SideNavItems, SideNavLink } from '@carbon/react';
4
- import { Wallet, Money } from '@carbon/react/icons';
3
+ import { SideNav, SideNavItems, SideNavLink, SideNavMenu, SideNavMenuItem } from '@carbon/react';
4
+ import { Wallet, Money, Settings } from '@carbon/react/icons';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import { UserHasAccess, navigate } from '@openmrs/esm-framework';
7
7
  import AddBillableService from './create-edit/add-billable-service.component';
8
8
  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
+ import CashPointConfiguration from './cash-point/cash-point-configuration.component';
12
+ import PaymentModesConfig from './payyment-modes/payment-modes-config.component';
11
13
  import styles from './billable-services.scss';
12
14
 
13
15
  const BillableServiceHome: React.FC = () => {
@@ -18,6 +20,10 @@ const BillableServiceHome: React.FC = () => {
18
20
  navigate({ to: `${basePath}/${path}` });
19
21
  };
20
22
 
23
+ const handleCloseAddService = () => {
24
+ navigate({ to: `${basePath}` });
25
+ };
26
+
21
27
  return (
22
28
  <BrowserRouter basename={`${window.spaBase}/billable-services`}>
23
29
  <main className={styles.mainSection}>
@@ -31,6 +37,14 @@ const BillableServiceHome: React.FC = () => {
31
37
  <SideNavLink onClick={() => handleNavigation('waive-bill')} renderIcon={Money}>
32
38
  {t('billWaiver', 'Bill waiver')}
33
39
  </SideNavLink>
40
+ <SideNavMenu title={t('billingSettings', 'Billing Settings')} renderIcon={Settings}>
41
+ <SideNavMenuItem onClick={() => handleNavigation('cash-point-config')}>
42
+ {t('cashPointConfig', 'Cash Point Config')}
43
+ </SideNavMenuItem>
44
+ <SideNavMenuItem onClick={() => handleNavigation('payment-modes-config')}>
45
+ {t('paymentModesConfig', 'Payment Modes Config')}
46
+ </SideNavMenuItem>
47
+ </SideNavMenu>
34
48
  </UserHasAccess>
35
49
  </SideNavItems>
36
50
  </SideNav>
@@ -39,8 +53,10 @@ const BillableServiceHome: React.FC = () => {
39
53
  <BillingHeader title={t('billServicesManagement', 'Bill services management')} />
40
54
  <Routes>
41
55
  <Route path="/" element={<BillableServicesDashboard />} />
42
- <Route path="/add-service" element={<AddBillableService />} />
56
+ <Route path="/add-service" element={<AddBillableService onClose={handleCloseAddService} />} />
43
57
  <Route path="/waive-bill" element={<BillWaiver />} />
58
+ <Route path="/cash-point-config" element={<CashPointConfiguration />} />
59
+ <Route path="/payment-modes-config" element={<PaymentModesConfig />} />
44
60
  </Routes>
45
61
  </section>
46
62
  </main>
@@ -0,0 +1,17 @@
1
+ import { ClickableTile } from '@carbon/react';
2
+ import React from 'react';
3
+ import styles from './item.scss';
4
+ import { Receipt } from '@carbon/react/icons';
5
+
6
+ const Item = () => {
7
+ // items
8
+ const openmrsSpaBase = window['getOpenmrsSpaBase']();
9
+
10
+ return (
11
+ <ClickableTile className={styles.customTile} id="menu-item" href={`${openmrsSpaBase}billable-services`}>
12
+ <div className="customTileTitle">{<Receipt size={24} />}</div>
13
+ <div>Billable Services</div>
14
+ </ClickableTile>
15
+ );
16
+ };
17
+ export default Item;
@@ -0,0 +1,14 @@
1
+ .customTile {
2
+ background-color: #f0f0f0;
3
+ border-radius: 4px;
4
+ padding: 20px;
5
+ width: 50px;
6
+ height: 100px;
7
+ text-align: center;
8
+ margin: 2px;
9
+ }
10
+
11
+ .customTileTitle {
12
+ font-size: 14px;
13
+ color: #333;
14
+ }