Skip to content

Fully-excluding all budget lines on an invoice makes it disappear from the report wizard's step-3 list #1892

Description

@steilerDev

[e2e-test-engineer]

BUG: Fully-excluding all budget lines on an invoice makes it disappear from the report wizard's step-3 list

Severity: Major
Component: Frontend UI (Bank Report Wizard, ReportInvoiceList.tsx)
Found in: e2e/tests/budget/reportWizardExpansion.spec.ts — Scenario 4 ("Excluding every line on an invoice clamps its amount to zero, renders TriState unchecked, and keeps it counted as included"), authored against Story #1891.

Steps to Reproduce

  1. Go to /budget/reports, pick any use case (e.g. "Claim"), pick a source.
  2. On Step 3, find an invoice that has 2+ budget lines whose allocatedPortions sum to exactly the invoice's allocatedAmount (the normal case — no deposits, no split-source ibls).
  3. Expand the invoice row (chevron button).
  4. Uncheck every line's "Include" checkbox in the items sub-table (excluding every line).

Expected Behavior

Per the story's Frontend Spec ("Design decision" section) and item 5 ("Parent TriStateCheckbox... All-items-excluded renders unchecked but invoice stays counted as included for claiming/PDF — DO NOT two-way sync"), the row should remain visible, showing €0.00 (clamped, never negative), with its TriState checkbox rendering unchecked (not indeterminate). The row's own expand/collapse control should stay available so the user can re-include a line.

Actual Behavior

The invoice row disappears from the Step 3 list entirely. Root cause:

  • applyLineExclusions() (client/src/lib/reportExclusions.ts) correctly clamps the invoice's allocatedAmount to exactly 0 when all lines are excluded (newAmount = allocatedAmount - excludedPortion, lineKind: newAmount < 0 ? 'refund-adjustment' : 'invoice' — for newAmount === 0, lineKind stays 'invoice').
  • ReportInvoiceList.tsx's allocatedInvoices filter is:
    report.invoices.filter((inv) => inv.allocatedAmount > 0 || inv.lineKind === 'refund-adjustment')
    A net-zero, non-refund invoice satisfies neither condition (0 > 0 is false, lineKind !== 'refund-adjustment'), so it is filtered out of allocatedInvoices — which drives both the visible row list and the selectedCount/runningTotal displayed in the SelectionActionBar.

Because the row (and its expand toggle, which lives inside the row) disappears, there is no UI path left to re-include any of the excluded lines short of reloading the page — which resets every exclusion in the session, not just this invoice's.

Note: this is a display-only regression. The actual PDF export (generateReportPdf) and the claim submission (handleMarkClaimed) both operate on excludedInvoiceIds against the original, unfiltered report state — neither is affected by this filter, so the invoice is still correctly included in the generated PDF and still gets claimed in full.

Environment

  • Browser: Chromium (desktop project)
  • Viewport: desktop (not viewport-specific — pure state/filter logic)
  • Docker: N/A (client-only bug, reproducible in dev server too)

Evidence

E2E test e2e/tests/budget/reportWizardExpansion.spec.ts Scenario 4 asserts the spec-conformant behavior (row visible at €0.00, TriState unchecked) and is expected to fail in CI until this is fixed, per the project's test-failure-debugging protocol (correct tests are not weakened to match buggy code).

Relevant code:

  • client/src/lib/reportExclusions.ts lines 26-35 (the clamp-to-zero logic, correct)
  • client/src/components/reports/ReportInvoiceList.tsxallocatedInvoices useMemo (inv.allocatedAmount > 0 || inv.lineKind === 'refund-adjustment')

Notes

Suggested fix direction: extend the allocatedInvoices filter to also retain invoices where the original (pre-line-exclusion) allocatedAmount !== 0 — i.e. filter on the un-adjusted report object rather than the already-adjusted effectiveReport prop, or add an explicit "this invoice has budget lines" escape hatch to the filter so a client-side-only zero-out never removes a row that the server actually returned as allocated.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions