fix(goals): exclude pending transactions from pace + mobile-stack funding rows

Two audit fixes that pair well.

PF audit B20: pace, family velocity, and the funding widget's
30/90-day totals all summed Entry amounts over the linked accounts
*including provider-pending transactions*. A pending Plaid/SimpleFIN
deposit inflated pace today; the next sync that reversed or dropped
it silently shrunk pace tomorrow, with no signal to the user.
Worse, the reconciler could match a pending transaction and flip
the pledge to "matched" before the underlying entry vanished.

`.merge(Transaction.excluding_pending)` on the three Entry queries
(Goal#pace, Family#savings_inflow_velocity, the funding widget's
`inflow_totals_map`) brings the existing
`Transaction::PENDING_PROVIDERS`-aware scope into play. Single-line
fix across the three call sites.

UX audit: funding-account rows used `grid-cols-[24px_1fr_48px_120px]`
at every breakpoint. On a 375pt iPhone viewport that left ~50px for
the name column after `p-5` padding + container chrome — name
truncated to "Ban…" and the per-row % column squeezed against the
weight/totals stack. The percent number is also already encoded in
the distribution bar above the rows; on mobile it can disappear
without losing signal.

Drop the % column at < sm:
- mobile grid: `grid-cols-[24px_minmax(0,1fr)_auto]` (avatar / name /
  totals)
- sm+: original 4-column layout with the per-row %
- per-row balance subline + accountable label now also drops `.00`
  cents (consistency with the rest of the page).
This commit is contained in:
Guillem Arias
2026-05-14 21:23:15 +02:00
parent a695fb528c
commit 26d9ad76bf
4 changed files with 12 additions and 5 deletions

View File

@@ -25,19 +25,23 @@
<% rows.each_with_index do |row, idx| %>
<% account = row[:account] %>
<% color = color_for(account) %>
<div class="px-4 py-3 grid grid-cols-[24px_minmax(0,1fr)_48px_120px] items-center gap-3">
<div class="px-4 py-3 grid grid-cols-[24px_minmax(0,1fr)_auto] sm:grid-cols-[24px_minmax(0,1fr)_48px_120px] items-center gap-3">
<%= render Goals::AvatarComponent.new(name: account.name, color: color, size: "sm") %>
<div class="min-w-0">
<p class="text-sm font-medium text-primary truncate"><%= account.name %></p>
<p class="text-xs text-subdued tabular-nums privacy-sensitive">
<%= accountable_label(account) %> · <%= row[:balance_money].format %>
<%= accountable_label(account) %> · <%= row[:balance_money].format(precision: 0) %>
</p>
</div>
<p class="text-sm text-secondary tabular-nums text-right privacy-sensitive">
<%= rows.size > 1 ? "#{percent_for(row[:balance])}%" : "" %>
</p>
<% if rows.size > 1 %>
<p class="hidden sm:block text-sm text-secondary tabular-nums text-right privacy-sensitive">
<%= percent_for(row[:balance]) %>%
</p>
<% else %>
<span class="hidden sm:block"></span>
<% end %>
<div class="text-right space-y-0.5">
<div class="flex items-baseline justify-end gap-1.5">

View File

@@ -70,6 +70,7 @@ class Goals::FundingAccountsBreakdownComponent < ApplicationComponent
.joins("INNER JOIN transactions ON transactions.id = entries.entryable_id AND entries.entryable_type = 'Transaction'")
.where(account_id: account_ids, date: TREND_WINDOW_DAYS.days.ago.to_date..Date.current)
.where(excluded: false)
.merge(Transaction.excluding_pending)
.pluck(:account_id, :date, :amount)
result = Hash.new { |h, k| h[k] = { last_30: 0.to_d, last_90: 0.to_d } }

View File

@@ -71,6 +71,7 @@ class Family < ApplicationRecord
.joins("INNER JOIN transactions ON transactions.id = entries.entryable_id AND entries.entryable_type = 'Transaction'")
.where(account_id: account_ids, date: range)
.where(excluded: false)
.merge(Transaction.excluding_pending)
.sum(:amount)
-net.to_d

View File

@@ -129,6 +129,7 @@ class Goal < ApplicationRecord
.joins("INNER JOIN transactions ON transactions.id = entries.entryable_id AND entries.entryable_type = 'Transaction'")
.where(account_id: account_ids, date: 90.days.ago.to_date..Date.current)
.where(excluded: false)
.merge(Transaction.excluding_pending)
.sum(:amount)
(-net.to_d / 3).round(2)
end