mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-06 05:55:21 +02:00
Update flow assertions for forward calculator
This commit is contained in:
parent
91d970c7fe
commit
8616b2c0de
2 changed files with 178 additions and 103 deletions
|
@ -11,7 +11,7 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
# When syncing forwards, we don't care about the account balance. We generate everything based on entries, starting from 0.
|
||||
test "no entries sync" do
|
||||
account = create_account_with_ledger(
|
||||
account: { type: Depository, balance: 20000, cash_balance: 20000, currency: "USD" },
|
||||
account: { type: Depository, currency: "USD" },
|
||||
entries: []
|
||||
)
|
||||
|
||||
|
@ -25,7 +25,9 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
{
|
||||
date: Date.current,
|
||||
legacy_balances: { balance: 0, cash_balance: 0 },
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 0, end_non_cash: 0, end: 0 }
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 0, end_non_cash: 0, end: 0 },
|
||||
flows: 0,
|
||||
adjustments: 0
|
||||
}
|
||||
]
|
||||
)
|
||||
|
@ -34,7 +36,7 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
# Our system ensures all manual accounts have an opening anchor (for UX), but we should be able to handle a missing anchor by starting at 0 (i.e. "fresh account with no history")
|
||||
test "account without opening anchor starts at zero balance" do
|
||||
account = create_account_with_ledger(
|
||||
account: { type: Depository, balance: 20000, cash_balance: 20000, currency: "USD" },
|
||||
account: { type: Depository, currency: "USD" },
|
||||
entries: [
|
||||
{ type: "transaction", date: 2.days.ago.to_date, amount: -1000 }
|
||||
]
|
||||
|
@ -49,12 +51,16 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
{
|
||||
date: 3.days.ago.to_date,
|
||||
legacy_balances: { balance: 0, cash_balance: 0 },
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 0, end_non_cash: 0, end: 0 }
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 0, end_non_cash: 0, end: 0 },
|
||||
flows: 0,
|
||||
adjustments: 0
|
||||
},
|
||||
{
|
||||
date: 2.days.ago.to_date,
|
||||
legacy_balances: { balance: 1000, cash_balance: 1000 },
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 1000, end_non_cash: 0, end: 1000 }
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 1000, end_non_cash: 0, end: 1000 },
|
||||
flows: { cash_inflows: 1000, cash_outflows: 0 },
|
||||
adjustments: 0
|
||||
}
|
||||
]
|
||||
)
|
||||
|
@ -62,7 +68,7 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
|
||||
test "reconciliation valuation sets absolute balance before applying subsequent transactions" do
|
||||
account = create_account_with_ledger(
|
||||
account: { type: Depository, balance: 20000, cash_balance: 20000, currency: "USD" },
|
||||
account: { type: Depository, currency: "USD" },
|
||||
entries: [
|
||||
{ type: "reconciliation", date: 3.days.ago.to_date, balance: 18000 },
|
||||
{ type: "transaction", date: 2.days.ago.to_date, amount: -1000 }
|
||||
|
@ -78,12 +84,16 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
{
|
||||
date: 3.days.ago.to_date,
|
||||
legacy_balances: { balance: 18000, cash_balance: 18000 },
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 18000, end_non_cash: 0, end: 18000 }
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 18000, end_non_cash: 0, end: 18000 },
|
||||
flows: 0,
|
||||
adjustments: { cash_adjustments: 18000, non_cash_adjustments: 0 }
|
||||
},
|
||||
{
|
||||
date: 2.days.ago.to_date,
|
||||
legacy_balances: { balance: 19000, cash_balance: 19000 },
|
||||
balances: { start: 18000, start_cash: 18000, start_non_cash: 0, end_cash: 19000, end_non_cash: 0, end: 19000 }
|
||||
balances: { start: 18000, start_cash: 18000, start_non_cash: 0, end_cash: 19000, end_non_cash: 0, end: 19000 },
|
||||
flows: { cash_inflows: 1000, cash_outflows: 0 },
|
||||
adjustments: 0
|
||||
}
|
||||
]
|
||||
)
|
||||
|
@ -92,7 +102,7 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
test "cash-only accounts (depository, credit card) use valuations where cash balance equals total balance" do
|
||||
[ Depository, CreditCard ].each do |account_type|
|
||||
account = create_account_with_ledger(
|
||||
account: { type: account_type, balance: 10000, cash_balance: 10000, currency: "USD" },
|
||||
account: { type: account_type, currency: "USD" },
|
||||
entries: [
|
||||
{ type: "opening_anchor", date: 3.days.ago.to_date, balance: 17000 },
|
||||
{ type: "reconciliation", date: 2.days.ago.to_date, balance: 18000 }
|
||||
|
@ -107,12 +117,16 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
{
|
||||
date: 3.days.ago.to_date,
|
||||
legacy_balances: { balance: 17000, cash_balance: 17000 },
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 17000, end_non_cash: 0, end: 17000 }
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 17000, end_non_cash: 0, end: 17000 },
|
||||
flows: 0,
|
||||
adjustments: { cash_adjustments: 17000, non_cash_adjustments: 0 }
|
||||
},
|
||||
{
|
||||
date: 2.days.ago.to_date,
|
||||
legacy_balances: { balance: 18000, cash_balance: 18000 },
|
||||
balances: { start: 17000, start_cash: 17000, start_non_cash: 0, end_cash: 18000, end_non_cash: 0, end: 18000 }
|
||||
balances: { start: 17000, start_cash: 17000, start_non_cash: 0, end_cash: 18000, end_non_cash: 0, end: 18000 },
|
||||
flows: 0,
|
||||
adjustments: { cash_adjustments: 1000, non_cash_adjustments: 0 }
|
||||
}
|
||||
]
|
||||
)
|
||||
|
@ -122,7 +136,7 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
test "non-cash accounts (property, loan) use valuations where cash balance is always zero" do
|
||||
[ Property, Loan ].each do |account_type|
|
||||
account = create_account_with_ledger(
|
||||
account: { type: account_type, balance: 10000, cash_balance: 10000, currency: "USD" },
|
||||
account: { type: account_type, currency: "USD" },
|
||||
entries: [
|
||||
{ type: "opening_anchor", date: 3.days.ago.to_date, balance: 17000 },
|
||||
{ type: "reconciliation", date: 2.days.ago.to_date, balance: 18000 }
|
||||
|
@ -137,12 +151,16 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
{
|
||||
date: 3.days.ago.to_date,
|
||||
legacy_balances: { balance: 17000, cash_balance: 0.0 },
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 0, end_non_cash: 17000, end: 17000 }
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 0, end_non_cash: 17000, end: 17000 },
|
||||
flows: 0,
|
||||
adjustments: { cash_adjustments: 0, non_cash_adjustments: 17000 }
|
||||
},
|
||||
{
|
||||
date: 2.days.ago.to_date,
|
||||
legacy_balances: { balance: 18000, cash_balance: 0.0 },
|
||||
balances: { start: 17000, start_cash: 0, start_non_cash: 17000, end_cash: 0, end_non_cash: 18000, end: 18000 }
|
||||
balances: { start: 17000, start_cash: 0, start_non_cash: 17000, end_cash: 0, end_non_cash: 18000, end: 18000 },
|
||||
flows: 0,
|
||||
adjustments: { cash_adjustments: 0, non_cash_adjustments: 1000 }
|
||||
}
|
||||
]
|
||||
)
|
||||
|
@ -151,7 +169,7 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
|
||||
test "mixed accounts (investment) use valuations where cash balance is total minus holdings" do
|
||||
account = create_account_with_ledger(
|
||||
account: { type: Investment, balance: 10000, cash_balance: 10000, currency: "USD" },
|
||||
account: { type: Investment, currency: "USD" },
|
||||
entries: [
|
||||
{ type: "opening_anchor", date: 3.days.ago.to_date, balance: 17000 },
|
||||
{ type: "reconciliation", date: 2.days.ago.to_date, balance: 18000 }
|
||||
|
@ -167,12 +185,16 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
{
|
||||
date: 3.days.ago.to_date,
|
||||
legacy_balances: { balance: 17000, cash_balance: 17000 },
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 17000, end_non_cash: 0, end: 17000 }
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 17000, end_non_cash: 0, end: 17000 },
|
||||
flows: { market_flows: 0 },
|
||||
adjustments: { cash_adjustments: 17000, non_cash_adjustments: 0 } # Since no holdings present, adjustment is all cash
|
||||
},
|
||||
{
|
||||
date: 2.days.ago.to_date,
|
||||
legacy_balances: { balance: 18000, cash_balance: 18000 },
|
||||
balances: { start: 17000, start_cash: 17000, start_non_cash: 0, end_cash: 18000, end_non_cash: 0, end: 18000 }
|
||||
balances: { start: 17000, start_cash: 17000, start_non_cash: 0, end_cash: 18000, end_non_cash: 0, end: 18000 },
|
||||
flows: { market_flows: 0 },
|
||||
adjustments: { cash_adjustments: 1000, non_cash_adjustments: 0 } # Since no holdings present, adjustment is all cash
|
||||
}
|
||||
]
|
||||
)
|
||||
|
@ -184,7 +206,7 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
|
||||
test "transactions on depository accounts affect cash balance" do
|
||||
account = create_account_with_ledger(
|
||||
account: { type: Depository, balance: 20000, cash_balance: 20000, currency: "USD" },
|
||||
account: { type: Depository, currency: "USD" },
|
||||
entries: [
|
||||
{ type: "opening_anchor", date: 5.days.ago.to_date, balance: 20000 },
|
||||
{ type: "transaction", date: 4.days.ago.to_date, amount: -500 }, # income
|
||||
|
@ -200,22 +222,30 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
{
|
||||
date: 5.days.ago.to_date,
|
||||
legacy_balances: { balance: 20000, cash_balance: 20000 },
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 20000, end_non_cash: 0, end: 20000 }
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 20000, end_non_cash: 0, end: 20000 },
|
||||
flows: 0,
|
||||
adjustments: { cash_adjustments: 20000, non_cash_adjustments: 0 }
|
||||
},
|
||||
{
|
||||
date: 4.days.ago.to_date,
|
||||
legacy_balances: { balance: 20500, cash_balance: 20500 },
|
||||
balances: { start: 20000, start_cash: 20000, start_non_cash: 0, end_cash: 20500, end_non_cash: 0, end: 20500 }
|
||||
balances: { start: 20000, start_cash: 20000, start_non_cash: 0, end_cash: 20500, end_non_cash: 0, end: 20500 },
|
||||
flows: { cash_inflows: 500, cash_outflows: 0 },
|
||||
adjustments: 0
|
||||
},
|
||||
{
|
||||
date: 3.days.ago.to_date,
|
||||
legacy_balances: { balance: 20500, cash_balance: 20500 },
|
||||
balances: { start: 20500, start_cash: 20500, start_non_cash: 0, end_cash: 20500, end_non_cash: 0, end: 20500 }
|
||||
balances: { start: 20500, start_cash: 20500, start_non_cash: 0, end_cash: 20500, end_non_cash: 0, end: 20500 },
|
||||
flows: 0,
|
||||
adjustments: 0
|
||||
},
|
||||
{
|
||||
date: 2.days.ago.to_date,
|
||||
legacy_balances: { balance: 20400, cash_balance: 20400 },
|
||||
balances: { start: 20500, start_cash: 20500, start_non_cash: 0, end_cash: 20400, end_non_cash: 0, end: 20400 }
|
||||
balances: { start: 20500, start_cash: 20500, start_non_cash: 0, end_cash: 20400, end_non_cash: 0, end: 20400 },
|
||||
flows: { cash_inflows: 0, cash_outflows: 100 },
|
||||
adjustments: 0
|
||||
}
|
||||
]
|
||||
)
|
||||
|
@ -224,7 +254,7 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
|
||||
test "transactions on credit card accounts affect cash balance inversely" do
|
||||
account = create_account_with_ledger(
|
||||
account: { type: CreditCard, balance: 10000, cash_balance: 10000, currency: "USD" },
|
||||
account: { type: CreditCard, currency: "USD" },
|
||||
entries: [
|
||||
{ type: "opening_anchor", date: 5.days.ago.to_date, balance: 1000 },
|
||||
{ type: "transaction", date: 4.days.ago.to_date, amount: -500 }, # CC payment
|
||||
|
@ -240,22 +270,30 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
{
|
||||
date: 5.days.ago.to_date,
|
||||
legacy_balances: { balance: 1000, cash_balance: 1000 },
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 1000, end_non_cash: 0, end: 1000 }
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 1000, end_non_cash: 0, end: 1000 },
|
||||
flows: 0,
|
||||
adjustments: { cash_adjustments: 1000, non_cash_adjustments: 0 }
|
||||
},
|
||||
{
|
||||
date: 4.days.ago.to_date,
|
||||
legacy_balances: { balance: 500, cash_balance: 500 },
|
||||
balances: { start: 1000, start_cash: 1000, start_non_cash: 0, end_cash: 500, end_non_cash: 0, end: 500 }
|
||||
balances: { start: 1000, start_cash: 1000, start_non_cash: 0, end_cash: 500, end_non_cash: 0, end: 500 },
|
||||
flows: { cash_inflows: 0, cash_outflows: 500 },
|
||||
adjustments: 0
|
||||
},
|
||||
{
|
||||
date: 3.days.ago.to_date,
|
||||
legacy_balances: { balance: 500, cash_balance: 500 },
|
||||
balances: { start: 500, start_cash: 500, start_non_cash: 0, end_cash: 500, end_non_cash: 0, end: 500 }
|
||||
balances: { start: 500, start_cash: 500, start_non_cash: 0, end_cash: 500, end_non_cash: 0, end: 500 },
|
||||
flows: 0,
|
||||
adjustments: 0
|
||||
},
|
||||
{
|
||||
date: 2.days.ago.to_date,
|
||||
legacy_balances: { balance: 600, cash_balance: 600 },
|
||||
balances: { start: 500, start_cash: 500, start_non_cash: 0, end_cash: 600, end_non_cash: 0, end: 600 }
|
||||
balances: { start: 500, start_cash: 500, start_non_cash: 0, end_cash: 600, end_non_cash: 0, end: 600 },
|
||||
flows: { cash_inflows: 0, cash_outflows: 100 },
|
||||
adjustments: 0
|
||||
}
|
||||
]
|
||||
)
|
||||
|
@ -263,15 +301,12 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
|
||||
test "depository account with transactions and balance reconciliations" do
|
||||
account = create_account_with_ledger(
|
||||
account: { type: Depository, balance: 20000, cash_balance: 20000, currency: "USD" },
|
||||
account: { type: Depository, currency: "USD" },
|
||||
entries: [
|
||||
{ type: "opening_anchor", date: 10.days.ago.to_date, balance: 20000 },
|
||||
{ type: "transaction", date: 8.days.ago.to_date, amount: -5000 },
|
||||
{ type: "reconciliation", date: 6.days.ago.to_date, balance: 17000 },
|
||||
{ type: "transaction", date: 6.days.ago.to_date, amount: -500 },
|
||||
{ type: "transaction", date: 4.days.ago.to_date, amount: -500 },
|
||||
{ type: "reconciliation", date: 3.days.ago.to_date, balance: 17000 },
|
||||
{ type: "transaction", date: 1.day.ago.to_date, amount: 100 }
|
||||
{ type: "opening_anchor", date: 4.days.ago.to_date, balance: 20000 },
|
||||
{ type: "transaction", date: 3.days.ago.to_date, amount: -5000 },
|
||||
{ type: "reconciliation", date: 2.days.ago.to_date, balance: 17000 },
|
||||
{ type: "transaction", date: 1.day.ago.to_date, amount: -500 }
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -280,63 +315,41 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
assert_calculated_ledger_balances(
|
||||
calculated_data: calculated,
|
||||
expected_data: [
|
||||
{
|
||||
date: 10.days.ago.to_date,
|
||||
legacy_balances: { balance: 20000, cash_balance: 20000 },
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 20000, end_non_cash: 0, end: 20000 }
|
||||
},
|
||||
{
|
||||
date: 9.days.ago.to_date,
|
||||
legacy_balances: { balance: 20000, cash_balance: 20000 },
|
||||
balances: { start: 20000, start_cash: 20000, start_non_cash: 0, end_cash: 20000, end_non_cash: 0, end: 20000 }
|
||||
},
|
||||
{
|
||||
date: 8.days.ago.to_date,
|
||||
legacy_balances: { balance: 25000, cash_balance: 25000 },
|
||||
balances: { start: 20000, start_cash: 20000, start_non_cash: 0, end_cash: 25000, end_non_cash: 0, end: 25000 }
|
||||
},
|
||||
{
|
||||
date: 7.days.ago.to_date,
|
||||
legacy_balances: { balance: 25000, cash_balance: 25000 },
|
||||
balances: { start: 25000, start_cash: 25000, start_non_cash: 0, end_cash: 25000, end_non_cash: 0, end: 25000 }
|
||||
},
|
||||
{
|
||||
date: 6.days.ago.to_date,
|
||||
legacy_balances: { balance: 17000, cash_balance: 17000 },
|
||||
balances: { start: 25000, start_cash: 25000, start_non_cash: 0, end_cash: 17000, end_non_cash: 0, end: 17000 }
|
||||
},
|
||||
{
|
||||
date: 5.days.ago.to_date,
|
||||
legacy_balances: { balance: 17000, cash_balance: 17000 },
|
||||
balances: { start: 17000, start_cash: 17000, start_non_cash: 0, end_cash: 17000, end_non_cash: 0, end: 17000 }
|
||||
},
|
||||
{
|
||||
date: 4.days.ago.to_date,
|
||||
legacy_balances: { balance: 17500, cash_balance: 17500 },
|
||||
balances: { start: 17000, start_cash: 17000, start_non_cash: 0, end_cash: 17500, end_non_cash: 0, end: 17500 }
|
||||
legacy_balances: { balance: 20000, cash_balance: 20000 },
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 20000, end_non_cash: 0, end: 20000 },
|
||||
flows: 0,
|
||||
adjustments: { cash_adjustments: 20000, non_cash_adjustments: 0 }
|
||||
},
|
||||
{
|
||||
date: 3.days.ago.to_date,
|
||||
legacy_balances: { balance: 17000, cash_balance: 17000 },
|
||||
balances: { start: 17500, start_cash: 17500, start_non_cash: 0, end_cash: 17000, end_non_cash: 0, end: 17000 }
|
||||
legacy_balances: { balance: 25000, cash_balance: 25000 },
|
||||
balances: { start: 20000, start_cash: 20000, start_non_cash: 0, end_cash: 25000, end_non_cash: 0, end: 25000 },
|
||||
flows: { cash_inflows: 5000, cash_outflows: 0 },
|
||||
adjustments: 0
|
||||
},
|
||||
{
|
||||
date: 2.days.ago.to_date,
|
||||
legacy_balances: { balance: 17000, cash_balance: 17000 },
|
||||
balances: { start: 17000, start_cash: 17000, start_non_cash: 0, end_cash: 17000, end_non_cash: 0, end: 17000 }
|
||||
balances: { start: 25000, start_cash: 25000, start_non_cash: 0, end_cash: 17000, end_non_cash: 0, end: 17000 },
|
||||
flows: 0,
|
||||
adjustments: { cash_adjustments: -8000, non_cash_adjustments: 0 }
|
||||
},
|
||||
{
|
||||
date: 1.day.ago.to_date,
|
||||
legacy_balances: { balance: 16900, cash_balance: 16900 },
|
||||
balances: { start: 17000, start_cash: 17000, start_non_cash: 0, end_cash: 16900, end_non_cash: 0, end: 16900 }
|
||||
legacy_balances: { balance: 17500, cash_balance: 17500 },
|
||||
balances: { start: 17000, start_cash: 17000, start_non_cash: 0, end_cash: 17500, end_non_cash: 0, end: 17500 },
|
||||
flows: { cash_inflows: 500, cash_outflows: 0 },
|
||||
adjustments: 0
|
||||
}
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
test "accounts with transactions in multiple currencies convert to the account currency" do
|
||||
test "accounts with transactions in multiple currencies convert to the account currency and flows are stored in account currency" do
|
||||
account = create_account_with_ledger(
|
||||
account: { type: Depository, balance: 20000, cash_balance: 20000, currency: "USD" },
|
||||
account: { type: Depository, currency: "USD" },
|
||||
entries: [
|
||||
{ type: "opening_anchor", date: 4.days.ago.to_date, balance: 100 },
|
||||
{ type: "transaction", date: 3.days.ago.to_date, amount: -100 },
|
||||
|
@ -357,22 +370,30 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
{
|
||||
date: 4.days.ago.to_date,
|
||||
legacy_balances: { balance: 100, cash_balance: 100 },
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 100, end_non_cash: 0, end: 100 }
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 100, end_non_cash: 0, end: 100 },
|
||||
flows: 0,
|
||||
adjustments: { cash_adjustments: 100, non_cash_adjustments: 0 }
|
||||
},
|
||||
{
|
||||
date: 3.days.ago.to_date,
|
||||
legacy_balances: { balance: 200, cash_balance: 200 },
|
||||
balances: { start: 100, start_cash: 100, start_non_cash: 0, end_cash: 200, end_non_cash: 0, end: 200 }
|
||||
balances: { start: 100, start_cash: 100, start_non_cash: 0, end_cash: 200, end_non_cash: 0, end: 200 },
|
||||
flows: { cash_inflows: 100, cash_outflows: 0 },
|
||||
adjustments: 0
|
||||
},
|
||||
{
|
||||
date: 2.days.ago.to_date,
|
||||
legacy_balances: { balance: 500, cash_balance: 500 },
|
||||
balances: { start: 200, start_cash: 200, start_non_cash: 0, end_cash: 500, end_non_cash: 0, end: 500 }
|
||||
balances: { start: 200, start_cash: 200, start_non_cash: 0, end_cash: 500, end_non_cash: 0, end: 500 },
|
||||
flows: { cash_inflows: 300, cash_outflows: 0 },
|
||||
adjustments: 0
|
||||
},
|
||||
{
|
||||
date: 1.day.ago.to_date,
|
||||
legacy_balances: { balance: 1100, cash_balance: 1100 },
|
||||
balances: { start: 500, start_cash: 500, start_non_cash: 0, end_cash: 1100, end_non_cash: 0, end: 1100 }
|
||||
balances: { start: 500, start_cash: 500, start_non_cash: 0, end_cash: 1100, end_non_cash: 0, end: 1100 },
|
||||
flows: { cash_inflows: 600, cash_outflows: 0 }, # Cash inflow is the USD equivalent of €500 (converted for balances table)
|
||||
adjustments: 0
|
||||
}
|
||||
]
|
||||
)
|
||||
|
@ -381,7 +402,7 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
# A loan is a special case where despite being a "non-cash" account, it is typical to have "payment" transactions that reduce the loan principal (non cash balance)
|
||||
test "loan payment transactions affect non cash balance" do
|
||||
account = create_account_with_ledger(
|
||||
account: { type: Loan, balance: 10000, cash_balance: 0, currency: "USD" },
|
||||
account: { type: Loan, currency: "USD" },
|
||||
entries: [
|
||||
{ type: "opening_anchor", date: 2.days.ago.to_date, balance: 20000 },
|
||||
# "Loan payment" of $2000, which reduces the principal
|
||||
|
@ -399,12 +420,16 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
{
|
||||
date: 2.days.ago.to_date,
|
||||
legacy_balances: { balance: 20000, cash_balance: 0 },
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 0, end_non_cash: 20000, end: 20000 }
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 0, end_non_cash: 20000, end: 20000 },
|
||||
flows: 0,
|
||||
adjustments: { cash_adjustments: 0, non_cash_adjustments: 20000 } # Valuations adjust non-cash balance for non-cash accounts like Loans
|
||||
},
|
||||
{
|
||||
date: 1.day.ago.to_date,
|
||||
legacy_balances: { balance: 18000, cash_balance: 0 },
|
||||
balances: { start: 20000, start_cash: 0, start_non_cash: 20000, end_cash: 0, end_non_cash: 18000, end: 18000 }
|
||||
balances: { start: 20000, start_cash: 0, start_non_cash: 20000, end_cash: 0, end_non_cash: 18000, end: 18000 },
|
||||
flows: { non_cash_inflows: 2000, non_cash_outflows: 0, cash_inflows: 0, cash_outflows: 0 }, # Loans are "special cases" where transactions do affect non-cash balance
|
||||
adjustments: 0
|
||||
}
|
||||
]
|
||||
)
|
||||
|
@ -413,7 +438,7 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
test "non cash accounts can only use valuations and transactions will be recorded but ignored for balance calculation" do
|
||||
[ Property, Vehicle, OtherAsset, OtherLiability ].each do |account_type|
|
||||
account = create_account_with_ledger(
|
||||
account: { type: account_type, balance: 10000, cash_balance: 10000, currency: "USD" },
|
||||
account: { type: account_type, currency: "USD" },
|
||||
entries: [
|
||||
{ type: "opening_anchor", date: 3.days.ago.to_date, balance: 500000 },
|
||||
|
||||
|
@ -430,12 +455,16 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
{
|
||||
date: 3.days.ago.to_date,
|
||||
legacy_balances: { balance: 500000, cash_balance: 0 },
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 0, end_non_cash: 500000, end: 500000 }
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 0, end_non_cash: 500000, end: 500000 },
|
||||
flows: 0,
|
||||
adjustments: { cash_adjustments: 0, non_cash_adjustments: 500000 }
|
||||
},
|
||||
{
|
||||
date: 2.days.ago.to_date,
|
||||
legacy_balances: { balance: 500000, cash_balance: 0 },
|
||||
balances: { start: 500000, start_cash: 0, start_non_cash: 500000, end_cash: 0, end_non_cash: 500000, end: 500000 }
|
||||
balances: { start: 500000, start_cash: 0, start_non_cash: 500000, end_cash: 0, end_non_cash: 500000, end: 500000 },
|
||||
flows: 0, # Despite having a transaction, non-cash accounts ignore it for balance calculation
|
||||
adjustments: 0
|
||||
}
|
||||
]
|
||||
)
|
||||
|
@ -452,7 +481,7 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
# Holdings are calculated separately and fed into the balance calculator; treated as "non-cash"
|
||||
test "investment account calculates balance from transactions and trades and treats holdings as non-cash, additive to balance" do
|
||||
account = create_account_with_ledger(
|
||||
account: { type: Investment, balance: 10000, cash_balance: 10000, currency: "USD" },
|
||||
account: { type: Investment, currency: "USD" },
|
||||
entries: [
|
||||
# Account starts with brokerage cash of $5000 and no holdings
|
||||
{ type: "opening_anchor", date: 3.days.ago.to_date, balance: 5000 },
|
||||
|
@ -462,7 +491,7 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
holdings: [
|
||||
# Holdings calculator will calculate $1000 worth of holdings
|
||||
{ date: 1.day.ago.to_date, ticker: "AAPL", qty: 10, price: 100, amount: 1000 },
|
||||
{ date: Date.current, ticker: "AAPL", qty: 10, price: 100, amount: 1000 }
|
||||
{ date: Date.current, ticker: "AAPL", qty: 10, price: 110, amount: 1100 } # Price increased by 10%, so holdings value goes up by $100 without a trade
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -476,29 +505,36 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
|
|||
{
|
||||
date: 3.days.ago.to_date,
|
||||
legacy_balances: { balance: 5000, cash_balance: 5000 },
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 5000, end_non_cash: 0, end: 5000 }
|
||||
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 5000, end_non_cash: 0, end: 5000 },
|
||||
flows: 0,
|
||||
adjustments: { cash_adjustments: 5000, non_cash_adjustments: 0 }
|
||||
},
|
||||
{
|
||||
date: 2.days.ago.to_date,
|
||||
legacy_balances: { balance: 5000, cash_balance: 5000 },
|
||||
balances: { start: 5000, start_cash: 5000, start_non_cash: 0, end_cash: 5000, end_non_cash: 0, end: 5000 }
|
||||
balances: { start: 5000, start_cash: 5000, start_non_cash: 0, end_cash: 5000, end_non_cash: 0, end: 5000 },
|
||||
flows: 0,
|
||||
adjustments: 0
|
||||
},
|
||||
{
|
||||
date: 1.day.ago.to_date,
|
||||
legacy_balances: { balance: 5000, cash_balance: 4000 },
|
||||
balances: { start: 5000, start_cash: 5000, start_non_cash: 0, end_cash: 4000, end_non_cash: 1000, end: 5000 }
|
||||
balances: { start: 5000, start_cash: 5000, start_non_cash: 0, end_cash: 4000, end_non_cash: 1000, end: 5000 },
|
||||
flows: { cash_inflows: 0, cash_outflows: 1000, non_cash_inflows: 1000, non_cash_outflows: 0, net_market_flows: 0 }, # Decrease cash by 1000, increase holdings by 1000 (i.e. "buy" of $1000 worth of AAPL)
|
||||
adjustments: 0
|
||||
},
|
||||
{
|
||||
date: Date.current,
|
||||
legacy_balances: { balance: 5000, cash_balance: 4000 },
|
||||
balances: { start: 5000, start_cash: 4000, start_non_cash: 1000, end_cash: 4000, end_non_cash: 1000, end: 5000 }
|
||||
balances: { start: 5000, start_cash: 4000, start_non_cash: 1000, end_cash: 4000, end_non_cash: 1000, end: 5000 },
|
||||
flows: { net_market_flows: 100 }, # Holdings value increased by 100, despite no change in portfolio quantities
|
||||
adjustments: 0
|
||||
}
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assert_balances(calculated_data:, expected_balances:)
|
||||
# Sort calculated data by date to ensure consistent ordering
|
||||
sorted_data = calculated_data.sort_by(&:date)
|
||||
|
|
|
@ -12,6 +12,8 @@ module LedgerTestingHelper
|
|||
created_account = families(:empty).accounts.create!(
|
||||
name: "Test Account",
|
||||
accountable: account_type.new,
|
||||
balance: 0, # Doesn't matter, ledger derives this
|
||||
cash_balance: 0, # Doesn't matter, ledger derives this
|
||||
**account_attrs
|
||||
)
|
||||
|
||||
|
@ -170,25 +172,62 @@ module LedgerTestingHelper
|
|||
end
|
||||
|
||||
# Flow assertions
|
||||
if flows.any?
|
||||
assert_equal flows[:cash_inflows], calculated_balance.cash_inflows.to_d,
|
||||
"Cash inflows mismatch for #{date}" if flows.key?(:cash_inflows)
|
||||
# If flows passed is 0, we assert all columns are 0
|
||||
if flows.is_a?(Integer) && flows == 0
|
||||
assert_equal 0, calculated_balance.cash_inflows.to_d,
|
||||
"Cash inflows mismatch for #{date}"
|
||||
|
||||
assert_equal flows[:cash_outflows], calculated_balance.cash_outflows.to_d,
|
||||
"Cash outflows mismatch for #{date}" if flows.key?(:cash_outflows)
|
||||
assert_equal 0, calculated_balance.cash_outflows.to_d,
|
||||
"Cash outflows mismatch for #{date}"
|
||||
|
||||
assert_equal flows[:non_cash_inflows], calculated_balance.non_cash_inflows.to_d,
|
||||
"Non-cash inflows mismatch for #{date}" if flows.key?(:non_cash_inflows)
|
||||
assert_equal 0, calculated_balance.non_cash_inflows.to_d,
|
||||
"Non-cash inflows mismatch for #{date}"
|
||||
|
||||
assert_equal flows[:non_cash_outflows], calculated_balance.non_cash_outflows.to_d,
|
||||
"Non-cash outflows mismatch for #{date}" if flows.key?(:non_cash_outflows)
|
||||
assert_equal 0, calculated_balance.non_cash_outflows.to_d,
|
||||
"Non-cash outflows mismatch for #{date}"
|
||||
|
||||
assert_equal flows[:net_market_flows], calculated_balance.net_market_flows.to_d,
|
||||
"Net market flows mismatch for #{date}" if flows.key?(:net_market_flows)
|
||||
assert_equal 0, calculated_balance.net_market_flows.to_d,
|
||||
"Net market flows mismatch for #{date}"
|
||||
elsif flows.is_a?(Hash) && flows.any?
|
||||
# Cash flows - must be asserted together
|
||||
if flows.key?(:cash_inflows) || flows.key?(:cash_outflows)
|
||||
assert flows.key?(:cash_inflows) && flows.key?(:cash_outflows),
|
||||
"Cash inflows and outflows must be asserted together for #{date}"
|
||||
|
||||
assert_equal flows[:cash_inflows], calculated_balance.cash_inflows.to_d,
|
||||
"Cash inflows mismatch for #{date}"
|
||||
|
||||
assert_equal flows[:cash_outflows], calculated_balance.cash_outflows.to_d,
|
||||
"Cash outflows mismatch for #{date}"
|
||||
end
|
||||
|
||||
# Non-cash flows - must be asserted together
|
||||
if flows.key?(:non_cash_inflows) || flows.key?(:non_cash_outflows)
|
||||
assert flows.key?(:non_cash_inflows) && flows.key?(:non_cash_outflows),
|
||||
"Non-cash inflows and outflows must be asserted together for #{date}"
|
||||
|
||||
assert_equal flows[:non_cash_inflows], calculated_balance.non_cash_inflows.to_d,
|
||||
"Non-cash inflows mismatch for #{date}"
|
||||
|
||||
assert_equal flows[:non_cash_outflows], calculated_balance.non_cash_outflows.to_d,
|
||||
"Non-cash outflows mismatch for #{date}"
|
||||
end
|
||||
|
||||
# Market flows - can be asserted independently
|
||||
if flows.key?(:net_market_flows)
|
||||
assert_equal flows[:net_market_flows], calculated_balance.net_market_flows.to_d,
|
||||
"Net market flows mismatch for #{date}"
|
||||
end
|
||||
end
|
||||
|
||||
# Adjustment assertions
|
||||
if adjustments.any?
|
||||
if adjustments.is_a?(Integer) && adjustments == 0
|
||||
assert_equal 0, calculated_balance.cash_adjustments.to_d,
|
||||
"Cash adjustments mismatch for #{date}"
|
||||
|
||||
assert_equal 0, calculated_balance.non_cash_adjustments.to_d,
|
||||
"Non-cash adjustments mismatch for #{date}"
|
||||
elsif adjustments.is_a?(Hash) && adjustments.any?
|
||||
assert_equal adjustments[:cash_adjustments], calculated_balance.cash_adjustments.to_d,
|
||||
"Cash adjustments mismatch for #{date}" if adjustments.key?(:cash_adjustments)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue