@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
@@ -1,4 +1,7 @@
1
1
  import React from 'react';
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import { mockBill, mockPatient } from '@mocks/bills.mock';
4
+ import { waitForLoadingToFinish } from '@tools/test-helpers';
2
5
  import userEvent from '@testing-library/user-event';
3
6
  import { render, screen, waitFor } from '@testing-library/react';
4
7
  import { useReactToPrint } from 'react-to-print';
@@ -10,44 +13,49 @@ import {
10
13
  usePatient,
11
14
  } from '@openmrs/esm-framework';
12
15
  import { configSchema, type BillingConfig } from '../config-schema';
13
- import { BillStatus } from '../types';
14
- import { mockBill, mockPatient } from 'mocks/bills.mock';
16
+ import {
17
+ type BillDiscount,
18
+ BillDiscountStatus,
19
+ BillDiscountType,
20
+ BillStatus,
21
+ RefundStatus,
22
+ type MappedBill,
23
+ } from '../types';
15
24
  import { useBill } from '../billing.resource';
16
25
  import { usePaymentModes } from './payments/payment.resource';
17
- import { waitForLoadingToFinish } from 'tools/test-helpers';
18
26
  import Invoice from './invoice.component';
19
27
 
20
- const mockUseConfig = jest.mocked(useConfig<BillingConfig>);
21
- const mockUseBill = jest.mocked(useBill);
22
- const mockUsePatient = jest.mocked(usePatient);
23
- const mockUsePaymentModes = jest.mocked(usePaymentModes);
24
- const mockUseReactToPrint = jest.mocked(useReactToPrint);
25
- const mockShowModal = jest.mocked(showModal);
28
+ const mockUseConfig = vi.mocked(useConfig<BillingConfig>);
29
+ const mockUseBill = vi.mocked(useBill);
30
+ const mockUsePatient = vi.mocked(usePatient);
31
+ const mockUsePaymentModes = vi.mocked(usePaymentModes);
32
+ const mockUseReactToPrint = vi.mocked(useReactToPrint);
33
+ const mockShowModal = vi.mocked(showModal);
26
34
 
