diff --git a/app/models/account/balance/calculator.rb b/app/models/account/balance/calculator.rb index 53083dc2b25..24bd451aa0c 100644 --- a/app/models/account/balance/calculator.rb +++ b/app/models/account/balance/calculator.rb @@ -42,7 +42,7 @@ def calculate private def convert_balances_to_family_currency - rates = ExchangeRate.get_rate_series( + rates = ExchangeRate.get_rates( @account.currency, @account.family.currency, @calc_start_date..Date.current @@ -54,28 +54,38 @@ def convert_balances_to_family_currency return [] end - @daily_balances.map do |balance| - rate = rates.find { |rate| rate.date == balance[:date] } - raise "Rate for #{@account.currency} to #{@account.family.currency} on #{balance[:date]} not found" if rate.nil? - converted_balance = balance[:balance] * rate.rate + @daily_balances.map.with_index do |balance, index| + converted_balance = balance[:balance] * rates[index].rate { date: balance[:date], balance: converted_balance, currency: @account.family.currency, updated_at: Time.current } end end # For calculation, all transactions and valuations need to be normalized to the same currency (the account's primary currency) def normalize_entries_to_account_currency(entries, value_key) - entries.map do |entry| - currency = entry.currency - date = entry.date - value = entry.send(value_key) + grouped_entries = entries.group_by(&:currency) + normalized_entries = [] + grouped_entries.each do |currency, entries| if currency != @account.currency - value = ExchangeRate.convert(value:, from: currency, to: @account.currency, date:) - currency = @account.currency + dates = entries.map(&:date).uniq + rates = ExchangeRate.get_rates(currency, @account.currency, dates).to_a + if rates.length != dates.length + @errors << :sync_message_missing_rates + else + entries.each do |entry| + ## There can be several entries on the same date so we cannot rely on indeces + rate = rates.find { |rate| rate.date == entry.date } + value = entry.send(value_key) + value *= rate.rate + normalized_entries << entry.attributes.merge(value_key.to_s => value, "currency" => currency) + end + end + else + normalized_entries.concat(entries) end - - entry.attributes.merge(value_key.to_s => value, "currency" => currency) end + + normalized_entries end def normalized_valuations @@ -97,8 +107,8 @@ def implied_start_balance return @account.balance_on(@calc_start_date) end - oldest_valuation_date = normalized_valuations.first&.dig("date") - oldest_transaction_date = normalized_transactions.first&.dig("date") + oldest_valuation_date = normalized_valuations.first&.date + oldest_transaction_date = normalized_transactions.first&.date oldest_entry_date = [ oldest_valuation_date, oldest_transaction_date ].compact.min if oldest_entry_date.present? && oldest_entry_date == oldest_valuation_date diff --git a/app/models/account/syncable.rb b/app/models/account/syncable.rb index 82a90c7ba38..1b47caebe02 100644 --- a/app/models/account/syncable.rb +++ b/app/models/account/syncable.rb @@ -20,7 +20,7 @@ def sync(start_date = nil) update!(status: "ok", last_sync_date: Date.today, balance: new_balance, sync_errors: calculator.errors, sync_warnings: calculator.warnings) rescue => e - update!(status: "error") + update!(status: "error", sync_errors: [ :sync_message_unknown_error ]) logger.error("Failed to sync account #{id}: #{e.message}") end diff --git a/app/models/exchange_rate.rb b/app/models/exchange_rate.rb index 1950f340ce8..b3260b5561c 100644 --- a/app/models/exchange_rate.rb +++ b/app/models/exchange_rate.rb @@ -15,8 +15,8 @@ def find_rate_or_fetch(from:, to:, date:) find_rate(from:, to:, date:) || fetch_rate_from_provider(from:, to:, date:)&.tap(&:save!) end - def get_rate_series(from, to, date_range) - where(base_currency: from, converted_currency: to, date: date_range).order(:date) + def get_rates(from, to, dates) + where(base_currency: from, converted_currency: to, date: dates).order(:date) end def convert(value:, from:, to:, date:) diff --git a/config/locales/views/account/en.yml b/config/locales/views/account/en.yml index 30b863cfda9..38248678f98 100644 --- a/config/locales/views/account/en.yml +++ b/config/locales/views/account/en.yml @@ -37,6 +37,7 @@ en: confirm_title: Delete account? sync_message_missing_rates: Since exchange rates haven't been synced, balance graphs may not reflect accurate values. + sync_message_unknown_error: An error has occurred during the sync. summary: new: New account sync: