1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-10 07:55:21 +02:00

Sync required exchange rates for accounts

This commit is contained in:
Zach Gollwitzer 2025-03-11 09:48:19 -04:00
parent c66401dc0f
commit 8988f7bb6e
4 changed files with 120 additions and 0 deletions

View file

@ -19,10 +19,16 @@ class Account::Balance::Syncer
if strategy == :forward
update_account_info
end
sync_exchange_rates
end
end
private
def sync_exchange_rates
Account::ExchangeRateSync.new(account).sync_rates
end
def sync_holdings
@holdings = Account::Holding::Syncer.new(account, strategy: strategy).sync_holdings
end

View file

@ -0,0 +1,32 @@
class Account::ExchangeRateSync
def initialize(account)
@account = account
end
def sync_rates
Rails.logger.tagged("Account::ExchangeRateSync") do
unless needs_rate_sync?
Rails.logger.info("No exchange rate sync needed for account #{@account.id}")
return
end
rates = ExchangeRate.find_rates(
from: @account.currency,
to: target_currency,
start_date: @account.start_date,
cache: true # caches from provider to DB
)
Rails.logger.info("Synced #{rates.count} exchange rates for account #{@account.id}")
end
end
private
def target_currency
@account.family.currency
end
def needs_rate_sync?
@account.currency != target_currency
end
end

View file

@ -35,4 +35,27 @@ class Account::ChartableTest < ActiveSupport::TestCase
assert_equal 3000, series.values.find { |v| v.date == 20.days.ago.to_date }.trend.current.amount
assert_equal 3500, series.values.last.trend.current.amount
end
test "generates correct totals for multi currency families" do
family = families(:empty)
family.update!(currency: "USD")
usd_account = family.accounts.create!(name: "Asset", currency: "USD", balance: 5000, accountable: Depository.new)
eur_account = family.accounts.create!(name: "Asset", currency: "EUR", balance: 1000, accountable: Depository.new)
usd_account.balances.create!(date: 3.days.ago.to_date, balance: 5000, currency: "USD")
eur_account.balances.create!(date: 3.days.ago.to_date, balance: 1000, currency: "EUR")
# 1 EUR = 1.1 USD, so 1000 EUR = 1100 USD
ExchangeRate.create!(from_currency: "EUR", to_currency: "USD", date: 3.days.ago.to_date, rate: 1.1)
ExchangeRate.create!(from_currency: "EUR", to_currency: "USD", date: 2.days.ago.to_date, rate: 1.1)
ExchangeRate.create!(from_currency: "EUR", to_currency: "USD", date: 1.days.ago.to_date, rate: 1.1)
ExchangeRate.create!(from_currency: "EUR", to_currency: "USD", date: Date.current, rate: 1.1)
series = family.accounts.balance_series(currency: "USD", period: Period.last_7_days)
assert_equal 0, series.values.first.trend.current.amount
assert_equal 6100, series.values.find { |v| v.date == 3.days.ago.to_date }.trend.current.amount
assert_equal 6100, series.values.last.trend.current.amount
end
end

View file

@ -0,0 +1,59 @@
require "test_helper"
require "ostruct"
class Account::ExchangeRateSyncTest < ActiveSupport::TestCase
include Account::EntriesTestHelper
setup do
@family = families(:empty)
@family.update!(currency: "USD")
# Foreign account (currency is not in the family's primary currency, so it will require exchange rates for net worth rollups)
@account = @family.accounts.create!(name: "Test Account", currency: "EUR", balance: 10000, accountable: Depository.new)
@provider = mock
ExchangeRate.stubs(:provider).returns(@provider)
end
test "syncs required exchange rates for an account" do
create_valuation(account: @account, date: 5.days.ago.to_date, amount: 9500, currency: "EUR")
# Since we had a valuation 5 days ago, this account starts 6 days ago and needs daily exchange rates looking forward
assert_equal 6.days.ago.to_date, @account.start_date
@provider.expects(:fetch_exchange_rates)
.with(
from: "EUR",
to: "USD",
start_date: 6.days.ago.to_date,
end_date: Date.current
).returns(
OpenStruct.new(
success?: true,
rates: [
OpenStruct.new(date: 6.days.ago.to_date, rate: 1.1),
OpenStruct.new(date: 5.days.ago.to_date, rate: 1.2),
OpenStruct.new(date: 4.days.ago.to_date, rate: 1.3),
OpenStruct.new(date: 3.days.ago.to_date, rate: 1.4),
OpenStruct.new(date: 2.days.ago.to_date, rate: 1.5),
OpenStruct.new(date: 1.day.ago.to_date, rate: 1.6),
OpenStruct.new(date: Date.current, rate: 1.7)
]
)
)
assert_difference "ExchangeRate.count", 7 do
Account::ExchangeRateSync.new(@account).sync_rates
end
end
test "does not sync rates for a domestic account" do
@account.update!(currency: "USD")
@provider.expects(:fetch_exchange_rates).never
assert_no_difference "ExchangeRate.count" do
Account::ExchangeRateSync.new(@account).sync_rates
end
end
end