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

Flatten balance model

This commit is contained in:
Zach Gollwitzer 2025-04-13 09:12:43 -04:00
parent 21ed7d27d6
commit 37e84b411d
17 changed files with 81 additions and 79 deletions

View file

@ -106,7 +106,7 @@ end
# BAD!!
# Unnecessary test - in this case, this is simply testing ActiveRecord's functionality
test "saves balance" do
balance_record = Account::Balance.new(balance: 100, currency: "USD")
balance_record = Balance.new(balance: 100, currency: "USD")
assert balance_record.save
end

View file

@ -7,7 +7,7 @@ module Account::Chartable
series_interval = interval || period.interval
balances = Account::Balance.find_by_sql([
balances = Balance.find_by_sql([
balance_series_query,
{
start_date: period.start_date,

View file

@ -44,7 +44,7 @@ class Account::Entry < ApplicationRecord
end
def balance_trend(entries, balances)
Account::BalanceTrendCalculator.new(self, entries, balances).trend
Balance::TrendCalculator.new(self, entries, balances).trend
end
def display_name

View file

@ -1,4 +1,6 @@
class Account::Balance < ApplicationRecord
class Balance < ApplicationRecord
self.table_name = "account_balances"
include Monetizable
belongs_to :account

View file

@ -1,4 +1,4 @@
class Account::Balance::BaseCalculator
class Balance::BaseCalculator
attr_reader :account
def initialize(account)
@ -13,11 +13,11 @@ class Account::Balance::BaseCalculator
private
def sync_cache
@sync_cache ||= Account::Balance::SyncCache.new(account)
@sync_cache ||= Balance::SyncCache.new(account)
end
def build_balance(date, cash_balance, holdings_value)
Account::Balance.new(
Balance.new(
account_id: account.id,
date: date,
balance: holdings_value + cash_balance,

View file

@ -1,4 +1,4 @@
class Account::Balance::ForwardCalculator < Account::Balance::BaseCalculator
class Balance::ForwardCalculator < Balance::BaseCalculator
private
def calculate_balances
current_cash_balance = 0

View file

@ -1,4 +1,4 @@
class Account::Balance::ReverseCalculator < Account::Balance::BaseCalculator
class Balance::ReverseCalculator < Balance::BaseCalculator
private
def calculate_balances
current_cash_balance = account.cash_balance

View file

@ -1,4 +1,4 @@
class Account::Balance::SyncCache
class Balance::SyncCache
def initialize(account)
@account = account
end

View file

@ -1,4 +1,4 @@
class Account::Balance::Syncer
class Balance::Syncer
attr_reader :account, :strategy
def initialize(account, strategy:)
@ -7,7 +7,7 @@ class Account::Balance::Syncer
end
def sync_balances
Account::Balance.transaction do
Balance.transaction do
sync_holdings
calculate_balances
@ -63,9 +63,9 @@ class Account::Balance::Syncer
def calculator
if strategy == :reverse
Account::Balance::ReverseCalculator.new(account)
Balance::ReverseCalculator.new(account)
else
Account::Balance::ForwardCalculator.new(account)
Balance::ForwardCalculator.new(account)
end
end
end

View file

@ -2,7 +2,7 @@
# In most cases, this is sufficient. However, for the "Activity View", we need to show intraday balances
# to show users how each entry affects their balances. This class calculates intraday balances by
# interpolating between end-of-day balances.
class Account::BalanceTrendCalculator
class Balance::TrendCalculator
BalanceTrend = Struct.new(:trend, :cash, keyword_init: true)
class << self

View file

@ -76,7 +76,7 @@
<div>
<div class="rounded-tl-lg rounded-tr-lg bg-container border-alpha-black-25 shadow-xs">
<div class="space-y-4">
<% calculator = Account::BalanceTrendCalculator.for(@entries) %>
<% calculator = Balance::TrendCalculator.for(@entries) %>
<%= entries_by_date(@entries) do |entries| %>
<% entries.each do |entry| %>
<%= render entry, balance_trend: calculator&.trend_for(entry), view_ctx: "account" %>

View file

@ -65,7 +65,7 @@ class Settings::HostingsControllerTest < ActionDispatch::IntegrationTest
assert_not ExchangeRate.exists?(exchange_rate.id)
assert_not Security::Price.exists?(security_price.id)
assert_not Holding.exists?(holding.id)
assert_not Account::Balance.exists?(account_balance.id)
assert_not Balance.exists?(account_balance.id)
end
test "can clear data only when admin" do

View file

@ -1,51 +0,0 @@
require "test_helper"
class Account::Balance::SyncerTest < ActiveSupport::TestCase
include Account::EntriesTestHelper
setup do
@account = families(:empty).accounts.create!(
name: "Test",
balance: 20000,
cash_balance: 20000,
currency: "USD",
accountable: Investment.new
)
end
test "syncs balances" do
Holding::Syncer.any_instance.expects(:sync_holdings).returns([]).once
@account.expects(:start_date).returns(2.days.ago.to_date)
Account::Balance::ForwardCalculator.any_instance.expects(:calculate).returns(
[
Account::Balance.new(date: 1.day.ago.to_date, balance: 1000, cash_balance: 1000, currency: "USD"),
Account::Balance.new(date: Date.current, balance: 1000, cash_balance: 1000, currency: "USD")
]
)
assert_difference "@account.balances.count", 2 do
Account::Balance::Syncer.new(@account, strategy: :forward).sync_balances
end
end
test "purges stale balances and holdings" do
# Balance before start date is stale
@account.expects(:start_date).returns(2.days.ago.to_date).twice
stale_balance = Account::Balance.new(date: 3.days.ago.to_date, balance: 10000, cash_balance: 10000, currency: "USD")
Account::Balance::ForwardCalculator.any_instance.expects(:calculate).returns(
[
stale_balance,
Account::Balance.new(date: 2.days.ago.to_date, balance: 10000, cash_balance: 10000, currency: "USD"),
Account::Balance.new(date: 1.day.ago.to_date, balance: 1000, cash_balance: 1000, currency: "USD"),
Account::Balance.new(date: Date.current, balance: 1000, cash_balance: 1000, currency: "USD")
]
)
assert_difference "@account.balances.count", 3 do
Account::Balance::Syncer.new(@account, strategy: :forward).sync_balances
end
end
end

View file

@ -1,6 +1,6 @@
require "test_helper"
class Account::Balance::ForwardCalculatorTest < ActiveSupport::TestCase
class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
include Account::EntriesTestHelper
setup do
@ -18,7 +18,7 @@ class Account::Balance::ForwardCalculatorTest < ActiveSupport::TestCase
assert_equal 0, @account.balances.count
expected = [ 0, 0 ]
calculated = Account::Balance::ForwardCalculator.new(@account).calculate
calculated = Balance::ForwardCalculator.new(@account).calculate
assert_equal expected, calculated.map(&:balance)
end
@ -28,7 +28,7 @@ class Account::Balance::ForwardCalculatorTest < ActiveSupport::TestCase
create_valuation(account: @account, date: 2.days.ago.to_date, amount: 19000)
expected = [ 0, 17000, 17000, 19000, 19000, 19000 ]
calculated = Account::Balance::ForwardCalculator.new(@account).calculate.sort_by(&:date).map(&:balance)
calculated = Balance::ForwardCalculator.new(@account).calculate.sort_by(&:date).map(&:balance)
assert_equal expected, calculated
end
@ -38,7 +38,7 @@ class Account::Balance::ForwardCalculatorTest < ActiveSupport::TestCase
create_transaction(account: @account, date: 2.days.ago.to_date, amount: 100) # expense
expected = [ 0, 500, 500, 400, 400, 400 ]
calculated = Account::Balance::ForwardCalculator.new(@account).calculate.sort_by(&:date).map(&:balance)
calculated = Balance::ForwardCalculator.new(@account).calculate.sort_by(&:date).map(&:balance)
assert_equal expected, calculated
end
@ -52,7 +52,7 @@ class Account::Balance::ForwardCalculatorTest < ActiveSupport::TestCase
create_transaction(account: @account, date: 1.day.ago.to_date, amount: 100)
expected = [ 0, 5000, 5000, 17000, 17000, 17500, 17000, 17000, 16900, 16900 ]
calculated = Account::Balance::ForwardCalculator.new(@account).calculate.sort_by(&:date).map(&:balance)
calculated = Balance::ForwardCalculator.new(@account).calculate.sort_by(&:date).map(&:balance)
assert_equal expected, calculated
end
@ -67,7 +67,7 @@ class Account::Balance::ForwardCalculatorTest < ActiveSupport::TestCase
create_transaction(account: @account, date: 1.day.ago.to_date, amount: -500, currency: "EUR") # €500 * 1.2 = $600
expected = [ 0, 100, 400, 1000, 1000 ]
calculated = Account::Balance::ForwardCalculator.new(@account).calculate.sort_by(&:date).map(&:balance)
calculated = Balance::ForwardCalculator.new(@account).calculate.sort_by(&:date).map(&:balance)
assert_equal expected, calculated
end

View file

@ -1,6 +1,6 @@
require "test_helper"
class Account::Balance::ReverseCalculatorTest < ActiveSupport::TestCase
class Balance::ReverseCalculatorTest < ActiveSupport::TestCase
include Account::EntriesTestHelper
setup do
@ -18,7 +18,7 @@ class Account::Balance::ReverseCalculatorTest < ActiveSupport::TestCase
assert_equal 0, @account.balances.count
expected = [ @account.balance, @account.balance ]
calculated = Account::Balance::ReverseCalculator.new(@account).calculate
calculated = Balance::ReverseCalculator.new(@account).calculate
assert_equal expected, calculated.map(&:balance)
end
@ -28,7 +28,7 @@ class Account::Balance::ReverseCalculatorTest < ActiveSupport::TestCase
create_valuation(account: @account, date: 2.days.ago.to_date, amount: 19000)
expected = [ 17000, 17000, 19000, 19000, 20000, 20000 ]
calculated = Account::Balance::ReverseCalculator.new(@account).calculate.sort_by(&:date).map(&:balance)
calculated = Balance::ReverseCalculator.new(@account).calculate.sort_by(&:date).map(&:balance)
assert_equal expected, calculated
end
@ -38,7 +38,7 @@ class Account::Balance::ReverseCalculatorTest < ActiveSupport::TestCase
create_transaction(account: @account, date: 2.days.ago.to_date, amount: 100) # expense
expected = [ 19600, 20100, 20100, 20000, 20000, 20000 ]
calculated = Account::Balance::ReverseCalculator.new(@account).calculate.sort_by(&:date).map(&:balance)
calculated = Balance::ReverseCalculator.new(@account).calculate.sort_by(&:date).map(&:balance)
assert_equal expected, calculated
end
@ -52,7 +52,7 @@ class Account::Balance::ReverseCalculatorTest < ActiveSupport::TestCase
create_transaction(account: @account, date: 1.day.ago.to_date, amount: 100)
expected = [ 12000, 17000, 17000, 17000, 16500, 17000, 17000, 20100, 20000, 20000 ]
calculated = Account::Balance::ReverseCalculator.new(@account).calculate.sort_by(&:date).map(&:balance)
calculated = Balance::ReverseCalculator.new(@account).calculate.sort_by(&:date).map(&:balance)
assert_equal expected, calculated
end

View file

@ -0,0 +1,51 @@
require "test_helper"
class Balance::SyncerTest < ActiveSupport::TestCase
include Account::EntriesTestHelper
setup do
@account = families(:empty).accounts.create!(
name: "Test",
balance: 20000,
cash_balance: 20000,
currency: "USD",
accountable: Investment.new
)
end
test "syncs balances" do
Holding::Syncer.any_instance.expects(:sync_holdings).returns([]).once
@account.expects(:start_date).returns(2.days.ago.to_date)
Balance::ForwardCalculator.any_instance.expects(:calculate).returns(
[
Balance.new(date: 1.day.ago.to_date, balance: 1000, cash_balance: 1000, currency: "USD"),
Balance.new(date: Date.current, balance: 1000, cash_balance: 1000, currency: "USD")
]
)
assert_difference "@account.balances.count", 2 do
Balance::Syncer.new(@account, strategy: :forward).sync_balances
end
end
test "purges stale balances and holdings" do
# Balance before start date is stale
@account.expects(:start_date).returns(2.days.ago.to_date).twice
stale_balance = Balance.new(date: 3.days.ago.to_date, balance: 10000, cash_balance: 10000, currency: "USD")
Balance::ForwardCalculator.any_instance.expects(:calculate).returns(
[
stale_balance,
Balance.new(date: 2.days.ago.to_date, balance: 10000, cash_balance: 10000, currency: "USD"),
Balance.new(date: 1.day.ago.to_date, balance: 1000, cash_balance: 1000, currency: "USD"),
Balance.new(date: Date.current, balance: 1000, cash_balance: 1000, currency: "USD")
]
)
assert_difference "@account.balances.count", 3 do
Balance::Syncer.new(@account, strategy: :forward).sync_balances
end
end
end