mirror of
https://github.com/we-promise/sure.git
synced 2026-04-18 03:24:09 +00:00
Add gains by tax treatment to investment report with grouped subtype dropdown (#701)
* Add tax treatment metrics to reports, forms, and models - Implement `build_gains_by_tax_treatment` for grouping gains by tax treatment - Update investment performance view with tax treatment breakdown - Add tax treatment field to crypto and investments forms - Introduce `realized_gain_loss` calculation in the Trade model - Group investment subtypes by region for improved dropdown organization * Optimize investment performance report by reducing N+1 queries - Eager-load associations in `build_gains_by_tax_treatment` to minimize database queries - Preload holdings for realized gain/loss calculations in trades - Refactor views to standardize "no data" placeholder using translations - Adjust styling in tax treatment breakdown for improved layout * Enhance investment performance translations and optimize holdings lookup logic - Update `holdings_count` and `sells_count` translations to handle pluralization - Refactor views to use pluralized translation keys with count interpolation - Optimize preloaded holdings lookup in `Trade` to ensure deterministic selection using `select` and `max_by` * Refine preloaded holdings logic in `Trade` model - Treat empty preloaded holdings as authoritative to prevent unnecessary DB queries - Add explicit fallback behavior for database query when holdings are not preloaded --------- Co-authored-by: luckyPipewrench <luckypipewrench@proton.me>
This commit is contained in:
@@ -415,10 +415,75 @@ class ReportsController < ApplicationController
|
||||
period_contributions: period_totals.contributions,
|
||||
period_withdrawals: period_totals.withdrawals,
|
||||
top_holdings: investment_statement.top_holdings(limit: 5),
|
||||
accounts: investment_accounts.to_a
|
||||
accounts: investment_accounts.to_a,
|
||||
gains_by_tax_treatment: build_gains_by_tax_treatment(investment_statement)
|
||||
}
|
||||
end
|
||||
|
||||
def build_gains_by_tax_treatment(investment_statement)
|
||||
currency = Current.family.currency
|
||||
# Eager-load account and accountable to avoid N+1 when accessing tax_treatment
|
||||
current_holdings = investment_statement.current_holdings
|
||||
.includes(account: :accountable)
|
||||
.to_a
|
||||
|
||||
# Group holdings by tax treatment (from account)
|
||||
holdings_by_treatment = current_holdings.group_by { |h| h.account.tax_treatment || :taxable }
|
||||
|
||||
# Get sell trades in period with realized gains
|
||||
# Eager-load security, account, and accountable to avoid N+1
|
||||
sell_trades = Current.family.trades
|
||||
.joins(:entry)
|
||||
.where(entries: { date: @period.date_range })
|
||||
.where("trades.qty < 0")
|
||||
.includes(:security, entry: { account: :accountable })
|
||||
.to_a
|
||||
|
||||
# Preload holdings for all accounts that have sell trades to avoid N+1 in realized_gain_loss
|
||||
account_ids = sell_trades.map { |t| t.entry.account_id }.uniq
|
||||
holdings_by_account = Holding
|
||||
.where(account_id: account_ids)
|
||||
.where("date <= ?", @period.date_range.end)
|
||||
.order(date: :desc)
|
||||
.group_by(&:account_id)
|
||||
|
||||
# Inject preloaded holdings into trades for realized_gain_loss calculation
|
||||
sell_trades.each do |trade|
|
||||
trade.instance_variable_set(:@preloaded_holdings, holdings_by_account[trade.entry.account_id] || [])
|
||||
end
|
||||
|
||||
trades_by_treatment = sell_trades.group_by { |t| t.entry.account.tax_treatment || :taxable }
|
||||
|
||||
# Build metrics per treatment
|
||||
%i[taxable tax_deferred tax_exempt tax_advantaged].each_with_object({}) do |treatment, hash|
|
||||
holdings = holdings_by_treatment[treatment] || []
|
||||
trades = trades_by_treatment[treatment] || []
|
||||
|
||||
# Sum unrealized gains from holdings (only those with known cost basis)
|
||||
unrealized = holdings.sum do |h|
|
||||
trend = h.trend
|
||||
trend ? trend.value : 0
|
||||
end
|
||||
|
||||
# Sum realized gains from sell trades
|
||||
realized = trades.sum do |t|
|
||||
gain = t.realized_gain_loss
|
||||
gain ? gain.value : 0
|
||||
end
|
||||
|
||||
# Only include treatment groups that have some activity
|
||||
next if holdings.empty? && trades.empty?
|
||||
|
||||
hash[treatment] = {
|
||||
holdings: holdings,
|
||||
sell_trades: trades,
|
||||
unrealized_gain: Money.new(unrealized, currency),
|
||||
realized_gain: Money.new(realized, currency),
|
||||
total_gain: Money.new(unrealized + realized, currency)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def build_net_worth_metrics
|
||||
balance_sheet = Current.family.balance_sheet
|
||||
currency = Current.family.currency
|
||||
|
||||
Reference in New Issue
Block a user