@openmrs/esm-billing-app 1.0.2-pre.112 → 1.0.2-pre.1124

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 (241) 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/2177.js +1 -1
  15. package/dist/2177.js.map +1 -1
  16. package/dist/2524.js +1 -0
  17. package/dist/2524.js.map +1 -0
  18. package/dist/2690.js +1 -1
  19. package/dist/3099.js +1 -1
  20. package/dist/3584.js +1 -1
  21. package/dist/3717.js +2 -0
  22. package/dist/3717.js.map +1 -0
  23. package/dist/4055.js +1 -1
  24. package/dist/4132.js +1 -1
  25. package/dist/4300.js +1 -1
  26. package/dist/4335.js +1 -1
  27. package/dist/439.js +1 -0
  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/5442.js +1 -1
  38. package/dist/5661.js +1 -1
  39. package/dist/6022.js +1 -1
  40. package/dist/6468.js +1 -1
  41. package/dist/6540.js +1 -1
  42. package/dist/6540.js.map +1 -1
  43. package/dist/6589.js +1 -0
  44. package/dist/6679.js +1 -1
  45. package/dist/6840.js +1 -1
  46. package/dist/6859.js +1 -1
  47. package/dist/7097.js +1 -1
  48. package/dist/7159.js +1 -1
  49. package/dist/723.js +1 -1
  50. package/dist/7255.js +1 -1
  51. package/dist/7255.js.map +1 -1
  52. package/dist/7617.js +1 -1
  53. package/dist/795.js +1 -1
  54. package/dist/8163.js +1 -1
  55. package/dist/8349.js +1 -1
  56. package/dist/8371.js +1 -0
  57. package/dist/8572.js +1 -0
  58. package/dist/8572.js.map +1 -0
  59. package/dist/8618.js +1 -1
  60. package/dist/8708.js +2 -0
  61. package/dist/{6557.js.LICENSE.txt → 8708.js.LICENSE.txt} +22 -0
  62. package/dist/8708.js.map +1 -0
  63. package/dist/890.js +1 -1
  64. package/dist/9214.js +1 -1
  65. package/dist/9538.js +1 -1
  66. package/dist/9569.js +1 -1
  67. package/dist/961.js +1 -1
  68. package/dist/961.js.map +1 -1
  69. package/dist/986.js +1 -1
  70. package/dist/9879.js +1 -1
  71. package/dist/9895.js +1 -1
  72. package/dist/9900.js +1 -1
  73. package/dist/9913.js +1 -1
  74. package/dist/main.js +1 -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 +321 -249
  78. package/dist/openmrs-esm-billing-app.js.map +1 -1
  79. package/dist/routes.json +1 -1
  80. package/e2e/README.md +19 -18
  81. package/e2e/commands/billing-operations.ts +96 -0
  82. package/e2e/commands/index.ts +3 -0
  83. package/e2e/commands/patient-operations.ts +82 -0
  84. package/e2e/commands/types.ts +73 -0
  85. package/e2e/core/test.ts +12 -1
  86. package/e2e/fixtures/api.ts +2 -3
  87. package/e2e/pages/billing-dashboard-page.ts +84 -0
  88. package/e2e/pages/billing-form-page.ts +114 -0
  89. package/e2e/pages/index.ts +4 -1
  90. package/e2e/pages/invoice-page.ts +113 -0
  91. package/e2e/pages/payment-page.ts +104 -0
  92. package/e2e/specs/billing-dashboard.spec.ts +203 -0
  93. package/e2e/specs/billing-patient-chart.spec.ts +738 -0
  94. package/e2e/support/github/Dockerfile +1 -1
  95. package/e2e/support/github/run-e2e-docker-env.sh +29 -32
  96. package/example.env +15 -1
  97. package/package.json +19 -16
  98. package/playwright.config.ts +4 -2
  99. package/src/bill-history/bill-history.component.tsx +15 -17
  100. package/src/bill-history/bill-history.scss +4 -94
  101. package/src/bill-history/bill-history.test.tsx +37 -77
  102. package/src/bill-item-actions/bill-item-actions.scss +21 -5
  103. package/src/bill-item-actions/edit-bill-item.modal.tsx +227 -0
  104. package/src/bill-item-actions/edit-bill-item.test.tsx +233 -40
  105. package/src/billable-services/bill-waiver/bill-selection.component.tsx +5 -5
  106. package/src/billable-services/bill-waiver/bill-waiver-form.component.tsx +37 -39
  107. package/src/billable-services/bill-waiver/patient-bills.component.tsx +7 -7
  108. package/src/billable-services/bill-waiver/utils.ts +13 -3
  109. package/src/billable-services/{create-edit/add-billable-service.scss → billable-service-form/billable-service-form.scss} +32 -64
  110. package/src/billable-services/billable-service-form/billable-service-form.test.tsx +1099 -0
  111. package/src/billable-services/billable-service-form/billable-service-form.workspace.tsx +516 -0
  112. package/src/billable-services/billable-service.resource.ts +71 -27
  113. package/src/billable-services/billable-services-home.component.tsx +13 -42
  114. package/src/billable-services/billable-services-left-panel-link.component.tsx +50 -0
  115. package/src/billable-services/billable-services-left-panel-menu.component.tsx +48 -0
  116. package/src/billable-services/billable-services-menu-item/item.component.tsx +5 -4
  117. package/src/billable-services/billable-services.component.tsx +156 -152
  118. package/src/billable-services/billable-services.scss +29 -0
  119. package/src/billable-services/billable-services.test.tsx +6 -49
  120. package/src/billable-services/cash-point/add-cash-point.modal.tsx +170 -0
  121. package/src/billable-services/cash-point/cash-point-configuration.component.tsx +19 -193
  122. package/src/billable-services/cash-point/cash-point-configuration.scss +1 -5
  123. package/src/billable-services/dashboard/dashboard.component.tsx +0 -2
  124. package/src/billable-services/payment-modes/delete-payment-mode.modal.tsx +77 -0
  125. package/src/billable-services/payment-modes/payment-mode-form.modal.tsx +131 -0
  126. package/src/billable-services/payment-modes/payment-modes-config.component.tsx +139 -0
  127. package/src/billable-services/{payyment-modes → payment-modes}/payment-modes-config.scss +5 -4
  128. package/src/billable-services-admin-card-link.component.test.tsx +2 -2
  129. package/src/billable-services-admin-card-link.component.tsx +1 -1
  130. package/src/billing-dashboard/billing-dashboard.scss +1 -1
  131. package/src/billing-form/billing-checkin-form.component.tsx +29 -29
  132. package/src/billing-form/billing-checkin-form.scss +0 -7
  133. package/src/billing-form/billing-checkin-form.test.tsx +100 -27
  134. package/src/billing-form/billing-form.component.tsx +226 -284
  135. package/src/billing-form/billing-form.scss +143 -0
  136. package/src/billing-form/visit-attributes/visit-attributes-form.component.tsx +47 -41
  137. package/src/billing-form/visit-attributes/visit-attributes-form.scss +16 -3
  138. package/src/billing.resource.test.ts +503 -0
  139. package/src/billing.resource.ts +68 -76
  140. package/src/bills-table/bills-table.component.tsx +17 -24
  141. package/src/bills-table/bills-table.test.tsx +98 -54
  142. package/src/config-schema.ts +84 -31
  143. package/src/dashboard.meta.ts +5 -3
  144. package/src/helpers/functions.ts +5 -4
  145. package/src/index.ts +75 -13
  146. package/src/invoice/invoice-table.component.tsx +36 -70
  147. package/src/invoice/invoice-table.scss +8 -5
  148. package/src/invoice/invoice-table.test.tsx +273 -62
  149. package/src/invoice/invoice.component.tsx +44 -38
  150. package/src/invoice/invoice.scss +11 -4
  151. package/src/invoice/invoice.test.tsx +324 -120
  152. package/src/invoice/payments/invoice-breakdown/invoice-breakdown.scss +9 -9
  153. package/src/invoice/payments/payment-form/payment-form.component.tsx +45 -36
  154. package/src/invoice/payments/payment-form/payment-form.scss +5 -6
  155. package/src/invoice/payments/payment-form/payment-form.test.tsx +217 -67
  156. package/src/invoice/payments/payment-history/payment-history.component.tsx +6 -4
  157. package/src/invoice/payments/payment-history/payment-history.test.tsx +9 -14
  158. package/src/invoice/payments/payments.component.tsx +56 -68
  159. package/src/invoice/payments/payments.scss +4 -3
  160. package/src/invoice/payments/payments.test.tsx +282 -0
  161. package/src/invoice/payments/utils.ts +15 -27
  162. package/src/invoice/printable-invoice/print-receipt.component.tsx +3 -3
  163. package/src/invoice/printable-invoice/print-receipt.test.tsx +14 -25
  164. package/src/invoice/printable-invoice/printable-footer.component.tsx +2 -2
  165. package/src/invoice/printable-invoice/printable-footer.test.tsx +4 -13
  166. package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +20 -11
  167. package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +95 -16
  168. package/src/invoice/printable-invoice/printable-invoice.component.tsx +21 -35
  169. package/src/left-panel-link.component.tsx +9 -8
  170. package/src/left-panel-link.test.tsx +1 -4
  171. package/src/metrics-cards/metrics-cards.component.tsx +16 -6
  172. package/src/metrics-cards/metrics-cards.scss +4 -0
  173. package/src/metrics-cards/metrics-cards.test.tsx +18 -5
  174. package/src/modal/require-payment-modal.test.tsx +27 -22
  175. package/src/modal/{require-payment-modal.component.tsx → require-payment.modal.tsx} +18 -19
  176. package/src/routes.json +44 -20
  177. package/src/types/index.ts +87 -24
  178. package/translations/am.json +140 -80
  179. package/translations/ar.json +141 -81
  180. package/translations/ar_SY.json +141 -81
  181. package/translations/bn.json +143 -83
  182. package/translations/cs.json +230 -0
  183. package/translations/de.json +141 -81
  184. package/translations/en.json +142 -81
  185. package/translations/en_US.json +141 -81
  186. package/translations/es.json +140 -80
  187. package/translations/es_MX.json +141 -81
  188. package/translations/fr.json +224 -164
  189. package/translations/he.json +140 -80
  190. package/translations/hi.json +141 -81
  191. package/translations/hi_IN.json +141 -81
  192. package/translations/id.json +141 -81
  193. package/translations/it.json +166 -106
  194. package/translations/ka.json +141 -81
  195. package/translations/km.json +140 -80
  196. package/translations/ku.json +141 -81
  197. package/translations/ky.json +141 -81
  198. package/translations/lg.json +141 -81
  199. package/translations/ne.json +141 -81
  200. package/translations/pl.json +141 -81
  201. package/translations/pt.json +141 -81
  202. package/translations/pt_BR.json +141 -81
  203. package/translations/qu.json +141 -81
  204. package/translations/ro_RO.json +225 -165
  205. package/translations/ru_RU.json +141 -81
  206. package/translations/si.json +141 -81
  207. package/translations/sq.json +230 -0
  208. package/translations/sw.json +141 -81
  209. package/translations/sw_KE.json +141 -81
  210. package/translations/tr.json +141 -81
  211. package/translations/tr_TR.json +141 -81
  212. package/translations/uk.json +141 -81
  213. package/translations/uz.json +141 -81
  214. package/translations/uz@Latn.json +141 -81
  215. package/translations/uz_UZ.json +141 -81
  216. package/translations/vi.json +141 -81
  217. package/translations/zh.json +141 -81
  218. package/translations/zh_CN.json +171 -111
  219. package/translations/zh_TW.json +230 -0
  220. package/dist/1146.js.LICENSE.txt +0 -21
  221. package/dist/2352.js +0 -1
  222. package/dist/2352.js.map +0 -1
  223. package/dist/246.js +0 -1
  224. package/dist/246.js.map +0 -1
  225. package/dist/4689.js +0 -2
  226. package/dist/4689.js.map +0 -1
  227. package/dist/6557.js +0 -2
  228. package/dist/6557.js.map +0 -1
  229. package/dist/8638.js +0 -1
  230. package/dist/8638.js.map +0 -1
  231. package/dist/9968.js +0 -1
  232. package/dist/9968.js.map +0 -1
  233. package/e2e/pages/home-page.ts +0 -9
  234. package/e2e/specs/sample-test.spec.ts +0 -11
  235. package/src/bill-item-actions/edit-bill-item.component.tsx +0 -221
  236. package/src/billable-services/create-edit/add-billable-service.component.tsx +0 -401
  237. package/src/billable-services/create-edit/add-billable-service.test.tsx +0 -154
  238. package/src/billable-services/dashboard/service-metrics.component.tsx +0 -41
  239. package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +0 -280
  240. package/src/invoice/payments/payments.component.test.tsx +0 -121
  241. /package/dist/{4689.js.LICENSE.txt → 3717.js.LICENSE.txt} +0 -0
