mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-05 05:25:24 +02:00
Plaid sync tests and multi-currency investment support (#1531)
* Plaid sync tests and multi-currency investment support * Fix system test * Cleanup * Remove data migration
This commit is contained in:
parent
b2a56aefc1
commit
800eb4c146
21 changed files with 406 additions and 165 deletions
|
@ -9,9 +9,6 @@ class Account::Holding < ApplicationRecord
|
|||
validates :qty, :currency, presence: true
|
||||
|
||||
scope :chronological, -> { order(:date) }
|
||||
scope :current, -> { where(date: Date.current).order(amount: :desc) }
|
||||
scope :in_period, ->(period) { period.date_range.nil? ? all : where(date: period.date_range) }
|
||||
scope :known_value, -> { where.not(amount: nil) }
|
||||
scope :for, ->(security) { where(security_id: security).order(:date) }
|
||||
|
||||
delegate :ticker, to: :security
|
||||
|
@ -29,7 +26,7 @@ class Account::Holding < ApplicationRecord
|
|||
|
||||
# Basic approximation of cost-basis
|
||||
def avg_cost
|
||||
avg_cost = account.holdings.for(security).where("date <= ?", date).average(:price)
|
||||
avg_cost = account.holdings.for(security).where(currency: currency).where("date <= ?", date).average(:price)
|
||||
Money.new(avg_cost, currency)
|
||||
end
|
||||
|
||||
|
|
|
@ -52,13 +52,15 @@ class Account::HoldingCalculator
|
|||
|
||||
next if price.blank?
|
||||
|
||||
converted_price = Money.new(price.price, price.currency).exchange_to(account.currency, fallback_rate: 1).amount
|
||||
|
||||
account.holdings.build(
|
||||
security: security.dig(:security),
|
||||
date: date,
|
||||
qty: qty,
|
||||
price: price.price,
|
||||
currency: price.currency,
|
||||
amount: qty * price.price
|
||||
price: converted_price,
|
||||
currency: account.currency,
|
||||
amount: qty * converted_price
|
||||
)
|
||||
end.compact
|
||||
end
|
||||
|
@ -145,7 +147,7 @@ class Account::HoldingCalculator
|
|||
def load_current_holding_quantities
|
||||
holding_quantities = load_empty_holding_quantities
|
||||
|
||||
account.holdings.where(date: Date.current).map do |holding|
|
||||
account.holdings.where(date: Date.current, currency: account.currency).map do |holding|
|
||||
holding_quantities[holding.security_id] = holding.qty
|
||||
end
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ class Account::Syncer
|
|||
balances = sync_balances(holdings)
|
||||
account.reload
|
||||
update_account_info(balances, holdings) unless account.plaid_account_id.present?
|
||||
convert_foreign_records(balances)
|
||||
convert_records_to_family_currency(balances, holdings) unless account.currency == account.family.currency
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -37,12 +37,7 @@ class Account::Syncer
|
|||
current_time = Time.now
|
||||
|
||||
Account.transaction do
|
||||
account.holdings.upsert_all(
|
||||
calculated_holdings.map { |h| h.attributes
|
||||
.slice("date", "currency", "qty", "price", "amount", "security_id")
|
||||
.merge("updated_at" => current_time) },
|
||||
unique_by: %i[account_id security_id date currency]
|
||||
) if calculated_holdings.any?
|
||||
load_holdings(calculated_holdings)
|
||||
|
||||
# Purge outdated holdings
|
||||
account.holdings.delete_by("date < ? OR security_id NOT IN (?)", account_start_date, calculated_holdings.map(&:security_id))
|
||||
|
@ -65,24 +60,7 @@ class Account::Syncer
|
|||
calculated_balances
|
||||
end
|
||||
|
||||
def convert_foreign_records(balances)
|
||||
converted_balances = convert_balances(balances)
|
||||
load_balances(converted_balances)
|
||||
end
|
||||
|
||||
def load_balances(balances)
|
||||
current_time = Time.now
|
||||
account.balances.upsert_all(
|
||||
balances.map { |b| b.attributes
|
||||
.slice("date", "balance", "cash_balance", "currency")
|
||||
.merge("updated_at" => current_time) },
|
||||
unique_by: %i[account_id date currency]
|
||||
) if balances.any?
|
||||
end
|
||||
|
||||
def convert_balances(balances)
|
||||
return [] if account.currency == account.family.currency
|
||||
|
||||
def convert_records_to_family_currency(balances, holdings)
|
||||
from_currency = account.currency
|
||||
to_currency = account.family.currency
|
||||
|
||||
|
@ -92,7 +70,7 @@ class Account::Syncer
|
|||
start_date: balances.first.date
|
||||
)
|
||||
|
||||
balances.map do |balance|
|
||||
converted_balances = balances.map do |balance|
|
||||
exchange_rate = exchange_rates.find { |er| er.date == balance.date }
|
||||
|
||||
account.balances.build(
|
||||
|
@ -101,5 +79,41 @@ class Account::Syncer
|
|||
currency: to_currency
|
||||
) if exchange_rate.present?
|
||||
end
|
||||
|
||||
converted_holdings = holdings.map do |holding|
|
||||
exchange_rate = exchange_rates.find { |er| er.date == holding.date }
|
||||
|
||||
account.holdings.build(
|
||||
security: holding.security,
|
||||
date: holding.date,
|
||||
amount: exchange_rate.rate * holding.amount,
|
||||
currency: to_currency
|
||||
) if exchange_rate.present?
|
||||
end
|
||||
|
||||
Account.transaction do
|
||||
load_balances(converted_balances)
|
||||
load_holdings(converted_holdings)
|
||||
end
|
||||
end
|
||||
|
||||
def load_balances(balances = [])
|
||||
current_time = Time.now
|
||||
account.balances.upsert_all(
|
||||
balances.map { |b| b.attributes
|
||||
.slice("date", "balance", "cash_balance", "currency")
|
||||
.merge("updated_at" => current_time) },
|
||||
unique_by: %i[account_id date currency]
|
||||
)
|
||||
end
|
||||
|
||||
def load_holdings(holdings = [])
|
||||
current_time = Time.now
|
||||
account.holdings.upsert_all(
|
||||
holdings.map { |h| h.attributes
|
||||
.slice("date", "currency", "qty", "price", "amount", "security_id")
|
||||
.merge("updated_at" => current_time) },
|
||||
unique_by: %i[account_id security_id date currency]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue