1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-02 20:15:22 +02:00

Dashboard View and Calculations (#521)

* Handle Turbo updates with tabs

Fixes #491

* Add Filterable concern for controllers

* Add trendline chart

* Extract common UI to partials

* Series refactor

* Put placeholders for calculations in

* Add classification generated column to account

* Add basic net worth calculation

* Add net worth tests

* Get net worth graph working

* Fix lint errors

* Implement asset grouping query

* Make trends and series more intuitive

* Fully functional dashboard

* Remove logging
This commit is contained in:
Zach Gollwitzer 2024-03-06 09:56:59 -05:00 committed by GitHub
parent 680a91d807
commit 6f0e410684
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 594 additions and 74 deletions

View file

@ -11,27 +11,110 @@ class Account < ApplicationRecord
before_create :check_currency
def balance_series(period)
MoneySeries.new(
balances.in_period(period).order(:date),
{ trend_type: classification }
)
def trend(period = Period.all)
first = balances.in_period(period).order(:date).first
last = balances.in_period(period).order(date: :desc).first
Trend.new(current: last.balance, previous: first.balance, type: classification)
end
def valuation_series
MoneySeries.new(
valuations.order(:date),
{ trend_type: classification, amount_accessor: :value }
)
end
# TODO: We will need a better way to encapsulate large queries & transformation logic, but leaving all in one spot until
# we have a better understanding of the requirements
def self.by_group(period = Period.all)
ranked_balances_cte = joins(:balances)
.select("
account_balances.account_id,
account_balances.balance,
account_balances.date,
ROW_NUMBER() OVER (PARTITION BY account_balances.account_id ORDER BY date ASC) AS rn_asc,
ROW_NUMBER() OVER (PARTITION BY account_balances.account_id ORDER BY date DESC) AS rn_desc
")
def check_currency
if self.currency == self.family.currency
self.converted_balance = self.balance
self.converted_currency = self.currency
else
self.converted_balance = ExchangeRate.convert(self.currency, self.family.currency, self.balance)
self.converted_currency = self.family.currency
if period.date_range
ranked_balances_cte = ranked_balances_cte.where("account_balances.date BETWEEN ? AND ?", period.date_range.begin, period.date_range.end)
end
accounts_with_period_balances = AccountBalance.with(
ranked_balances: ranked_balances_cte
)
.from("ranked_balances AS rb")
.joins("JOIN accounts a ON a.id = rb.account_id")
.select("
a.name,
a.accountable_type,
a.classification,
SUM(CASE WHEN rb.rn_asc = 1 THEN rb.balance ELSE 0 END) AS start_balance,
MAX(CASE WHEN rb.rn_asc = 1 THEN rb.date ELSE NULL END) as start_date,
SUM(CASE WHEN rb.rn_desc = 1 THEN rb.balance ELSE 0 END) AS end_balance,
MAX(CASE WHEN rb.rn_desc = 1 THEN rb.date ELSE NULL END) as end_date
")
.where("rb.rn_asc = 1 OR rb.rn_desc = 1")
.group("a.id")
.order("end_balance")
.to_a
assets = accounts_with_period_balances.select { |row| row.classification == "asset" }
liabilities = accounts_with_period_balances.select { |row| row.classification == "liability" }
total_assets = assets.sum(&:end_balance)
total_liabilities = liabilities.sum(&:end_balance)
{
asset: {
total: total_assets,
groups: assets.group_by(&:accountable_type).transform_values do |rows|
end_balance = rows.sum(&:end_balance)
start_balance = rows.sum(&:start_balance)
{
start_balance: start_balance,
end_balance: end_balance,
allocation: (end_balance / total_assets * 100).round(2),
trend: Trend.new(current: end_balance, previous: start_balance, type: "asset"),
accounts: rows.map do |account|
{
name: account.name,
start_balance: account.start_balance,
end_balance: account.end_balance,
allocation: (account.end_balance / total_assets * 100).round(2),
trend: Trend.new(current: account.end_balance, previous: account.start_balance, type: "asset")
}
end
}
end
},
liability: {
total: total_liabilities,
groups: liabilities.group_by(&:accountable_type).transform_values do |rows|
end_balance = rows.sum(&:end_balance)
start_balance = rows.sum(&:start_balance)
{
start_balance: start_balance,
end_balance: end_balance,
allocation: (end_balance / total_liabilities * 100).round(2),
trend: Trend.new(current: end_balance, previous: start_balance, type: "liability"),
accounts: rows.map do |account|
{
name: account.name,
start_balance: account.start_balance,
end_balance: account.end_balance,
allocation: (account.end_balance / total_liabilities * 100).round(2),
trend: Trend.new(current: account.end_balance, previous: account.start_balance, type: "liability")
}
end
}
end
}
}
end
private
def check_currency
if self.currency == self.family.currency
self.converted_balance = self.balance
self.converted_currency = self.currency
else
self.converted_balance = ExchangeRate.convert(self.currency, self.family.currency, self.balance)
self.converted_currency = self.family.currency
end
end
end