mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-19 13:19:39 +02:00
Start and end balance anchors for historical account balances (#2455)
* Add kind field to valuation * Fix schema conflict * Add kind to valuation * Scaffold opening balance manager * Opening balance manager implementation * Update account import to use opening balance manager + tests * Update account to use opening balance manager * Fix test assertions, usage of current balance manager * Lint fixes * Add Opening Balance manager, add tests to forward calculator * Add credit card to "all cash" designation * Simplify valuation model * Add current balance manager with tests * Add current balance logic to reverse calculator and plaid sync * Tweaks to initial calc logic * Ledger testing helper, tweak assertions for reverse calculator * Update test assertions * Extract balance transformer, simplify calculators * Algo simplifications * Final tweaks to calculators * Cleanup * Fix error, propagate sync errors up to parent * Update migration script, valuation naming
This commit is contained in:
parent
9110ab27d2
commit
c1d98fe73b
35 changed files with 1903 additions and 355 deletions
82
app/models/balance/base_calculator.rb
Normal file
82
app/models/balance/base_calculator.rb
Normal file
|
@ -0,0 +1,82 @@
|
|||
class Balance::BaseCalculator
|
||||
attr_reader :account
|
||||
|
||||
def initialize(account)
|
||||
@account = account
|
||||
end
|
||||
|
||||
def calculate
|
||||
raise NotImplementedError, "Subclasses must implement this method"
|
||||
end
|
||||
|
||||
private
|
||||
def sync_cache
|
||||
@sync_cache ||= Balance::SyncCache.new(account)
|
||||
end
|
||||
|
||||
def holdings_value_for_date(date)
|
||||
holdings = sync_cache.get_holdings(date)
|
||||
holdings.sum(&:amount)
|
||||
end
|
||||
|
||||
def derive_cash_balance_on_date_from_total(total_balance:, date:)
|
||||
if balance_type == :investment
|
||||
total_balance - holdings_value_for_date(date)
|
||||
elsif balance_type == :cash
|
||||
total_balance
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def derive_cash_balance(cash_balance, date)
|
||||
entries = sync_cache.get_entries(date)
|
||||
|
||||
if balance_type == :non_cash
|
||||
0
|
||||
else
|
||||
cash_balance + signed_entry_flows(entries)
|
||||
end
|
||||
end
|
||||
|
||||
def derive_non_cash_balance(non_cash_balance, date, direction: :forward)
|
||||
entries = sync_cache.get_entries(date)
|
||||
# Loans are a special case (loan payment reducing principal, which is non-cash)
|
||||
if balance_type == :non_cash && account.accountable_type == "Loan"
|
||||
non_cash_balance + signed_entry_flows(entries)
|
||||
elsif balance_type == :investment
|
||||
# For reverse calculations, we need the previous day's holdings
|
||||
target_date = direction == :forward ? date : date.prev_day
|
||||
holdings_value_for_date(target_date)
|
||||
else
|
||||
non_cash_balance
|
||||
end
|
||||
end
|
||||
|
||||
def signed_entry_flows(entries)
|
||||
raise NotImplementedError, "Directional calculators must implement this method"
|
||||
end
|
||||
|
||||
def balance_type
|
||||
case account.accountable_type
|
||||
when "Depository", "CreditCard"
|
||||
:cash
|
||||
when "Property", "Vehicle", "OtherAsset", "Loan", "OtherLiability"
|
||||
:non_cash
|
||||
when "Investment", "Crypto"
|
||||
:investment
|
||||
else
|
||||
raise "Unknown account type: #{account.accountable_type}"
|
||||
end
|
||||
end
|
||||
|
||||
def build_balance(date:, cash_balance:, non_cash_balance:)
|
||||
Balance.new(
|
||||
account_id: account.id,
|
||||
date: date,
|
||||
balance: non_cash_balance + cash_balance,
|
||||
cash_balance: cash_balance,
|
||||
currency: account.currency
|
||||
)
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue