mirror of
https://github.com/we-promise/sure
synced 2026-04-25 17:15:07 +02:00
* Initial account sharing changes * Update schema.rb * Update schema.rb * Change sharing UI to modal * UX fixes and sharing controls * Scope include in finances better * Update totals.rb * Update totals.rb * Scope reports to finance account scope * Update impersonation_sessions_controller_test.rb * Review fixes * Update schema.rb * Update show.html.erb * FIX db validation * Refine edit permissions * Review items * Review * Review * Add application level helper * Critical review * Address remaining review items * Fix modals * more scoping * linter * small UI fix * Fix: Sync broadcasts push unscoped balance sheet to all users * Update sync_complete_event.rb The fix removes the sidebar broadcasts (which rendered unscoped account groups using family.balance_sheet without user context) along with the now-unused sidebar_targets, account_group, and family_balance_sheet private methods. The sidebar will still update correctly — when the sync completes, Family::SyncCompleteEvent#broadcast fires family.broadcast_refresh, which triggers a morph-based page refresh for each user with their own authenticated session, rendering properly scoped sidebar content.
99 lines
3.2 KiB
Ruby
99 lines
3.2 KiB
Ruby
class BalanceSheet::AccountTotals
|
|
def initialize(family, user: nil, sync_status_monitor:)
|
|
@family = family
|
|
@user = user
|
|
@sync_status_monitor = sync_status_monitor
|
|
end
|
|
|
|
def asset_accounts
|
|
@asset_accounts ||= account_rows.filter { |t| t.classification == "asset" }
|
|
end
|
|
|
|
def liability_accounts
|
|
@liability_accounts ||= account_rows.filter { |t| t.classification == "liability" }
|
|
end
|
|
|
|
private
|
|
attr_reader :family, :user, :sync_status_monitor
|
|
|
|
AccountRow = Data.define(:account, :converted_balance, :is_syncing, :included_in_finances) do
|
|
def syncing? = is_syncing
|
|
def included_in_finances? = included_in_finances
|
|
|
|
# Allows Rails path helpers to generate URLs from the wrapper
|
|
def to_param = account.to_param
|
|
delegate_missing_to :account
|
|
end
|
|
|
|
def visible_accounts
|
|
@visible_accounts ||= begin
|
|
scope = family.accounts.visible.with_attached_logo
|
|
scope = scope.accessible_by(user) if user
|
|
scope
|
|
end
|
|
end
|
|
|
|
def finance_account_ids
|
|
@finance_account_ids ||= if user
|
|
family.accounts.included_in_finances_for(user).pluck(:id).to_set
|
|
else
|
|
nil
|
|
end
|
|
end
|
|
|
|
# Wraps each account in an AccountRow with its converted balance and sync status.
|
|
def account_rows
|
|
@account_rows ||= accounts.map do |account|
|
|
AccountRow.new(
|
|
account: account,
|
|
converted_balance: converted_balance_for(account),
|
|
is_syncing: sync_status_monitor.account_syncing?(account),
|
|
included_in_finances: finance_account_ids.nil? || finance_account_ids.include?(account.id)
|
|
)
|
|
end
|
|
end
|
|
|
|
# Returns the cache key for storing visible account IDs, invalidated on data updates.
|
|
def cache_key
|
|
shares_version = user ? AccountShare.where(user: user).maximum(:updated_at)&.to_i : nil
|
|
family.build_cache_key(
|
|
[ "balance_sheet_account_ids", user&.id, shares_version ].compact.join("_"),
|
|
invalidate_on_data_updates: true
|
|
)
|
|
end
|
|
|
|
# Loads visible accounts, caching their IDs to speed up subsequent requests.
|
|
# On cache miss, loads records once and writes IDs; on hit, filters by cached IDs.
|
|
def accounts
|
|
@accounts ||= begin
|
|
ids = Rails.cache.read(cache_key)
|
|
|
|
if ids
|
|
visible_accounts.where(id: ids).to_a
|
|
else
|
|
records = visible_accounts.to_a
|
|
Rails.cache.write(cache_key, records.map(&:id))
|
|
records
|
|
end
|
|
end
|
|
end
|
|
|
|
# Batch-fetches today's exchange rates for all foreign currencies present in accounts.
|
|
# @return [Hash{String => Numeric}] currency code to rate mapping
|
|
def exchange_rates
|
|
@exchange_rates ||= begin
|
|
foreign_currencies = accounts.filter_map { |a| a.currency if a.currency != family.currency }
|
|
ExchangeRate.rates_for(foreign_currencies, to: family.currency, date: Date.current)
|
|
end
|
|
end
|
|
|
|
# Converts an account's balance to the family's currency using pre-fetched exchange rates.
|
|
# @return [BigDecimal] balance in the family's currency
|
|
def converted_balance_for(account)
|
|
return account.balance if account.currency == family.currency
|
|
|
|
rate = exchange_rates[account.currency]
|
|
account.balance * rate
|
|
end
|
|
end
|