mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-05 05:25:24 +02:00
Budgeting V1 (#1609)
* Budgeting V1 * Basic UI template * Fully scaffolded budgeting v1 * Basic working budget * Finalize donut chart for budgets * Allow categorization of loan payments for budget * Include loan payments in incomes_and_expenses scope * Add budget allocations progress * Empty states * Clean up budget methods * Category aggregation queries * Handle overage scenarios in form * Finalize budget donut chart controller * Passing tests * Fix allocation naming * Add income category migration * Native support for uncategorized budget category * Formatting * Fix subcategory sort order, padding * Fix calculation for category rollups in budget
This commit is contained in:
parent
413ec6cbed
commit
195ec85d96
61 changed files with 2044 additions and 140 deletions
|
@ -17,6 +17,8 @@ class Family < ApplicationRecord
|
|||
has_many :issues, through: :accounts
|
||||
has_many :holdings, through: :accounts
|
||||
has_many :plaid_items, dependent: :destroy
|
||||
has_many :budgets, dependent: :destroy
|
||||
has_many :budget_categories, through: :budgets
|
||||
|
||||
validates :locale, inclusion: { in: I18n.available_locales.map(&:to_s) }
|
||||
validates :date_format, inclusion: { in: DATE_FORMATS }
|
||||
|
@ -56,6 +58,22 @@ class Family < ApplicationRecord
|
|||
).link_token
|
||||
end
|
||||
|
||||
def income_categories_with_totals(date: Date.current)
|
||||
categories_with_stats(classification: "income", date: date)
|
||||
end
|
||||
|
||||
def expense_categories_with_totals(date: Date.current)
|
||||
categories_with_stats(classification: "expense", date: date)
|
||||
end
|
||||
|
||||
def category_stats
|
||||
CategoryStats.new(self)
|
||||
end
|
||||
|
||||
def budgeting_stats
|
||||
BudgetingStats.new(self)
|
||||
end
|
||||
|
||||
def snapshot(period = Period.all)
|
||||
query = accounts.active.joins(:balances)
|
||||
.where("account_balances.currency = ?", self.currency)
|
||||
|
@ -172,4 +190,41 @@ class Family < ApplicationRecord
|
|||
def primary_user
|
||||
users.order(:created_at).first
|
||||
end
|
||||
|
||||
def oldest_entry_date
|
||||
entries.order(:date).first&.date || Date.current
|
||||
end
|
||||
|
||||
private
|
||||
CategoriesWithTotals = Struct.new(:total_money, :category_totals, keyword_init: true)
|
||||
CategoryWithStats = Struct.new(:category, :amount_money, :percentage, keyword_init: true)
|
||||
|
||||
def categories_with_stats(classification:, date: Date.current)
|
||||
totals = category_stats.month_category_totals(date: date)
|
||||
|
||||
classified_totals = totals.category_totals.select { |t| t.classification == classification }
|
||||
|
||||
if classification == "income"
|
||||
total = totals.total_income
|
||||
categories_scope = categories.incomes
|
||||
else
|
||||
total = totals.total_expense
|
||||
categories_scope = categories.expenses
|
||||
end
|
||||
|
||||
categories_with_uncategorized = categories_scope + [ categories_scope.uncategorized ]
|
||||
|
||||
CategoriesWithTotals.new(
|
||||
total_money: Money.new(total, currency),
|
||||
category_totals: categories_with_uncategorized.map do |category|
|
||||
ct = classified_totals.find { |ct| ct.category_id == category&.id }
|
||||
|
||||
CategoryWithStats.new(
|
||||
category: category,
|
||||
amount_money: Money.new(ct&.amount || 0, currency),
|
||||
percentage: ct&.percentage || 0
|
||||
)
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue