1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-07-19 05:09:38 +02:00
Maybe/app/models/plaid_account.rb

154 lines
4.6 KiB
Ruby
Raw Normal View History

class PlaidAccount < ApplicationRecord
include Plaidable
TYPE_MAPPING = {
"depository" => Depository,
"credit" => CreditCard,
"loan" => Loan,
"investment" => Investment,
"other" => OtherAsset
}
belongs_to :plaid_item
has_one :account, dependent: :destroy
accepts_nested_attributes_for :account
class << self
def find_or_create_from_plaid_data!(plaid_data, family)
find_or_create_by!(plaid_id: plaid_data.account_id) do |a|
a.account = family.accounts.new(
name: plaid_data.name,
balance: plaid_data.balances.current,
currency: plaid_data.balances.iso_currency_code,
accountable: TYPE_MAPPING[plaid_data.type].new
)
end
end
end
def sync_account_data!(plaid_account_data)
update!(
current_balance: plaid_account_data.balances.current,
available_balance: plaid_account_data.balances.available,
currency: plaid_account_data.balances.iso_currency_code,
plaid_type: plaid_account_data.type,
plaid_subtype: plaid_account_data.subtype,
account_attributes: {
id: account.id,
2024-12-10 18:54:09 -05:00
# Plaid guarantees at least 1 of these
balance: plaid_account_data.balances.current || plaid_account_data.balances.available,
cash_balance: derive_plaid_cash_balance(plaid_account_data.balances)
}
)
end
def sync_investments!(transactions:, holdings:, securities:)
PlaidInvestmentSync.new(self).sync!(transactions:, holdings:, securities:)
end
def sync_credit_data!(plaid_credit_data)
account.update!(
accountable_attributes: {
id: account.accountable_id,
minimum_payment: plaid_credit_data.minimum_payment_amount,
apr: plaid_credit_data.aprs.first&.apr_percentage
}
)
end
def sync_mortgage_data!(plaid_mortgage_data)
create_initial_loan_balance(plaid_mortgage_data)
account.update!(
accountable_attributes: {
id: account.accountable_id,
rate_type: plaid_mortgage_data.interest_rate&.type,
interest_rate: plaid_mortgage_data.interest_rate&.percentage
}
)
end
def sync_student_loan_data!(plaid_student_loan_data)
create_initial_loan_balance(plaid_student_loan_data)
account.update!(
accountable_attributes: {
id: account.accountable_id,
rate_type: "fixed",
interest_rate: plaid_student_loan_data.interest_rate_percentage
}
)
end
def sync_transactions!(added:, modified:, removed:)
added.each do |plaid_txn|
account.entries.find_or_create_by!(plaid_id: plaid_txn.transaction_id) do |t|
t.name = plaid_txn.name
t.amount = plaid_txn.amount
t.currency = plaid_txn.iso_currency_code
t.date = plaid_txn.date
t.entryable = Account::Transaction.new(
category: get_category(plaid_txn.personal_finance_category.primary),
merchant: get_merchant(plaid_txn.merchant_name)
)
end
end
modified.each do |plaid_txn|
existing_txn = account.entries.find_by(plaid_id: plaid_txn.transaction_id)
existing_txn.update!(
amount: plaid_txn.amount,
date: plaid_txn.date
)
end
removed.each do |plaid_txn|
account.entries.find_by(plaid_id: plaid_txn.transaction_id)&.destroy
end
end
private
def family
plaid_item.family
end
def create_initial_loan_balance(loan_data)
if loan_data.origination_principal_amount.present? && loan_data.origination_date.present?
account.entries.find_or_create_by!(plaid_id: loan_data.account_id) do |e|
e.name = "Initial Principal"
e.amount = loan_data.origination_principal_amount
e.currency = account.currency
e.date = loan_data.origination_date
e.entryable = Account::Valuation.new
end
end
end
# See https://plaid.com/documents/transactions-personal-finance-category-taxonomy.csv
def get_category(plaid_category)
ignored_categories = [ "BANK_FEES", "TRANSFER_IN", "TRANSFER_OUT", "LOAN_PAYMENTS", "OTHER" ]
return nil if ignored_categories.include?(plaid_category)
family.categories.find_or_create_by!(name: plaid_category.titleize)
end
def get_merchant(plaid_merchant_name)
return nil if plaid_merchant_name.blank?
family.merchants.find_or_create_by!(name: plaid_merchant_name)
end
2024-12-10 18:54:09 -05:00
def derive_plaid_cash_balance(plaid_balances)
if account.investment?
plaid_balances.available || 0
else
# For now, we will not distinguish between "cash" and "overall" balance for non-investment accounts
plaid_balances.current || plaid_balances.available
end
end
end