1
0
Fork 0
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)
Some checks are pending
Publish Docker image / ci (push) Waiting to run
Publish Docker image / Build docker image (push) Blocked by required conditions

* 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:
Paul Imoke 2025-02-11 14:28:06 +00:00 committed by GitHub
parent 077694bbde
commit fb6c6fa6bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 60 additions and 13 deletions

View file

@ -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

View file

@ -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

View file

@ -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>

View 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>

View file

@ -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">

View file

@ -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>

View file

@ -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 %>