@openmrs/esm-billing-app 1.1.2-pre.9 → 1.2.0

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 (390) hide show
  1. package/.turbo/cache/31f1dfc7f71601df-meta.json +1 -0
  2. package/.turbo/cache/31f1dfc7f71601df.tar.zst +0 -0
  3. package/.turbo/turbo-build.log +13 -42
  4. package/__mocks__/bills.mock.ts +3 -2
  5. package/dist/1480.js +1 -0
  6. package/dist/1480.js.map +1 -0
  7. package/dist/1564.js +1 -0
  8. package/dist/1564.js.map +1 -0
  9. package/dist/1578.js +1 -0
  10. package/dist/1578.js.map +1 -0
  11. package/dist/1646.js +1 -0
  12. package/dist/1646.js.map +1 -0
  13. package/dist/1869.js +1 -0
  14. package/dist/1869.js.map +1 -0
  15. package/dist/1877.js +1 -0
  16. package/dist/1877.js.map +1 -0
  17. package/dist/1899.js +1 -0
  18. package/dist/1899.js.map +1 -0
  19. package/dist/196.js +2 -0
  20. package/dist/196.js.map +1 -0
  21. package/dist/2250.js +43 -0
  22. package/dist/2250.js.map +1 -0
  23. package/dist/2269.js +1 -0
  24. package/dist/2269.js.map +1 -0
  25. package/dist/2317.js +1 -0
  26. package/dist/2317.js.map +1 -0
  27. package/dist/2416.js +1 -0
  28. package/dist/2416.js.map +1 -0
  29. package/dist/2489.js +1 -0
  30. package/dist/2489.js.map +1 -0
  31. package/dist/282.js +1 -0
  32. package/dist/282.js.map +1 -0
  33. package/dist/2881.js +1 -0
  34. package/dist/2881.js.map +1 -0
  35. package/dist/2997.js +1 -0
  36. package/dist/2997.js.map +1 -0
  37. package/dist/3378.js +1 -0
  38. package/dist/3378.js.map +1 -0
  39. package/dist/3379.js +1 -0
  40. package/dist/3379.js.map +1 -0
  41. package/dist/3784.js +1 -0
  42. package/dist/3784.js.map +1 -0
  43. package/dist/3963.js +1 -0
  44. package/dist/3963.js.map +1 -0
  45. package/dist/4106.js +1 -0
  46. package/dist/4106.js.map +1 -0
  47. package/dist/4111.js +1 -0
  48. package/dist/4111.js.map +1 -0
  49. package/dist/434.js +1 -0
  50. package/dist/434.js.map +1 -0
  51. package/dist/4348.js +1 -0
  52. package/dist/4348.js.map +1 -0
  53. package/dist/4383.js +1 -0
  54. package/dist/4383.js.map +1 -0
  55. package/dist/4658.js +1 -0
  56. package/dist/4658.js.map +1 -0
  57. package/dist/4870.js +1 -0
  58. package/dist/4870.js.map +1 -0
  59. package/dist/4928.js +1 -0
  60. package/dist/4928.js.map +1 -0
  61. package/dist/5098.js +1 -0
  62. package/dist/5098.js.map +1 -0
  63. package/dist/5117.js +1 -0
  64. package/dist/5117.js.map +1 -0
  65. package/dist/5132.js +1 -0
  66. package/dist/5132.js.map +1 -0
  67. package/dist/5145.js +1 -0
  68. package/dist/5145.js.map +1 -0
  69. package/dist/5390.js +1 -0
  70. package/dist/5390.js.map +1 -0
  71. package/dist/5503.js +1 -0
  72. package/dist/5503.js.map +1 -0
  73. package/dist/556.js +1 -0
  74. package/dist/556.js.map +1 -0
  75. package/dist/5644.js +1 -0
  76. package/dist/5644.js.map +1 -0
  77. package/dist/5898.js +1 -0
  78. package/dist/5898.js.map +1 -0
  79. package/dist/5940.js +1 -0
  80. package/dist/5940.js.map +1 -0
  81. package/dist/6047.js +1 -0
  82. package/dist/6047.js.map +1 -0
  83. package/dist/6237.js +1 -0
  84. package/dist/6237.js.map +1 -0
  85. package/dist/6362.js +1 -0
  86. package/dist/6362.js.map +1 -0
  87. package/dist/6371.js +1 -0
  88. package/dist/6371.js.map +1 -0
  89. package/dist/6377.js +1 -0
  90. package/dist/6377.js.map +1 -0
  91. package/dist/6444.js +1 -0
  92. package/dist/6444.js.map +1 -0
  93. package/dist/6508.js +1 -0
  94. package/dist/6508.js.map +1 -0
  95. package/dist/6594.js +1 -0
  96. package/dist/6594.js.map +1 -0
  97. package/dist/6724.js +1 -0
  98. package/dist/6724.js.map +1 -0
  99. package/dist/6904.js +1 -0
  100. package/dist/6904.js.map +1 -0
  101. package/dist/7045.js +1 -0
  102. package/dist/7045.js.map +1 -0
  103. package/dist/7175.js +1 -0
  104. package/dist/7175.js.map +1 -0
  105. package/dist/7182.js +1 -0
  106. package/dist/7182.js.map +1 -0
  107. package/dist/7247.js +1 -0
  108. package/dist/7247.js.map +1 -0
  109. package/dist/7742.js +1 -0
  110. package/dist/7742.js.map +1 -0
  111. package/dist/7912.js +1 -0
  112. package/dist/7912.js.map +1 -0
  113. package/dist/8358.js +1 -0
  114. package/dist/8358.js.map +1 -0
  115. package/dist/8359.js +1 -0
  116. package/dist/8359.js.map +1 -0
  117. package/dist/8695.js +1 -0
  118. package/dist/8695.js.map +1 -0
  119. package/dist/903.js +1 -0
  120. package/dist/903.js.map +1 -0
  121. package/dist/9072.js +1 -0
  122. package/dist/9072.js.map +1 -0
  123. package/dist/9414.js +1 -0
  124. package/dist/9414.js.map +1 -0
  125. package/dist/9655.js +11 -0
  126. package/dist/9655.js.map +1 -0
  127. package/dist/9806.js +1 -0
  128. package/dist/9806.js.map +1 -0
  129. package/dist/990.js +1 -0
  130. package/dist/990.js.map +1 -0
  131. package/dist/main.js +17 -2
  132. package/dist/main.js.map +1 -1
  133. package/dist/openmrs-esm-billing-app.js +6 -1
  134. package/dist/openmrs-esm-billing-app.js.buildmanifest.json +643 -436
  135. package/dist/openmrs-esm-billing-app.js.map +1 -1
  136. package/dist/routes.json +1 -1
  137. package/e2e/commands/billing-operations.ts +21 -0
  138. package/e2e/commands/types.ts +9 -1
  139. package/e2e/pages/discounts-page.ts +75 -0
  140. package/e2e/pages/index.ts +1 -0
  141. package/e2e/pages/invoice-page.ts +7 -7
  142. package/e2e/specs/bill-discounts.spec.ts +255 -0
  143. package/e2e/specs/billing-dashboard.spec.ts +3 -3
  144. package/e2e/specs/billing-patient-chart.spec.ts +2 -2
  145. package/package.json +13 -22
  146. package/rspack.config.js +1 -0
  147. package/src/bill-history/bill-action-menu.component.tsx +20 -2
  148. package/src/bill-history/bill-history.test.tsx +23 -22
  149. package/src/bill-item-actions/edit-bill-item.modal.tsx +1 -1
  150. package/src/bill-item-actions/edit-bill-item.test.tsx +29 -27
  151. package/src/billable-services/billable-service-form/billable-service-form.test.tsx +74 -73
  152. package/src/billable-services/billable-services-home.component.tsx +4 -2
  153. package/src/billable-services/billable-services.test.tsx +8 -7
  154. package/src/billable-services/dashboard/dashboard.test.tsx +3 -2
  155. package/src/billable-services-admin-card-link.test.tsx +2 -1
  156. package/src/billing-dashboard/billing-dashboard.test.tsx +19 -3
  157. package/src/billing-form/billing-checkin-form.component.tsx +7 -3
  158. package/src/billing-form/billing-checkin-form.test.tsx +22 -21
  159. package/src/billing-form/billing-form.resource.test.ts +7 -6
  160. package/src/billing-form/billing-form.test.tsx +77 -40
  161. package/src/billing-form/billing-form.workspace.tsx +25 -6
  162. package/src/billing-form/visit-attributes/visit-attributes-form.component.tsx +6 -2
  163. package/src/billing.resource.test.ts +43 -41
  164. package/src/billing.resource.ts +65 -16
  165. package/src/bills-table/bills-table.component.tsx +15 -4
  166. package/src/bills-table/bills-table.test.tsx +72 -71
  167. package/src/config-schema.ts +0 -7
  168. package/src/discounts/admin/discount-requests-left-panel-link.component.tsx +43 -0
  169. package/src/discounts/admin/discount-requests.component.tsx +316 -0
  170. package/src/discounts/admin/discount-requests.scss +133 -0
  171. package/src/discounts/admin/discount-requests.test.tsx +104 -0
  172. package/src/discounts/admin/review-bill-discounts/bill-line-items-table/bill-line-items-table.component.tsx +42 -0
  173. package/src/discounts/admin/review-bill-discounts/bill-line-items-table/bill-line-items-table.scss +76 -0
  174. package/src/discounts/admin/review-bill-discounts/bill-payments-table/bill-payments-table.component.tsx +50 -0
  175. package/src/discounts/admin/review-bill-discounts/bill-payments-table/bill-payments-table.scss +63 -0
  176. package/src/discounts/admin/review-bill-discounts/bill-receipt-rail/bill-receipt-rail.component.tsx +73 -0
  177. package/src/discounts/admin/review-bill-discounts/bill-receipt-rail/bill-receipt-rail.scss +54 -0
  178. package/src/discounts/admin/review-bill-discounts/bill-totals-summary/bill-totals-summary.component.tsx +95 -0
  179. package/src/discounts/admin/review-bill-discounts/bill-totals-summary/bill-totals-summary.scss +128 -0
  180. package/src/discounts/admin/review-bill-discounts/discount-card/discount-card.component.tsx +158 -0
  181. package/src/discounts/admin/review-bill-discounts/discount-card/discount-card.scss +164 -0
  182. package/src/discounts/admin/review-bill-discounts/discount-review-stack/discount-review-stack.component.tsx +86 -0
  183. package/src/discounts/admin/review-bill-discounts/discount-review-stack/discount-review-stack.scss +40 -0
  184. package/src/discounts/admin/review-bill-discounts/review-bill-discounts.modal.scss +14 -0
  185. package/src/discounts/admin/review-bill-discounts/review-bill-discounts.modal.test.tsx +153 -0
  186. package/src/discounts/admin/review-bill-discounts/review-bill-discounts.modal.tsx +167 -0
  187. package/src/discounts/admin/review-bill-discounts/review-bill-discounts.utils.ts +42 -0
  188. package/src/discounts/discounts-table.component.tsx +109 -0
  189. package/src/discounts/discounts-table.scss +37 -0
  190. package/src/discounts/discounts-table.test.tsx +67 -0
  191. package/src/discounts/discounts.resource.ts +71 -0
  192. package/src/discounts/request-discount.modal.scss +88 -0
  193. package/src/discounts/request-discount.modal.test.tsx +161 -0
  194. package/src/discounts/request-discount.modal.tsx +253 -0
  195. package/src/index.ts +52 -21
  196. package/src/invoice/invoice-table.component.tsx +116 -18
  197. package/src/invoice/invoice-table.test.tsx +165 -13
  198. package/src/invoice/invoice.component.tsx +111 -7
  199. package/src/invoice/invoice.test.tsx +366 -66
  200. package/src/invoice/line-item-action-menu.component.tsx +31 -1
  201. package/src/invoice/payments/payment-form/payment-form.test.tsx +20 -19
  202. package/src/invoice/payments/payment-history/payment-history.test.tsx +13 -10
  203. package/src/invoice/payments/payments.component.tsx +20 -6
  204. package/src/invoice/payments/payments.test.tsx +88 -23
  205. package/src/invoice/printable-invoice/print-receipt.test.tsx +10 -28
  206. package/src/invoice/printable-invoice/printable-footer.test.tsx +5 -4
  207. package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +3 -3
  208. package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +26 -11
  209. package/src/invoice/printable-invoice/printable-invoice.component.tsx +38 -15
  210. package/src/left-panel-link.test.tsx +3 -3
  211. package/src/metrics-cards/metrics-cards.test.tsx +11 -10
  212. package/src/modal/delete-bill-confirmation.modal.test.tsx +134 -0
  213. package/src/modal/delete-bill-confirmation.modal.tsx +98 -0
  214. package/src/modal/delete-line-item-confirmation.modal.test.tsx +11 -9
  215. package/src/modal/finalize-bill-confirmation.modal.test.tsx +10 -8
  216. package/src/modal/require-payment-modal.test.tsx +12 -11
  217. package/src/payment-status-tag/payment-status-tag.component.tsx +50 -0
  218. package/src/payment-status-tag/payment-status-tag.scss +6 -0
  219. package/src/payment-status-tag/payment-status-tag.test.tsx +113 -0
  220. package/src/refunds/admin/refund-requests-left-panel-link.component.tsx +43 -0
  221. package/src/refunds/admin/refund-requests.component.tsx +324 -0
  222. package/src/refunds/admin/refund-requests.scss +133 -0
  223. package/src/refunds/admin/refund-requests.test.tsx +99 -0
  224. package/src/refunds/admin/review-bill-refunds/bill-line-items-table/bill-line-items-table.component.tsx +42 -0
  225. package/src/refunds/admin/review-bill-refunds/bill-line-items-table/bill-line-items-table.scss +76 -0
  226. package/src/refunds/admin/review-bill-refunds/bill-payments-table/bill-payments-table.component.tsx +50 -0
  227. package/src/refunds/admin/review-bill-refunds/bill-payments-table/bill-payments-table.scss +63 -0
  228. package/src/refunds/admin/review-bill-refunds/bill-receipt-rail/bill-receipt-rail.component.tsx +84 -0
  229. package/src/refunds/admin/review-bill-refunds/bill-receipt-rail/bill-receipt-rail.scss +54 -0
  230. package/src/refunds/admin/review-bill-refunds/bill-totals-summary/bill-totals-summary.component.tsx +83 -0
  231. package/src/refunds/admin/review-bill-refunds/bill-totals-summary/bill-totals-summary.scss +65 -0
  232. package/src/refunds/admin/review-bill-refunds/refund-card/refund-card.component.tsx +170 -0
  233. package/src/refunds/admin/review-bill-refunds/refund-card/refund-card.scss +155 -0
  234. package/src/refunds/admin/review-bill-refunds/refund-review-stack/refund-review-stack.component.tsx +86 -0
  235. package/src/refunds/admin/review-bill-refunds/refund-review-stack/refund-review-stack.scss +40 -0
  236. package/src/refunds/admin/review-bill-refunds/review-bill-refunds.modal.scss +14 -0
  237. package/src/refunds/admin/review-bill-refunds/review-bill-refunds.modal.test.tsx +313 -0
  238. package/src/refunds/admin/review-bill-refunds/review-bill-refunds.modal.tsx +188 -0
  239. package/src/refunds/admin/review-bill-refunds/review-bill-refunds.utils.ts +66 -0
  240. package/src/refunds/refunds-table.component.tsx +137 -0
  241. package/src/refunds/refunds-table.scss +37 -0
  242. package/src/refunds/refunds-table.test.tsx +105 -0
  243. package/src/refunds/refunds.resource.test.ts +44 -0
  244. package/src/refunds/refunds.resource.ts +42 -0
  245. package/src/refunds/refunds.types.test.ts +15 -0
  246. package/src/refunds/request-refund.modal.scss +84 -0
  247. package/src/refunds/request-refund.modal.test.tsx +204 -0
  248. package/src/refunds/request-refund.modal.tsx +218 -0
  249. package/src/routes.json +36 -2
  250. package/src/types/index.ts +116 -1
  251. package/src/visit-bills/visit-bills-panel.component.tsx +151 -0
  252. package/src/visit-bills/visit-bills-panel.scss +31 -0
  253. package/src/visit-bills/visit-bills-panel.test.tsx +113 -0
  254. package/tools/empty-module.ts +1 -0
  255. package/tools/setup-tests.ts +9 -9
  256. package/translations/am.json +154 -16
  257. package/translations/ar.json +154 -16
  258. package/translations/ar_SY.json +154 -16
  259. package/translations/bn.json +154 -16
  260. package/translations/cs.json +154 -16
  261. package/translations/de.json +154 -16
  262. package/translations/en.json +154 -16
  263. package/translations/en_US.json +154 -16
  264. package/translations/es.json +154 -16
  265. package/translations/es_MX.json +154 -16
  266. package/translations/fr.json +154 -16
  267. package/translations/he.json +154 -16
  268. package/translations/hi.json +154 -16
  269. package/translations/hi_IN.json +154 -16
  270. package/translations/id.json +154 -16
  271. package/translations/it.json +154 -16
  272. package/translations/ka.json +154 -16
  273. package/translations/km.json +154 -16
  274. package/translations/ku.json +154 -16
  275. package/translations/ky.json +154 -16
  276. package/translations/lg.json +154 -16
  277. package/translations/ne.json +154 -16
  278. package/translations/pl.json +154 -16
  279. package/translations/pt.json +154 -16
  280. package/translations/pt_BR.json +154 -16
  281. package/translations/qu.json +154 -16
  282. package/translations/ro_RO.json +154 -16
  283. package/translations/ru_RU.json +154 -16
  284. package/translations/si.json +154 -16
  285. package/translations/sq.json +154 -16
  286. package/translations/sw.json +154 -16
  287. package/translations/sw_KE.json +154 -16
  288. package/translations/tr.json +154 -16
  289. package/translations/tr_TR.json +154 -16
  290. package/translations/uk.json +154 -16
  291. package/translations/uz.json +154 -16
  292. package/translations/uz@Latn.json +154 -16
  293. package/translations/uz_UZ.json +154 -16
  294. package/translations/vi.json +154 -16
  295. package/translations/zh.json +154 -16
  296. package/translations/zh_CN.json +179 -41
  297. package/translations/zh_TW.json +154 -16
  298. package/tsconfig.json +3 -3
  299. package/vitest.config.js +28 -0
  300. package/.turbo/cache/4e30f71f570fc412-meta.json +0 -1
  301. package/.turbo/cache/4e30f71f570fc412.tar.zst +0 -0
  302. package/dist/1119.js +0 -1
  303. package/dist/1197.js +0 -1
  304. package/dist/1435.js +0 -1
  305. package/dist/1435.js.map +0 -1
  306. package/dist/1807.js +0 -1
  307. package/dist/1807.js.map +0 -1
  308. package/dist/2146.js +0 -1
  309. package/dist/2177.js +0 -2
  310. package/dist/2177.js.LICENSE.txt +0 -9
  311. package/dist/2177.js.map +0 -1
  312. package/dist/2690.js +0 -1
  313. package/dist/2704.js +0 -1
  314. package/dist/2704.js.map +0 -1
  315. package/dist/3002.js +0 -1
  316. package/dist/3002.js.map +0 -1
  317. package/dist/3041.js +0 -1
  318. package/dist/3041.js.map +0 -1
  319. package/dist/3099.js +0 -1
  320. package/dist/3184.js +0 -2
  321. package/dist/3184.js.LICENSE.txt +0 -14
  322. package/dist/3184.js.map +0 -1
  323. package/dist/3584.js +0 -1
  324. package/dist/4055.js +0 -1
  325. package/dist/4132.js +0 -1
  326. package/dist/4225.js +0 -1
  327. package/dist/4225.js.map +0 -1
  328. package/dist/4300.js +0 -1
  329. package/dist/4335.js +0 -1
  330. package/dist/439.js +0 -1
  331. package/dist/4618.js +0 -1
  332. package/dist/4652.js +0 -1
  333. package/dist/4944.js +0 -1
  334. package/dist/5173.js +0 -1
  335. package/dist/5241.js +0 -1
  336. package/dist/5422.js +0 -1
  337. package/dist/5422.js.map +0 -1
  338. package/dist/5442.js +0 -1
  339. package/dist/5661.js +0 -1
  340. package/dist/6022.js +0 -1
  341. package/dist/6404.js +0 -1
  342. package/dist/6404.js.map +0 -1
  343. package/dist/6468.js +0 -1
  344. package/dist/6540.js +0 -2
  345. package/dist/6540.js.LICENSE.txt +0 -9
  346. package/dist/6540.js.map +0 -1
  347. package/dist/6589.js +0 -1
  348. package/dist/6606.js +0 -1
  349. package/dist/6606.js.map +0 -1
  350. package/dist/6679.js +0 -1
  351. package/dist/6792.js +0 -1
  352. package/dist/6792.js.map +0 -1
  353. package/dist/6840.js +0 -1
  354. package/dist/6859.js +0 -1
  355. package/dist/7097.js +0 -1
  356. package/dist/7159.js +0 -1
  357. package/dist/723.js +0 -1
  358. package/dist/7255.js +0 -1
  359. package/dist/7255.js.map +0 -1
  360. package/dist/7617.js +0 -1
  361. package/dist/795.js +0 -1
  362. package/dist/8163.js +0 -1
  363. package/dist/8341.js +0 -2
  364. package/dist/8341.js.LICENSE.txt +0 -52
  365. package/dist/8341.js.map +0 -1
  366. package/dist/8349.js +0 -1
  367. package/dist/8371.js +0 -1
  368. package/dist/8421.js +0 -1
  369. package/dist/8421.js.map +0 -1
  370. package/dist/8618.js +0 -1
  371. package/dist/890.js +0 -1
  372. package/dist/9214.js +0 -1
  373. package/dist/9538.js +0 -1
  374. package/dist/9569.js +0 -1
  375. package/dist/961.js +0 -2
  376. package/dist/961.js.LICENSE.txt +0 -19
  377. package/dist/961.js.map +0 -1
  378. package/dist/986.js +0 -1
  379. package/dist/9879.js +0 -1
  380. package/dist/9895.js +0 -1
  381. package/dist/9900.js +0 -1
  382. package/dist/9913.js +0 -1
  383. package/dist/main.js.LICENSE.txt +0 -62
  384. package/src/billable-services/bill-waiver/bill-selection.component.tsx +0 -76
  385. package/src/billable-services/bill-waiver/bill-waiver-form.component.tsx +0 -107
  386. package/src/billable-services/bill-waiver/bill-waiver-form.scss +0 -34
  387. package/src/billable-services/bill-waiver/bill-waiver.component.tsx +0 -34
  388. package/src/billable-services/bill-waiver/bill-waiver.scss +0 -10
  389. package/src/billable-services/bill-waiver/patient-bills.component.tsx +0 -134
  390. package/webpack.config.js +0 -1
