1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-06 05:55:21 +02:00

Initial data objects

This commit is contained in:
Zach Gollwitzer 2025-07-18 07:33:03 -04:00
parent 8c97c9d31a
commit df367b420a
4 changed files with 85 additions and 4 deletions

View file

@ -0,0 +1,70 @@
# Data used to build the paginated feed of account "activity" (events like transfers, deposits, withdrawals, etc.)
# This data object is useful for avoiding N+1 queries and having an easy way to pass around the required data to the
# activity feed component in controllers and background jobs that refresh it.
class Account::ActivityFeedData
attr_reader :account
def initialize(account, entries)
@account = account
@entries = entries
end
# We read balances so we can show "start of day" -> "end of day" balances for each entry date group in the feed
def balances
end
def transfers
return [] unless has_transfers?
@transfers ||= Transfer.where(inflow_transaction_id: transaction_ids).or(Transfer.where(outflow_transaction_id: transaction_ids))
end
# If the account has entries denominated in a different currency than the main account, we attach necessary
# exchange rates required to "roll up" the entry group balance into the normal account currency.
def exchange_rates
return [] unless needs_exchange_rates?
@exchange_rates ||= begin
rate_requirements = required_exchange_rates
return [] if rate_requirements.empty?
# Build a single SQL query with all date/currency pairs
conditions = rate_requirements.map do |req|
"(date = ? AND from_currency = ? AND to_currency = ?)"
end.join(" OR ")
# Flatten the parameters array in the same order
params = rate_requirements.flat_map do |req|
[ req.date, req.from, req.to ]
end
ExchangeRate.where(conditions, *params)
end
end
private
attr_reader :entries
RequiredExchangeRate = Data.define(:date, :from, :to)
def needs_exchange_rates?
entries.any? { |entry| entry.currency != account.currency }
end
def required_exchange_rates
multi_currency_entries = entries.select { |entry| entry.currency != account.currency }
multi_currency_entries.map do |entry|
RequiredExchangeRate.new(date: entry.date, from: entry.currency, to: account.currency)
end.uniq
end
def has_transfers?
entries.any? { |entry| entry.transaction? && entry.transaction.transfer? }
end
def transaction_ids
entries.select { |entry| entry.transaction? }.pluck(:entryable_id)
end
end

View file

@ -3,13 +3,13 @@ module Account::Reconcileable
def create_reconciliation(balance:, date:, dry_run: false)
result = reconciliation_manager.reconcile_balance(balance: balance, date: date, dry_run: dry_run)
sync_later if result.success?
sync_later if result.success? && !dry_run
result
end
def update_reconciliation(existing_valuation_entry, balance:, date:, dry_run: false)
result = reconciliation_manager.reconcile_balance(balance: balance, date: date, existing_valuation_entry: existing_valuation_entry, dry_run: dry_run)
sync_later if result.success?
sync_later if result.success? && !dry_run
result
end

View file

@ -0,0 +1,11 @@
class TransactionsFeedData
attr_reader :family
def initialize(family, transactions)
@family = family
@transactions = transactions
end
private
attr_reader :transactions
end

View file

@ -2,8 +2,8 @@
<div class="space-y-4 text-sm text-secondary">
<% if account.investment? %>
<% holdings_value = reconciliation_dry_run.new_balance - reconciliation_dry_run.new_cash_balance %>
<% brokerage_cash = reconciliation_dry_run.new_cash_balance %>
<% brokerage_cash = reconciliation_dry_run.new_cash_balance || 0 %>
<% holdings_value = reconciliation_dry_run.new_balance - brokerage_cash %>
<p>This will <%= action_verb %> the account value on <span class="font-medium text-primary"><%= entry.date.strftime("%B %d, %Y") %></span> to:</p>