mirror of
https://github.com/we-promise/sure.git
synced 2026-05-30 15:59:02 +00:00
- IncomeStatement#trimmed_mean_expense(months:, trim_pct:) — trailing-N- month mean monthly expense with the top/bottom trim_pct% of months dropped, so one-off spikes don't skew the anchor. Family#retirement_ spending_baseline now uses it (was median). - Goal::Retirement#fi_number — 25× the annual target (4% rule). - "Why this target?" card on the show page: Last-12-months anchor → Target → FI number (25×), with a "Use my average" button that sets target_spend to the trimmed-mean baseline. Money is privacy-sensitive. - Header gains a green-dot "Active plan" DS::Pill badge when projectable. Tests: trimmed_mean returns non-negative; fi_number = 25× annual target; baseline returns Money. Rubocop + erb_lint clean.
43 lines
1.5 KiB
Ruby
43 lines
1.5 KiB
Ruby
class RetirementController < ApplicationController
|
|
include RetirementScoped
|
|
|
|
def show
|
|
@glide = @plan.glide_payload
|
|
@baseline = Current.family.retirement_spending_baseline(user: Current.user)
|
|
@pension_sources = @plan.pension_sources.order(:start_age)
|
|
@adjustments = @plan.adjustments.ordered
|
|
@statements = @plan.statements.chronological.reverse
|
|
@bucket_account_ids = @plan.retirement_bucket_entries.pluck(:account_id).to_set
|
|
@bucket_candidates = Current.family.accounts.visible.alphabetically
|
|
@breadcrumbs = [
|
|
[ t("breadcrumbs.home"), root_path ],
|
|
[ t("breadcrumbs.retirement"), nil ]
|
|
]
|
|
end
|
|
|
|
def update
|
|
if @plan.update(retirement_params: merged_plan_params)
|
|
redirect_to retirement_path, notice: t(".updated")
|
|
else
|
|
redirect_to retirement_path, alert: @plan.errors.full_messages.to_sentence
|
|
end
|
|
end
|
|
|
|
# Live what-if: recompute against transient inputs WITHOUT persisting, and
|
|
# stream the KPI cards back. The plan is only saved via #update.
|
|
def forecast
|
|
@plan.assign_attributes(retirement_params: merged_plan_params)
|
|
render turbo_stream: turbo_stream.replace(
|
|
"retirement_kpis", partial: "retirement/kpis", locals: { plan: @plan }
|
|
)
|
|
end
|
|
|
|
private
|
|
def merged_plan_params
|
|
raw = params.fetch(:retirement, {}).permit(
|
|
:birth_year, :retire_age, :target_spend, :monthly_savings, :real_return_pct
|
|
).to_h
|
|
(@plan.retirement_params || {}).merge(raw.reject { |_, v| v.to_s.strip.empty? })
|
|
end
|
|
end
|