@@ -0,0 +1,313 @@
1
+ import React from 'react';
2
+ import { describe, expect, it, vi, beforeEach } from 'vitest';
3
+ import { render, screen, waitFor } from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
+ import { showSnackbar, useSession } from '@openmrs/esm-framework';
6
+ import ReviewBillRefundsModal from './review-bill-refunds.modal';
7
+ import { actOnRefund, voidRefund } from '../../refunds.resource';
8
+ import { useBill } from '../../../billing.resource';
9
+ import { RefundStatus, BillStatus, type PatientInvoice } from '../../../types';
10
+
11
+ vi.mock('@openmrs/esm-framework', () => ({
12
+ showSnackbar: vi.fn(),
13
+ useSession: vi.fn(),
14
+ useConfig: vi.fn().mockReturnValue({ defaultCurrency: 'USD' }),
15
+ formatDate: () => '2026-05-21',
16
+ parseDate: (s: string) => new Date(s),
17
+ getCoreTranslation: (key: string) => key,
18
+ restBaseUrl: '/ws/rest/v1',
19
+ }));
20
+ vi.mock('../../refunds.resource');
21
+ vi.mock('../../../billing.resource', () => ({
22
+ useBill: vi.fn().mockReturnValue({ bill: null, mutate: vi.fn(), isLoading: false, isValidating: false }),
23
+ }));
24
+
25
+ globalThis.i18next = { language: 'en-US' } as any;
26
+
27
+ const closeModal = vi.fn();
28
+ const onMutate = vi.fn();
29
+
30
+ const makeBill = (refunds: any[] = []): PatientInvoice =>
31
+ ({
32
+ uuid: 'b1',
33
+ status: BillStatus.PAID,
34
+ total: 5000,
35
+ amountAfterDiscount: 5000,
36
+ receiptNumber: 'INV-1',
37
+ patient: { uuid: 'p1', display: 'John Doe', links: [] },
38
+ cashier: { uuid: 'c1', display: 'cashier', links: [] },
39
+ dateCreated: '2026-05-21T00:00:00.000+0000',
40
+ lineItems: [],
41
+ payments: [{ uuid: 'pay1', amountTendered: 5000, amount: 5000, voided: false } as any],
42
+ refunds,
43
+ discounts: [],
44
+ }) as any;
45
+
46
+ const requestedRefund = {
47
+ uuid: 'r1',
48
+ billUuid: 'b1',
49
+ lineItemUuid: null,
50
+ refundAmount: 500,
51
+ reason: 'overcharged',
52
+ initiator: { uuid: 'u1', display: 'cashier' },
53
+ approver: null,
54
+ completer: null,
55
+ dateApproved: null,
56
+ dateCompleted: null,
57
+ dateCreated: '2026-05-21T00:00:00.000+0000',
58
+ status: RefundStatus.REQUESTED,
59
+ voided: false,
60
+ };
61
+
62
+ beforeEach(() => {
63
+ vi.clearAllMocks();
64
+ vi.mocked(useSession).mockReturnValue({ user: { uuid: 'u-admin' } } as any);
65
+ });
66
+
67
+ describe('ReviewBillRefundsModal', () => {
68
+ it('renders the receipt rail and review stack', () => {
69
+ render(<ReviewBillRefundsModal closeModal={closeModal} bill={makeBill([requestedRefund])} onMutate={onMutate} />);
70
+ expect(screen.getByText(/John Doe/)).toBeInTheDocument();
71
+ expect(screen.getByText(/requested refunds/i)).toBeInTheDocument();
72
+ });
73
+
74
+ it('approves a REQUESTED refund', async () => {
75
+ vi.mocked(actOnRefund).mockResolvedValue({} as any);
76
+ const user = userEvent.setup();
77
+ render(<ReviewBillRefundsModal closeModal={closeModal} bill={makeBill([requestedRefund])} onMutate={onMutate} />);
78
+ await user.click(screen.getByRole('button', { name: /approve/i }));
79
+ await waitFor(() =>
80
+ expect(actOnRefund).toHaveBeenCalledWith('r1', { status: RefundStatus.APPROVED, approver: 'u-admin' }),
81
+ );
82
+ expect(onMutate).toHaveBeenCalled();
83
+ expect(showSnackbar).toHaveBeenCalledWith(expect.objectContaining({ kind: 'success' }));
84
+ });
85
+
86
+ it('blocks approve when totalApprovedRefunds + refundAmount exceeds amountAfterDiscount', async () => {
87
+ const bigRefund = { ...requestedRefund, uuid: 'r2', refundAmount: 6000 };
88
+ const user = userEvent.setup();
89
+ render(<ReviewBillRefundsModal closeModal={closeModal} bill={makeBill([bigRefund])} onMutate={onMutate} />);
90
+ await user.click(screen.getByRole('button', { name: /approve/i }));
91
+ expect(actOnRefund).not.toHaveBeenCalled();
92
+ expect(showSnackbar).toHaveBeenCalledWith(expect.objectContaining({ kind: 'error' }));
93
+ });
94
+
95
+ it('rejects a REQUESTED refund after inline confirm', async () => {
96
+ vi.mocked(actOnRefund).mockResolvedValue({} as any);
97
+ const user = userEvent.setup();
98
+ render(<ReviewBillRefundsModal closeModal={closeModal} bill={makeBill([requestedRefund])} onMutate={onMutate} />);
99
+ await user.click(screen.getByRole('button', { name: /reject/i }));
100
+ expect(screen.getByText(/reject this refund/i)).toBeInTheDocument();
101
+ await user.click(screen.getByRole('button', { name: /confirm reject/i }));
102
+ await waitFor(() =>
103
+ expect(actOnRefund).toHaveBeenCalledWith('r1', { status: RefundStatus.REJECTED, approver: 'u-admin' }),
104
+ );
105
+ });
106
+
107
+ it('shows read-only state for APPROVED refund', () => {
108
+ const approvedRefund = { ...requestedRefund, uuid: 'r3', status: RefundStatus.APPROVED };
109
+ render(<ReviewBillRefundsModal closeModal={closeModal} bill={makeBill([approvedRefund])} onMutate={onMutate} />);
110
+ expect(screen.getByText(/awaiting cashier processing/i)).toBeInTheDocument();
111
+ expect(screen.queryByRole('button', { name: /approve/i })).not.toBeInTheDocument();
112
+ });
113
+
114
+ it('blocks approve when completed refunds + new refund amount exceeds amountAfterDiscount', async () => {
115
+ const completedRefund = {
116
+ ...requestedRefund,
117
+ uuid: 'r-done',
118
+ refundAmount: 700,
119
+ status: RefundStatus.COMPLETED,
120
+ voided: false,
121
+ };
122
+ const newRequest = { ...requestedRefund, uuid: 'r-new', refundAmount: 500 };
123
+ const bill = makeBill([completedRefund, newRequest]);
124
+ // amountAfterDiscount = 5000 on makeBill, override to 1000 to make the sum exceed
125
+ const smallBill = { ...bill, amountAfterDiscount: 1000 };
126
+ const user = userEvent.setup();
127
+ render(<ReviewBillRefundsModal closeModal={closeModal} bill={smallBill as any} onMutate={onMutate} />);
128
+ await user.click(screen.getByRole('button', { name: /approve/i }));
129
+ expect(actOnRefund).not.toHaveBeenCalled();
130
+ expect(showSnackbar).toHaveBeenCalledWith(expect.objectContaining({ kind: 'error' }));
131
+ });
132
+
133
+ it('does not throw when session.user is null and approve is clicked', async () => {
134
+ vi.mocked(useSession).mockReturnValue({ user: null } as any);
135
+ vi.mocked(actOnRefund).mockResolvedValue({} as any);
136
+ const user = userEvent.setup();
137
+ render(<ReviewBillRefundsModal closeModal={closeModal} bill={makeBill([requestedRefund])} onMutate={onMutate} />);
138
+ await user.click(screen.getByRole('button', { name: /approve/i }));
139
+ await waitFor(() =>
140
+ expect(actOnRefund).toHaveBeenCalledWith('r1', { status: RefundStatus.APPROVED, approver: undefined }),
141
+ );
142
+ });
143
+
144
+ it('shows a progress bar above the content and disables action buttons while loading', () => {
145
+ vi.mocked(useBill).mockReturnValueOnce({
146
+ bill: null,
147
+ mutate: vi.fn(),
148
+ isLoading: true,
149
+ isValidating: false,
150
+ error: undefined,
151
+ } as any);
152
+ render(<ReviewBillRefundsModal closeModal={closeModal} bill={makeBill([requestedRefund])} onMutate={onMutate} />);
153
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
154
+ expect(screen.getByText(/requested refunds/i)).toBeInTheDocument();
155
+ expect(screen.getByRole('button', { name: /approve/i })).toBeDisabled();
156
+ expect(screen.getByRole('button', { name: /reject/i })).toBeDisabled();
157
+ });
158
+
159
+ it('shows an error notification and hides bill content when bill fetch fails', () => {
160
+ vi.mocked(useBill).mockReturnValueOnce({
161
+ bill: null,
162
+ mutate: vi.fn(),
163
+ isLoading: false,
164
+ isValidating: false,
165
+ error: new Error('Network error'),
166
+ } as any);
167
+ render(<ReviewBillRefundsModal closeModal={closeModal} bill={makeBill([requestedRefund])} onMutate={onMutate} />);
168
+ expect(screen.getByText(/failed to load bill/i)).toBeInTheDocument();
169
+ expect(screen.queryByText(/requested refunds/i)).not.toBeInTheDocument();
170
+ expect(screen.queryByRole('button', { name: /approve/i })).not.toBeInTheDocument();
171
+ });
172
+
173
+ describe('line item refunds', () => {
174
+ const lineItem = { uuid: 'li-1', item: 'Lab Test', price: 1000, quantity: 1, status: 'PENDING' as any };
175
+ const lineItemRefund = { ...requestedRefund, uuid: 'r-li', lineItemUuid: 'li-1', refundAmount: 500 };
176
+
177
+ it('shows the line item name as the refund scope instead of "Whole bill"', () => {
178
+ const bill = { ...makeBill([lineItemRefund]), lineItems: [lineItem] };
179
+ render(<ReviewBillRefundsModal closeModal={closeModal} bill={bill as any} onMutate={onMutate} />);
180
+ // "Lab Test" appears in both the receipt rail and the refund card scope
181
+ expect(screen.getAllByText('Lab Test').length).toBeGreaterThan(0);
182
+ // No refund should show "Whole bill" when all refunds are line-item-scoped
183
+ expect(screen.queryByText(/whole bill/i)).not.toBeInTheDocument();
184
+ });
185
+
186
+ it('approves a line-item refund within the line item total', async () => {
187
+ vi.mocked(actOnRefund).mockResolvedValue({} as any);
188
+ const bill = { ...makeBill([lineItemRefund]), lineItems: [lineItem] };
189
+ const user = userEvent.setup();
190
+ render(<ReviewBillRefundsModal closeModal={closeModal} bill={bill as any} onMutate={onMutate} />);
191
+ await user.click(screen.getByRole('button', { name: /approve/i }));
192
+ await waitFor(() =>
193
+ expect(actOnRefund).toHaveBeenCalledWith('r-li', { status: RefundStatus.APPROVED, approver: 'u-admin' }),
194
+ );
195
+ expect(showSnackbar).toHaveBeenCalledWith(expect.objectContaining({ kind: 'success' }));
196
+ });
197
+
198
+ it('blocks approving a line-item refund that would exceed the line item total', async () => {
199
+ const overRefund = { ...lineItemRefund, refundAmount: 1500 };
200
+ const bill = { ...makeBill([overRefund]), lineItems: [lineItem] };
201
+ const user = userEvent.setup();
202
+ render(<ReviewBillRefundsModal closeModal={closeModal} bill={bill as any} onMutate={onMutate} />);
203
+ await user.click(screen.getByRole('button', { name: /approve/i }));
204
+ expect(actOnRefund).not.toHaveBeenCalled();
205
+ expect(showSnackbar).toHaveBeenCalledWith(expect.objectContaining({ kind: 'error' }));
206
+ });
207
+ });
208
+
209
+ describe('delete (void) flow', () => {
210
+ // Carbon danger buttons get accessible name "danger{Label}" (e.g. "dangerDelete"),
211
+ // so we match with /delete/i (no anchors) which safely covers "dangerDelete".
212
+ it('shows the delete confirm prompt when Delete is clicked', async () => {
213
+ const user = userEvent.setup();
214
+ render(<ReviewBillRefundsModal closeModal={closeModal} bill={makeBill([requestedRefund])} onMutate={onMutate} />);
215
+ await user.click(screen.getByRole('button', { name: /delete/i }));
216
+ expect(screen.getByText(/delete this refund/i)).toBeInTheDocument();
217
+ expect(screen.getByRole('button', { name: /confirm delete/i })).toBeInTheDocument();
218
+ });
219
+
220
+ it('cancels delete and restores the default actions when cancel is clicked', async () => {
221
+ const user = userEvent.setup();
222
+ render(<ReviewBillRefundsModal closeModal={closeModal} bill={makeBill([requestedRefund])} onMutate={onMutate} />);
223
+ await user.click(screen.getByRole('button', { name: /delete/i }));
224
+ await user.click(screen.getByRole('button', { name: /^cancel$/i }));
225
+ expect(screen.queryByText(/delete this refund/i)).not.toBeInTheDocument();
226
+ expect(screen.getByRole('button', { name: /delete/i })).toBeInTheDocument();
227
+ });
228
+
229
+ it('calls voidRefund and shows a success snackbar when delete is confirmed', async () => {
230
+ vi.mocked(voidRefund).mockResolvedValue({} as any);
231
+ const user = userEvent.setup();
232
+ render(<ReviewBillRefundsModal closeModal={closeModal} bill={makeBill([requestedRefund])} onMutate={onMutate} />);
233
+ await user.click(screen.getByRole('button', { name: /delete/i }));
234
+ await user.click(screen.getByRole('button', { name: /confirm delete/i }));
235
+ await waitFor(() => expect(voidRefund).toHaveBeenCalledWith('r1', 'Voided by admin'));
236
+ expect(onMutate).toHaveBeenCalled();
237
+ expect(showSnackbar).toHaveBeenCalledWith(expect.objectContaining({ kind: 'success' }));
238
+ });
239
+
240
+ it('shows an error snackbar and does not call onMutate when delete fails', async () => {
241
+ vi.mocked(voidRefund).mockRejectedValue(new Error('Network error'));
242
+ const user = userEvent.setup();
243
+ render(<ReviewBillRefundsModal closeModal={closeModal} bill={makeBill([requestedRefund])} onMutate={onMutate} />);
244
+ await user.click(screen.getByRole('button', { name: /delete/i }));
245
+ await user.click(screen.getByRole('button', { name: /confirm delete/i }));
246
+ await waitFor(() => expect(showSnackbar).toHaveBeenCalledWith(expect.objectContaining({ kind: 'error' })));
247
+ expect(onMutate).not.toHaveBeenCalled();
248
+ });
249
+ });
250
+
251
+ it('disables Approve on all cards while any card approval is in flight', async () => {
252
+ let resolveApprove: (v: any) => void;
253
+ vi.mocked(actOnRefund).mockReturnValue(new Promise((res) => (resolveApprove = res)));
254
+
255
+ const refund2 = { ...requestedRefund, uuid: 'r2', reason: 'duplicate charge' };
256
+ const user = userEvent.setup();
257
+ render(
258
+ <ReviewBillRefundsModal
259
+ closeModal={closeModal}
260
+ bill={makeBill([requestedRefund, refund2])}
261
+ onMutate={onMutate}
262
+ />,
263
+ );
264
+
265
+ const [approveA, approveB] = screen.getAllByRole('button', { name: /approve/i });
266
+ await user.click(approveA);
267
+
268
+ expect(approveB).toBeDisabled();
269
+ resolveApprove({});
270
+ });
271
+
272
+ it('disables Confirm reject on any card while another card approval is in flight', async () => {
273
+ let resolveApprove: (v: any) => void;
274
+ vi.mocked(actOnRefund).mockReturnValue(new Promise((res) => (resolveApprove = res)));
275
+
276
+ const refund2 = { ...requestedRefund, uuid: 'r2', reason: 'duplicate charge' };
277
+ const user = userEvent.setup();
278
+ render(
279
+ <ReviewBillRefundsModal
280
+ closeModal={closeModal}
281
+ bill={makeBill([requestedRefund, refund2])}
282
+ onMutate={onMutate}
283
+ />,
284
+ );
285
+
286
+ // Open reject confirmation on card 2 (no API call yet)
287
+ // Filter to plain Reject buttons (not "Confirm reject") — there is one per REQUESTED card
288
+ const rejectButtons = screen
289
+ .getAllByRole('button', { name: /reject/i })
290
+ .filter((btn) => !/confirm/i.test(btn.textContent ?? ''));
291
+ await user.click(rejectButtons[1]);
292
+ expect(screen.getByText(/reject this refund/i)).toBeInTheDocument();
293
+
294
+ // Approve card 1 — leaves processing in flight
295
+ await user.click(screen.getByRole('button', { name: /approve/i }));
296
+
297
+ // Confirm reject on card 2 should now be disabled
298
+ expect(screen.getByRole('button', { name: /confirm reject/i })).toBeDisabled();
299
+ resolveApprove({});
300
+ });
301
+
302
+ it('does not throw when session.user is null and reject-confirm is clicked', async () => {
303
+ vi.mocked(useSession).mockReturnValue({ user: null } as any);
304
+ vi.mocked(actOnRefund).mockResolvedValue({} as any);
305
+ const user = userEvent.setup();
306
+ render(<ReviewBillRefundsModal closeModal={closeModal} bill={makeBill([requestedRefund])} onMutate={onMutate} />);
307
+ await user.click(screen.getByRole('button', { name: /reject/i }));
308
+ await user.click(screen.getByRole('button', { name: /confirm reject/i }));
309
+ await waitFor(() =>
310
+ expect(actOnRefund).toHaveBeenCalledWith('r1', { status: RefundStatus.REJECTED, approver: undefined }),
311
+ );
312
+ });
313
+ });
@@ -0,0 +1,188 @@
1
+ import React, { useCallback, useState } from 'react';
2
+ import { InlineNotification, ModalBody, ModalHeader, ProgressBar } from '@carbon/react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { showSnackbar, useSession } from '@openmrs/esm-framework';
5
+ import BillReceiptRail from './bill-receipt-rail/bill-receipt-rail.component';
6
+ import RefundReviewStack from './refund-review-stack/refund-review-stack.component';
7
+ import { useBill } from '../../../billing.resource';
8
+ import { actOnRefund, voidRefund } from '../../refunds.resource';
9
+ import { useReviewRefundModel } from './review-bill-refunds.utils';
10
+ import { RefundStatus, type BillRefund, type PatientInvoice } from '../../../types';
11
+ import styles from './review-bill-refunds.modal.scss';
12
+
13
+ const extractErrorMessage = (e: any): string =>
14
+ e?.responseBody?.error?.message ?? (e instanceof Error ? e.message : String(e));
15
+
16
+ interface Props {
17
+ closeModal: () => void;
18
+ bill: PatientInvoice;
19
+ onMutate: () => void;
20
+ }
21
+
22
+ const ReviewBillRefundsModal: React.FC<Props> = ({ closeModal, bill, onMutate }) => {
23
+ const { t } = useTranslation();
24
+ const session = useSession();
25
+ const { bill: localBill, mutate: localMutate, isLoading, isValidating, error } = useBill(bill.uuid, false);
26
+ const activeBill = localBill ?? bill;
27
+ const [processingRefundId, setProcessingRefundId] = useState<string | null>(null);
28
+ const [rejectingId, setRejectingId] = useState<string | null>(null);
29
+ const [voidingId, setVoidingId] = useState<string | null>(null);
30
+
31
+ const {
32
+ activeRefunds,
33
+ requestedRefunds,
34
+ decidedRefunds,
35
+ approvedRefunds,
36
+ completedRefunds,
37
+ totalApprovedRefunds,
38
+ totalCompletedRefunds,
39
+ lineItems,
40
+ payments,
41
+ paymentsTotal,
42
+ subtotal,
43
+ } = useReviewRefundModel(activeBill);
44
+
45
+ const handleApprove = useCallback(
46
+ async (r: BillRefund) => {
47
+ const isCommitted = (s: RefundStatus) =>
48
+ s === RefundStatus.REQUESTED || s === RefundStatus.APPROVED || s === RefundStatus.COMPLETED;
49
+ const otherCommitted = activeRefunds
50
+ .filter((x) => x.uuid !== r.uuid && isCommitted(x.status))
51
+ .reduce((sum, x) => sum + x.refundAmount, 0);
52
+
53
+ if (r.lineItemUuid != null) {
54
+ const lineItem = lineItems.find((li) => li.uuid === r.lineItemUuid);
55
+ if (lineItem != null) {
56
+ const lineTotal = lineItem.price * lineItem.quantity;
57
+ const otherOnLine = activeRefunds
58
+ .filter((x) => x.uuid !== r.uuid && x.lineItemUuid === r.lineItemUuid && isCommitted(x.status))
59
+ .reduce((sum, x) => sum + x.refundAmount, 0);
60
+ if (otherOnLine + r.refundAmount > lineTotal) {
61
+ showSnackbar({
62
+ title: t('approveBlockedRefund', 'Cannot approve refund'),
63
+ subtitle: t('approveBlockedExceedsLineTotal', 'Approving this refund would exceed the line item total.'),
64
+ kind: 'error',
65
+ });
66
+ return;
67
+ }
68
+ }
69
+ }
70
+
71
+ if (otherCommitted + r.refundAmount > activeBill.amountAfterDiscount) {
72
+ showSnackbar({
73
+ title: t('approveBlockedRefund', 'Cannot approve refund'),
74
+ subtitle: t('approveBlockedExceedsTotal', 'Approving this refund would exceed the bill total.'),
75
+ kind: 'error',
76
+ });
77
+ return;
78
+ }
79
+
80
+ setProcessingRefundId(r.uuid);
81
+ try {
82
+ await actOnRefund(r.uuid, { status: RefundStatus.APPROVED, approver: session.user?.uuid });
83
+ showSnackbar({ title: t('refundApproved', 'Refund approved'), kind: 'success' });
84
+ await localMutate();
85
+ onMutate();
86
+ } catch (e: unknown) {
87
+ showSnackbar({
88
+ title: t('approveFailed', 'Approve failed'),
89
+ subtitle: extractErrorMessage(e),
90
+ kind: 'error',
91
+ });
92
+ } finally {
93
+ setProcessingRefundId(null);
94
+ }
95
+ },
96
+ [activeRefunds, activeBill.amountAfterDiscount, lineItems, t, session.user?.uuid, localMutate, onMutate],
97
+ );
98
+
99
+ const confirmReject = useCallback(
100
+ async (r: BillRefund) => {
101
+ setProcessingRefundId(r.uuid);
102
+ try {
103
+ await actOnRefund(r.uuid, { status: RefundStatus.REJECTED, approver: session.user?.uuid });
104
+ showSnackbar({ title: t('refundRejected', 'Refund rejected'), kind: 'success' });
105
+ await localMutate();
106
+ onMutate();
107
+ } catch (e: unknown) {
108
+ showSnackbar({ title: t('rejectFailed', 'Reject failed'), subtitle: extractErrorMessage(e), kind: 'error' });
109
+ } finally {
110
+ setProcessingRefundId(null);
111
+ setRejectingId(null);
112
+ }
113
+ },
114
+ [t, session.user?.uuid, localMutate, onMutate],
115
+ );
116
+
117
+ const confirmVoid = useCallback(
118
+ async (r: BillRefund) => {
119
+ setProcessingRefundId(r.uuid);
120
+ try {
121
+ await voidRefund(r.uuid, 'Voided by admin');
122
+ showSnackbar({ title: t('refundDeleted', 'Refund deleted'), kind: 'success' });
123
+ await localMutate();
124
+ onMutate();
125
+ } catch (e: unknown) {
126
+ showSnackbar({ title: t('deleteFailed', 'Delete failed'), subtitle: extractErrorMessage(e), kind: 'error' });
127
+ } finally {
128
+ setProcessingRefundId(null);
129
+ setVoidingId(null);
130
+ }
131
+ },
132
+ [t, localMutate, onMutate],
133
+ );
134
+
135
+ const showProgress = !!processingRefundId || isLoading || isValidating;
136
+
137
+ return (
138
+ <>
139
+ <ModalHeader closeModal={closeModal} title={t('reviewRefunds', 'Review refunds')} />
140
+ <ModalBody>
141
+ {error ? (
142
+ <InlineNotification
143
+ kind="error"
144
+ title={t('billLoadFailed', 'Failed to load bill')}
145
+ subtitle={t('billLoadFailedSubtitle', 'Bill data could not be loaded. Please close and try again.')}
146
+ lowContrast
147
+ hideCloseButton
148
+ />
149
+ ) : (
150
+ <>
151
+ {showProgress && <ProgressBar label="" hideLabel />}
152
+ <div className={styles.layout}>
153
+ <BillReceiptRail
154
+ bill={activeBill}
155
+ lineItems={lineItems}
156
+ payments={payments}
157
+ paymentsTotal={paymentsTotal}
158
+ subtotal={subtotal}
159
+ totalApprovedRefunds={totalApprovedRefunds}
160
+ approvedRefunds={approvedRefunds}
161
+ completedRefunds={completedRefunds}
162
+ totalCompletedRefunds={totalCompletedRefunds}
163
+ />
164
+ <RefundReviewStack
165
+ requestedRefunds={requestedRefunds}
166
+ decidedRefunds={decidedRefunds}
167
+ lineItems={lineItems}
168
+ processingRefundId={processingRefundId}
169
+ disabled={isLoading || isValidating}
170
+ rejectingId={rejectingId}
171
+ voidingId={voidingId}
172
+ onApprove={handleApprove}
173
+ onStartReject={setRejectingId}
174
+ onCancelReject={() => setRejectingId(null)}
175
+ onConfirmReject={confirmReject}
176
+ onStartVoid={setVoidingId}
177
+ onCancelVoid={() => setVoidingId(null)}
178
+ onConfirmVoid={confirmVoid}
179
+ />
180
+ </div>
181
+ </>
182
+ )}
183
+ </ModalBody>
184
+ </>
185
+ );
186
+ };
187
+
188
+ export default ReviewBillRefundsModal;
@@ -0,0 +1,66 @@
1
+ import { useMemo } from 'react';
2
+ import { RefundStatus, type BillRefund, type LineItem, type PatientInvoice, type Payment } from '../../../types';
3
+
4
+ export function useReviewRefundModel(bill: PatientInvoice) {
5
+ const activeRefunds: BillRefund[] = useMemo(
6
+ () => (bill.refunds ?? []).filter((r: BillRefund) => !r.voided),
7
+ [bill.refunds],
8
+ );
9
+
10
+ const {
11
+ requestedRefunds,
12
+ decidedRefunds,
13
+ approvedRefunds,
14
+ completedRefunds,
15
+ totalApprovedRefunds,
16
+ totalCompletedRefunds,
17
+ totalCommittedRefunds,
18
+ lineItems,
19
+ payments,
20
+ paymentsTotal,
21
+ subtotal,
22
+ } = useMemo(() => {
23
+ const requestedRefunds = activeRefunds.filter((r) => r.status === RefundStatus.REQUESTED);
24
+ const decidedRefunds = activeRefunds.filter((r) => r.status !== RefundStatus.REQUESTED);
25
+ const approvedRefunds = activeRefunds.filter((r) => r.status === RefundStatus.APPROVED);
26
+ const completedRefunds = activeRefunds.filter((r) => r.status === RefundStatus.COMPLETED);
27
+ const totalApprovedRefunds = approvedRefunds.reduce((sum, r) => sum + r.refundAmount, 0);
28
+ const totalCompletedRefunds = completedRefunds.reduce((sum, r) => sum + r.refundAmount, 0);
29
+ const totalCommittedRefunds = totalApprovedRefunds + totalCompletedRefunds;
30
+
31
+ const lineItems = (bill.lineItems ?? []).filter((li: LineItem) => !li.voided);
32
+ const payments = (bill.payments ?? []).filter((p: Payment) => !p.voided);
33
+ const paymentsTotal = payments.reduce((sum: number, p: Payment) => sum + (p.amountTendered ?? 0), 0);
34
+
35
+ const subtotal = bill.amountAfterDiscount;
36
+
37
+ return {
38
+ requestedRefunds,
39
+ decidedRefunds,
40
+ approvedRefunds,
41
+ completedRefunds,
42
+ totalApprovedRefunds,
43
+ totalCompletedRefunds,
44
+ totalCommittedRefunds,
45
+ lineItems,
46
+ payments,
47
+ paymentsTotal,
48
+ subtotal,
49
+ };
50
+ }, [activeRefunds, bill.lineItems, bill.payments, bill.amountAfterDiscount]);
51
+
52
+ return {
53
+ activeRefunds,
54
+ requestedRefunds,
55
+ decidedRefunds,
56
+ approvedRefunds,
57
+ completedRefunds,
58
+ totalApprovedRefunds,
59
+ totalCompletedRefunds,
60
+ totalCommittedRefunds,
61
+ lineItems,
62
+ payments,
63
+ paymentsTotal,
64
+ subtotal,
65
+ };
66
+ }