mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-08 23:15:24 +02:00
Simplify balance trends in activity view
This commit is contained in:
parent
d72e026aa2
commit
993c147aa9
4 changed files with 27 additions and 95 deletions
|
@ -5,90 +5,26 @@
|
||||||
class Balance::TrendCalculator
|
class Balance::TrendCalculator
|
||||||
BalanceTrend = Struct.new(:trend, :cash, keyword_init: true)
|
BalanceTrend = Struct.new(:trend, :cash, keyword_init: true)
|
||||||
|
|
||||||
class << self
|
def initialize(balances)
|
||||||
def for(entries)
|
|
||||||
return nil if entries.blank?
|
|
||||||
|
|
||||||
account = entries.first.account
|
|
||||||
|
|
||||||
date_range = entries.minmax_by(&:date)
|
|
||||||
min_entry_date, max_entry_date = date_range.map(&:date)
|
|
||||||
|
|
||||||
# In case view is filtered and there are entry gaps, refetch all entries in range
|
|
||||||
all_entries = account.entries.where(date: min_entry_date..max_entry_date).chronological.to_a
|
|
||||||
balances = account.balances.where(date: (min_entry_date - 1.day)..max_entry_date).chronological.to_a
|
|
||||||
holdings = account.holdings.where(date: (min_entry_date - 1.day)..max_entry_date).to_a
|
|
||||||
|
|
||||||
new(all_entries, balances, holdings)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(entries, balances, holdings)
|
|
||||||
@entries = entries
|
|
||||||
@balances = balances
|
@balances = balances
|
||||||
@holdings = holdings
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def trend_for(entry)
|
def trend_for(date)
|
||||||
intraday_balance = nil
|
balance = @balances.find { |b| b.date == date }
|
||||||
intraday_cash_balance = nil
|
prior_balance = @balances.find { |b| b.date == date - 1.day }
|
||||||
|
|
||||||
start_of_day_balance = balances.find { |b| b.date == entry.date - 1.day && b.currency == entry.currency }
|
return BalanceTrend.new(trend: nil) unless balance.present?
|
||||||
end_of_day_balance = balances.find { |b| b.date == entry.date && b.currency == entry.currency }
|
|
||||||
|
|
||||||
return BalanceTrend.new(trend: nil) if start_of_day_balance.blank? || end_of_day_balance.blank?
|
|
||||||
|
|
||||||
todays_holdings_value = holdings.select { |h| h.date == entry.date }.sum(&:amount)
|
|
||||||
|
|
||||||
prior_balance = start_of_day_balance.balance
|
|
||||||
prior_cash_balance = start_of_day_balance.cash_balance
|
|
||||||
current_balance = nil
|
|
||||||
current_cash_balance = nil
|
|
||||||
|
|
||||||
todays_entries = entries.select { |e| e.date == entry.date }
|
|
||||||
|
|
||||||
todays_entries.each_with_index do |e, idx|
|
|
||||||
if e.valuation?
|
|
||||||
current_balance = e.amount
|
|
||||||
current_cash_balance = e.amount
|
|
||||||
else
|
|
||||||
multiplier = e.account.liability? ? 1 : -1
|
|
||||||
balance_change = e.trade? ? 0 : multiplier * e.amount
|
|
||||||
cash_change = multiplier * e.amount
|
|
||||||
|
|
||||||
current_balance = prior_balance + balance_change
|
|
||||||
current_cash_balance = prior_cash_balance + cash_change
|
|
||||||
end
|
|
||||||
|
|
||||||
if e.id == entry.id
|
|
||||||
# Final entry should always match the end-of-day balances
|
|
||||||
if idx == todays_entries.size - 1
|
|
||||||
intraday_balance = end_of_day_balance.balance
|
|
||||||
intraday_cash_balance = end_of_day_balance.cash_balance
|
|
||||||
else
|
|
||||||
intraday_balance = current_balance
|
|
||||||
intraday_cash_balance = current_cash_balance
|
|
||||||
end
|
|
||||||
|
|
||||||
break
|
|
||||||
else
|
|
||||||
prior_balance = current_balance
|
|
||||||
prior_cash_balance = current_cash_balance
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return BalanceTrend.new(trend: nil) unless intraday_balance.present?
|
|
||||||
|
|
||||||
BalanceTrend.new(
|
BalanceTrend.new(
|
||||||
trend: Trend.new(
|
trend: Trend.new(
|
||||||
current: Money.new(intraday_balance, entry.currency),
|
current: Money.new(balance.balance, balance.currency),
|
||||||
previous: Money.new(prior_balance, entry.currency),
|
previous: Money.new(prior_balance.balance, balance.currency),
|
||||||
favorable_direction: entry.account.favorable_direction
|
favorable_direction: balance.account.favorable_direction
|
||||||
),
|
),
|
||||||
cash: Money.new(intraday_cash_balance, entry.currency),
|
cash: Money.new(balance.cash_balance, balance.currency),
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
attr_reader :entries, :balances, :holdings
|
attr_reader :balances
|
||||||
end
|
end
|
||||||
|
|
|
@ -77,10 +77,11 @@
|
||||||
<div>
|
<div>
|
||||||
<div class="rounded-tl-lg rounded-tr-lg bg-container border-alpha-black-25 shadow-xs">
|
<div class="rounded-tl-lg rounded-tr-lg bg-container border-alpha-black-25 shadow-xs">
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<% calculator = Balance::TrendCalculator.for(@entries) %>
|
<% calculator = Balance::TrendCalculator.new(@account.balances) %>
|
||||||
|
|
||||||
<%= entries_by_date(@entries) do |entries| %>
|
<%= entries_by_date(@entries) do |entries| %>
|
||||||
<% entries.each do |entry| %>
|
<% entries.each_with_index do |entry, index| %>
|
||||||
<%= render entry, balance_trend: calculator&.trend_for(entry), view_ctx: "account" %>
|
<%= render entry, balance_trend: index == 0 ? calculator.trend_for(entry.date) : nil, view_ctx: "account" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,12 +4,11 @@
|
||||||
|
|
||||||
<%= turbo_frame_tag dom_id(entry) do %>
|
<%= turbo_frame_tag dom_id(entry) do %>
|
||||||
<%= turbo_frame_tag dom_id(transaction) do %>
|
<%= turbo_frame_tag dom_id(transaction) do %>
|
||||||
<div class="grid grid-cols-12 items-center text-primary text-sm font-medium p-4 md:p-4
|
<div class="grid grid-cols-12 items-center text-primary text-sm font-medium p-4 lg:p-4
|
||||||
<%= @focused_record == entry || @focused_record == transaction ?
|
<%= @focused_record == entry || @focused_record == transaction ?
|
||||||
"border border-gray-900 rounded-lg" : "" %>">
|
"border border-gray-900 rounded-lg" : "" %>">
|
||||||
|
|
||||||
<div class="pr-4 md:pr-10 flex items-center gap-3 md:gap-4
|
<div class="pr-4 lg:pr-10 flex items-center gap-3 lg:gap-4 col-span-8 lg:col-span-6">
|
||||||
<%= balance_trend ? "col-span-8 md:col-span-6" : "col-span-8" %>">
|
|
||||||
<%= check_box_tag dom_id(entry, "selection"),
|
<%= check_box_tag dom_id(entry, "selection"),
|
||||||
disabled: transaction.transfer?,
|
disabled: transaction.transfer?,
|
||||||
class: "checkbox checkbox--light",
|
class: "checkbox checkbox--light",
|
||||||
|
@ -58,7 +57,7 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-secondary text-xs font-normal hidden md:block">
|
<div class="text-secondary text-xs font-normal hidden lg:block">
|
||||||
<% if transaction.transfer? %>
|
<% if transaction.transfer? %>
|
||||||
<%= render "transfers/account_links",
|
<%= render "transfers/account_links",
|
||||||
transfer: transaction.transfer,
|
transfer: transaction.transfer,
|
||||||
|
@ -76,26 +75,24 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="hidden md:flex items-center gap-1 col-span-2">
|
<div class="hidden lg:flex items-center gap-1 col-span-2">
|
||||||
<%= render "transactions/transaction_category", transaction: transaction %>
|
<%= render "transactions/transaction_category", transaction: transaction %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-span-4 md:col-span-2 ml-auto text-right">
|
<div class="col-span-4 lg:col-span-2 ml-auto text-right">
|
||||||
<%= content_tag :p,
|
<%= content_tag :p,
|
||||||
transaction.transfer? && view_ctx == "global" ? "+/- #{format_money(entry.amount_money.abs)}" : format_money(-entry.amount_money),
|
transaction.transfer? && view_ctx == "global" ? "+/- #{format_money(entry.amount_money.abs)}" : format_money(-entry.amount_money),
|
||||||
class: ["text-green-600": entry.amount.negative?] %>
|
class: ["text-green-600": entry.amount.negative?] %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<% if balance_trend %>
|
<div class="col-span-2 justify-self-end hidden lg:block">
|
||||||
<div class="col-span-2 justify-self-end hidden md:block">
|
<% if balance_trend&.trend %>
|
||||||
<% if balance_trend.trend %>
|
<%= tag.p format_money(balance_trend.trend.current),
|
||||||
<%= tag.p format_money(balance_trend.trend.current),
|
|
||||||
class: "font-medium text-sm text-primary" %>
|
class: "font-medium text-sm text-primary" %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= tag.p "--", class: "font-medium text-sm text-gray-400" %>
|
<%= tag.p "--", class: "font-medium text-sm text-gray-400" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -14,9 +14,7 @@
|
||||||
data: { id: entry.id, "bulk-select-target": "row", action: "bulk-select#toggleRowSelection" } %>
|
data: { id: entry.id, "bulk-select-target": "row", action: "bulk-select#toggleRowSelection" } %>
|
||||||
|
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<%= tag.div class: "w-6 h-6 rounded-full p-1.5 flex items-center justify-center", style: "color: #{color}" do %>
|
<%= render FilledIconComponent.new(icon: icon, size: "sm", hex_color: color, rounded: true) %>
|
||||||
<%= icon(icon, size: "sm", color: "current") %>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<div class="truncate text-primary">
|
<div class="truncate text-primary">
|
||||||
<%= link_to entry.name,
|
<%= link_to entry.name,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue