@openmrs/esm-billing-app 1.0.2-pre.90 → 1.0.2-pre.905

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 (214) 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/1537.js +1 -0
  10. package/dist/1537.js.map +1 -0
  11. package/dist/1856.js +1 -0
  12. package/dist/1856.js.map +1 -0
  13. package/dist/2146.js +1 -1
  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/3099.js +1 -1
  18. package/dist/3584.js +1 -1
  19. package/dist/3717.js +2 -0
  20. package/dist/3717.js.map +1 -0
  21. package/dist/4055.js +1 -1
  22. package/dist/4132.js +1 -1
  23. package/dist/4300.js +1 -1
  24. package/dist/4335.js +1 -1
  25. package/dist/4618.js +1 -1
  26. package/dist/4652.js +1 -1
  27. package/dist/4692.js +1 -0
  28. package/dist/4692.js.map +1 -0
  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/5442.js +1 -1
  37. package/dist/5661.js +1 -1
  38. package/dist/6022.js +1 -1
  39. package/dist/6468.js +1 -1
  40. package/dist/6540.js +1 -1
  41. package/dist/6540.js.map +1 -1
  42. package/dist/6679.js +1 -1
  43. package/dist/6840.js +1 -1
  44. package/dist/6859.js +1 -1
  45. package/dist/7097.js +1 -1
  46. package/dist/7159.js +1 -1
  47. package/dist/723.js +1 -1
  48. package/dist/7255.js +1 -1
  49. package/dist/7255.js.map +1 -1
  50. package/dist/7617.js +1 -1
  51. package/dist/795.js +1 -1
  52. package/dist/8163.js +1 -1
  53. package/dist/8349.js +1 -1
  54. package/dist/8618.js +1 -1
  55. package/dist/8708.js +2 -0
  56. package/dist/{6557.js.LICENSE.txt → 8708.js.LICENSE.txt} +22 -0
  57. package/dist/8708.js.map +1 -0
  58. package/dist/890.js +1 -1
  59. package/dist/9214.js +1 -1
  60. package/dist/9538.js +1 -1
  61. package/dist/9569.js +1 -1
  62. package/dist/961.js +1 -1
  63. package/dist/961.js.map +1 -1
  64. package/dist/986.js +1 -1
  65. package/dist/9879.js +1 -1
  66. package/dist/9895.js +1 -1
  67. package/dist/9900.js +1 -1
  68. package/dist/9913.js +1 -1
  69. package/dist/main.js +1 -1
  70. package/dist/main.js.map +1 -1
  71. package/dist/openmrs-esm-billing-app.js +1 -1
  72. package/dist/openmrs-esm-billing-app.js.buildmanifest.json +282 -296
  73. package/dist/openmrs-esm-billing-app.js.map +1 -1
  74. package/dist/routes.json +1 -1
  75. package/e2e/README.md +19 -18
  76. package/e2e/core/test.ts +1 -1
  77. package/e2e/fixtures/api.ts +1 -1
  78. package/e2e/specs/sample-test.spec.ts +0 -1
  79. package/e2e/support/github/Dockerfile +1 -1
  80. package/package.json +18 -15
  81. package/src/bill-history/bill-history.component.tsx +20 -28
  82. package/src/bill-history/bill-history.scss +4 -94
  83. package/src/bill-history/bill-history.test.tsx +37 -78
  84. package/src/bill-item-actions/bill-item-actions.scss +21 -5
  85. package/src/bill-item-actions/edit-bill-item.modal.tsx +225 -0
  86. package/src/bill-item-actions/edit-bill-item.test.tsx +214 -40
  87. package/src/billable-services/bill-waiver/bill-selection.component.tsx +5 -5
  88. package/src/billable-services/bill-waiver/bill-waiver-form.component.tsx +28 -32
  89. package/src/billable-services/bill-waiver/patient-bills.component.tsx +7 -7
  90. package/src/billable-services/bill-waiver/utils.ts +13 -3
  91. package/src/billable-services/{create-edit/add-billable-service.scss → billable-service-form/billable-service-form.scss} +32 -64
  92. package/src/billable-services/billable-service-form/billable-service-form.test.tsx +893 -0
  93. package/src/billable-services/billable-service-form/billable-service-form.workspace.tsx +504 -0
  94. package/src/billable-services/billable-service.resource.ts +42 -26
  95. package/src/billable-services/billable-services-home.component.tsx +13 -42
  96. package/src/billable-services/billable-services-left-panel-link.component.tsx +48 -0
  97. package/src/billable-services/billable-services-left-panel-menu.component.tsx +46 -0
  98. package/src/billable-services/billable-services-menu-item/item.component.tsx +5 -4
  99. package/src/billable-services/billable-services.component.tsx +156 -152
  100. package/src/billable-services/billable-services.scss +29 -0
  101. package/src/billable-services/billable-services.test.tsx +6 -49
  102. package/src/billable-services/cash-point/add-cash-point.modal.tsx +170 -0
  103. package/src/billable-services/cash-point/cash-point-configuration.component.tsx +19 -193
  104. package/src/billable-services/cash-point/cash-point-configuration.scss +1 -5
  105. package/src/billable-services/dashboard/dashboard.component.tsx +0 -2
  106. package/src/billable-services/payment-modes/add-payment-mode.modal.tsx +121 -0
  107. package/src/billable-services/payment-modes/delete-payment-mode.modal.tsx +74 -0
  108. package/src/billable-services/payment-modes/payment-modes-config.component.tsx +125 -0
  109. package/src/billable-services/{payyment-modes → payment-modes}/payment-modes-config.scss +5 -4
  110. package/src/billable-services-admin-card-link.component.test.tsx +2 -2
  111. package/src/billable-services-admin-card-link.component.tsx +1 -1
  112. package/src/billing-dashboard/billing-dashboard.scss +1 -1
  113. package/src/billing-form/billing-checkin-form.component.tsx +21 -17
  114. package/src/billing-form/billing-checkin-form.test.tsx +99 -26
  115. package/src/billing-form/billing-form.component.tsx +222 -292
  116. package/src/billing-form/billing-form.scss +143 -0
  117. package/src/billing-form/visit-attributes/visit-attributes-form.component.tsx +1 -1
  118. package/src/billing.resource.ts +69 -74
  119. package/src/bills-table/bills-table.component.tsx +3 -3
  120. package/src/bills-table/bills-table.test.tsx +98 -54
  121. package/src/config-schema.ts +52 -24
  122. package/src/dashboard.meta.ts +4 -2
  123. package/src/helpers/functions.ts +5 -4
  124. package/src/index.ts +71 -9
  125. package/src/invoice/invoice-table.component.tsx +36 -70
  126. package/src/invoice/invoice-table.scss +8 -5
  127. package/src/invoice/invoice-table.test.tsx +273 -62
  128. package/src/invoice/invoice.component.tsx +39 -32
  129. package/src/invoice/invoice.scss +11 -4
  130. package/src/invoice/invoice.test.tsx +324 -120
  131. package/src/invoice/payments/invoice-breakdown/invoice-breakdown.scss +9 -9
  132. package/src/invoice/payments/payment-form/payment-form.component.tsx +43 -34
  133. package/src/invoice/payments/payment-form/payment-form.scss +5 -6
  134. package/src/invoice/payments/payment-form/payment-form.test.tsx +216 -66
  135. package/src/invoice/payments/payment-history/payment-history.component.tsx +6 -4
  136. package/src/invoice/payments/payment-history/payment-history.test.tsx +9 -14
  137. package/src/invoice/payments/payments.component.tsx +55 -67
  138. package/src/invoice/payments/payments.scss +4 -3
  139. package/src/invoice/payments/payments.test.tsx +282 -0
  140. package/src/invoice/payments/utils.ts +15 -27
  141. package/src/invoice/printable-invoice/print-receipt.component.tsx +3 -2
  142. package/src/invoice/printable-invoice/print-receipt.test.tsx +14 -25
  143. package/src/invoice/printable-invoice/printable-footer.component.tsx +2 -2
  144. package/src/invoice/printable-invoice/printable-footer.test.tsx +4 -13
  145. package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +12 -11
  146. package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +16 -14
  147. package/src/invoice/printable-invoice/printable-invoice.component.tsx +20 -34
  148. package/src/left-panel-link.test.tsx +1 -4
  149. package/src/metrics-cards/metrics-cards.component.tsx +16 -6
  150. package/src/metrics-cards/metrics-cards.scss +4 -0
  151. package/src/metrics-cards/metrics-cards.test.tsx +18 -5
  152. package/src/modal/require-payment-modal.test.tsx +27 -22
  153. package/src/modal/{require-payment-modal.component.tsx → require-payment.modal.tsx} +18 -19
  154. package/src/routes.json +44 -20
  155. package/src/types/index.ts +81 -23
  156. package/translations/am.json +132 -75
  157. package/translations/ar.json +133 -76
  158. package/translations/ar_SY.json +133 -76
  159. package/translations/bn.json +135 -78
  160. package/translations/de.json +133 -76
  161. package/translations/en.json +133 -78
  162. package/translations/en_US.json +133 -76
  163. package/translations/es.json +132 -75
  164. package/translations/es_MX.json +133 -76
  165. package/translations/fr.json +138 -81
  166. package/translations/he.json +132 -75
  167. package/translations/hi.json +133 -76
  168. package/translations/hi_IN.json +133 -76
  169. package/translations/id.json +133 -76
  170. package/translations/it.json +159 -102
  171. package/translations/ka.json +133 -76
  172. package/translations/km.json +132 -75
  173. package/translations/ku.json +133 -76
  174. package/translations/ky.json +133 -76
  175. package/translations/lg.json +133 -76
  176. package/translations/ne.json +133 -76
  177. package/translations/pl.json +133 -76
  178. package/translations/pt.json +133 -76
  179. package/translations/pt_BR.json +133 -76
  180. package/translations/qu.json +133 -76
  181. package/translations/ro_RO.json +222 -165
  182. package/translations/ru_RU.json +133 -76
  183. package/translations/si.json +133 -76
  184. package/translations/sw.json +133 -76
  185. package/translations/sw_KE.json +133 -76
  186. package/translations/tr.json +133 -76
  187. package/translations/tr_TR.json +133 -76
  188. package/translations/uk.json +133 -76
  189. package/translations/uz.json +133 -76
  190. package/translations/uz@Latn.json +133 -76
  191. package/translations/uz_UZ.json +133 -76
  192. package/translations/vi.json +133 -76
  193. package/translations/zh.json +133 -76
  194. package/translations/zh_CN.json +164 -107
  195. package/dist/1146.js.LICENSE.txt +0 -21
  196. package/dist/2352.js +0 -1
  197. package/dist/2352.js.map +0 -1
  198. package/dist/246.js +0 -1
  199. package/dist/246.js.map +0 -1
  200. package/dist/4689.js +0 -2
  201. package/dist/4689.js.map +0 -1
  202. package/dist/6557.js +0 -2
  203. package/dist/6557.js.map +0 -1
  204. package/dist/8638.js +0 -1
  205. package/dist/8638.js.map +0 -1
  206. package/dist/9968.js +0 -1
  207. package/dist/9968.js.map +0 -1
  208. package/src/bill-item-actions/edit-bill-item.component.tsx +0 -221
  209. package/src/billable-services/create-edit/add-billable-service.component.tsx +0 -401
  210. package/src/billable-services/create-edit/add-billable-service.test.tsx +0 -154
  211. package/src/billable-services/dashboard/service-metrics.component.tsx +0 -41
  212. package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +0 -280
  213. package/src/invoice/payments/payments.component.test.tsx +0 -121
  214. /package/dist/{4689.js.LICENSE.txt → 3717.js.LICENSE.txt} +0 -0
@@ -73,6 +73,9 @@
73
73
 
74
74
  .grid {
75
75
  margin: layout.$spacing-05;
76
+ display: flex;
77
+ flex-direction: column;
78
+ gap: layout.$spacing-05;
76
79
  }
77
80
 
78
81
  .row {
@@ -85,3 +88,143 @@
85
88
  .spacer {
86
89
  margin-top: layout.$spacing-05;
87
90
  }
91
+
92
+ .selectedItemsContainer {
93
+ margin-top: layout.$spacing-05;
94
+
95
+ h4 {
96
+ margin-bottom: layout.$spacing-05;
97
+ color: $text-02;
98
+ font-size: 1rem;
99
+ font-weight: 600;
100
+ }
101
+
102
+ .itemCard {
103
+ border: 1px solid $ui-03;
104
+ border-radius: layout.$spacing-02;
105
+ padding: layout.$spacing-05;
106
+ margin-bottom: layout.$spacing-05;
107
+ background-color: $ui-01;
108
+
109
+ .itemHeader {
110
+ display: flex;
111
+ justify-content: space-between;
112
+ align-items: center;
113
+ margin-bottom: layout.$spacing-05;
114
+
115
+ .itemName {
116
+ font-weight: 600;
117
+ font-size: 1rem;
118
+ color: $text-02;
119
+ }
120
+ }
121
+
122
+ .itemControls {
123
+ display: flex;
124
+ flex-wrap: wrap;
125
+ gap: layout.$spacing-05;
126
+ margin-top: layout.$spacing-03;
127
+ align-items: flex-end; .controlSection {
128
+ display: flex;
129
+ flex-direction: column;
130
+ min-width: 0;
131
+
132
+ &:first-child {
133
+ flex: 1 1 auto;
134
+ min-width: 250px;
135
+ }
136
+
137
+ &:nth-child(2) {
138
+ flex: 0 0 140px;
139
+ width: 140px;
140
+ }
141
+
142
+ &:last-child {
143
+ flex: 0 0 180px;
144
+ width: 180px;
145
+ margin-left: auto;
146
+ text-align: right;
147
+ }
148
+
149
+ label {
150
+ font-size: 0.875rem;
151
+ font-weight: 500;
152
+ margin-bottom: layout.$spacing-03;
153
+ color: $text-02;
154
+ white-space: nowrap;
155
+ overflow: hidden;
156
+ text-overflow: ellipsis;
157
+ }
158
+
159
+ :global(.cds--list-box),
160
+ :global(.cds--number),
161
+ :global(.cds--combo-box),
162
+ :global(.cds--list-box__wrapper),
163
+ :global(.cds--list-box__field) {
164
+ width: 100% !important;
165
+ max-width: 100% !important;
166
+ min-width: 0 !important;
167
+ }
168
+
169
+ .priceDisplay,
170
+ .totalDisplay {
171
+ font-size: 0.875rem;
172
+ font-size: 0.875rem;
173
+ font-weight: 600;
174
+ color: $text-02;
175
+ padding: layout.$spacing-03 0;
176
+ word-break: break-word;
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ .grandTotal {
183
+ text-align: right;
184
+ padding: layout.$spacing-05;
185
+ border-top: 2px solid $interactive-01;
186
+ margin-top: layout.$spacing-05;
187
+ background-color: $ui-01;
188
+ font-size: 1.125rem;
189
+ font-weight: 600;
190
+ color: $text-02;
191
+ }
192
+ }
193
+
194
+ @media (max-width: 1056px) {
195
+ .selectedItemsContainer {
196
+ .itemCard {
197
+ .itemControls {
198
+ .controlSection {
199
+ &:nth-child(2) {
200
+ flex: 1 1 100%;
201
+ width: 100%;
202
+ }
203
+ }
204
+ }
205
+ }
206
+ }
207
+ }
208
+
209
+ @media (max-width: 768px) {
210
+ .selectedItemsContainer {
211
+ .itemCard {
212
+ padding: layout.$spacing-04;
213
+
214
+ .itemControls {
215
+ flex-direction: column;
216
+ gap: layout.$spacing-03;
217
+
218
+ .controlSection {
219
+ &:first-child,
220
+ &:nth-child(2),
221
+ &:last-child {
222
+ flex: 1 1 100%;
223
+ width: 100%;
224
+ max-width: 100%;
225
+ }
226
+ }
227
+ }
228
+ }
229
+ }
230
+ }
@@ -100,7 +100,7 @@ const VisitAttributesForm: React.FC<VisitAttributesFormProps> = ({ setAttributes
100
100
  <InlineLoading
101
101
  status="active"
102
102
  iconDescription={t('loadingDescription', 'Loading')}
103
- description={t('loading', 'Loading data...')}
103
+ description={t('loading', 'Loading data') + '...'}
104
104
  />
105
105
  );
106
106
  }
@@ -1,48 +1,79 @@
1
- import { useContext } from 'react';
2
- import dayjs from 'dayjs';
3
- import isEmpty from 'lodash-es/isEmpty';
4
- import sortBy from 'lodash-es/sortBy';
5
1
  import useSWR from 'swr';
6
- import { formatDate, parseDate, openmrsFetch, useSession, useVisit, restBaseUrl } from '@openmrs/esm-framework';
7
- import { apiBasePath, omrsDateFormat } from './constants';
8
- import type { FacilityDetail, MappedBill, PatientInvoice } from './types';
9
- import SelectedDateContext from './hooks/selectedDateContext';
10
-
11
- export const useBills = (patientUuid: string = '', billStatus: string = '') => {
12
- const { selectedDate } = useContext(SelectedDateContext);
13
- const endDate = dayjs().endOf('day').format(omrsDateFormat);
14
- const url = `${apiBasePath}bill?q=&v=full`;
15
-
16
- const patientUrl = `${apiBasePath}bill?patientUuid=${patientUuid}&v=full`;
17
-
18
- const { data, error, isLoading, isValidating, mutate } = useSWR<{ data: { results: Array<PatientInvoice> } }>(
19
- isEmpty(patientUuid) ? url : patientUrl,
20
- openmrsFetch,
21
- );
2
+ import sortBy from 'lodash-es/sortBy';
3
+ import {
4
+ formatDate,
5
+ parseDate,
6
+ openmrsFetch,
7
+ useSession,
8
+ useVisit,
9
+ type SessionLocation,
10
+ useOpenmrsFetchAll,
11
+ } from '@openmrs/esm-framework';
12
+ import { apiBasePath } from './constants';
13
+ import type {
14
+ MappedBill,
15
+ PatientInvoice,
16
+ BillableItem,
17
+ BillPaymentPayload,
18
+ CreateBillPayload,
19
+ UpdateBillPayload,
20
+ } from './types';
21
+
22
+ const mapBillProperties = (bill: PatientInvoice): MappedBill => {
23
+ const activeLineItems = bill?.lineItems?.filter((item) => !item.voided) || [];
24
+
25
+ const status =
26
+ activeLineItems.length > 1
27
+ ? activeLineItems.some((item) => item.paymentStatus === 'PENDING')
28
+ ? 'PENDING'
29
+ : 'PAID'
30
+ : bill.status;
22
31
 
23
- const mapBillProperties = (bill: PatientInvoice): MappedBill => ({
32
+ return {
24
33
  id: bill?.id,
25
34
  uuid: bill?.uuid,
26
35
  patientName: bill?.patient?.display.split('-')?.[1],
27
36
  identifier: bill?.patient?.display.split('-')?.[0],
28
37
  patientUuid: bill?.patient?.uuid,
29
- status: bill.lineItems.some((item) => item.paymentStatus === 'PENDING') ? 'PENDING' : 'PAID',
38
+ status,
30
39
  receiptNumber: bill?.receiptNumber,
31
40
  cashier: bill?.cashier,
32
41
  cashPointUuid: bill?.cashPoint?.uuid,
33
42
  cashPointName: bill?.cashPoint?.name,
34
43
  cashPointLocation: bill?.cashPoint?.location?.display,
35
44
  dateCreated: bill?.dateCreated ? formatDate(parseDate(bill.dateCreated), { mode: 'wide' }) : '--',
36
- lineItems: bill?.lineItems,
37
- billingService: bill?.lineItems.map((bill) => bill?.item || bill?.billableService || '--').join(' '),
38
- payments: bill?.payments,
45
+ lineItems: activeLineItems,
46
+ billingService: activeLineItems.map((lineItem) => lineItem?.item || lineItem?.billableService || '--').join(' '),
47
+ payments: bill.payments,
39
48
  display: bill?.display,
40
- totalAmount: bill?.lineItems?.map((item) => item.price * item.quantity).reduce((prev, curr) => prev + curr, 0),
41
- });
49
+ totalAmount: activeLineItems?.map((item) => item.price * item.quantity).reduce((prev, curr) => prev + curr, 0),
50
+ tenderedAmount: bill?.payments?.map((item) => item.amountTendered).reduce((prev, curr) => prev + curr, 0),
51
+ };
52
+ };
53
+
54
+ export const useBills = (patientUuid: string = '', billStatus: string = '') => {
55
+ // Build URL with status parameter if provided
56
+ const buildUrl = () => {
57
+ let url = `${apiBasePath}bill?v=full`;
58
+
59
+ if (patientUuid) {
60
+ url += `&patientUuid=${patientUuid}`;
61
+ }
62
+
63
+ if (billStatus) {
64
+ url += `&status=${billStatus}`;
65
+ }
66
+
67
+ return url;
68
+ };
69
+
70
+ const { data, error, isLoading, isValidating, mutate } = useSWR<{ data: { results: Array<PatientInvoice> } }>(
71
+ buildUrl(),
72
+ openmrsFetch,
73
+ );
42
74
 
43
75
  const sortedBills = sortBy(data?.data?.results ?? [], ['dateCreated']).reverse();
44
- const filteredBills = billStatus === '' ? sortedBills : sortedBills?.filter((bill) => bill?.status === billStatus);
45
- const mappedResults = filteredBills?.map((bill) => mapBillProperties(bill));
76
+ const mappedResults = sortedBills?.map((bill) => mapBillProperties(bill));
46
77
 
47
78
  return {
48
79
  bills: mappedResults,
@@ -60,31 +91,6 @@ export const useBill = (billUuid: string) => {
60
91
  openmrsFetch,
61
92
  );
62
93
 
63
- const mapBillProperties = (bill: PatientInvoice): MappedBill => ({
64
- id: bill?.id,
65
- uuid: bill?.uuid,
66
- patientName: bill?.patient?.display.split('-')?.[1],
67
- identifier: bill?.patient?.display.split('-')?.[0],
68
- patientUuid: bill?.patient?.uuid,
69
- status:
70
- bill.lineItems.length > 1
71
- ? bill.lineItems.some((item) => item.paymentStatus === 'PENDING')
72
- ? 'PENDING'
73
- : 'PAID'
74
- : bill.status,
75
- receiptNumber: bill?.receiptNumber,
76
- cashier: bill?.cashier,
77
- cashPointUuid: bill?.cashPoint?.uuid,
78
- cashPointName: bill?.cashPoint?.name,
79
- cashPointLocation: bill?.cashPoint?.location?.display,
80
- dateCreated: bill?.dateCreated ? formatDate(parseDate(bill.dateCreated), { mode: 'wide' }) : '--',
81
- lineItems: bill?.lineItems,
82
- billingService: bill?.lineItems.map((bill) => bill.item).join(' '),
83
- payments: bill.payments,
84
- totalAmount: bill?.lineItems?.map((item) => item.price * item.quantity).reduce((prev, curr) => prev + curr, 0),
85
- tenderedAmount: bill?.payments?.map((item) => item.amountTendered).reduce((prev, curr) => prev + curr, 0),
86
- });
87
-
88
94
  const formattedBill = data?.data ? mapBillProperties(data?.data) : null;
89
95
 
90
96
  return {
@@ -96,7 +102,7 @@ export const useBill = (billUuid: string) => {
96
102
  };
97
103
  };
98
104
 
99
- export const processBillPayment = (payload, billUuid: string) => {
105
+ export const processBillPayment = (payload: BillPaymentPayload, billUuid: string) => {
100
106
  const url = `${apiBasePath}bill/${billUuid}`;
101
107
 
102
108
  return openmrsFetch(url, {
@@ -108,13 +114,9 @@ export const processBillPayment = (payload, billUuid: string) => {
108
114
  });
109
115
  };
110
116
 
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 };
117
+ export function useDefaultFacility(): { data: SessionLocation | null } {
118
+ const { sessionLocation } = useSession();
119
+ return { data: sessionLocation };
118
120
  }
119
121
 
120
122
  export const usePatientPaymentInfo = (patientUuid: string) => {
@@ -130,19 +132,12 @@ export const usePatientPaymentInfo = (patientUuid: string) => {
130
132
  return paymentInformation;
131
133
  };
132
134
 
133
- export function useFetchSearchResults(searchVal, category) {
134
- let url = ``;
135
- if (category == 'Stock Item') {
136
- url = `${restBaseUrl}/stockmanagement/stockitem?v=default&limit=10&q=${searchVal}`;
137
- } else {
138
- url = `${apiBasePath}billableService?v=custom:(uuid,name,shortName,serviceStatus,serviceType:(display),servicePrices:(uuid,name,price,paymentMode))`;
139
- }
140
- const { data, error, isLoading, isValidating } = useSWR(searchVal ? url : null, openmrsFetch, {});
141
-
142
- return { data: data?.data, error, isLoading: isLoading, isValidating };
135
+ export function useBillableServices() {
136
+ const url = `${apiBasePath}billableService?v=custom:(uuid,name,shortName,serviceStatus,serviceType:(display),servicePrices:(uuid,name,price,paymentMode))`;
137
+ return useOpenmrsFetchAll<BillableItem>(url);
143
138
  }
144
139
 
145
- export const processBillItems = (payload) => {
140
+ export const processBillItems = (payload: CreateBillPayload) => {
146
141
  const url = `${apiBasePath}bill`;
147
142
  return openmrsFetch(url, {
148
143
  method: 'POST',
@@ -153,7 +148,7 @@ export const processBillItems = (payload) => {
153
148
  });
154
149
  };
155
150
 
156
- export const updateBillItems = (payload) => {
151
+ export const updateBillItems = (payload: UpdateBillPayload) => {
157
152
  const url = `${apiBasePath}bill/${payload.uuid}`;
158
153
  return openmrsFetch(url, {
159
154
  method: 'POST',
@@ -149,7 +149,7 @@ const BillsTable = () => {
149
149
  return (
150
150
  <div className={styles.errorContainer}>
151
151
  <Layer>
152
- <ErrorState error={error} headerTitle={t('billsList', 'Bill list')} />
152
+ <ErrorState error={error} headerTitle={t('billList', 'Bill list')} />
153
153
  </Layer>
154
154
  </div>
155
155
  );
@@ -168,7 +168,7 @@ const BillsTable = () => {
168
168
  label=""
169
169
  onChange={handleFilterChange}
170
170
  size={responsiveSize}
171
- titleText={t('filterBy', 'Filter by') + ':'}
171
+ titleText={t('filterBy', 'Filter by:')}
172
172
  type="inline"
173
173
  />
174
174
  </div>
@@ -190,7 +190,7 @@ const BillsTable = () => {
190
190
  useZebraStyles={rowData?.length > 1 ? true : false}>
191
191
  {({ rows, headers, getRowProps, getTableProps }) => (
192
192
  <TableContainer>
193
- <Table {...getTableProps()} aria-label="bill list">
193
+ <Table {...getTableProps()} aria-label={t('billList', 'Bill list')}>
194
194
  <TableHead>
195
195
  <TableRow>
196
196
  {headers.map((header) => (
@@ -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
  });
@@ -156,15 +196,18 @@ describe('BillsTable', () => {
156
196
  const patientNameLink = screen.getByRole('link', { name: 'John Doe' });
157
197
  expect(patientNameLink).toBeInTheDocument();
158
198
 
159
- expect(patientNameLink.getAttribute('href')).toEqual('/home/billing/patient/uuid1/1');
199
+ expect(patientNameLink).toHaveAttribute('href', '/home/billing/patient/uuid1/1');
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 />);