diff --git a/app/models/goal.rb b/app/models/goal.rb index daea1e73f..72b470cd9 100644 --- a/app/models/goal.rb +++ b/app/models/goal.rb @@ -281,13 +281,17 @@ class Goal < ApplicationRecord end end - # Monthly extra needed beyond the current pace to hit the target on time. - # Clamps at zero — never asks the user to "make up" a deficit they're - # already ahead of. + # Monthly extra needed beyond the current pace + currently-open pledges + # to hit the target on time. Pending pledges are approximate (one-off + # amounts treated as this-month inflow) but excluding them produced the + # bad case where the alert demanded $X/mo while the user had already + # pledged $X — telling them to act on top of the action they just took. + # Clamps at zero so a fully-covered goal doesn't surface a $0 demand. def catch_up_delta_money return Money.new(0, currency) if monthly_target_amount.nil? - delta = [ monthly_target_amount.to_d - pace.to_d, 0 ].max + pending = open_pledges.to_a.sum { |p| p.amount.to_d } + delta = [ monthly_target_amount.to_d - pace.to_d - pending, 0 ].max Money.new(delta, currency) end diff --git a/app/views/goals/show.html.erb b/app/views/goals/show.html.erb index 0e3e80cc8..602f546d8 100644 --- a/app/views/goals/show.html.erb +++ b/app/views/goals/show.html.erb @@ -139,11 +139,13 @@ <% end %> <% end %> - <% elsif @goal.status == :behind && @goal.monthly_target_amount %> + <% elsif @goal.status == :behind && @goal.monthly_target_amount && @goal.catch_up_delta_money.amount.positive? %> <%# Title uses the *delta* — the amount the user must add to current pace - to make the date. The body carries the absolute numbers. Pre-fill the + to make the date, *after* subtracting open pledges. When pending + pledges already cover the gap, `catch_up_delta_money` returns zero + and this branch suppresses — no "Save $0/mo" demand. Pre-fill the pledge CTA with the delta too, so submitting once funds the gap - instead of double-counting on top of the existing pace. %> + instead of double-counting on top of pace and pending. %> <%= render DS::Alert.new(variant: "warning", title: t("goals.show.catch_up.title", amount: @goal.catch_up_delta_money.format(precision: 0))) do %>
<%= t("goals.show.catch_up.body", avg: @goal.pace_money.format(precision: 0), required: Money.new(@goal.monthly_target_amount, @goal.currency).format(precision: 0)) %>