@@ -64,25 +64,18 @@ const BillsTable = () => {
64
64
  const searchResults = useMemo(() => {
65
65
  if (!bills?.length) return bills;
66
66
 
67
- return bills
68
- .map((bill) => {
69
- if (bill.payments?.length > 0) {
70
- const totalPaid = bill.payments.reduce((sum, payment) => sum + payment.amountTendered, 0);
71
- if (totalPaid >= bill.totalAmount) {
72
- bill.status = 'PAID';
73
- }
74
- }
75
- return bill;
76
- })
77
- .filter((bill) => {
78
- const statusMatch = billPaymentStatus === '' ? true : bill.status === billPaymentStatus;
79
- const searchMatch = !searchString
80
- ? true
81
- : bill.patientName.toLowerCase().includes(searchString.toLowerCase()) ||
82
- bill.identifier.toLowerCase().includes(searchString.toLowerCase());
67
+ return bills.filter((bill) => {
68
+ const statusMatch =
69
+ billPaymentStatus === '' ||
70
+ billPaymentStatus === bill.status ||
71
+ (billPaymentStatus === 'PENDING' && bill.status === 'POSTED');
72
+ const searchMatch = !searchString
73
+ ? true
74
+ : bill.patientName.toLowerCase().includes(searchString.toLowerCase()) ||
75
+ bill.identifier.toLowerCase().includes(searchString.toLowerCase());
83
76
 
84
- return statusMatch && searchMatch;
85
- });
77
+ return statusMatch && searchMatch;
78
+ });
86
79
  }, [bills, searchString, billPaymentStatus]);
87
80
 
88
81
  const { paginated, goTo, results, currentPage } = usePagination(searchResults, pageSize);
@@ -121,9 +114,9 @@ const BillsTable = () => {
121
114
  );
122
115
 
123
116
  const filterItems = [
124
- { id: '', text: 'All bills' },
125
- { id: 'PENDING', text: 'Pending bills' },
126
- { id: 'PAID', text: 'Paid bills' },
117
+ { id: '', text: t('allBills', 'All bills') },
118
+ { id: 'PENDING', text: t('pendingBills', 'Pending bills') },
119
+ { id: 'PAID', text: t('paidBills', 'Paid bills') },
127
120
  ];
128
121
 
129
122
  const handleFilterChange = ({ selectedItem }) => {
@@ -149,7 +142,7 @@ const BillsTable = () => {
149
142
  return (
150
143
  <div className={styles.errorContainer}>
151
144
  <Layer>
152
- <ErrorState error={error} headerTitle={t('billsList', 'Bill list')} />
145
+ <ErrorState error={error} headerTitle={t('billList', 'Bill list')} />
153
146
  </Layer>
154
147
  </div>
155
148
  );
@@ -168,7 +161,7 @@ const BillsTable = () => {
168
161
  label=""
169
162
  onChange={handleFilterChange}
170
163
  size={responsiveSize}
171
- titleText={t('filterBy', 'Filter by') + ':'}
164
+ titleText={t('filterBy', 'Filter by:')}
172
165
  type="inline"
173
166
  />
174
167
  </div>
@@ -190,7 +183,7 @@ const BillsTable = () => {
190
183
  useZebraStyles={rowData?.length > 1 ? true : false}>
191
184
  {({ rows, headers, getRowProps, getTableProps }) => (
192
185
  <TableContainer>
193
- <Table {...getTableProps()} aria-label="bill list">
186
+ <Table {...getTableProps()} aria-label={t('billList', 'Bill list')}>
194
187
  <TableHead>
195
188
  <TableRow>
196
189
  {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 />);
@@ -1,85 +1,138 @@
1
- import { Type } from '@openmrs/esm-framework';
2
-
3
- export interface BillingConfig {}
1
+ import { Type, validators } from '@openmrs/esm-framework';
4
2
 
5
3
  export const configSchema = {
6
- patientCatergory: {
4
+ logo: {
5
+ src: {
6
+ _type: Type.String,
7
+ _default: '',
8
+ _description: 'The path or URL to the logo image. If set to an empty string, the alt text will be used.',
9
+ _validators: [validators.isUrl],
10
+ },
11
+ alt: {
12
+ _type: Type.String,
13
+ _default: '',
14
+ _description:
15
+ 'The alternative text for the logo image, displayed when the image cannot be loaded or on hover. If not provided and src is empty, the default OpenMRS SVG sprite will be used.',
16
+ },
17
+ },
18
+ country: {
19
+ _type: Type.String,
20
+ _description: 'The text that gets printed on the top right of the invoice, typically the name of the country',
21
+ _default: 'Kenya',
22
+ },
23
+ patientCategory: {
7
24
  _type: Type.Object,
8
- _description: 'Patient Category Custom UUIDs',
25
+ _description:
26
+ 'Visit Attribute Type UUIDs - These are custom attribute types that can be attached to patient visits. Each UUID represents a Visit Attribute Type in OpenMRS that stores payment-related information for the visit.',
9
27
  _default: {
28
+ // Payment details attribute type (e.g., "Paying" or "Non-paying")
10
29
  paymentDetails: 'fbc0702d-b4c9-4968-be63-af8ad3ad6239',
30
+ // Payment method attribute type (e.g., cash, insurance, mobile money)
11
31
  paymentMethods: '8553afa0-bdb9-4d3c-8a98-05fa9350aa85',
32
+ // Insurance policy number attribute type
12
33
  policyNumber: '3a988e33-a6c0-4b76-b924-01abb998944b',
34
+ // Insurance scheme name attribute type
13
35
  insuranceScheme: 'aac48226-d143-4274-80e0-264db4e368ee',
36
+ // Patient category classification attribute type
14
37
  patientCategory: '3b9dfac8-9e4d-11ee-8c90-0242ac120002',
38
+ // Form payload pending status attribute type
15
39
  formPayloadPending: '919b51c9-8e2e-468f-8354-181bf3e55786',
16
40
  },
17
41
  },
18
-
19
- catergoryConcepts: {
42
+ categoryConcepts: {
20
43
  _type: Type.Object,
21
- _description: 'Patient Category Concept UUIDs',
44
+ _description:
45
+ 'Concept UUIDs - These are OpenMRS Concept UUIDs that represent the actual values/options for payment categories. These concepts are stored as values in the visit attributes defined in patientCategory.',
22
46
  _default: {
47
+ // Concept UUID representing "Paying" payment category
23
48
  payingDetails: '44b34972-6630-4e5a-a9f6-a6eb0f109650',
49
+ // Concept UUID representing "Non-paying" payment category
24
50
  nonPayingDetails: 'f3fb2d88-cccd-422c-8766-be101ba7bd2e',
51
+ // Concept UUID representing "Insurance" as a payment method
25
52
  insuranceDetails: 'beac329b-f1dc-4a33-9e7c-d95821a137a6',
26
53
  },
27
54
  },
28
-
29
55
  nonPayingPatientCategories: {
30
56
  _type: Type.Object,
31
- _description: 'Concept UUIDs for non-paying patient categories',
57
+ _description:
58
+ 'Concept UUIDs for non-paying patient categories - These are OpenMRS Concept UUIDs that identify specific patient categories eligible for non-paying status (e.g., children under 5, students). These are used as values when a patient is categorized as non-paying.',
32
59
  _default: {
60
+ // Concept UUID for "Child Under 5" category
33
61
  childUnder5: '1528AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
62
+ // Concept UUID for "Student" category
34
63
  student: '159465AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
35
64
  },
36
65
  },
37
-
38
66
  postBilledItems: {
39
67
  _type: Type.Object,
40
- _description: 'Post Bill Items such as cashPoints, cashier, priceUUid when submitting a bill',
68
+ _description:
69
+ 'Default UUIDs used when creating bills - These UUIDs identify default resources (cash points, cashier providers) that are used when submitting a bill if not explicitly provided.',
41
70
  _default: {
71
+ // Default Cash Point UUID - The cashier station/location where bills are processed
42
72
  cashPoint: '54065383-b4d4-42d2-af4d-d250a1fd2590',
73
+ // Default Cashier Provider UUID - The provider/user UUID that identifies the cashier processing the bill
43
74
  cashier: 'f9badd80-ab76-11e2-9e96-0800200c9a66',
75
+ // Default Price UUID - The service price UUID used when creating bills (currently unused in code)
44
76
  priceUuid: '7b9171ac-d3c1-49b4-beff-c9902aee5245',
45
77
  },
46
78
  },
47
-
48
79
  serviceTypes: {
49
80
  _type: Type.Object,
50
- _description: 'Post Bill Items such as cashPoints, cashier, priceUUid when submitting a bill',
81
+ _description:
82
+ 'Service Type Concept Set UUID - This is an OpenMRS Concept Set UUID. The set members of this concept represent the different types of services that can be billed (e.g., Consultation, Lab Test, Procedure).',
51
83
  _default: {
84
+ // Concept Set UUID - The concept set whose members represent the different service types that can be billed
52
85
  billableService: '21b8cf43-9f9f-4d02-9f4a-d710ece54261',
53
86
  },
54
87
  },
55
-
56
88
  defaultCurrency: {
57
89
  _type: Type.String,
58
90
  _description: 'The default currency for the application. Specify the currency code (e.g., KES, UGX, GBP).',
59
91
  _default: 'KES',
60
92
  },
61
-
62
93
  pageSize: {
63
94
  _type: Type.Number,
64
95
  _description: 'The default page size',
65
96
  _default: 10,
66
97
  },
67
-
68
- showEditBillButton: {
69
- _type: Type.Boolean,
70
- _description: 'Whether to show the edit bill button or not.',
71
- _default: false,
72
- },
73
98
  };
74
99
 
75
- export interface ConfigObject {
76
- patientCatergory: Object;
100
+ /**
101
+ * Configuration interface for the Billing module.
102
+ * All UUIDs represent OpenMRS resources that must exist in the system.
103
+ * See the configSchema above for detailed descriptions of each property.
104
+ */
105
+ export interface BillingConfig {
106
+ logo: {
107
+ src: string;
108
+ alt: string;
109
+ };
110
+ country: string;
111
+ patientCategory: {
112
+ paymentDetails: string;
113
+ paymentMethods: string;
114
+ policyNumber: string;
115
+ insuranceScheme: string;
116
+ patientCategory: string;
117
+ formPayloadPending: string;
118
+ };
119
+ categoryConcepts: {
120
+ payingDetails: string;
121
+ nonPayingDetails: string;
122
+ insuranceDetails: string;
123
+ };
124
+ nonPayingPatientCategories: {
125
+ childUnder5: string;
126
+ student: string;
127
+ };
128
+ postBilledItems: {
129
+ cashPoint: string;
130
+ cashier: string;
131
+ priceUuid: string;
132
+ };
133
+ serviceTypes: {
134
+ billableService: string;
135
+ };
77
136
  defaultCurrency: string;
78
- catergoryConcepts: Object;
79
- pageSize;
80
- object;
81
- showEditBillButton: boolean;
82
- postBilledItems: Object;
83
- serviceTypes: Object;
84
- nonPayingPatientCategories: Object;
137
+ pageSize: number;
85
138
  }
@@ -1,8 +1,10 @@
1
- export const dashboardMeta = {
1
+ import { type DashboardLinkConfig } from '@openmrs/esm-patient-common-lib';
2
+
3
+ export const dashboardMeta: DashboardLinkConfig & { slot: string; columns: number; hideDashboardTitle: boolean } = {
2
4
  slot: 'patient-chart-billing-dashboard-slot',
3
5
  columns: 1,
4
- title: 'Billing history',
6
+ title: 'billingHistory',
5
7
  hideDashboardTitle: true,
6
- icon: '',
8
+ icon: 'omrs-icon-money',
7
9
  path: 'Billing history',
8
10
  };
@@ -1,3 +1,4 @@
1
+ import { getCoreTranslation } from '@openmrs/esm-framework';
1
2
  import { type Payment, type LineItem } from '../types';
2
3
 
3
4
  // amount already paid
@@ -51,13 +52,13 @@ export const convertToCurrency = (amountToConvert: number, currencyType?: string
51
52
  export const getGender = (gender: string, t) => {
52
53
  switch (gender) {
53
54
  case 'male':
54
- return t('male', 'Male');
55
+ return getCoreTranslation('male');
55
56
  case 'female':
56
- return t('female', 'Female');
57
+ return getCoreTranslation('female');
57
58
  case 'other':
58
- return t('other', 'Other');
59
+ return getCoreTranslation('other');
59
60
  case 'unknown':
60
- return t('unknown', 'Unknown');
61
+ return getCoreTranslation('unknown');
61
62
  default:
62
63
  return gender;
63
64
  }
package/src/index.ts CHANGED
@@ -3,14 +3,20 @@ import { createDashboardLink } from '@openmrs/esm-patient-common-lib';
3
3
  import { createLeftPanelLink } from './left-panel-link.component';
4
4
  import { dashboardMeta } from './dashboard.meta';
5
5
  import { defineConfigSchema, getAsyncLifecycle, getSyncLifecycle } from '@openmrs/esm-framework';
6
+ import { Wallet, Money, Settings } from '@carbon/react/icons';
7
+ import { createBillableServicesLeftPanelLink } from './billable-services/billable-services-left-panel-link.component';
8
+ import { createBillableServicesLeftPanelMenu } from './billable-services/billable-services-left-panel-menu.component';
9
+ import AddCashPointModal from './billable-services/cash-point/add-cash-point.modal';
6
10
  import appMenu from './billable-services/billable-services-menu-item/item.component';
7
11
  import BillableServiceHome from './billable-services/billable-services-home.component';
8
12
  import BillableServicesCardLink from './billable-services-admin-card-link.component';
9
13
  import BillHistory from './bill-history/bill-history.component';
10
14
  import BillingCheckInForm from './billing-form/billing-checkin-form.component';
11
- import RequirePaymentModal from './modal/require-payment-modal.component';
15
+ import DeletePaymentModeModal from './billable-services/payment-modes/delete-payment-mode.modal';
16
+ import EditBillLineItemModal from './bill-item-actions/edit-bill-item.modal';
17
+ import PaymentModeFormModal from './billable-services/payment-modes/payment-mode-form.modal';
18
+ import RequirePaymentModal from './modal/require-payment.modal';
12
19
  import RootComponent from './root.component';
13
- import ServiceMetrics from './billable-services/dashboard/service-metrics.component';
14
20
  import VisitAttributeTags from './invoice/payments/visit-tags/visit-attribute.component';
15
21
 
16
22
  const moduleName = '@openmrs/esm-billing-app';
@@ -20,26 +26,28 @@ const options = {
20
26
  moduleName,
21
27
  };
22
28
 
23
- // t('billing', 'Billing')
29
+ export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
30
+
24
31
  export const billingDashboardLink = getSyncLifecycle(
32
+ // t('billing', 'Billing')
25
33
  createLeftPanelLink({
26
34
  name: 'billing',
27
- title: 'Billing',
35
+ title: 'billing',
28
36
  }),
29
37
  options,
30
38
  );
31
39
 
32
- export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
33
-
34
40
  export function startupApp() {
35
41
  defineConfigSchema(moduleName, configSchema);
36
42
  }
37
43
 
44
+ // t('billingHistory', 'Billing History')
38
45
  export const billingSummaryDashboardLink = getSyncLifecycle(
39
46
  createDashboardLink({ ...dashboardMeta, moduleName }),
40
47
  options,
41
48
  );
42
49
 
50
+ // t('billableServices', 'Billable services')
43
51
  export const billableServicesAppMenuItem = getSyncLifecycle(appMenu, options);
44
52
 
45
53
  export const billableServicesCardLink = getSyncLifecycle(BillableServicesCardLink, options);
@@ -52,16 +60,70 @@ export const billingPatientSummary = getSyncLifecycle(BillHistory, options);
52
60
 
53
61
  export const requirePaymentModal = getSyncLifecycle(RequirePaymentModal, options);
54
62
 
55
- export const root = getSyncLifecycle(RootComponent, options);
63
+ export const paymentModeFormModal = getSyncLifecycle(PaymentModeFormModal, options);
56
64
 
57
- export const serviceMetrics = getSyncLifecycle(ServiceMetrics, options);
65
+ export const deletePaymentModeModal = getSyncLifecycle(DeletePaymentModeModal, options);
58
66
 
59
- export const visitAttributeTags = getSyncLifecycle(VisitAttributeTags, options);
67
+ export const addCashPointModal = getSyncLifecycle(AddCashPointModal, options);
60
68
 
61
- export const editBillLineItemDialog = getAsyncLifecycle(() => import('./bill-item-actions/edit-bill-item.component'), {
62
- featureName: 'edit bill line item',
63
- moduleName,
64
- });
69
+ export const editBillLineItemModal = getSyncLifecycle(EditBillLineItemModal, options);
70
+
71
+ export const root = getSyncLifecycle(RootComponent, options);
72
+
73
+ export const visitAttributeTags = getSyncLifecycle(VisitAttributeTags, options);
65
74
 
66
75
  // t('billingForm', 'Billing form')
67
76
  export const billingFormWorkspace = getAsyncLifecycle(() => import('./billing-form/billing-form.component'), options);
77
+
78
+ // t('billableServiceForm', 'Billable service form')
79
+ export const billableServiceFormWorkspace = getAsyncLifecycle(
80
+ () => import('./billable-services/billable-service-form/billable-service-form.workspace'),
81
+ options,
82
+ );
83
+
84
+ // t('billableServices', 'Billable services')
85
+ export const billableServicesLeftPanelLink = getSyncLifecycle(
86
+ createBillableServicesLeftPanelLink({
87
+ name: 'billable-services',
88
+ title: 'billableServices',
89
+ path: '',
90
+ icon: Wallet,
91
+ }),
92
+ options,
93
+ );
94
+
95
+ // t('billWaiver', 'Bill waiver')
96
+ export const billWaiverLeftPanelLink = getSyncLifecycle(
97
+ createBillableServicesLeftPanelLink({
98
+ name: 'bill-waiver',
99
+ title: 'billWaiver',
100
+ path: 'waive-bill',
101
+ icon: Money,
102
+ privilege: 'coreapps.systemAdministration',
103
+ }),
104
+ options,
105
+ );
106
+
107
+ // t('billingSettings', 'Billing settings')
108
+ // t('cashPointConfig', 'Cash point configuration')
109
+ // t('paymentModesConfig', 'Payment modes configuration')
110
+ export const billingSettingsLeftPanelMenu = getSyncLifecycle(
111
+ createBillableServicesLeftPanelMenu({
112
+ title: 'billingSettings',
113
+ icon: Settings,
114
+ privilege: 'coreapps.systemAdministration',
115
+ items: [
116
+ {
117
+ name: 'cash-point-config',
118
+ title: 'cashPointConfig',
119
+ path: 'cash-point-config',
120
+ },
121
+ {
122
+ name: 'payment-modes-config',
123
+ title: 'paymentModesConfig',
124
+ path: 'payment-modes-config',
125
+ },
126
+ ],
127
+ }),
128
+ options,
129
+ );