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:
parent
21ed7d27d6
commit
37e84b411d
17 changed files with 81 additions and 79 deletions
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
class Account::Balance < ApplicationRecord
|
||||
class Balance < ApplicationRecord
|
||||
self.table_name = "account_balances"
|
||||
|
||||
include Monetizable
|
||||
|
||||
belongs_to :account
|
|
@ -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,
|
|
@ -1,4 +1,4 @@
|
|||
class Account::Balance::ForwardCalculator < Account::Balance::BaseCalculator
|
||||
class Balance::ForwardCalculator < Balance::BaseCalculator
|
||||
private
|
||||
def calculate_balances
|
||||
current_cash_balance = 0
|
|
@ -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
|
|
@ -1,4 +1,4 @@
|
|||
class Account::Balance::SyncCache
|
||||
class Balance::SyncCache
|
||||
def initialize(account)
|
||||
@account = account
|
||||
end
|
|
@ -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
|
|
@ -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
|
|
@ -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" %>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
51
test/models/balance/syncer_test.rb
Normal file
51
test/models/balance/syncer_test.rb
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue