mirror of
https://github.com/we-promise/sure.git
synced 2026-05-30 07:49:01 +00:00
Introduces retirement and FIRE (Financial Independence, Retire Early) planning as a new top-level feature in the sidebar navigation. Key features: - RetirementConfig model: stores retirement planning parameters per family (birth year, retirement age, target income, pension system, etc.) - PensionEntry model: tracks pension statements (Renteninformation) over time with pension points, current/projected monthly pension - German GRV pension calculations: - Estimated monthly pension from Entgeltpunkte x Rentenwert - After-tax pension estimation - Monthly pension gap analysis - FIRE calculations: - FIRE number (capital needed via 4% rule, inflation-adjusted) - FIRE progress percentage from current portfolio value - Estimated FIRE date (iterative monthly projection) - Required monthly savings to close pension gap - Dashboard view with overview cards, FIRE progress bar, assumptions panel, and pension history table with add/delete entries - Setup and edit views for configuring retirement parameters - Full i18n support (English + German) - Minitest coverage for models and controller Database: 2 new tables (retirement_configs, pension_entries) with UUID PKs Routes: singular resource with setup, add/destroy pension entry actions This is an initial implementation focused on the German GRV pension system. The architecture supports extending to other pension systems (custom/other). Open to suggestions and improvements from the community - contributions for additional pension systems, visualization charts, or calculation refinements are very welcome.
97 lines
2.8 KiB
Ruby
97 lines
2.8 KiB
Ruby
class RetirementController < ApplicationController
|
|
before_action :set_retirement_config, only: %i[show edit update]
|
|
before_action :require_retirement_config, only: %i[add_pension_entry destroy_pension_entry]
|
|
|
|
def show
|
|
@pension_entries = @retirement_config.pension_entries_with_gains
|
|
end
|
|
|
|
def setup
|
|
@retirement_config = Current.family.retirement_config || Current.family.build_retirement_config(
|
|
birth_year: 1990,
|
|
currency: Current.family.currency
|
|
)
|
|
end
|
|
|
|
def create
|
|
# Guard against duplicate creation (unique index on family_id)
|
|
if Current.family.retirement_config
|
|
redirect_to retirement_path
|
|
return
|
|
end
|
|
|
|
@retirement_config = Current.family.build_retirement_config(retirement_config_params)
|
|
|
|
if @retirement_config.save
|
|
redirect_to retirement_path, notice: t(".created")
|
|
else
|
|
render :setup, status: :unprocessable_entity
|
|
end
|
|
end
|
|
|
|
def edit
|
|
end
|
|
|
|
def update
|
|
if @retirement_config.update(retirement_config_params)
|
|
redirect_to retirement_path, notice: t(".updated")
|
|
else
|
|
render :edit, status: :unprocessable_entity
|
|
end
|
|
end
|
|
|
|
def add_pension_entry
|
|
@pension_entry = @retirement_config.pension_entries.build(pension_entry_params)
|
|
|
|
if @pension_entry.save
|
|
redirect_to retirement_path, notice: t(".pension_entry_added")
|
|
else
|
|
@pension_entries = @retirement_config.pension_entries_with_gains
|
|
render :show, status: :unprocessable_entity
|
|
end
|
|
end
|
|
|
|
def destroy_pension_entry
|
|
@pension_entry = @retirement_config.pension_entries.find(params[:id])
|
|
@pension_entry.destroy!
|
|
redirect_to retirement_path, notice: t(".pension_entry_removed")
|
|
end
|
|
|
|
private
|
|
|
|
def set_retirement_config
|
|
@retirement_config = Current.family.retirement_config
|
|
|
|
unless @retirement_config
|
|
redirect_to setup_retirement_path
|
|
end
|
|
end
|
|
|
|
def require_retirement_config
|
|
@retirement_config = Current.family.retirement_config
|
|
|
|
unless @retirement_config
|
|
redirect_to setup_retirement_path, alert: t("retirement.show.setup_required")
|
|
end
|
|
end
|
|
|
|
def retirement_config_params
|
|
params.require(:retirement_config).permit(
|
|
:country, :pension_system, :birth_year, :retirement_age,
|
|
:target_monthly_income, :currency, :expected_return_pct,
|
|
:inflation_pct, :tax_rate_pct, :current_monthly_savings,
|
|
:contribution_start_year, :expected_annual_points, :rentenwert
|
|
)
|
|
end
|
|
|
|
# Params are scoped under :pension_entry because the form uses
|
|
# form_with url: ..., scope: :pension_entry. Switching to form_with model:
|
|
# would change the nesting — update this permit list accordingly.
|
|
def pension_entry_params
|
|
params.require(:pension_entry).permit(
|
|
:recorded_at, :current_points, :current_monthly_pension,
|
|
:projected_monthly_pension, :notes
|
|
)
|
|
end
|
|
end
|