class Account::Syncer def initialize(account, start_date: nil) @account = account @start_date = start_date end def run holdings = sync_holdings balances = sync_balances(holdings) account.reload update_account_info(balances, holdings) unless account.plaid_account_id.present? convert_records_to_family_currency(balances, holdings) unless account.currency == account.family.currency end private attr_reader :account, :start_date def account_start_date @account_start_date ||= (account.entries.chronological.first&.date || Date.current) - 1.day end def update_account_info(balances, holdings) new_balance = balances.sort_by(&:date).last.balance new_holdings_value = holdings.select { |h| h.date == Date.current }.sum(&:amount) new_cash_balance = new_balance - new_holdings_value account.update!( balance: new_balance, cash_balance: new_cash_balance ) end def sync_holdings calculator = Account::HoldingCalculator.new(account) calculated_holdings = calculator.calculate(reverse: account.plaid_account_id.present?) current_time = Time.now Account.transaction do 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)) end calculated_holdings end def sync_balances(holdings) calculator = Account::BalanceCalculator.new(account, holdings: holdings) calculated_balances = calculator.calculate(reverse: account.plaid_account_id.present?, start_date: start_date) Account.transaction do load_balances(calculated_balances) # Purge outdated balances account.balances.delete_by("date < ?", account_start_date) end calculated_balances end def convert_records_to_family_currency(balances, holdings) from_currency = account.currency to_currency = account.family.currency exchange_rates = ExchangeRate.find_rates( from: from_currency, to: to_currency, start_date: balances.first.date ) converted_balances = balances.map do |balance| exchange_rate = exchange_rates.find { |er| er.date == balance.date } account.balances.build( date: balance.date, balance: exchange_rate.rate * balance.balance, 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