mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-19 21:29:38 +02:00
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
This commit is contained in:
parent
077694bbde
commit
fb6c6fa6bb
7 changed files with 60 additions and 13 deletions
|
@ -133,10 +133,10 @@ class Budget < ApplicationRecord
|
||||||
end
|
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
|
def allocated_spending
|
||||||
budget_categories.sum(:budgeted_spending)
|
budget_categories.reject { |bc| bc.subcategory? }.sum(&:budgeted_spending)
|
||||||
end
|
end
|
||||||
|
|
||||||
def allocated_percent
|
def allocated_percent
|
||||||
|
|
|
@ -79,4 +79,29 @@ class BudgetCategory < ApplicationRecord
|
||||||
|
|
||||||
segments
|
segments
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<% currency = Money::Currency.new(budget_category.budget.currency) %>
|
<% currency = Money::Currency.new(budget_category.budget.currency) %>
|
||||||
|
|
||||||
<div class="w-full flex gap-3">
|
<div id=<%= dom_id(budget_category, :form) %> class="w-full flex gap-3">
|
||||||
<div class="w-1 h-3 rounded-xl mt-1" style="background-color: <%= budget_category.category.color %>"></div>
|
<div class="w-1 h-3 rounded-xl mt-1" style="background-color: <%= budget_category.category.color %>"></div>
|
||||||
|
|
||||||
<div class="text-sm mr-3">
|
<div class="text-sm mr-3">
|
||||||
|
@ -22,6 +22,7 @@
|
||||||
step: currency.step,
|
step: currency.step,
|
||||||
id: dom_id(budget_category, :budgeted_spending),
|
id: dom_id(budget_category, :budgeted_spending),
|
||||||
min: 0,
|
min: 0,
|
||||||
|
max: budget_category.max_allocation,
|
||||||
data: { auto_submit_form_target: "auto" } %>
|
data: { auto_submit_form_target: "auto" } %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
11
app/views/budget_categories/_confirm_button.html.erb
Normal file
11
app/views/budget_categories/_confirm_button.html.erb
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<div id="<%= dom_id(budget, :confirm_button) %>">
|
||||||
|
<% if budget.allocations_valid? %>
|
||||||
|
<%= link_to "Confirm",
|
||||||
|
budget_path(budget),
|
||||||
|
class: "block btn btn--primary w-full text-center" %>
|
||||||
|
<% else %>
|
||||||
|
<span class="block btn btn--secondary w-full text-center text-gray-400 cursor-not-allowed">
|
||||||
|
Confirm
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<% budget_category = budget.uncategorized_budget_category %>
|
<% budget_category = budget.uncategorized_budget_category %>
|
||||||
|
|
||||||
<div class="flex gap-3">
|
<div id="<%= dom_id(budget, :uncategorized_budget_category_form) %>" class="flex gap-3">
|
||||||
<div class="w-1 h-3 rounded-xl mt-1" style="background-color: <%= budget_category.category.color %>"></div>
|
<div class="w-1 h-3 rounded-xl mt-1" style="background-color: <%= budget_category.category.color %>"></div>
|
||||||
|
|
||||||
<div class="text-sm mr-3">
|
<div class="text-sm mr-3">
|
||||||
|
|
|
@ -45,15 +45,7 @@
|
||||||
<%= render "budget_categories/uncategorized_budget_category_form", budget: @budget %>
|
<%= render "budget_categories/uncategorized_budget_category_form", budget: @budget %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<% if @budget.allocations_valid? %>
|
<%= render "budget_categories/confirm_button", budget: @budget %>
|
||||||
<%= link_to "Confirm",
|
|
||||||
budget_path(@budget),
|
|
||||||
class: "block btn btn--primary w-full text-center" %>
|
|
||||||
<% else %>
|
|
||||||
<span class="block btn btn--secondary w-full text-center text-gray-400 cursor-not-allowed">
|
|
||||||
Confirm
|
|
||||||
</span>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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, :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 %>
|
Loading…
Add table
Add a link
Reference in a new issue