1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-07-19 13:19:39 +02:00
Maybe/app/models/account.rb

103 lines
3.4 KiB
Ruby
Raw Normal View History

2024-02-02 09:05:04 -06:00
class Account < ApplicationRecord
include Syncable
include Monetizable
broadcasts_refreshes
validates :family, presence: true
2024-02-02 09:05:04 -06:00
belongs_to :family
belongs_to :institution, optional: true
has_many :balances, dependent: :destroy
has_many :valuations, dependent: :destroy
has_many :transactions, dependent: :destroy
has_many :imports, dependent: :destroy
2024-02-02 11:09:31 -06:00
monetize :balance
enum :status, { ok: "ok", syncing: "syncing", error: "error" }, validate: true
scope :active, -> { where(is_active: true) }
scope :assets, -> { where(classification: "asset") }
scope :liabilities, -> { where(classification: "liability") }
scope :alphabetically, -> { order(:name) }
scope :ungrouped, -> { where(institution_id: nil) }
delegated_type :accountable, types: Accountable::TYPES, dependent: :destroy
2024-02-02 23:06:29 +00:00
def balance_on(date)
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) } ]
end
def self.some_syncing?
exists?(status: "syncing")
end
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, 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_group(type, currency)
self.where(accountable_type: type).each do |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
def self.create_with_optional_start_balance!(attributes:, start_date: nil, start_balance: nil)
account = self.new(attributes.except(:accountable_type))
account.accountable = Accountable.from_type(attributes[:accountable_type])&.new
# Always build the initial valuation
account.valuations.build(date: Date.current, value: attributes[:balance], currency: account.currency)
# Conditionally build the optional start valuation
if start_date.present? && start_balance.present?
account.valuations.build(date: start_date, value: start_balance, currency: account.currency)
end
account.save!
account
end
2024-02-02 09:05:04 -06:00
end