@openmrs/esm-billing-app 1.1.2-pre.8 → 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.
- package/.turbo/cache/31f1dfc7f71601df-meta.json +1 -0
- package/.turbo/cache/31f1dfc7f71601df.tar.zst +0 -0
- package/.turbo/turbo-build.log +13 -42
- package/__mocks__/bills.mock.ts +3 -2
- package/dist/1480.js +1 -0
- package/dist/1480.js.map +1 -0
- package/dist/1564.js +1 -0
- package/dist/1564.js.map +1 -0
- package/dist/1578.js +1 -0
- package/dist/1578.js.map +1 -0
- package/dist/1646.js +1 -0
- package/dist/1646.js.map +1 -0
- package/dist/1869.js +1 -0
- package/dist/1869.js.map +1 -0
- package/dist/1877.js +1 -0
- package/dist/1877.js.map +1 -0
- package/dist/1899.js +1 -0
- package/dist/1899.js.map +1 -0
- package/dist/196.js +2 -0
- package/dist/196.js.map +1 -0
- package/dist/2250.js +43 -0
- package/dist/2250.js.map +1 -0
- package/dist/2269.js +1 -0
- package/dist/2269.js.map +1 -0
- package/dist/2317.js +1 -0
- package/dist/2317.js.map +1 -0
- package/dist/2416.js +1 -0
- package/dist/2416.js.map +1 -0
- package/dist/2489.js +1 -0
- package/dist/2489.js.map +1 -0
- package/dist/282.js +1 -0
- package/dist/282.js.map +1 -0
- package/dist/2881.js +1 -0
- package/dist/2881.js.map +1 -0
- package/dist/2997.js +1 -0
- package/dist/2997.js.map +1 -0
- package/dist/3378.js +1 -0
- package/dist/3378.js.map +1 -0
- package/dist/3379.js +1 -0
- package/dist/3379.js.map +1 -0
- package/dist/3784.js +1 -0
- package/dist/3784.js.map +1 -0
- package/dist/3963.js +1 -0
- package/dist/3963.js.map +1 -0
- package/dist/4106.js +1 -0
- package/dist/4106.js.map +1 -0
- package/dist/4111.js +1 -0
- package/dist/4111.js.map +1 -0
- package/dist/434.js +1 -0
- package/dist/434.js.map +1 -0
- package/dist/4348.js +1 -0
- package/dist/4348.js.map +1 -0
- package/dist/4383.js +1 -0
- package/dist/4383.js.map +1 -0
- package/dist/4658.js +1 -0
- package/dist/4658.js.map +1 -0
- package/dist/4870.js +1 -0
- package/dist/4870.js.map +1 -0
- package/dist/4928.js +1 -0
- package/dist/4928.js.map +1 -0
- package/dist/5098.js +1 -0
- package/dist/5098.js.map +1 -0
- package/dist/5117.js +1 -0
- package/dist/5117.js.map +1 -0
- package/dist/5132.js +1 -0
- package/dist/5132.js.map +1 -0
- package/dist/5145.js +1 -0
- package/dist/5145.js.map +1 -0
- package/dist/5390.js +1 -0
- package/dist/5390.js.map +1 -0
- package/dist/5503.js +1 -0
- package/dist/5503.js.map +1 -0
- package/dist/556.js +1 -0
- package/dist/556.js.map +1 -0
- package/dist/5644.js +1 -0
- package/dist/5644.js.map +1 -0
- package/dist/5898.js +1 -0
- package/dist/5898.js.map +1 -0
- package/dist/5940.js +1 -0
- package/dist/5940.js.map +1 -0
- package/dist/6047.js +1 -0
- package/dist/6047.js.map +1 -0
- package/dist/6237.js +1 -0
- package/dist/6237.js.map +1 -0
- package/dist/6362.js +1 -0
- package/dist/6362.js.map +1 -0
- package/dist/6371.js +1 -0
- package/dist/6371.js.map +1 -0
- package/dist/6377.js +1 -0
- package/dist/6377.js.map +1 -0
- package/dist/6444.js +1 -0
- package/dist/6444.js.map +1 -0
- package/dist/6508.js +1 -0
- package/dist/6508.js.map +1 -0
- package/dist/6594.js +1 -0
- package/dist/6594.js.map +1 -0
- package/dist/6724.js +1 -0
- package/dist/6724.js.map +1 -0
- package/dist/6904.js +1 -0
- package/dist/6904.js.map +1 -0
- package/dist/7045.js +1 -0
- package/dist/7045.js.map +1 -0
- package/dist/7175.js +1 -0
- package/dist/7175.js.map +1 -0
- package/dist/7182.js +1 -0
- package/dist/7182.js.map +1 -0
- package/dist/7247.js +1 -0
- package/dist/7247.js.map +1 -0
- package/dist/7742.js +1 -0
- package/dist/7742.js.map +1 -0
- package/dist/7912.js +1 -0
- package/dist/7912.js.map +1 -0
- package/dist/8358.js +1 -0
- package/dist/8358.js.map +1 -0
- package/dist/8359.js +1 -0
- package/dist/8359.js.map +1 -0
- package/dist/8695.js +1 -0
- package/dist/8695.js.map +1 -0
- package/dist/903.js +1 -0
- package/dist/903.js.map +1 -0
- package/dist/9072.js +1 -0
- package/dist/9072.js.map +1 -0
- package/dist/9414.js +1 -0
- package/dist/9414.js.map +1 -0
- package/dist/9655.js +11 -0
- package/dist/9655.js.map +1 -0
- package/dist/9806.js +1 -0
- package/dist/9806.js.map +1 -0
- package/dist/990.js +1 -0
- package/dist/990.js.map +1 -0
- package/dist/main.js +17 -2
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-billing-app.js +6 -1
- package/dist/openmrs-esm-billing-app.js.buildmanifest.json +643 -436
- package/dist/openmrs-esm-billing-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/e2e/commands/billing-operations.ts +21 -0
- package/e2e/commands/types.ts +9 -1
- package/e2e/pages/discounts-page.ts +75 -0
- package/e2e/pages/index.ts +1 -0
- package/e2e/pages/invoice-page.ts +7 -7
- package/e2e/specs/bill-discounts.spec.ts +255 -0
- package/e2e/specs/billing-dashboard.spec.ts +3 -3
- package/e2e/specs/billing-patient-chart.spec.ts +2 -2
- package/package.json +13 -22
- package/rspack.config.js +1 -0
- package/src/bill-history/bill-action-menu.component.tsx +20 -2
- package/src/bill-history/bill-history.test.tsx +23 -22
- package/src/bill-item-actions/edit-bill-item.modal.tsx +1 -1
- package/src/bill-item-actions/edit-bill-item.test.tsx +29 -27
- package/src/billable-services/billable-service-form/billable-service-form.test.tsx +74 -73
- package/src/billable-services/billable-services-home.component.tsx +4 -2
- package/src/billable-services/billable-services.test.tsx +8 -7
- package/src/billable-services/dashboard/dashboard.test.tsx +3 -2
- package/src/billable-services-admin-card-link.test.tsx +2 -1
- package/src/billing-dashboard/billing-dashboard.test.tsx +19 -3
- package/src/billing-form/billing-checkin-form.component.tsx +7 -3
- package/src/billing-form/billing-checkin-form.test.tsx +22 -21
- package/src/billing-form/billing-form.resource.test.ts +7 -6
- package/src/billing-form/billing-form.test.tsx +77 -40
- package/src/billing-form/billing-form.workspace.tsx +25 -6
- package/src/billing-form/visit-attributes/visit-attributes-form.component.tsx +6 -2
- package/src/billing.resource.test.ts +43 -41
- package/src/billing.resource.ts +65 -16
- package/src/bills-table/bills-table.component.tsx +15 -4
- package/src/bills-table/bills-table.test.tsx +72 -71
- package/src/config-schema.ts +0 -7
- package/src/discounts/admin/discount-requests-left-panel-link.component.tsx +43 -0
- package/src/discounts/admin/discount-requests.component.tsx +316 -0
- package/src/discounts/admin/discount-requests.scss +133 -0
- package/src/discounts/admin/discount-requests.test.tsx +104 -0
- package/src/discounts/admin/review-bill-discounts/bill-line-items-table/bill-line-items-table.component.tsx +42 -0
- package/src/discounts/admin/review-bill-discounts/bill-line-items-table/bill-line-items-table.scss +76 -0
- package/src/discounts/admin/review-bill-discounts/bill-payments-table/bill-payments-table.component.tsx +50 -0
- package/src/discounts/admin/review-bill-discounts/bill-payments-table/bill-payments-table.scss +63 -0
- package/src/discounts/admin/review-bill-discounts/bill-receipt-rail/bill-receipt-rail.component.tsx +73 -0
- package/src/discounts/admin/review-bill-discounts/bill-receipt-rail/bill-receipt-rail.scss +54 -0
- package/src/discounts/admin/review-bill-discounts/bill-totals-summary/bill-totals-summary.component.tsx +95 -0
- package/src/discounts/admin/review-bill-discounts/bill-totals-summary/bill-totals-summary.scss +128 -0
- package/src/discounts/admin/review-bill-discounts/discount-card/discount-card.component.tsx +158 -0
- package/src/discounts/admin/review-bill-discounts/discount-card/discount-card.scss +164 -0
- package/src/discounts/admin/review-bill-discounts/discount-review-stack/discount-review-stack.component.tsx +86 -0
- package/src/discounts/admin/review-bill-discounts/discount-review-stack/discount-review-stack.scss +40 -0
- package/src/discounts/admin/review-bill-discounts/review-bill-discounts.modal.scss +14 -0
- package/src/discounts/admin/review-bill-discounts/review-bill-discounts.modal.test.tsx +153 -0
- package/src/discounts/admin/review-bill-discounts/review-bill-discounts.modal.tsx +167 -0
- package/src/discounts/admin/review-bill-discounts/review-bill-discounts.utils.ts +42 -0
- package/src/discounts/discounts-table.component.tsx +109 -0
- package/src/discounts/discounts-table.scss +37 -0
- package/src/discounts/discounts-table.test.tsx +67 -0
- package/src/discounts/discounts.resource.ts +71 -0
- package/src/discounts/request-discount.modal.scss +88 -0
- package/src/discounts/request-discount.modal.test.tsx +161 -0
- package/src/discounts/request-discount.modal.tsx +253 -0
- package/src/index.ts +52 -21
- package/src/invoice/invoice-table.component.tsx +116 -18
- package/src/invoice/invoice-table.test.tsx +165 -13
- package/src/invoice/invoice.component.tsx +111 -7
- package/src/invoice/invoice.test.tsx +366 -66
- package/src/invoice/line-item-action-menu.component.tsx +31 -1
- package/src/invoice/payments/payment-form/payment-form.test.tsx +20 -19
- package/src/invoice/payments/payment-history/payment-history.test.tsx +13 -10
- package/src/invoice/payments/payments.component.tsx +20 -6
- package/src/invoice/payments/payments.test.tsx +88 -23
- package/src/invoice/printable-invoice/print-receipt.test.tsx +10 -28
- package/src/invoice/printable-invoice/printable-footer.test.tsx +5 -4
- package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +3 -3
- package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +26 -11
- package/src/invoice/printable-invoice/printable-invoice.component.tsx +38 -15
- package/src/left-panel-link.test.tsx +3 -3
- package/src/metrics-cards/metrics-cards.test.tsx +11 -10
- package/src/modal/delete-bill-confirmation.modal.test.tsx +134 -0
- package/src/modal/delete-bill-confirmation.modal.tsx +98 -0
- package/src/modal/delete-line-item-confirmation.modal.test.tsx +11 -9
- package/src/modal/finalize-bill-confirmation.modal.test.tsx +10 -8
- package/src/modal/require-payment-modal.test.tsx +12 -11
- package/src/payment-status-tag/payment-status-tag.component.tsx +50 -0
- package/src/payment-status-tag/payment-status-tag.scss +6 -0
- package/src/payment-status-tag/payment-status-tag.test.tsx +113 -0
- package/src/refunds/admin/refund-requests-left-panel-link.component.tsx +43 -0
- package/src/refunds/admin/refund-requests.component.tsx +324 -0
- package/src/refunds/admin/refund-requests.scss +133 -0
- package/src/refunds/admin/refund-requests.test.tsx +99 -0
- package/src/refunds/admin/review-bill-refunds/bill-line-items-table/bill-line-items-table.component.tsx +42 -0
- package/src/refunds/admin/review-bill-refunds/bill-line-items-table/bill-line-items-table.scss +76 -0
- package/src/refunds/admin/review-bill-refunds/bill-payments-table/bill-payments-table.component.tsx +50 -0
- package/src/refunds/admin/review-bill-refunds/bill-payments-table/bill-payments-table.scss +63 -0
- package/src/refunds/admin/review-bill-refunds/bill-receipt-rail/bill-receipt-rail.component.tsx +84 -0
- package/src/refunds/admin/review-bill-refunds/bill-receipt-rail/bill-receipt-rail.scss +54 -0
- package/src/refunds/admin/review-bill-refunds/bill-totals-summary/bill-totals-summary.component.tsx +83 -0
- package/src/refunds/admin/review-bill-refunds/bill-totals-summary/bill-totals-summary.scss +65 -0
- package/src/refunds/admin/review-bill-refunds/refund-card/refund-card.component.tsx +170 -0
- package/src/refunds/admin/review-bill-refunds/refund-card/refund-card.scss +155 -0
- package/src/refunds/admin/review-bill-refunds/refund-review-stack/refund-review-stack.component.tsx +86 -0
- package/src/refunds/admin/review-bill-refunds/refund-review-stack/refund-review-stack.scss +40 -0
- package/src/refunds/admin/review-bill-refunds/review-bill-refunds.modal.scss +14 -0
- package/src/refunds/admin/review-bill-refunds/review-bill-refunds.modal.test.tsx +313 -0
- package/src/refunds/admin/review-bill-refunds/review-bill-refunds.modal.tsx +188 -0
- package/src/refunds/admin/review-bill-refunds/review-bill-refunds.utils.ts +66 -0
- package/src/refunds/refunds-table.component.tsx +137 -0
- package/src/refunds/refunds-table.scss +37 -0
- package/src/refunds/refunds-table.test.tsx +105 -0
- package/src/refunds/refunds.resource.test.ts +44 -0
- package/src/refunds/refunds.resource.ts +42 -0
- package/src/refunds/refunds.types.test.ts +15 -0
- package/src/refunds/request-refund.modal.scss +84 -0
- package/src/refunds/request-refund.modal.test.tsx +204 -0
- package/src/refunds/request-refund.modal.tsx +218 -0
- package/src/routes.json +36 -2
- package/src/types/index.ts +116 -1
- package/src/visit-bills/visit-bills-panel.component.tsx +151 -0
- package/src/visit-bills/visit-bills-panel.scss +31 -0
- package/src/visit-bills/visit-bills-panel.test.tsx +113 -0
- package/tools/empty-module.ts +1 -0
- package/tools/setup-tests.ts +9 -9
- package/translations/am.json +154 -16
- package/translations/ar.json +154 -16
- package/translations/ar_SY.json +154 -16
- package/translations/bn.json +154 -16
- package/translations/cs.json +154 -16
- package/translations/de.json +154 -16
- package/translations/en.json +154 -16
- package/translations/en_US.json +154 -16
- package/translations/es.json +154 -16
- package/translations/es_MX.json +154 -16
- package/translations/fr.json +154 -16
- package/translations/he.json +154 -16
- package/translations/hi.json +154 -16
- package/translations/hi_IN.json +154 -16
- package/translations/id.json +154 -16
- package/translations/it.json +154 -16
- package/translations/ka.json +154 -16
- package/translations/km.json +154 -16
- package/translations/ku.json +154 -16
- package/translations/ky.json +154 -16
- package/translations/lg.json +154 -16
- package/translations/ne.json +154 -16
- package/translations/pl.json +154 -16
- package/translations/pt.json +154 -16
- package/translations/pt_BR.json +154 -16
- package/translations/qu.json +154 -16
- package/translations/ro_RO.json +154 -16
- package/translations/ru_RU.json +154 -16
- package/translations/si.json +154 -16
- package/translations/sq.json +154 -16
- package/translations/sw.json +154 -16
- package/translations/sw_KE.json +154 -16
- package/translations/tr.json +154 -16
- package/translations/tr_TR.json +154 -16
- package/translations/uk.json +154 -16
- package/translations/uz.json +154 -16
- package/translations/uz@Latn.json +154 -16
- package/translations/uz_UZ.json +154 -16
- package/translations/vi.json +154 -16
- package/translations/zh.json +154 -16
- package/translations/zh_CN.json +179 -41
- package/translations/zh_TW.json +154 -16
- package/tsconfig.json +3 -3
- package/vitest.config.js +28 -0
- package/.turbo/cache/6c998b0f30a031ab-meta.json +0 -1
- package/.turbo/cache/6c998b0f30a031ab.tar.zst +0 -0
- package/dist/1119.js +0 -1
- package/dist/1197.js +0 -1
- package/dist/1435.js +0 -1
- package/dist/1435.js.map +0 -1
- package/dist/1807.js +0 -1
- package/dist/1807.js.map +0 -1
- package/dist/2146.js +0 -1
- package/dist/2177.js +0 -2
- package/dist/2177.js.LICENSE.txt +0 -9
- package/dist/2177.js.map +0 -1
- package/dist/2690.js +0 -1
- package/dist/2704.js +0 -1
- package/dist/2704.js.map +0 -1
- package/dist/3002.js +0 -1
- package/dist/3002.js.map +0 -1
- package/dist/3041.js +0 -1
- package/dist/3041.js.map +0 -1
- package/dist/3099.js +0 -1
- package/dist/3184.js +0 -2
- package/dist/3184.js.LICENSE.txt +0 -14
- package/dist/3184.js.map +0 -1
- package/dist/3584.js +0 -1
- package/dist/4055.js +0 -1
- package/dist/4132.js +0 -1
- package/dist/4225.js +0 -1
- package/dist/4225.js.map +0 -1
- package/dist/4300.js +0 -1
- package/dist/4335.js +0 -1
- package/dist/439.js +0 -1
- package/dist/4618.js +0 -1
- package/dist/4652.js +0 -1
- package/dist/4944.js +0 -1
- package/dist/5173.js +0 -1
- package/dist/5241.js +0 -1
- package/dist/5422.js +0 -1
- package/dist/5422.js.map +0 -1
- package/dist/5442.js +0 -1
- package/dist/5661.js +0 -1
- package/dist/6022.js +0 -1
- package/dist/6404.js +0 -1
- package/dist/6404.js.map +0 -1
- package/dist/6468.js +0 -1
- package/dist/6540.js +0 -2
- package/dist/6540.js.LICENSE.txt +0 -9
- package/dist/6540.js.map +0 -1
- package/dist/6589.js +0 -1
- package/dist/6606.js +0 -1
- package/dist/6606.js.map +0 -1
- package/dist/6679.js +0 -1
- package/dist/6792.js +0 -1
- package/dist/6792.js.map +0 -1
- package/dist/6840.js +0 -1
- package/dist/6859.js +0 -1
- package/dist/7097.js +0 -1
- package/dist/7159.js +0 -1
- package/dist/723.js +0 -1
- package/dist/7255.js +0 -1
- package/dist/7255.js.map +0 -1
- package/dist/7617.js +0 -1
- package/dist/795.js +0 -1
- package/dist/8163.js +0 -1
- package/dist/8341.js +0 -2
- package/dist/8341.js.LICENSE.txt +0 -52
- package/dist/8341.js.map +0 -1
- package/dist/8349.js +0 -1
- package/dist/8371.js +0 -1
- package/dist/8421.js +0 -1
- package/dist/8421.js.map +0 -1
- package/dist/8618.js +0 -1
- package/dist/890.js +0 -1
- package/dist/9214.js +0 -1
- package/dist/9538.js +0 -1
- package/dist/9569.js +0 -1
- package/dist/961.js +0 -2
- package/dist/961.js.LICENSE.txt +0 -19
- package/dist/961.js.map +0 -1
- package/dist/986.js +0 -1
- package/dist/9879.js +0 -1
- package/dist/9895.js +0 -1
- package/dist/9900.js +0 -1
- package/dist/9913.js +0 -1
- package/dist/main.js.LICENSE.txt +0 -62
- package/src/billable-services/bill-waiver/bill-selection.component.tsx +0 -76
- package/src/billable-services/bill-waiver/bill-waiver-form.component.tsx +0 -107
- package/src/billable-services/bill-waiver/bill-waiver-form.scss +0 -34
- package/src/billable-services/bill-waiver/bill-waiver.component.tsx +0 -34
- package/src/billable-services/bill-waiver/bill-waiver.scss +0 -10
- package/src/billable-services/bill-waiver/patient-bills.component.tsx +0 -134
- package/webpack.config.js +0 -1
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { expect } from '@playwright/test';
|
|
2
|
+
import { test } from '../core/test';
|
|
3
|
+
import { deleteBill, ensureServiceHasPrices, extractNumericValue, waitForSuccessNotification } from '../commands';
|
|
4
|
+
import {
|
|
5
|
+
BillingDashboardPage,
|
|
6
|
+
BillingFormPage,
|
|
7
|
+
DiscountRequestModal,
|
|
8
|
+
DiscountRequestsAdminPage,
|
|
9
|
+
InvoicePage,
|
|
10
|
+
PaymentPage,
|
|
11
|
+
ReviewBillDiscountsModal,
|
|
12
|
+
} from '../pages';
|
|
13
|
+
|
|
14
|
+
test.describe('Bill discount workflow', () => {
|
|
15
|
+
test.describe.configure({ mode: 'serial' });
|
|
16
|
+
|
|
17
|
+
let testServiceName: string;
|
|
18
|
+
let expectedServicePrice: number;
|
|
19
|
+
const billsToCleanup = new Set<string>();
|
|
20
|
+
|
|
21
|
+
test.beforeAll(async ({ api }) => {
|
|
22
|
+
const serviceUuid = process.env.E2E_TEST_SERVICE_UUID;
|
|
23
|
+
if (!serviceUuid) {
|
|
24
|
+
throw new Error('E2E_TEST_SERVICE_UUID must be configured in .env file');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const service = await ensureServiceHasPrices(api, serviceUuid, 30.0);
|
|
28
|
+
testServiceName = service.name;
|
|
29
|
+
|
|
30
|
+
const cashPrice = service.servicePrices.find((sp) => sp.name === 'Cash');
|
|
31
|
+
if (!cashPrice) {
|
|
32
|
+
throw new Error('Cash price not found for test service');
|
|
33
|
+
}
|
|
34
|
+
expectedServicePrice = parseFloat(cashPrice.price);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test.afterEach(async ({ api }) => {
|
|
38
|
+
for (const billUuid of billsToCleanup) {
|
|
39
|
+
try {
|
|
40
|
+
await deleteBill(api, billUuid);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error(`Failed to delete bill ${billUuid}:`, error);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
billsToCleanup.clear();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('Approved line-item discount reduces the bill amount due and payment settles the bill', async ({
|
|
49
|
+
page,
|
|
50
|
+
api,
|
|
51
|
+
patient,
|
|
52
|
+
}) => {
|
|
53
|
+
const billingDashboardPage = new BillingDashboardPage(page);
|
|
54
|
+
const billingFormPage = new BillingFormPage(page);
|
|
55
|
+
const invoicePage = new InvoicePage(page);
|
|
56
|
+
const paymentPage = new PaymentPage(page);
|
|
57
|
+
const requestDiscountModal = new DiscountRequestModal(page);
|
|
58
|
+
const discountRequestsPage = new DiscountRequestsAdminPage(page);
|
|
59
|
+
const reviewModal = new ReviewBillDiscountsModal(page);
|
|
60
|
+
|
|
61
|
+
const patientUuid = patient.uuid;
|
|
62
|
+
const patientName = patient.person.display;
|
|
63
|
+
const discountPercentage = 10;
|
|
64
|
+
const expectedDiscountAmount = (expectedServicePrice * discountPercentage) / 100;
|
|
65
|
+
const expectedNetAmount = expectedServicePrice - expectedDiscountAmount;
|
|
66
|
+
const justification = `e2e line-item discount ${Date.now()}`;
|
|
67
|
+
let billUuid: string;
|
|
68
|
+
let lineItemUuid: string;
|
|
69
|
+
|
|
70
|
+
await test.step('Given a finalized (POSTED) bill exists for the patient', async () => {
|
|
71
|
+
await page.goto(`patient/${patientUuid}/chart/billing-history`);
|
|
72
|
+
await page.getByRole('button', { name: /launch bill form|add bill|create bill/i }).click();
|
|
73
|
+
|
|
74
|
+
await billingFormPage.searchAndSelectBillableService(testServiceName);
|
|
75
|
+
await billingFormPage.selectPaymentMethodIfVisible();
|
|
76
|
+
await billingFormPage.saveBill();
|
|
77
|
+
await waitForSuccessNotification(page, /bill processed successfully/i);
|
|
78
|
+
|
|
79
|
+
const billsResponse = await api.get(`billing/bill?patient=${patientUuid}&v=full`);
|
|
80
|
+
const billsData = await billsResponse.json();
|
|
81
|
+
billUuid = billsData.results[0].uuid;
|
|
82
|
+
lineItemUuid = billsData.results[0].lineItems[0].uuid;
|
|
83
|
+
billsToCleanup.add(billUuid);
|
|
84
|
+
|
|
85
|
+
await billingDashboardPage.goto();
|
|
86
|
+
await billingDashboardPage.waitForBillsTableToLoad();
|
|
87
|
+
await billingDashboardPage.selectFilter('Pending confirmation');
|
|
88
|
+
await billingDashboardPage.waitForBillsTableToLoad();
|
|
89
|
+
await billingDashboardPage.clickInvoiceNumberLink(patientName);
|
|
90
|
+
await invoicePage.waitForInvoiceToLoad();
|
|
91
|
+
|
|
92
|
+
await invoicePage.finalizeBill();
|
|
93
|
+
await waitForSuccessNotification(page, /bill finalized/i);
|
|
94
|
+
await expect.poll(async () => await invoicePage.getInvoiceStatus()).toBe('POSTED');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
await test.step('When the cashier requests a 10% discount on the line item', async () => {
|
|
98
|
+
// Open the line-item overflow menu and click "Request discount".
|
|
99
|
+
// The test ids on these controls embed the line item uuid, which keeps
|
|
100
|
+
// the selector stable even if Carbon menu structure changes.
|
|
101
|
+
await page.getByTestId(`action-menu-${lineItemUuid}`).click();
|
|
102
|
+
await page.getByTestId(`request-discount-button-${lineItemUuid}`).click();
|
|
103
|
+
|
|
104
|
+
await requestDiscountModal.submitPercentageDiscount(discountPercentage, justification);
|
|
105
|
+
await waitForSuccessNotification(page, /discount request submitted/i);
|
|
106
|
+
await expect(requestDiscountModal.modal()).toBeHidden();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
await test.step('Then the discount appears as PENDING via the API and does not yet affect amount due', async () => {
|
|
110
|
+
const discountsResponse = await api.get(`billing/billDiscount?bill=${billUuid}&v=default`);
|
|
111
|
+
const discountsData = await discountsResponse.json();
|
|
112
|
+
expect(discountsData.results).toHaveLength(1);
|
|
113
|
+
const [discount] = discountsData.results;
|
|
114
|
+
expect(discount.status).toBe('PENDING');
|
|
115
|
+
expect(discount.lineItemUuid).toBe(lineItemUuid);
|
|
116
|
+
expect(discount.discountAmount).toBeCloseTo(expectedDiscountAmount, 2);
|
|
117
|
+
|
|
118
|
+
// Bill totals should still reflect the original price until approval.
|
|
119
|
+
const billResponse = await api.get(`billing/bill/${billUuid}`);
|
|
120
|
+
const billData = await billResponse.json();
|
|
121
|
+
expect(billData.amountAfterDiscount ?? billData.total).toBeCloseTo(expectedServicePrice, 2);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
await test.step('When the admin approves the discount from the discount requests dashboard', async () => {
|
|
125
|
+
await discountRequestsPage.goto();
|
|
126
|
+
await discountRequestsPage.waitForLoaded();
|
|
127
|
+
await discountRequestsPage.openReviewForPatient(patientName);
|
|
128
|
+
|
|
129
|
+
await reviewModal.waitForLoaded();
|
|
130
|
+
await reviewModal.approveFirstPending();
|
|
131
|
+
await waitForSuccessNotification(page, /discount approved/i);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
await test.step('Then the discount is APPROVED and net amount drops by the discount', async () => {
|
|
135
|
+
await expect
|
|
136
|
+
.poll(async () => {
|
|
137
|
+
const res = await api.get(`billing/billDiscount?bill=${billUuid}&v=default`);
|
|
138
|
+
const data = await res.json();
|
|
139
|
+
return data.results[0]?.status;
|
|
140
|
+
})
|
|
141
|
+
.toBe('APPROVED');
|
|
142
|
+
|
|
143
|
+
const billResponse = await api.get(`billing/bill/${billUuid}`);
|
|
144
|
+
const billData = await billResponse.json();
|
|
145
|
+
expect(billData.amountAfterDiscount).toBeCloseTo(expectedNetAmount, 2);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
await test.step('And the invoice page shows the discounted net amount and matching amount due', async () => {
|
|
149
|
+
await invoicePage.goto(patientUuid, billUuid);
|
|
150
|
+
await invoicePage.waitForInvoiceToLoad();
|
|
151
|
+
|
|
152
|
+
const amountDue = extractNumericValue(await invoicePage.getAmountDue());
|
|
153
|
+
expect(amountDue).toBeCloseTo(expectedNetAmount, 2);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
await test.step('When the cashier processes a payment that matches the discounted amount due', async () => {
|
|
157
|
+
await paymentPage.waitForPaymentForm();
|
|
158
|
+
await paymentPage.addPayment('Cash', expectedNetAmount);
|
|
159
|
+
expect(await paymentPage.isProcessPaymentButtonEnabled()).toBe(true);
|
|
160
|
+
await paymentPage.processPayment();
|
|
161
|
+
await waitForSuccessNotification(page, /payment processed successfully/i);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
await test.step('Then the bill is marked PAID with no outstanding balance', async () => {
|
|
165
|
+
await page.reload();
|
|
166
|
+
await invoicePage.waitForInvoiceToLoad();
|
|
167
|
+
await expect.poll(async () => await invoicePage.getInvoiceStatus()).toBe('PAID');
|
|
168
|
+
expect(extractNumericValue(await invoicePage.getAmountDue())).toBe(0);
|
|
169
|
+
|
|
170
|
+
const billResponse = await api.get(`billing/bill/${billUuid}`);
|
|
171
|
+
const billData = await billResponse.json();
|
|
172
|
+
expect(billData.status).toBe('PAID');
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('Rejected discount leaves the bill amount due unchanged', async ({ page, api, patient }) => {
|
|
177
|
+
const billingDashboardPage = new BillingDashboardPage(page);
|
|
178
|
+
const billingFormPage = new BillingFormPage(page);
|
|
179
|
+
const invoicePage = new InvoicePage(page);
|
|
180
|
+
const requestDiscountModal = new DiscountRequestModal(page);
|
|
181
|
+
const discountRequestsPage = new DiscountRequestsAdminPage(page);
|
|
182
|
+
const reviewModal = new ReviewBillDiscountsModal(page);
|
|
183
|
+
|
|
184
|
+
const patientUuid = patient.uuid;
|
|
185
|
+
const patientName = patient.person.display;
|
|
186
|
+
const justification = `e2e rejected discount ${Date.now()}`;
|
|
187
|
+
let billUuid: string;
|
|
188
|
+
let lineItemUuid: string;
|
|
189
|
+
|
|
190
|
+
await test.step('Given a finalized bill with a pending line-item discount request', async () => {
|
|
191
|
+
await page.goto(`patient/${patientUuid}/chart/billing-history`);
|
|
192
|
+
await page.getByRole('button', { name: /launch bill form|add bill|create bill/i }).click();
|
|
193
|
+
|
|
194
|
+
await billingFormPage.searchAndSelectBillableService(testServiceName);
|
|
195
|
+
await billingFormPage.selectPaymentMethodIfVisible();
|
|
196
|
+
await billingFormPage.saveBill();
|
|
197
|
+
await waitForSuccessNotification(page, /bill processed successfully/i);
|
|
198
|
+
|
|
199
|
+
const billsResponse = await api.get(`billing/bill?patient=${patientUuid}&v=full`);
|
|
200
|
+
const billsData = await billsResponse.json();
|
|
201
|
+
billUuid = billsData.results[0].uuid;
|
|
202
|
+
lineItemUuid = billsData.results[0].lineItems[0].uuid;
|
|
203
|
+
billsToCleanup.add(billUuid);
|
|
204
|
+
|
|
205
|
+
await billingDashboardPage.goto();
|
|
206
|
+
await billingDashboardPage.waitForBillsTableToLoad();
|
|
207
|
+
await billingDashboardPage.selectFilter('Pending confirmation');
|
|
208
|
+
await billingDashboardPage.waitForBillsTableToLoad();
|
|
209
|
+
await billingDashboardPage.clickInvoiceNumberLink(patientName);
|
|
210
|
+
await invoicePage.waitForInvoiceToLoad();
|
|
211
|
+
await invoicePage.finalizeBill();
|
|
212
|
+
await waitForSuccessNotification(page, /bill finalized/i);
|
|
213
|
+
await expect.poll(async () => await invoicePage.getInvoiceStatus()).toBe('POSTED');
|
|
214
|
+
|
|
215
|
+
await page.getByTestId(`action-menu-${lineItemUuid}`).click();
|
|
216
|
+
await page.getByTestId(`request-discount-button-${lineItemUuid}`).click();
|
|
217
|
+
await requestDiscountModal.submitPercentageDiscount(15, justification);
|
|
218
|
+
await waitForSuccessNotification(page, /discount request submitted/i);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
await test.step('When the admin rejects the discount request', async () => {
|
|
222
|
+
await discountRequestsPage.goto();
|
|
223
|
+
await discountRequestsPage.waitForLoaded();
|
|
224
|
+
await discountRequestsPage.openReviewForPatient(patientName);
|
|
225
|
+
|
|
226
|
+
await reviewModal.waitForLoaded();
|
|
227
|
+
await reviewModal.rejectFirstPending();
|
|
228
|
+
await waitForSuccessNotification(page, /discount rejected/i);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
await test.step('Then the discount is REJECTED and the bill total is unchanged', async () => {
|
|
232
|
+
await expect
|
|
233
|
+
.poll(async () => {
|
|
234
|
+
const res = await api.get(`billing/billDiscount?bill=${billUuid}&v=default`);
|
|
235
|
+
const data = await res.json();
|
|
236
|
+
return data.results[0]?.status;
|
|
237
|
+
})
|
|
238
|
+
.toBe('REJECTED');
|
|
239
|
+
|
|
240
|
+
const billResponse = await api.get(`billing/bill/${billUuid}`);
|
|
241
|
+
const billData = await billResponse.json();
|
|
242
|
+
// Net amount equals the original total when no discount is approved.
|
|
243
|
+
expect(billData.amountAfterDiscount ?? billData.total).toBeCloseTo(expectedServicePrice, 2);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
await test.step('And the rejected request is no longer in the pending queue', async () => {
|
|
247
|
+
await discountRequestsPage.goto();
|
|
248
|
+
await discountRequestsPage.waitForLoaded();
|
|
249
|
+
await discountRequestsPage.searchInput().fill(patientName);
|
|
250
|
+
await expect(
|
|
251
|
+
discountRequestsPage.requestsTable().locator('tbody tr').filter({ hasText: patientName }),
|
|
252
|
+
).toHaveCount(0);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
});
|
|
@@ -175,11 +175,11 @@ test.describe('Billing Dashboard workflow', () => {
|
|
|
175
175
|
expect(lineItem.status).toBe('PAID');
|
|
176
176
|
});
|
|
177
177
|
|
|
178
|
-
// Also verify backend line items have
|
|
178
|
+
// Also verify backend line items have status set to PAID
|
|
179
179
|
const billResponse = await api.get(`billing/bill/${billUuid}`);
|
|
180
180
|
const billData = await billResponse.json();
|
|
181
|
-
billData.lineItems.forEach((lineItem: {
|
|
182
|
-
expect(lineItem.
|
|
181
|
+
billData.lineItems.forEach((lineItem: { status: string }) => {
|
|
182
|
+
expect(lineItem.status).toBe('PAID');
|
|
183
183
|
});
|
|
184
184
|
});
|
|
185
185
|
|
|
@@ -535,8 +535,8 @@ test.describe('Billing: Patient Chart workflow', () => {
|
|
|
535
535
|
// Verify backend state
|
|
536
536
|
const billResponse = await api.get(`billing/bill/${billUuid}?v=full`);
|
|
537
537
|
const billData = await billResponse.json();
|
|
538
|
-
billData.lineItems.forEach((lineItem: {
|
|
539
|
-
expect(lineItem.
|
|
538
|
+
billData.lineItems.forEach((lineItem: { status: string }) => {
|
|
539
|
+
expect(lineItem.status).toBe('PAID');
|
|
540
540
|
});
|
|
541
541
|
});
|
|
542
542
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmrs/esm-billing-app",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "O3 frontend module for handling billing concerns in healthcare settings",
|
|
5
5
|
"browser": "dist/openmrs-esm-billing-app.js",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -9,18 +9,18 @@
|
|
|
9
9
|
"homepage": "https://github.com/openmrs/openmrs-esm-billing-app#readme",
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "openmrs develop",
|
|
12
|
-
"analyze": "
|
|
13
|
-
"build": "
|
|
14
|
-
"coverage": "yarn test --coverage",
|
|
12
|
+
"analyze": "rspack --mode=production --env.analyze=true",
|
|
13
|
+
"build": "rspack --mode production",
|
|
15
14
|
"debug": "npm run serve",
|
|
16
15
|
"extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.workspace.tsx' 'src/**/*.modal.tsx' 'src/index.ts' --config ./tools/i18next-parser.config.js",
|
|
17
16
|
"lint": "eslint src --ext ts,tsx --max-warnings=0",
|
|
18
17
|
"postinstall": "husky install",
|
|
19
18
|
"prettier": "prettier --config prettier.config.js --write \"src/**/*.{ts,tsx,css,scss}\" \"e2e/**/*.ts\"",
|
|
20
|
-
"serve": "
|
|
19
|
+
"serve": "rspack serve --mode=development",
|
|
21
20
|
"test-e2e": "playwright test",
|
|
22
|
-
"test": "cross-env TZ=UTC
|
|
23
|
-
"test:watch": "cross-env TZ=UTC
|
|
21
|
+
"test": "cross-env TZ=UTC vitest run --passWithNoTests",
|
|
22
|
+
"test:watch": "cross-env TZ=UTC vitest watch",
|
|
23
|
+
"coverage": "cross-env TZ=UTC vitest run --coverage --passWithNoTests",
|
|
24
24
|
"typescript": "tsc",
|
|
25
25
|
"verify": "turbo lint && turbo typescript && turbo test --color --concurrency=5"
|
|
26
26
|
},
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"zod": "^3.22.4"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
|
-
"@openmrs/esm-framework": "
|
|
54
|
+
"@openmrs/esm-framework": "10.x",
|
|
55
55
|
"react": "18.x",
|
|
56
56
|
"react-dom": "18.x",
|
|
57
57
|
"react-i18next": "16.x",
|
|
@@ -61,21 +61,17 @@
|
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@openmrs/esm-framework": "next",
|
|
63
63
|
"@playwright/test": "^1.53.2",
|
|
64
|
-
"@swc/cli": "^0.3.2",
|
|
65
|
-
"@swc/core": "^1.3.106",
|
|
66
|
-
"@swc/jest": "^0.2.31",
|
|
67
64
|
"@testing-library/dom": "^9.3.4",
|
|
68
65
|
"@testing-library/jest-dom": "^6.3.0",
|
|
69
66
|
"@testing-library/react": "14.3.1",
|
|
70
67
|
"@testing-library/user-event": "^14.5.2",
|
|
71
|
-
"@types/jest": "^29.5.11",
|
|
72
68
|
"@types/lodash-es": "^4.17.12",
|
|
73
69
|
"@types/react": "^18.3.21",
|
|
74
70
|
"@types/react-dom": "^18.3.0",
|
|
75
71
|
"@types/webpack-env": "^1.18.4",
|
|
76
72
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
77
73
|
"@typescript-eslint/parser": "^8.0.0",
|
|
78
|
-
"
|
|
74
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
79
75
|
"concurrently": "^8.2.2",
|
|
80
76
|
"cross-env": "^7.0.3",
|
|
81
77
|
"d3-selection": "^3.0.0",
|
|
@@ -91,29 +87,24 @@
|
|
|
91
87
|
"i18next": "^25.0.0",
|
|
92
88
|
"i18next-parser": "^9.3.0",
|
|
93
89
|
"identity-obj-proxy": "^3.0.0",
|
|
94
|
-
"
|
|
95
|
-
"jest-cli": "^29.7.0",
|
|
96
|
-
"jest-environment-jsdom": "^29.7.0",
|
|
90
|
+
"jsdom": "^29.0.2",
|
|
97
91
|
"lint-staged": "^15.2.10",
|
|
98
92
|
"lodash": "^4.17.21",
|
|
99
93
|
"openmrs": "next",
|
|
100
|
-
"pinst": "^3.0.0",
|
|
101
94
|
"prettier": "^3.2.4",
|
|
102
95
|
"react": "^18.3.1",
|
|
103
96
|
"react-dom": "^18.3.1",
|
|
104
97
|
"react-i18next": "^16.0.0",
|
|
105
98
|
"react-router-dom": "^6.21.3",
|
|
106
99
|
"rxjs": "^6.6.7",
|
|
107
|
-
"
|
|
108
|
-
"swr": "^2.2.4",
|
|
100
|
+
"swr": "2.2.5",
|
|
109
101
|
"turbo": "^2.5.2",
|
|
110
102
|
"typescript": "^5.0.0",
|
|
111
|
-
"
|
|
103
|
+
"vitest": "^4.0.18"
|
|
112
104
|
},
|
|
113
105
|
"lint-staged": {
|
|
114
106
|
"*.{ts,tsx}": "eslint --cache --fix --max-warnings 0",
|
|
115
107
|
"*.{css,scss,ts,tsx}": "prettier --write --list-different"
|
|
116
108
|
},
|
|
117
|
-
"packageManager": "yarn@4.10.3"
|
|
118
|
-
"stableVersion": "1.1.1"
|
|
109
|
+
"packageManager": "yarn@4.10.3"
|
|
119
110
|
}
|
package/rspack.config.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('openmrs/default-rspack-config');
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Layer, OverflowMenu, OverflowMenuItem } from '@carbon/react';
|
|
3
|
-
import { isDesktop, launchWorkspace2, useLayoutType } from '@openmrs/esm-framework';
|
|
3
|
+
import { isDesktop, launchWorkspace2, showModal, useLayoutType } from '@openmrs/esm-framework';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
|
-
import type
|
|
5
|
+
import { BillStatus, type MappedBill } from '../types';
|
|
6
6
|
import styles from './bill-action-menu.scss';
|
|
7
7
|
|
|
8
8
|
type BillActionMenuProps = {
|
|
@@ -14,6 +14,15 @@ type BillActionMenuProps = {
|
|
|
14
14
|
const BillActionMenu: React.FC<BillActionMenuProps> = ({ bill, patientUuid, onMutate }) => {
|
|
15
15
|
const { t } = useTranslation();
|
|
16
16
|
const layout = useLayoutType();
|
|
17
|
+
const isPending = bill?.status === BillStatus.PENDING;
|
|
18
|
+
|
|
19
|
+
const handleDeleteBill = () => {
|
|
20
|
+
const dispose = showModal('delete-bill-confirmation-modal', {
|
|
21
|
+
bill,
|
|
22
|
+
onSuccess: onMutate,
|
|
23
|
+
closeModal: () => dispose(),
|
|
24
|
+
});
|
|
25
|
+
};
|
|
17
26
|
|
|
18
27
|
return (
|
|
19
28
|
<Layer>
|
|
@@ -33,6 +42,15 @@ const BillActionMenu: React.FC<BillActionMenuProps> = ({ bill, patientUuid, onMu
|
|
|
33
42
|
})
|
|
34
43
|
}
|
|
35
44
|
/>
|
|
45
|
+
{isPending && (
|
|
46
|
+
<OverflowMenuItem
|
|
47
|
+
className={styles.menuItem}
|
|
48
|
+
hasDivider
|
|
49
|
+
isDelete
|
|
50
|
+
itemText={t('deleteBill', 'Delete bill')}
|
|
51
|
+
onClick={handleDeleteBill}
|
|
52
|
+
/>
|
|
53
|
+
)}
|
|
36
54
|
</OverflowMenu>
|
|
37
55
|
</Layer>
|
|
38
56
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
3
|
import userEvent from '@testing-library/user-event';
|
|
3
4
|
import { render, screen } from '@testing-library/react';
|
|
4
5
|
import { getDefaultsFromConfigSchema, launchWorkspace2, useConfig } from '@openmrs/esm-framework';
|
|
@@ -6,11 +7,11 @@ import { configSchema, type BillingConfig } from '../config-schema';
|
|
|
6
7
|
import { useBills } from '../billing.resource';
|
|
7
8
|
import BillHistory from './bill-history.component';
|
|
8
9
|
|
|
9
|
-
const mockUseConfig =
|
|
10
|
-
const mockUseBills =
|
|
10
|
+
const mockUseConfig = vi.mocked(useConfig<BillingConfig>);
|
|
11
|
+
const mockUseBills = vi.mocked<typeof useBills>(useBills);
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
useBills:
|
|
13
|
+
vi.mock('../billing.resource', () => ({
|
|
14
|
+
useBills: vi.fn(() => ({
|
|
14
15
|
bills: mockBillData,
|
|
15
16
|
isLoading: false,
|
|
16
17
|
isValidating: false,
|
|
@@ -46,13 +47,13 @@ describe('BillHistory', () => {
|
|
|
46
47
|
mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(configSchema), defaultCurrency: 'USD' });
|
|
47
48
|
});
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
it('should render loading datatable skeleton', () => {
|
|
50
51
|
mockUseBills.mockReturnValueOnce({
|
|
51
52
|
isLoading: true,
|
|
52
53
|
isValidating: false,
|
|
53
54
|
error: null,
|
|
54
55
|
bills: [],
|
|
55
|
-
mutate:
|
|
56
|
+
mutate: vi.fn(),
|
|
56
57
|
});
|
|
57
58
|
render(<BillHistory {...testProps} />);
|
|
58
59
|
const loadingSkeleton = screen.getByRole('table');
|
|
@@ -60,27 +61,27 @@ describe('BillHistory', () => {
|
|
|
60
61
|
expect(loadingSkeleton).toHaveClass('cds--skeleton cds--data-table cds--data-table--zebra');
|
|
61
62
|
});
|
|
62
63
|
|
|
63
|
-
|
|
64
|
+
it('should render error state when API call fails', () => {
|
|
64
65
|
mockUseBills.mockReturnValueOnce({
|
|
65
66
|
isLoading: false,
|
|
66
67
|
isValidating: false,
|
|
67
68
|
error: new Error('some error'),
|
|
68
69
|
bills: [],
|
|
69
|
-
mutate:
|
|
70
|
+
mutate: vi.fn(),
|
|
70
71
|
});
|
|
71
72
|
render(<BillHistory {...testProps} />);
|
|
72
73
|
const errorState = screen.getByText(/Error/);
|
|
73
74
|
expect(errorState).toBeInTheDocument();
|
|
74
75
|
});
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
it('should render bills table', async () => {
|
|
77
78
|
const user = userEvent.setup();
|
|
78
79
|
mockUseBills.mockReturnValueOnce({
|
|
79
80
|
isLoading: false,
|
|
80
81
|
isValidating: false,
|
|
81
82
|
error: null,
|
|
82
83
|
bills: mockBillData as any,
|
|
83
|
-
mutate:
|
|
84
|
+
mutate: vi.fn(),
|
|
84
85
|
});
|
|
85
86
|
render(<BillHistory {...testProps} />);
|
|
86
87
|
|
|
@@ -108,34 +109,34 @@ describe('BillHistory', () => {
|
|
|
108
109
|
await user.click(expandAllRowButton);
|
|
109
110
|
});
|
|
110
111
|
|
|
111
|
-
|
|
112
|
+
it('should render empty state view when there are no bills', () => {
|
|
112
113
|
mockUseBills.mockReturnValueOnce({
|
|
113
114
|
isLoading: false,
|
|
114
115
|
isValidating: false,
|
|
115
116
|
error: null,
|
|
116
117
|
bills: [],
|
|
117
|
-
mutate:
|
|
118
|
+
mutate: vi.fn(),
|
|
118
119
|
});
|
|
119
120
|
render(<BillHistory {...testProps} />);
|
|
120
121
|
const emptyState = screen.getByText(/There are no bills to display./);
|
|
121
122
|
expect(emptyState).toBeInTheDocument();
|
|
122
123
|
});
|
|
123
124
|
|
|
124
|
-
|
|
125
|
+
it('should show overflow menu with "Add items to bill" for PENDING bills', async () => {
|
|
125
126
|
const user = userEvent.setup();
|
|
126
127
|
const pendingBill = {
|
|
127
128
|
...mockBillData[0],
|
|
128
129
|
status: 'PENDING',
|
|
129
130
|
dateCreated: '2024-01-01',
|
|
130
131
|
receiptNumber: 'REC-001',
|
|
131
|
-
lineItems: [{ uuid: 'item-1', item: 'Test', quantity: 1, price: 100,
|
|
132
|
+
lineItems: [{ uuid: 'item-1', item: 'Test', quantity: 1, price: 100, status: 'PENDING' }],
|
|
132
133
|
};
|
|
133
134
|
mockUseBills.mockReturnValueOnce({
|
|
134
135
|
isLoading: false,
|
|
135
136
|
isValidating: false,
|
|
136
137
|
error: null,
|
|
137
138
|
bills: [pendingBill] as any,
|
|
138
|
-
mutate:
|
|
139
|
+
mutate: vi.fn(),
|
|
139
140
|
});
|
|
140
141
|
render(<BillHistory {...testProps} />);
|
|
141
142
|
|
|
@@ -145,36 +146,36 @@ describe('BillHistory', () => {
|
|
|
145
146
|
expect(screen.getByText(/add items to bill/i)).toBeInTheDocument();
|
|
146
147
|
});
|
|
147
148
|
|
|
148
|
-
|
|
149
|
+
it('should not show overflow menu for PAID bills', () => {
|
|
149
150
|
const paidBill = {
|
|
150
151
|
...mockBillData[0],
|
|
151
152
|
status: 'PAID',
|
|
152
153
|
dateCreated: '2024-01-01',
|
|
153
154
|
receiptNumber: 'REC-001',
|
|
154
|
-
lineItems: [{ uuid: 'item-1', item: 'Test', quantity: 1, price: 100,
|
|
155
|
+
lineItems: [{ uuid: 'item-1', item: 'Test', quantity: 1, price: 100, status: 'PAID' }],
|
|
155
156
|
};
|
|
156
157
|
mockUseBills.mockReturnValueOnce({
|
|
157
158
|
isLoading: false,
|
|
158
159
|
isValidating: false,
|
|
159
160
|
error: null,
|
|
160
161
|
bills: [paidBill] as any,
|
|
161
|
-
mutate:
|
|
162
|
+
mutate: vi.fn(),
|
|
162
163
|
});
|
|
163
164
|
render(<BillHistory {...testProps} />);
|
|
164
165
|
|
|
165
166
|
expect(screen.queryByTestId('action-menu-1')).not.toBeInTheDocument();
|
|
166
167
|
});
|
|
167
168
|
|
|
168
|
-
|
|
169
|
+
it('should launch workspace with billUuid when "Add items to bill" is clicked', async () => {
|
|
169
170
|
const user = userEvent.setup();
|
|
170
|
-
const mockMutate =
|
|
171
|
-
const mockLaunchWorkspace2 =
|
|
171
|
+
const mockMutate = vi.fn();
|
|
172
|
+
const mockLaunchWorkspace2 = vi.mocked(launchWorkspace2);
|
|
172
173
|
const pendingBill = {
|
|
173
174
|
...mockBillData[0],
|
|
174
175
|
status: 'PENDING',
|
|
175
176
|
dateCreated: '2024-01-01',
|
|
176
177
|
receiptNumber: 'REC-001',
|
|
177
|
-
lineItems: [{ uuid: 'item-1', item: 'Test', quantity: 1, price: 100,
|
|
178
|
+
lineItems: [{ uuid: 'item-1', item: 'Test', quantity: 1, price: 100, status: 'PENDING' }],
|
|
178
179
|
};
|
|
179
180
|
mockUseBills.mockReturnValueOnce({
|
|
180
181
|
isLoading: false,
|
|
@@ -161,7 +161,7 @@ const EditBillLineItemModal: React.FC<EditBillLineItemModalProps> = ({ bill, clo
|
|
|
161
161
|
{t('currentPrice', 'Current price')}: {convertToCurrency(item?.price, defaultCurrency)}
|
|
162
162
|
</p>
|
|
163
163
|
<p className={styles.label}>
|
|
164
|
-
{t('serviceStatus', 'Service status')}: {item?.
|
|
164
|
+
{t('serviceStatus', 'Service status')}: {item?.status}
|
|
165
165
|
</p>
|
|
166
166
|
<Controller
|
|
167
167
|
name="quantity"
|