1
0
Fork 0
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)
Some checks are pending
Publish Docker image / ci (push) Waiting to run
Publish Docker image / Build docker image (push) Blocked by required conditions

* Plaid sync tests and multi-currency investment support

* Fix system test

* Cleanup

* Remove data migration
This commit is contained in:
Zach Gollwitzer 2024-12-12 08:56:52 -05:00 committed by GitHub
parent b2a56aefc1
commit 800eb4c146
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 406 additions and 165 deletions

View file

@ -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

View file

@ -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

View file

@ -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