fix(invoice): make status breakdown summary deposit-aware (#1411)#1412
Conversation
InvoiceStatusBreakdown.summary (consumed by the InvoicesPage header and the Dashboard InvoicePipelineCard) was missed when #1405 migrated budget rollups to the deposit-aware split. A quotation invoice of €1000 with a pending deposit of €200 showed €1000 under Quotation and €0 under Pending — should be €800 (residual) and €200 (deposit). Adds aggregateInvoiceStatusBreakdown() to depositAggregateUtils.ts and rewrites listAllInvoices() summary to use it with a LEFT JOIN onto invoice_deposits. Per-invoice split: summary[parent].totalAmount accrues max(0, amount - Σ deposits), each summary[deposit.status].totalAmount accrues deposit.amount; count stays per-invoice (not per row). Summary remains GLOBAL (filter-independent) — the existing UX where the header cards stay stable while the user filters the list is preserved. The pre-existing "summary reflects global counts" test is unmodified. Wiki updated: API-Contract.md documents the deposit-aware semantic and adds quotation to the example summary block. Fixes #1411 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-Authored-By: Claude backend-developer (Haiku 4.5) <noreply@anthropic.com> Co-Authored-By: Claude qa-integration-tester (Sonnet 4.6) <noreply@anthropic.com>
|
[product-architect] Review: APPROVED with medium findings for refinement. Verdict
Verified
Medium findings (refinement-eligible, not blocking)
Not findings
Architecturally sound. Approving — please open the refactor tracking issue before merge so the duplication doesn't get forgotten. |
|
🎉 This PR is included in version 2.6.0-beta.4 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
…face revert errors (#1413) (#1414) * chore(invoice,budget): dedupe split helper, extract OverflowMenu, surface revert errors Closes the architect's medium-severity recommendations from the #1407 and #1412 reviews. Four scopes: 1. Extract splitByDeposits() helper in depositAggregateUtils.ts and reuse across the 4 call sites that inlined the proportional-split + dedup pattern (computeDepositAwareAggregates, computeStatusContribution, aggregateInvoiceStatusBreakdown, and computeDiscretionaryInvoiceAmount in budgetSourceService.ts). Behaviour-preserving — existing tests pass unmodified. 2. Extract a shared OverflowMenu component (client/src/components/ OverflowMenu/). DepositRow and DepositCard both consume it instead of duplicating ~330 lines of menu code. Full WAI-ARIA Menu Button keyboard nav, mobile 44px touch targets, design-token-only CSS, dark mode handled by the token cascade. Same aria-haspopup/role attributes as the inline implementation — existing E2E locators still work. 3. Replace inline style={{}} on the <tr> opacity transition with a CSS module class (.tableRowMutating), matching the prior fd73bca fix. 4. Surface API errors in handleRevertToPending, handleRevertToPaid, and handleStateConfirm. Menu-driven reverts show a section-level FormError banner (auto-dismiss 6s). State-confirm modal shows FormError inside the dialog. Two new i18n keys for network-error fallbacks; existing translateApiError() covers coded server errors. Fixes #1413 Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com> Co-Authored-By: Claude backend-developer (Haiku 4.5) <noreply@anthropic.com> Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com> Co-Authored-By: Claude translator (Sonnet 4.6) <noreply@anthropic.com> Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com> Co-Authored-By: Claude e2e-test-engineer (Sonnet 4.5) <noreply@anthropic.com> * fix(overflow-menu): canonical focus tokens and skip-disabled keyboard nav UX-designer review on PR #1414 found two non-blocking nits in the new OverflowMenu shared component: - Default item focus ring switched from inset 2px var(--color-primary) to inset 3px var(--color-focus-ring) — the canonical menu-item ring used elsewhere in the codebase. - Added missing .itemDanger:focus-visible rule with var(--color-focus-ring-danger) so destructive items have a distinguishable keyboard focus indicator. - Arrow-key / Home / End keyboard handlers and the initial-focus query now use [role="menuitem"]:not(:disabled), so the cursor skips disabled items. Refs #1413 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com> --------- Co-authored-by: Frank Steiler <frank@steiler.de> Co-authored-by: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
Summary
InvoiceStatusBreakdown.summary(used by the InvoicesPage header cards and Dashboard InvoicePipelineCard) was not deposit-aware: a €1000 quotation invoice with a €200 pending deposit incorrectly showed €1000 under Quotation and €0 under PendingaggregateInvoiceStatusBreakdown()todepositAggregateUtils.tsand rewriteslistAllInvoices()summary to split amounts across parent + deposit statuses via LEFT JOIN oninvoice_deposits; counts remain per-invoice (deposit rows never increment a count)Fixes #1411
Test plan
aggregateInvoiceStatusBreakdown()cover: no deposits, single deposit, multiple deposits with mixed statuses, deposit sum exceeding invoice amount (clamped to 0 residual)invoiceService.test.tsverifies deposit-aware summary inlistAllInvoices()for global unfiltered totals