mirror of
https://github.com/we-promise/sure.git
synced 2026-04-07 14:31:25 +00:00
* Add budget rollover: copy from previous month When navigating to an uninitialized budget month, show a prompt offering to copy amounts from the most recent initialized budget. Copies budgeted_spending, expected_income, and all matching category allocations. Also fixes over-allocation warning showing on uninitialized budgets. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Redirect copy_previous to categories wizard for review Matches the normal budget setup flow (edit → categories → show) so users can review/tweak copied allocations before confirming. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Address code review: eager-load categories, guard against overwrite - Add .includes(:budget_categories) to most_recent_initialized_budget to avoid N+1 when copy_from! iterates source categories - Guard copy_previous action against overwriting already-initialized budgets (prevents crafted POST from clobbering existing data) - Add i18n key for already_initialized flash message Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add invariant guards to copy_from! for defensive safety Validate that source budget belongs to the same family and precedes the target budget before copying. Protects against misuse from other callers beyond the controller. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix button overflow on small screens in copy previous prompt Stack buttons vertically on mobile, side-by-side on sm+ breakpoint. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
65 lines
1.7 KiB
Ruby
65 lines
1.7 KiB
Ruby
class BudgetsController < ApplicationController
|
|
before_action :set_budget, only: %i[show edit update copy_previous]
|
|
|
|
def index
|
|
redirect_to_current_month_budget
|
|
end
|
|
|
|
def show
|
|
@source_budget = @budget.most_recent_initialized_budget unless @budget.initialized?
|
|
end
|
|
|
|
def edit
|
|
render layout: "wizard"
|
|
end
|
|
|
|
def update
|
|
@budget.update!(budget_params)
|
|
redirect_to budget_budget_categories_path(@budget)
|
|
end
|
|
|
|
def copy_previous
|
|
if @budget.initialized?
|
|
redirect_to budget_path(@budget), alert: t("budgets.copy_previous.already_initialized")
|
|
return
|
|
end
|
|
|
|
source_budget = @budget.most_recent_initialized_budget
|
|
|
|
if source_budget
|
|
@budget.copy_from!(source_budget)
|
|
redirect_to budget_budget_categories_path(@budget), notice: t("budgets.copy_previous.success", source_name: source_budget.name)
|
|
else
|
|
redirect_to budget_path(@budget), alert: t("budgets.copy_previous.no_source")
|
|
end
|
|
end
|
|
|
|
def picker
|
|
render partial: "budgets/picker", locals: {
|
|
family: Current.family,
|
|
year: params[:year].to_i || Date.current.year
|
|
}
|
|
end
|
|
|
|
private
|
|
|
|
def budget_create_params
|
|
params.require(:budget).permit(:start_date)
|
|
end
|
|
|
|
def budget_params
|
|
params.require(:budget).permit(:budgeted_spending, :expected_income)
|
|
end
|
|
|
|
def set_budget
|
|
start_date = Budget.param_to_date(params[:month_year], family: Current.family)
|
|
@budget = Budget.find_or_bootstrap(Current.family, start_date: start_date)
|
|
raise ActiveRecord::RecordNotFound unless @budget
|
|
end
|
|
|
|
def redirect_to_current_month_budget
|
|
current_budget = Budget.find_or_bootstrap(Current.family, start_date: Date.current)
|
|
redirect_to budget_path(current_budget)
|
|
end
|
|
end
|