1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-07-25 08:09:38 +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:
Zach Gollwitzer 2025-01-16 14:36:37 -05:00 committed by GitHub
parent 413ec6cbed
commit 195ec85d96
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
61 changed files with 2044 additions and 140 deletions

View file

@ -42,34 +42,34 @@ class Transfer < ApplicationRecord
end
def auto_match_for_account(account)
matches = account.entries.account_transactions.joins("
JOIN account_entries ae2 ON
account_entries.amount = -ae2.amount AND
account_entries.currency = ae2.currency AND
account_entries.account_id <> ae2.account_id AND
ABS(account_entries.date - ae2.date) <= 4
").select(
"account_entries.id",
"account_entries.entryable_id AS e1_entryable_id",
"ae2.entryable_id AS e2_entryable_id",
"account_entries.amount AS e1_amount",
"ae2.amount AS e2_amount"
)
matches = Account::Entry.from("account_entries inflow_candidates")
.joins("
JOIN account_entries outflow_candidates ON (
inflow_candidates.amount < 0 AND
outflow_candidates.amount > 0 AND
inflow_candidates.amount = -outflow_candidates.amount AND
inflow_candidates.currency = outflow_candidates.currency AND
inflow_candidates.account_id <> outflow_candidates.account_id AND
inflow_candidates.date BETWEEN outflow_candidates.date - 4 AND outflow_candidates.date + 4 AND
inflow_candidates.date >= outflow_candidates.date
)
").joins("
LEFT JOIN transfers existing_transfers ON (
(existing_transfers.inflow_transaction_id = inflow_candidates.entryable_id AND
existing_transfers.outflow_transaction_id = outflow_candidates.entryable_id) OR
(existing_transfers.inflow_transaction_id = inflow_candidates.entryable_id) OR
(existing_transfers.outflow_transaction_id = outflow_candidates.entryable_id)
)
")
.where(existing_transfers: { id: nil })
.where("inflow_candidates.account_id = ? AND outflow_candidates.account_id = ?", account.id, account.id)
.pluck(:inflow_transaction_id, :outflow_transaction_id)
Transfer.transaction do
matches.each do |match|
inflow = match.e1_amount.negative? ? match.e1_entryable_id : match.e2_entryable_id
outflow = match.e1_amount.negative? ? match.e2_entryable_id : match.e1_entryable_id
# Skip all rejected, or already matched transfers
next if Transfer.exists?(
inflow_transaction_id: inflow,
outflow_transaction_id: outflow
)
matches.each do |inflow_transaction_id, outflow_transaction_id|
Transfer.create!(
inflow_transaction_id: inflow,
outflow_transaction_id: outflow
inflow_transaction_id: inflow_transaction_id,
outflow_transaction_id: outflow_transaction_id,
)
end
end
@ -109,6 +109,10 @@ class Transfer < ApplicationRecord
to_account.liability?
end
def categorizable?
to_account.accountable_type == "Loan"
end
private
def transfer_has_different_accounts
return unless inflow_transaction.present? && outflow_transaction.present?