Files
sure/app/controllers/retirement_controller.rb
Guillem Arias 174dd66914 feat(retirement): PR3b what-if KPIs + live forecast Turbo Stream
Surfaces the forecast on the page and makes the levers live.

- KPI cards (_kpis): Freedom date, Coast FIRE, Money-lasts-to + terminal
  value, with a "set your birth year" prompt until a plan is projectable.
  Wrapped in #retirement_kpis for Turbo Stream replacement; money carries
  privacy-sensitive.
- What-if form: birth_year / retire_age / target_spend / monthly_savings /
  real_return_pct. On input, retirement_what_if_controller debounces and
  POSTs the current values to PATCH /retirement/forecast, which recomputes
  against transient inputs and streams the KPI cards back WITHOUT
  persisting. "Save plan" submits to #update to persist retirement_params.
- RetirementController gains #update (persist) and #forecast (transient
  recompute → turbo_stream). Both reuse merged_plan_params, which drops
  blank fields so a partial what-if doesn't clobber stored values.

Tests: KPI section renders; update persists params; forecast streams
#retirement_kpis without writing the slider value back. Rubocop +
erb_lint + biome clean.

PR4 replaces this minimal form with the designed slider rail + glide
chart; the #forecast endpoint and the engine stay.
2026-05-29 11:45:31 +02:00

41 lines
1.4 KiB
Ruby

class RetirementController < ApplicationController
include RetirementScoped
def show
@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