mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-19 21:29:38 +02:00
Fetch exchange rates for accounts that require conversion for net worth rollups (#1983)
* Sync required exchange rates for accounts * Refactor into concern
This commit is contained in:
parent
7b751ac7ca
commit
b8a3ca7732
5 changed files with 113 additions and 1 deletions
|
@ -1,5 +1,5 @@
|
||||||
class Account < ApplicationRecord
|
class Account < ApplicationRecord
|
||||||
include Syncable, Monetizable, Issuable, Chartable, Enrichable, Linkable
|
include Syncable, Monetizable, Issuable, Chartable, Enrichable, Linkable, Convertible
|
||||||
|
|
||||||
validates :name, :balance, :currency, presence: true
|
validates :name, :balance, :currency, presence: true
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,8 @@ class Account::Balance::Syncer
|
||||||
if strategy == :forward
|
if strategy == :forward
|
||||||
update_account_info
|
update_account_info
|
||||||
end
|
end
|
||||||
|
|
||||||
|
account.sync_required_exchange_rates
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
28
app/models/account/convertible.rb
Normal file
28
app/models/account/convertible.rb
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
module Account::Convertible
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
def sync_required_exchange_rates
|
||||||
|
unless requires_exchange_rates?
|
||||||
|
Rails.logger.info("No exchange rate sync needed for account #{id}")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
rates = ExchangeRate.find_rates(
|
||||||
|
from: currency,
|
||||||
|
to: target_currency,
|
||||||
|
start_date: start_date,
|
||||||
|
cache: true # caches from provider to DB
|
||||||
|
)
|
||||||
|
|
||||||
|
Rails.logger.info("Synced #{rates.count} exchange rates for account #{id}")
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def target_currency
|
||||||
|
family.currency
|
||||||
|
end
|
||||||
|
|
||||||
|
def requires_exchange_rates?
|
||||||
|
currency != target_currency
|
||||||
|
end
|
||||||
|
end
|
|
@ -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 3000, series.values.find { |v| v.date == 20.days.ago.to_date }.trend.current.amount
|
||||||
assert_equal 3500, series.values.last.trend.current.amount
|
assert_equal 3500, series.values.last.trend.current.amount
|
||||||
end
|
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
|
end
|
||||||
|
|
59
test/models/account/convertible_test.rb
Normal file
59
test/models/account/convertible_test.rb
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
require "test_helper"
|
||||||
|
require "ostruct"
|
||||||
|
|
||||||
|
class Account::ConvertibleTest < 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.sync_required_exchange_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.sync_required_exchange_rates
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Add table
Add a link
Reference in a new issue