From fb6c6fa6bb9b41dbb1a7e499baf59f567e028172 Mon Sep 17 00:00:00 2001 From: Paul Imoke Date: Tue, 11 Feb 2025 14:28:06 +0000 Subject: [PATCH] fix: subcategories are not properly handled for budget allocations (#1844) * fix: `allocated_spending` logic * fix: subcategories exceeding parent limit * refactor: budget allocations and max allocation logic * feat: add stream for budget category form validation * feat: update uncategorized value via stream, refactor confirm button with stream * fix: ensure live updates for parent & sibling budgets in Turbo Stream * fix: lint issues --- app/models/budget.rb | 4 +-- app/models/budget_category.rb | 25 +++++++++++++++++++ .../_budget_category_form.html.erb | 3 ++- .../_confirm_button.html.erb | 11 ++++++++ ...ncategorized_budget_category_form.html.erb | 2 +- app/views/budget_categories/index.html.erb | 10 +------- .../budget_categories/update.turbo_stream.erb | 18 +++++++++++++ 7 files changed, 60 insertions(+), 13 deletions(-) create mode 100644 app/views/budget_categories/_confirm_button.html.erb diff --git a/app/models/budget.rb b/app/models/budget.rb index 560a5f17..134258ca 100644 --- a/app/models/budget.rb +++ b/app/models/budget.rb @@ -133,10 +133,10 @@ class Budget < ApplicationRecord end # ============================================================================= - # Budget allocations: How much user has budgeted for all categories combined + # Budget allocations: How much user has budgeted for all parent categories combined # ============================================================================= def allocated_spending - budget_categories.sum(:budgeted_spending) + budget_categories.reject { |bc| bc.subcategory? }.sum(&:budgeted_spending) end def allocated_percent diff --git a/app/models/budget_category.rb b/app/models/budget_category.rb index a57a3a97..5109198d 100644 --- a/app/models/budget_category.rb +++ b/app/models/budget_category.rb @@ -79,4 +79,29 @@ class BudgetCategory < ApplicationRecord segments end + + def siblings + budget.budget_categories.select { |bc| bc.category.parent_id == category.parent_id && bc.id != id } + end + + def max_allocation + return nil unless subcategory? + + parent_budget = budget.budget_categories.find { |bc| bc.category.id == category.parent_id }&.budgeted_spending + siblings_budget = siblings.sum(&:budgeted_spending) + + [ parent_budget - siblings_budget, 0 ].max + end + + def subcategories + return BudgetCategory.none unless category.parent_id.nil? + + budget.budget_categories + .joins(:category) + .where(categories: { parent_id: category.id }) + end + + def subcategory? + category.parent_id.present? + end end diff --git a/app/views/budget_categories/_budget_category_form.html.erb b/app/views/budget_categories/_budget_category_form.html.erb index 770d6293..08fd70e0 100644 --- a/app/views/budget_categories/_budget_category_form.html.erb +++ b/app/views/budget_categories/_budget_category_form.html.erb @@ -2,7 +2,7 @@ <% currency = Money::Currency.new(budget_category.budget.currency) %> -
+
class="w-full flex gap-3">
@@ -22,6 +22,7 @@ step: currency.step, id: dom_id(budget_category, :budgeted_spending), min: 0, + max: budget_category.max_allocation, data: { auto_submit_form_target: "auto" } %>
diff --git a/app/views/budget_categories/_confirm_button.html.erb b/app/views/budget_categories/_confirm_button.html.erb new file mode 100644 index 00000000..53c52533 --- /dev/null +++ b/app/views/budget_categories/_confirm_button.html.erb @@ -0,0 +1,11 @@ +
+ <% if budget.allocations_valid? %> + <%= link_to "Confirm", + budget_path(budget), + class: "block btn btn--primary w-full text-center" %> + <% else %> + + Confirm + + <% end %> +
\ No newline at end of file diff --git a/app/views/budget_categories/_uncategorized_budget_category_form.html.erb b/app/views/budget_categories/_uncategorized_budget_category_form.html.erb index 4e586379..f26a9f80 100644 --- a/app/views/budget_categories/_uncategorized_budget_category_form.html.erb +++ b/app/views/budget_categories/_uncategorized_budget_category_form.html.erb @@ -2,7 +2,7 @@ <% budget_category = budget.uncategorized_budget_category %> -
+
diff --git a/app/views/budget_categories/index.html.erb b/app/views/budget_categories/index.html.erb index f40879de..1ae9ea97 100644 --- a/app/views/budget_categories/index.html.erb +++ b/app/views/budget_categories/index.html.erb @@ -45,15 +45,7 @@ <%= render "budget_categories/uncategorized_budget_category_form", budget: @budget %>
- <% if @budget.allocations_valid? %> - <%= link_to "Confirm", - budget_path(@budget), - class: "block btn btn--primary w-full text-center" %> - <% else %> - - Confirm - - <% end %> + <%= render "budget_categories/confirm_button", budget: @budget %>
<% end %>
diff --git a/app/views/budget_categories/update.turbo_stream.erb b/app/views/budget_categories/update.turbo_stream.erb index 31a70c51..34313156 100644 --- a/app/views/budget_categories/update.turbo_stream.erb +++ b/app/views/budget_categories/update.turbo_stream.erb @@ -1 +1,19 @@ <%= turbo_stream.replace dom_id(@budget, :allocation_progress), partial: "budget_categories/allocation_progress", locals: { budget: @budget } %> + +<%= turbo_stream.replace dom_id(@budget, :uncategorized_budget_category_form), partial: "budget_categories/uncategorized_budget_category_form", locals: { budget: @budget } %> + +<%= turbo_stream.replace dom_id(@budget, :confirm_button), partial: "budget_categories/confirm_button", locals: { budget: @budget } %> + + +<% if @budget_category.subcategory? %> + <%# Update sibling subcategories when a subcategory changes %> + <% @budget_category.siblings.each do |sibling| %> + <%= turbo_stream.update dom_id(sibling, :form), partial: "budget_categories/budget_category_form", locals: { budget_category: sibling } %> + <% end %> + +<% else %> + <%# Update all subcategories when a parent category changes %> + <% @budget_category.subcategories.each do |subcategory| %> + <%= turbo_stream.update dom_id(subcategory, :form), partial: "budget_categories/budget_category_form", locals: { budget_category: subcategory } %> + <% end %> +<% end %> \ No newline at end of file