1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-05 13:35:21 +02:00

Net worth calculation (#508)

* Add classification generated column to account

* Add basic net worth calculation

* Add net worth tests

* Fix lint errors
This commit is contained in:
Zach Gollwitzer 2024-03-04 08:31:22 -05:00 committed by GitHub
parent 19f15e9391
commit facd74f733
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 156 additions and 40 deletions

View file

@ -52,9 +52,9 @@ module ApplicationHelper
def trend_styles(trend)
bg_class, text_class, symbol, icon = case trend.direction
when "up"
trend.type == :liability ? [ "bg-red-500/5", "text-red-500", "+", "arrow-up" ] : [ "bg-green-500/5", "text-green-500", "+", "arrow-up" ]
trend.type == "liability" ? [ "bg-red-500/5", "text-red-500", "+", "arrow-up" ] : [ "bg-green-500/5", "text-green-500", "+", "arrow-up" ]
when "down"
trend.type == :liability ? [ "bg-green-500/5", "text-green-500", "-", "arrow-down" ] : [ "bg-red-500/5", "text-red-500", "-", "arrow-down" ]
trend.type == "liability" ? [ "bg-green-500/5", "text-green-500", "-", "arrow-down" ] : [ "bg-red-500/5", "text-red-500", "-", "arrow-down" ]
when "flat"
[ "bg-gray-500/5", "text-gray-500", "", "minus" ]
else

View file

@ -9,24 +9,8 @@ class Account < ApplicationRecord
delegated_type :accountable, types: Accountable::TYPES, dependent: :destroy
delegate :type_name, to: :accountable
before_create :check_currency
def classification
classifications = {
"Account::Depository" => :asset,
"Account::Investment" => :asset,
"Account::Property" => :asset,
"Account::Vehicle" => :asset,
"Account::OtherAsset" => :asset,
"Account::Loan" => :liability,
"Account::Credit" => :liability,
"Account::OtherLiability" => :liability
}
classifications[accountable_type]
end
def balance_series(period)
MoneySeries.new(
balances.in_period(period).order(:date),

View file

@ -11,7 +11,7 @@ class Account::BalanceCalculator
oldest_entry = [ valuations.first, transactions.first ].compact.min_by(&:date)
net_transaction_flows = transactions.sum(&:amount)
net_transaction_flows *= -1 if @account.classification == :liability
net_transaction_flows *= -1 if @account.classification == "liability"
implied_start_balance = oldest_entry.is_a?(Valuation) ? oldest_entry.value : @account.balance + net_transaction_flows
prior_balance = implied_start_balance
@ -22,7 +22,7 @@ class Account::BalanceCalculator
current_balance = valuation.value
else
current_day_net_transaction_flows = transactions.select { |t| t.date == date }.sum(&:amount)
current_day_net_transaction_flows *= -1 if @account.classification == :liability
current_day_net_transaction_flows *= -1 if @account.classification == "liability"
current_balance = prior_balance - current_day_net_transaction_flows
end

View file

@ -2,4 +2,66 @@ class Family < ApplicationRecord
has_many :users, dependent: :destroy
has_many :accounts, dependent: :destroy
has_many :transactions, through: :accounts
def net_worth
accounts.sum("CASE WHEN classification = 'asset' THEN balance ELSE -balance END")
end
def assets
accounts.where(classification: "asset").sum(:balance)
end
def liabilities
accounts.where(classification: "liability").sum(:balance)
end
def net_worth_series(period = nil)
query = accounts.joins(:balances)
.select("account_balances.date, SUM(CASE WHEN accounts.classification = 'asset' THEN account_balances.balance ELSE -account_balances.balance END) AS balance, 'USD' as currency")
.group("account_balances.date")
.order("account_balances.date ASC")
if period && period.date_range
query = query.where("account_balances.date BETWEEN ? AND ?", period.date_range.begin, period.date_range.end)
end
MoneySeries.new(
query,
{ trend_type: "asset" }
)
end
def asset_series(period = nil)
query = accounts.joins(:balances)
.select("account_balances.date, SUM(account_balances.balance) AS balance, 'asset' AS classification, 'USD' AS currency")
.group("account_balances.date")
.order("account_balances.date ASC")
.where(classification: "asset")
if period && period.date_range
query = query.where("account_balances.date BETWEEN ? AND ?", period.date_range.begin, period.date_range.end)
end
MoneySeries.new(
query,
{ trend_type: "asset" }
)
end
def liability_series(period = nil)
query = accounts.joins(:balances)
.select("account_balances.date, SUM(account_balances.balance) AS balance, 'liability' AS classification, 'USD' AS currency")
.group("account_balances.date")
.order("account_balances.date ASC")
.where(classification: "liability")
if period && period.date_range
query = query.where("account_balances.date BETWEEN ? AND ?", period.date_range.begin, period.date_range.end)
end
MoneySeries.new(
query,
{ trend_type: "liability" }
)
end
end

View file

@ -1,6 +1,6 @@
class MoneySeries
def initialize(series, options = {})
@trend_type = options[:trend_type] || :asset # Defines whether a positive trend is good or bad
@trend_type = options[:trend_type] || "asset" # Defines whether a positive trend is good or bad
@accessor = options[:amount_accessor] || :balance
@series = series
end

View file

@ -1,10 +1,10 @@
class Trend
attr_reader :current, :previous, :type
def initialize(current:, previous: nil, type: :asset)
def initialize(current:, previous: nil, type: "asset")
@current = current
@previous = previous
@type = type # :asset means positive trend is good, :liability means negative trend is good
@type = type # asset means positive trend is good, liability means negative trend is good
end
def direction