27
- jest.mock('../helpers/functions', () => ({
28
- convertToCurrency: jest.fn((amount) => `USD ${amount}`),
35
+ vi.mock('../helpers/functions', () => ({
36
+ convertToCurrency: vi.fn((amount) => `USD ${amount}`),
29
37
  }));
30
38
 
31
39
  window.i18next = {
32
40
  language: 'en',
33
41
  } as any;
34
42
 
35
- jest.mock('./printable-invoice/print-receipt.component', () =>
36
- jest.fn(() => <div data-testid="mock-print-receipt">Print Receipt Mock</div>),
37
- );
43
+ vi.mock('./printable-invoice/print-receipt.component', () => ({
44
+ default: vi.fn(() => <div data-testid="mock-print-receipt">Print Receipt Mock</div>),
45
+ }));
38
46
 
39
- jest.mock('./printable-invoice/printable-invoice.component', () =>
40
- jest.fn(() => <div data-testid="mock-printable-invoice">Printable Invoice Mock</div>),
41
- );
47
+ vi.mock('./printable-invoice/printable-invoice.component', () => ({
48
+ default: vi.fn(() => <div data-testid="mock-printable-invoice">Printable Invoice Mock</div>),
49
+ }));
42
50
 
43
- jest.mock('./payments/payment.resource', () => ({
44
- usePaymentModes: jest.fn(),
45
- updateBillVisitAttribute: jest.fn(),
51
+ vi.mock('./payments/payment.resource', () => ({
52
+ usePaymentModes: vi.fn(),
53
+ updateBillVisitAttribute: vi.fn(),
46
54
  }));
47
55
 
48
- jest.mock('../billing.resource', () => ({
49
- useBill: jest.fn(),
50
- useDefaultFacility: jest.fn().mockReturnValue({
56
+ vi.mock('../billing.resource', () => ({
57
+ useBill: vi.fn(),
58
+ useDefaultFacility: vi.fn().mockReturnValue({
51
59
  data: {
52
60
  uuid: '54065383-b4d4-42d2-af4d-d250a1fd2590',
53
61
  display: 'MTRH',
@@ -55,23 +63,32 @@ jest.mock('../billing.resource', () => ({
55
63
  }),
56
64
  }));
57
65
 
58
- jest.mock('react-router-dom', () => ({
59
- useParams: jest.fn().mockReturnValue({
66
+ vi.mock('../discounts/discounts-table.component', () => ({
67
+ default: vi.fn(() => null),
68
+ }));
69
+
70
+ vi.mock('../refunds/refunds-table.component', () => ({
71
+ default: vi.fn(() => null),
72
+ }));
73
+
74
+ vi.mock('react-router-dom', () => ({
75
+ useParams: vi.fn().mockReturnValue({
60
76
  patientUuid: 'patientUuid',
61
77
  billUuid: 'billUuid',
62
78
  }),
63
79
  }));
64
80
 
65
- jest.mock('react-to-print', () => ({
66
- useReactToPrint: jest.fn(),
81
+ vi.mock('react-to-print', () => ({
82
+ useReactToPrint: vi.fn(),
67
83
  }));
68
84
 
69
85
  describe('Invoice', () => {
70
- const defaultBillData = {
86
+ const defaultBillData: MappedBill = {
71
87
  ...mockBill,
72
88
  uuid: 'test-uuid',
73
89
  status: BillStatus.PENDING,
74
90
  totalAmount: 1000,
91
+ netAmount: 1000,
75
92
  tenderedAmount: 0,
76
93
  receiptNumber: 'RCPT-001',
77
94
  dateCreated: '2024-01-01',
@@ -81,7 +98,7 @@ describe('Invoice', () => {
81
98
  item: 'Test Service',
82
99
  quantity: 1,
83
100
  price: 1000,
84
- paymentStatus: 'PENDING',
101
+ status: 'PENDING',
85
102
  billableService: 'Test Service',
86
103
  display: '',
87
104
  voided: false,
@@ -100,7 +117,7 @@ describe('Invoice', () => {
100
117
  isLoading: false,
101
118
  error: null,
102
119
  isValidating: false,
103
- mutate: jest.fn(),
120
+ mutate: vi.fn(),
104
121
  });
105
122
 
106
123
  mockUsePatient.mockReturnValue({
@@ -117,22 +134,38 @@ describe('Invoice', () => {
117
134
  ],
118
135
  isLoading: false,
119
136
  error: null,
120
- mutate: jest.fn(),
137
+ mutate: vi.fn(),
121
138
  });
122
139
 
123
140
  mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(configSchema), defaultCurrency: 'USD' });
124
141
 
125
- const printHandler = jest.fn();
142
+ const printHandler = vi.fn();
126
143
  mockUseReactToPrint.mockReturnValue(printHandler);
127
144
  });
128
145
 
146
+ const makeDiscount = (overrides: Partial<BillDiscount> = {}): BillDiscount => ({
147
+ uuid: 'discount-1',
148
+ billUuid: 'test-uuid',
149
+ lineItemUuid: null,
150
+ discountType: BillDiscountType.PERCENTAGE,
151
+ discountValue: 10,
152
+ discountAmount: 100,
153
+ justification: 'goodwill',
154
+ initiator: { uuid: 'u1', display: 'cashier' },
155
+ approver: null,
156
+ dateCreated: '2024-01-01',
157
+ status: BillDiscountStatus.APPROVED,
158
+ voided: false,
159
+ ...overrides,
160
+ });
161
+
129
162
  it('should render loading state when bill is loading', () => {
130
163
  mockUseBill.mockReturnValue({
131
164
  bill: null,
132
165
  isLoading: true,
133
166
  error: null,
134
167
  isValidating: false,
135
- mutate: jest.fn(),
168
+ mutate: vi.fn(),
136
169
  });
137
170
 
138
171
  render(<Invoice />);
@@ -157,7 +190,7 @@ describe('Invoice', () => {
157
190
  isLoading: false,
158
191
  error: new Error('Failed to load bill'),
159
192
  isValidating: false,
160
- mutate: jest.fn(),
193
+ mutate: vi.fn(),
161
194
  });
162
195
 
163
196
  render(<Invoice />);
@@ -171,24 +204,12 @@ describe('Invoice', () => {
171
204
  expect(screen.getAllByText(/total amount/i).length).toBeGreaterThan(0);
172
205
  expect(screen.getAllByText(/amount tendered/i).length).toBeGreaterThan(0);
173
206
  expect(screen.getByRole('heading', { name: /invoice number/i })).toBeInTheDocument();
174
- expect(screen.getByText(/date and time/i)).toBeInTheDocument();
207
+ expect(screen.getByText(/date bill created/i)).toBeInTheDocument();
175
208
  expect(screen.getByText(/invoice status/i)).toBeInTheDocument();
176
209
  expect(screen.getAllByText('RCPT-001').length).toBeGreaterThan(0);
177
210
  expect(screen.getAllByText('PENDING').length).toBeGreaterThan(0);
178
- });
179
-
180
- it('should render invoice table with line items', async () => {
181
- render(<Invoice />);
182
- await waitForLoadingToFinish();
183
-
184
211
  expect(screen.getByText(/line items/i)).toBeInTheDocument();
185
212
  expect(screen.getByText('Test Service')).toBeInTheDocument();
186
- });
187
-
188
- it('should render payments section', async () => {
189
- render(<Invoice />);
190
- await waitForLoadingToFinish();
191
-
192
213
  expect(screen.getByText(/payments/i)).toBeInTheDocument();
193
214
  });
194
215
 
@@ -202,7 +223,7 @@ describe('Invoice', () => {
202
223
  isLoading: false,
203
224
  error: null,
204
225
  isValidating: false,
205
- mutate: jest.fn(),
226
+ mutate: vi.fn(),
206
227
  });
207
228
 
208
229
  render(<Invoice />);
@@ -221,7 +242,7 @@ describe('Invoice', () => {
221
242
  isLoading: false,
222
243
  error: null,
223
244
  isValidating: false,
224
- mutate: jest.fn(),
245
+ mutate: vi.fn(),
225
246
  });
226
247
 
227
248
  render(<Invoice />);
@@ -238,7 +259,7 @@ describe('Invoice', () => {
238
259
  });
239
260
 
240
261
  it('should handle print button click', async () => {
241
- const handlePrintMock = jest.fn();
262
+ const handlePrintMock = vi.fn();
242
263
  const user = userEvent.setup();
243
264
  mockUseReactToPrint.mockReturnValue(handlePrintMock);
244
265
 
@@ -269,7 +290,7 @@ describe('Invoice', () => {
269
290
  });
270
291
 
271
292
  it('should search and filter line items in the table', async () => {
272
- const billWithMultipleItems = {
293
+ const billWithMultipleItems: MappedBill = {
273
294
  ...defaultBillData,
274
295
  lineItems: [
275
296
  {
@@ -277,7 +298,7 @@ describe('Invoice', () => {
277
298
  item: 'Lab Test',
278
299
  quantity: 1,
279
300
  price: 500,
280
- paymentStatus: 'PENDING',
301
+ status: 'PENDING',
281
302
  billableService: 'Lab Test',
282
303
  display: '',
283
304
  voided: false,
@@ -292,7 +313,7 @@ describe('Invoice', () => {
292
313
  item: 'X-Ray',
293
314
  quantity: 1,
294
315
  price: 500,
295
- paymentStatus: 'PENDING',
316
+ status: 'PENDING',
296
317
  billableService: 'X-Ray',
297
318
  display: '',
298
319
  voided: false,
@@ -310,7 +331,7 @@ describe('Invoice', () => {
310
331
  isLoading: false,
311
332
  error: null,
312
333
  isValidating: false,
313
- mutate: jest.fn(),
334
+ mutate: vi.fn(),
314
335
  });
315
336
 
316
337
  const user = userEvent.setup();
@@ -330,7 +351,7 @@ describe('Invoice', () => {
330
351
  });
331
352
 
332
353
  it('should handle bill data updates via mutate', async () => {
333
- const mockMutate = jest.fn();
354
+ const mockMutate = vi.fn();
334
355
  mockUseBill.mockReturnValue({
335
356
  bill: defaultBillData,
336
357
  isLoading: false,
@@ -378,7 +399,7 @@ describe('Invoice', () => {
378
399
  isLoading: true,
379
400
  error: null,
380
401
  isValidating: false,
381
- mutate: jest.fn(),
402
+ mutate: vi.fn(),
382
403
  });
383
404
 
384
405
  render(<Invoice />);
@@ -392,7 +413,7 @@ describe('Invoice', () => {
392
413
  isLoading: false,
393
414
  error: null,
394
415
  isValidating: false,
395
- mutate: jest.fn(),
416
+ mutate: vi.fn(),
396
417
  });
397
418
 
398
419
  mockUsePatient.mockReturnValue({
@@ -444,7 +465,7 @@ describe('Invoice', () => {
444
465
  });
445
466
 
446
467
  it('should pass mutate function to Payments component', async () => {
447
- const mockMutate = jest.fn();
468
+ const mockMutate = vi.fn();
448
469
  mockUseBill.mockReturnValue({
449
470
  bill: defaultBillData,
450
471
  isLoading: false,
@@ -471,7 +492,7 @@ describe('Invoice', () => {
471
492
  isLoading: false,
472
493
  error: null,
473
494
  isValidating: false,
474
- mutate: jest.fn(),
495
+ mutate: vi.fn(),
475
496
  });
476
497
 
477
498
  render(<Invoice />);
@@ -499,7 +520,7 @@ describe('Invoice', () => {
499
520
  isLoading: false,
500
521
  error: null,
501
522
  isValidating: false,
502
- mutate: jest.fn(),
523
+ mutate: vi.fn(),
503
524
  });
504
525
 
505
526
  render(<Invoice />);
@@ -525,7 +546,7 @@ describe('Invoice', () => {
525
546
  isLoading: false,
526
547
  error: null,
527
548
  isValidating: false,
528
- mutate: jest.fn(),
549
+ mutate: vi.fn(),
529
550
  });
530
551
 
531
552
  render(<Invoice />);
@@ -547,7 +568,7 @@ describe('Invoice', () => {
547
568
  isLoading: false,
548
569
  error: null,
549
570
  isValidating: false,
550
- mutate: jest.fn(),
571
+ mutate: vi.fn(),
551
572
  });
552
573
 
553
574
  render(<Invoice />);
@@ -562,7 +583,7 @@ describe('Invoice', () => {
562
583
  isLoading: false,
563
584
  error: null,
564
585
  isValidating: false,
565
- mutate: jest.fn(),
586
+ mutate: vi.fn(),
566
587
  });
567
588
 
568
589
  render(<Invoice />);
@@ -572,7 +593,7 @@ describe('Invoice', () => {
572
593
  });
573
594
 
574
595
  it('should open finalize confirmation modal when "Finalize bill" button is clicked', async () => {
575
- const mockMutate = jest.fn();
596
+ const mockMutate = vi.fn();
576
597
  const user = userEvent.setup();
577
598
 
578
599
  mockUseBill.mockReturnValue({
@@ -595,9 +616,288 @@ describe('Invoice', () => {
595
616
  });
596
617
  });
597
618
 
619
+ describe('discount entry points', () => {
620
+ it('renders the "Request discount" button when bill is eligible', async () => {
621
+ render(<Invoice />);
622
+ expect(await screen.findByRole('button', { name: /request discount/i })).toBeInTheDocument();
623
+ });
624
+
625
+ it('opens the request-discount modal on click', async () => {
626
+ const user = userEvent.setup();
627
+ render(<Invoice />);
628
+ await user.click(await screen.findByRole('button', { name: /request discount/i }));
629
+ expect(mockShowModal).toHaveBeenCalledWith('request-discount-modal', expect.any(Object));
630
+ });
631
+
632
+ it('shows Total amount / Discount / Net amount trio when an approved discount exists', async () => {
633
+ mockUseBill.mockReturnValue({
634
+ bill: {
635
+ ...defaultBillData,
636
+ totalAmount: 5000,
637
+ netAmount: 4500,
638
+ tenderedAmount: 500,
639
+ discounts: [makeDiscount({ status: BillDiscountStatus.APPROVED, discountAmount: 500 })],
640
+ },
641
+ isLoading: false,
642
+ error: null,
643
+ isValidating: false,
644
+ mutate: vi.fn(),
645
+ });
646
+ render(<Invoice />);
647
+ expect(await screen.findByRole('heading', { name: /^net amount$/i })).toBeInTheDocument();
648
+ expect(screen.getByRole('heading', { name: /^total amount$/i })).toBeInTheDocument();
649
+ expect(screen.getByRole('heading', { name: /^discount$/i })).toBeInTheDocument();
650
+ expect(screen.getAllByText(/^- USD 500$/).length).toBeGreaterThan(0);
651
+ // Amount due must be netAmount - tendered (4000), not totalAmount - tendered (4500).
652
+ expect(screen.getAllByText('USD 4000').length).toBeGreaterThan(0);
653
+ });
654
+
655
+ it('hides the Discount / Net amount trio when no approved discount exists, even if totalAmount differs from netAmount', async () => {
656
+ mockUseBill.mockReturnValue({
657
+ bill: {
658
+ ...defaultBillData,
659
+ totalAmount: 5000,
660
+ netAmount: 4500,
661
+ discounts: [makeDiscount({ status: BillDiscountStatus.PENDING })],
662
+ },
663
+ isLoading: false,
664
+ error: null,
665
+ isValidating: false,
666
+ mutate: vi.fn(),
667
+ });
668
+ render(<Invoice />);
669
+ await screen.findByRole('heading', { name: /^total amount$/i });
670
+ expect(screen.queryByRole('heading', { name: /^net amount$/i })).not.toBeInTheDocument();
671
+ expect(screen.queryByRole('heading', { name: /^discount$/i })).not.toBeInTheDocument();
672
+ });
673
+
674
+ it('passes an onMutate to the modal that revalidates the bill SWR cache', async () => {
675
+ const mockMutate = vi.fn();
676
+ mockUseBill.mockReturnValue({
677
+ bill: defaultBillData,
678
+ isLoading: false,
679
+ error: null,
680
+ isValidating: false,
681
+ mutate: mockMutate,
682
+ });
683
+ const user = userEvent.setup();
684
+ render(<Invoice />);
685
+ await user.click(await screen.findByRole('button', { name: /request discount/i }));
686
+ const [, props] = mockShowModal.mock.calls.at(-1)!;
687
+ (props as { onMutate: () => void }).onMutate();
688
+ expect(mockMutate).toHaveBeenCalled();
689
+ });
690
+ });
691
+
692
+ describe('discount eligibility — bill status', () => {
693
+ it.each([
694
+ [BillStatus.PAID, 1000],
695
+ [BillStatus.ADJUSTED, 0],
696
+ ])('hides the bill-level "Request discount" button for %s bills', (status, tendered) => {
697
+ mockUseBill.mockReturnValue({
698
+ bill: { ...defaultBillData, status, tenderedAmount: tendered },
699
+ isLoading: false,
700
+ error: null,
701
+ isValidating: false,
702
+ mutate: vi.fn(),
703
+ });
704
+ render(<Invoice />);
705
+ expect(screen.queryByRole('button', { name: /request discount/i })).not.toBeInTheDocument();
706
+ });
707
+
708
+ it.each([
709
+ [BillStatus.PAID, 1000],
710
+ [BillStatus.ADJUSTED, 0],
711
+ ])('hides the per-line-item "Request discount" action for %s bills', async (status, tendered) => {
712
+ mockUseBill.mockReturnValue({
713
+ bill: { ...defaultBillData, status, tenderedAmount: tendered },
714
+ isLoading: false,
715
+ error: null,
716
+ isValidating: false,
717
+ mutate: vi.fn(),
718
+ });
719
+ const user = userEvent.setup();
720
+ render(<Invoice />);
721
+ await user.click(screen.getByTestId('action-menu-item-1'));
722
+ expect(screen.queryByTestId('request-discount-button-item-1')).not.toBeInTheDocument();
723
+ });
724
+ });
725
+
726
+ describe('discount eligibility — line already discounted', () => {
727
+ it.each([BillDiscountStatus.PENDING, BillDiscountStatus.APPROVED, BillDiscountStatus.REJECTED])(
728
+ 'hides the per-line-item "Request discount" action when an existing %s discount targets the line',
729
+ async (discountStatus) => {
730
+ mockUseBill.mockReturnValue({
731
+ bill: { ...defaultBillData, discounts: [makeDiscount({ lineItemUuid: 'item-1', status: discountStatus })] },
732
+ isLoading: false,
733
+ error: null,
734
+ isValidating: false,
735
+ mutate: vi.fn(),
736
+ });
737
+ const user = userEvent.setup();
738
+ render(<Invoice />);
739
+ await user.click(screen.getByTestId('action-menu-item-1'));
740
+ expect(screen.queryByTestId('request-discount-button-item-1')).not.toBeInTheDocument();
741
+ },
742
+ );
743
+ });
744
+
745
+ describe('discount eligibility — cross-blocking between bill-level and line-level', () => {
746
+ it.each([BillDiscountStatus.PENDING, BillDiscountStatus.APPROVED, BillDiscountStatus.REJECTED])(
747
+ 'disables the bill-level "Request discount" button when a %s line-item discount exists',
748
+ (discountStatus) => {
749
+ mockUseBill.mockReturnValue({
750
+ bill: { ...defaultBillData, discounts: [makeDiscount({ lineItemUuid: 'item-1', status: discountStatus })] },
751
+ isLoading: false,
752
+ error: null,
753
+ isValidating: false,
754
+ mutate: vi.fn(),
755
+ });
756
+ render(<Invoice />);
757
+ expect(screen.getByRole('button', { name: /request discount/i })).toBeDisabled();
758
+ },
759
+ );
760
+
761
+ it.each([BillDiscountStatus.PENDING, BillDiscountStatus.APPROVED, BillDiscountStatus.REJECTED])(
762
+ 'hides the per-line-item "Request discount" action when a %s bill-level discount exists',
763
+ async (discountStatus) => {
764
+ mockUseBill.mockReturnValue({
765
+ bill: { ...defaultBillData, discounts: [makeDiscount({ lineItemUuid: null, status: discountStatus })] },
766
+ isLoading: false,
767
+ error: null,
768
+ isValidating: false,
769
+ mutate: vi.fn(),
770
+ });
771
+ const user = userEvent.setup();
772
+ render(<Invoice />);
773
+ await user.click(screen.getByTestId('action-menu-item-1'));
774
+ expect(screen.queryByTestId('request-discount-button-item-1')).not.toBeInTheDocument();
775
+ },
776
+ );
777
+ });
778
+
779
+ describe('refund entry points', () => {
780
+ const paidBill: MappedBill = {
781
+ ...defaultBillData,
782
+ status: BillStatus.PAID,
783
+ totalAmount: 1000,
784
+ netAmount: 1000,
785
+ tenderedAmount: 1000,
786
+ };
787
+
788
+ beforeEach(() => {
789
+ mockUseBill.mockReturnValue({
790
+ bill: paidBill,
791
+ isLoading: false,
792
+ error: null,
793
+ isValidating: false,
794
+ mutate: vi.fn(),
795
+ });
796
+ });
797
+
798
+ it('shows "Request refund" button for PAID bills with no active refund', async () => {
799
+ render(<Invoice />);
800
+ expect(await screen.findByRole('button', { name: /request refund/i })).toBeInTheDocument();
801
+ });
802
+
803
+ it('hides "Request refund" button when bill is in REFUND_REQUESTED status', async () => {
804
+ mockUseBill.mockReturnValue({
805
+ bill: { ...paidBill, status: BillStatus.REFUND_REQUESTED },
806
+ isLoading: false,
807
+ error: null,
808
+ isValidating: false,
809
+ mutate: vi.fn(),
810
+ });
811
+ render(<Invoice />);
812
+ await waitForLoadingToFinish();
813
+ expect(screen.queryByRole('button', { name: /request refund/i })).not.toBeInTheDocument();
814
+ });
815
+
816
+ const billWithActiveLineRefund: MappedBill = {
817
+ ...paidBill,
818
+ refunds: [
819
+ {
820
+ uuid: 'r-active',
821
+ billUuid: 'test-uuid',
822
+ lineItemUuid: 'item-1',
823
+ refundAmount: 200,
824
+ reason: 'duplicate',
825
+ initiator: { uuid: 'u1', display: 'cashier' },
826
+ approver: null,
827
+ completer: null,
828
+ dateApproved: null,
829
+ dateCompleted: null,
830
+ dateCreated: '2026-06-01T00:00:00.000+0000',
831
+ status: RefundStatus.REQUESTED,
832
+ voided: false,
833
+ },
834
+ ],
835
+ };
836
+
837
+ it('disables "Request refund" button when an active line-item refund is in progress', async () => {
838
+ mockUseBill.mockReturnValue({
839
+ bill: billWithActiveLineRefund,
840
+ isLoading: false,
841
+ error: null,
842
+ isValidating: false,
843
+ mutate: vi.fn(),
844
+ });
845
+ render(<Invoice />);
846
+ await waitForLoadingToFinish();
847
+ expect(
848
+ screen.getByRole('button', { name: /a refund is already in progress for one or more line items/i }),
849
+ ).toBeDisabled();
850
+ });
851
+
852
+ it('shows a tooltip explaining why "Request refund" is disabled when a line-item refund is in progress', async () => {
853
+ mockUseBill.mockReturnValue({
854
+ bill: billWithActiveLineRefund,
855
+ isLoading: false,
856
+ error: null,
857
+ isValidating: false,
858
+ mutate: vi.fn(),
859
+ });
860
+ render(<Invoice />);
861
+ await waitForLoadingToFinish();
862
+ expect(screen.getByText(/a refund is already in progress for one or more line items/i)).toBeInTheDocument();
863
+ });
864
+
865
+ it('passes remainingRefundable that deducts COMPLETED refunds when opening the request-refund modal', async () => {
866
+ const completedRefund = {
867
+ uuid: 'r-done',
868
+ billUuid: 'test-uuid',
869
+ lineItemUuid: null,
870
+ refundAmount: 300,
871
+ reason: 'overcharged',
872
+ initiator: { uuid: 'u1', display: 'cashier' },
873
+ approver: { uuid: 'u2', display: 'admin' },
874
+ completer: { uuid: 'u3', display: 'cashier2' },
875
+ dateApproved: '2026-05-21T00:00:00.000+0000',
876
+ dateCompleted: '2026-05-21T00:00:00.000+0000',
877
+ dateCreated: '2026-05-21T00:00:00.000+0000',
878
+ status: RefundStatus.COMPLETED,
879
+ voided: false,
880
+ };
881
+ mockUseBill.mockReturnValue({
882
+ bill: { ...paidBill, refunds: [completedRefund] },
883
+ isLoading: false,
884
+ error: null,
885
+ isValidating: false,
886
+ mutate: vi.fn(),
887
+ });
888
+ const user = userEvent.setup();
889
+ render(<Invoice />);
890
+ await user.click(await screen.findByRole('button', { name: /request refund/i }));
891
+ expect(mockShowModal).toHaveBeenCalledWith(
892
+ 'request-refund-modal',
893
+ expect.objectContaining({ remainingRefundable: 700 }),
894
+ );
895
+ });
896
+ });
897
+
598
898
  it('should launch workspace with billUuid when "Add items to bill" is clicked', async () => {
599
- const mockMutate = jest.fn();
600
- const mockLaunchWorkspace2 = jest.mocked(launchWorkspace2);
899
+ const mockMutate = vi.fn();
900
+ const mockLaunchWorkspace2 = vi.mocked(launchWorkspace2);
601
901
 
602
902
  mockUseBill.mockReturnValue({
603
903
  bill: defaultBillData,
@@ -1,4 +1,5 @@
1
1
  import React, { useCallback } from 'react';
2
+ import { useTranslation } from 'react-i18next';
2
3
  import { Layer, OverflowMenu, OverflowMenuItem } from '@carbon/react';
3
4
  import { getCoreTranslation, isDesktop, showModal, useLayoutType } from '@openmrs/esm-framework';
4
5
  import { BillStatus, type LineItem, type MappedBill } from '../types';
@@ -8,9 +9,22 @@ type LineItemActionMenuProps = {
8
9
  bill: MappedBill;
9
10
  item: LineItem;
10
11
  onMutate?: () => void;
12
+ showDiscountRequest?: boolean;
13
+ onDiscountRequest?: () => void;
14
+ showRefundRequest?: boolean;
15
+ onRefundRequest?: () => void;
11
16
  };
12
17
 
13
- const LineItemActionMenu: React.FC<LineItemActionMenuProps> = ({ bill, item, onMutate }) => {
18
+ const LineItemActionMenu: React.FC<LineItemActionMenuProps> = ({
19
+ bill,
20
+ item,
21
+ onMutate,
22
+ showDiscountRequest,
23
+ onDiscountRequest,
24
+ showRefundRequest,
25
+ onRefundRequest,
26
+ }) => {
27
+ const { t } = useTranslation();
14
28
  const layout = useLayoutType();
15
29
 
16
30
  const handleEditLineItem = useCallback(() => {
@@ -54,6 +68,22 @@ const LineItemActionMenu: React.FC<LineItemActionMenuProps> = ({ bill, item, onM
54
68
  itemText={getCoreTranslation('delete')}
55
69
  onClick={handleDeleteLineItem}
56
70
  />
71
+ {showDiscountRequest && (
72
+ <OverflowMenuItem
73
+ className={styles.menuitem}
74
+ data-testid={`request-discount-button-${item.uuid}`}
75
+ itemText={t('requestDiscount', 'Request discount')}
76
+ onClick={onDiscountRequest}
77
+ />
78
+ )}
79
+ {showRefundRequest && (
80
+ <OverflowMenuItem
81
+ className={styles.menuitem}
82
+ data-testid={`request-refund-button-${item.uuid}`}
83
+ itemText={t('requestRefund', 'Request refund')}
84
+ onClick={onRefundRequest}
85
+ />
86
+ )}
57
87
  </OverflowMenu>
58
88
  </Layer>
59
89
  );