Skip to content

Bug: budget-source budget-lines drill-down is deposit-blind, diverging from the work-item page #1897

Description

@steilerDev

[product-owner]

Type: Bug
Severity: MEDIUM
Priority: Should Have
Related: #1891 (found during product-architect audit of the dual-rail deposit aggregation on feat/1891-wizard-followup / PR #1894)

Summary

GET /api/budget-sources/:id/budget-lines is deposit-blind. getWorkItemLineInvoiceData and getHouseholdItemLineInvoiceData (server/src/services/budgetSourceService.ts:812-848 and :850-888) compute per-line paid amounts as:

COALESCE(SUM(CASE WHEN i.status IN ('paid', 'claimed') THEN ibl.itemized_amount ELSE 0 END), 0) AS actualCostPaid

This attributes the whole itemized amount based on the parent invoice's status and ignores deposits entirely. The same budget line rendered on the work-item page goes through the deposit-aware budgetServiceFactory.getInvoiceAggregates, so the two views disagree.

Reproduction

  1. Invoice €1000, status pending.
  2. One budget line €1000, attributed to source A.
  3. One deposit €400 on that invoice, status paid.
  4. Open the budget-source drill-down for A, then the work-item page for the same line.

Observed: drill-down shows actualCostPaid = 0; work-item page shows 400.

Expected: both views show 400.

This is a display inconsistency, not a conservation break — budget totals are unaffected. Pre-existing since deposits were introduced; not a #1891 regression.

Acceptance Criteria

  • GET /api/budget-sources/:id/budget-lines computes per-line paid amounts through the same deposit-aware path used by the work-item page (budgetServiceFactory.getInvoiceAggregates) rather than the parent-invoice-status SQL above.
  • Both the work-item and household-item drill-down variants are fixed (getWorkItemLineInvoiceData and getHouseholdItemLineInvoiceData).
  • Reproduction above yields actualCostPaid = 400 in the budget-source drill-down, matching the work-item page.
  • A line whose parent invoice is paid or claimed with no deposits continues to report the full itemized amount as paid (no regression to the existing common case).
  • Tests assert drill-down/work-item-page parity for: pending invoice with partial paid deposit, pending invoice with no deposits, paid invoice with no deposits, claimed invoice.

Rider (minor, same file)

  • budgetSourceService.ts:387-390 coerces any non-'claimed' status argument to 'paid' via a ternary:

    sumTaggedDepositContributions(railBRows, new Set([status === 'claimed' ? 'claimed' : 'paid']));

    This is latent today (only 'paid' and 'claimed' are ever passed) but silently mislabels any future status. Fix is new Set([status]).

  • The rider fix does not change behaviour for the currently-passed values — verified by test.

Notes

  • Fix should reuse the existing deposit-aware aggregate helper rather than duplicating deposit math in budgetSourceService. If the shapes don't line up, flag it to the product-architect rather than forking the logic.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions