mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-02 20:15:22 +02:00
Multi-Currency Part 2 (#543)
* Support all currencies, handle outside DB * Remove currencies from seed * Fix account balance namespace * Set default currency on authentication * Cache currency instances * Implement multi-currency syncs with tests * Series fallback, passing tests * Fix conflicts * Make value group concrete class that works with currency values * Fix migration conflict * Update tests to expect multi-currency results * Update account list to use group method * Namespace updates * Fetch unknown exchange rates from API * Fix date range bug * Ensure demo data works without external API * Enforce cascades only at DB level
This commit is contained in:
parent
de0cba9fed
commit
110855d077
55 changed files with 1226 additions and 714 deletions
|
@ -6,7 +6,7 @@ class Account < ApplicationRecord
|
|||
|
||||
broadcasts_refreshes
|
||||
belongs_to :family
|
||||
has_many :balances, class_name: "AccountBalance"
|
||||
has_many :balances
|
||||
has_many :valuations
|
||||
has_many :transactions
|
||||
|
||||
|
@ -20,8 +20,6 @@ class Account < ApplicationRecord
|
|||
|
||||
delegated_type :accountable, types: Accountable::TYPES, dependent: :destroy
|
||||
|
||||
before_create :check_currency
|
||||
|
||||
def self.ransackable_attributes(auth_object = nil)
|
||||
%w[name]
|
||||
end
|
||||
|
@ -30,6 +28,17 @@ class Account < ApplicationRecord
|
|||
balances.where("date <= ?", date).order(date: :desc).first&.balance
|
||||
end
|
||||
|
||||
# e.g. Wise, Revolut accounts that have transactions in multiple currencies
|
||||
def multi_currency?
|
||||
currencies = [ valuations.pluck(:currency), transactions.pluck(:currency) ].flatten.uniq
|
||||
currencies.count > 1
|
||||
end
|
||||
|
||||
# e.g. Accounts denominated in currency other than family currency
|
||||
def foreign_currency?
|
||||
currency != family.currency
|
||||
end
|
||||
|
||||
def self.by_provider
|
||||
# TODO: When 3rd party providers are supported, dynamically load all providers and their accounts
|
||||
[ { name: "Manual accounts", accounts: all.order(balance: :desc).group_by(&:accountable_type) } ]
|
||||
|
@ -39,35 +48,41 @@ class Account < ApplicationRecord
|
|||
exists?(status: "syncing")
|
||||
end
|
||||
|
||||
def series(period = Period.all)
|
||||
TimeSeries.from_collection(balances.in_period(period), :balance_money)
|
||||
|
||||
def series(period: Period.all, currency: self.currency)
|
||||
balance_series = balances.in_period(period).where(currency: Money::Currency.new(currency).iso_code)
|
||||
|
||||
if balance_series.empty? && period.date_range.end == Date.current
|
||||
converted_balance = balance_money.exchange_to(currency)
|
||||
if converted_balance
|
||||
TimeSeries.new([ { date: Date.current, value: converted_balance } ])
|
||||
else
|
||||
TimeSeries.new([])
|
||||
end
|
||||
else
|
||||
TimeSeries.from_collection(balance_series, :balance_money)
|
||||
end
|
||||
end
|
||||
|
||||
def self.by_group(period = Period.all)
|
||||
grouped_accounts = { assets: ValueGroup.new("Assets"), liabilities: ValueGroup.new("Liabilities") }
|
||||
def self.by_group(period: Period.all, currency: Money.default_currency)
|
||||
grouped_accounts = { assets: ValueGroup.new("Assets", currency), liabilities: ValueGroup.new("Liabilities", currency) }
|
||||
|
||||
Accountable.by_classification.each do |classification, types|
|
||||
types.each do |type|
|
||||
group = grouped_accounts[classification.to_sym].add_child_node(type)
|
||||
group = grouped_accounts[classification.to_sym].add_child_group(type, currency)
|
||||
Accountable.from_type(type).includes(:account).each do |accountable|
|
||||
account = accountable.account
|
||||
value_node = group.add_value_node(account)
|
||||
value_node.attach_series(account.series(period))
|
||||
next unless account
|
||||
|
||||
value_node = group.add_value_node(
|
||||
account,
|
||||
account.balance_money.exchange_to(currency) || Money.new(0, currency),
|
||||
account.series(period: period, currency: currency)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
grouped_accounts
|
||||
end
|
||||
|
||||
private
|
||||
def check_currency
|
||||
if self.currency == self.family.currency
|
||||
self.converted_balance = self.balance
|
||||
self.converted_currency = self.currency
|
||||
else
|
||||
self.converted_balance = ExchangeRate.convert(self.currency, self.family.currency, self.balance)
|
||||
self.converted_currency = self.family.currency
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue