Skip to content
This repository was archived by the owner on Jul 27, 2025. It is now read-only.

Commit dc8e1ab

Browse files
committed
Add confirmation dialog for balance reconciliation creates and updates
1 parent c1d98fe commit dc8e1ab

File tree

13 files changed

+180
-49
lines changed

13 files changed

+180
-49
lines changed

app/controllers/valuations_controller.rb

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
class ValuationsController < ApplicationController
22
include EntryableResource, StreamExtensions
33

4+
def confirm_create
5+
@account = Current.family.accounts.find(params.dig(:entry, :account_id))
6+
@entry = @account.entries.build(entry_params.merge(currency: @account.currency))
7+
8+
render :confirm_create
9+
end
10+
11+
def confirm_update
12+
@entry = Current.family.entries.find(params[:id])
13+
@account = @entry.account
14+
@entry.assign_attributes(entry_params.merge(currency: @account.currency))
15+
16+
render :confirm_update
17+
end
18+
419
def create
520
account = Current.family.accounts.find(params.dig(:entry, :account_id))
6-
7-
result = account.update_balance(
8-
balance: entry_params[:amount],
9-
date: entry_params[:date],
10-
currency: entry_params[:currency],
11-
notes: entry_params[:notes]
12-
)
21+
result = perform_balance_update(account, entry_params.merge(currency: account.currency))
1322

1423
if result.success?
1524
@success_message = result.updated? ? "Balance updated" : "No changes made. Account is already up to date."
@@ -25,12 +34,7 @@ def create
2534
end
2635

2736
def update
28-
result = @entry.account.update_balance(
29-
date: @entry.date,
30-
balance: entry_params[:amount],
31-
currency: entry_params[:currency],
32-
notes: entry_params[:notes]
33-
)
37+
result = perform_balance_update(@entry.account, entry_params.merge(currency: @entry.currency, existing_valuation_id: @entry.id))
3438

3539
if result.success?
3640
@entry.reload
@@ -59,4 +63,14 @@ def entry_params
5963
params.require(:entry)
6064
.permit(:date, :amount, :currency, :notes)
6165
end
66+
67+
def perform_balance_update(account, params)
68+
account.update_balance(
69+
balance: params[:amount],
70+
date: params[:date],
71+
currency: params[:currency],
72+
notes: params[:notes],
73+
existing_valuation_id: params[:existing_valuation_id]
74+
)
75+
end
6276
end

app/models/account.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@ def current_holdings
115115
end
116116

117117

118-
def update_balance(balance:, date: Date.current, currency: nil, notes: nil)
119-
Account::BalanceUpdater.new(self, balance:, currency:, date:, notes:).update
118+
def update_balance(balance:, date: Date.current, currency: nil, notes: nil, existing_valuation_id: nil)
119+
Account::BalanceUpdater.new(self, balance:, currency:, date:, notes:, existing_valuation_id:).update
120120
end
121121

122122
def start_date

app/models/account/balance_updater.rb

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
class Account::BalanceUpdater
2-
def initialize(account, balance:, currency: nil, date: Date.current, notes: nil)
2+
def initialize(account, balance:, currency: nil, date: Date.current, notes: nil, existing_valuation_id: nil)
33
@account = account
44
@balance = balance.to_d
55
@currency = currency
66
@date = date.to_date
77
@notes = notes
8+
@existing_valuation_id = existing_valuation_id
89
end
910

1011
def update
@@ -17,10 +18,15 @@ def update
1718
account.save!
1819
end
1920

20-
valuation_entry = account.entries.valuations.find_or_initialize_by(date: date) do |entry|
21-
entry.entryable = Valuation.new(kind: "reconciliation")
21+
valuation_entry = if existing_valuation_id
22+
account.entries.find(existing_valuation_id)
23+
else
24+
account.entries.valuations.find_or_initialize_by(date: date) do |entry|
25+
entry.entryable = Valuation.new(kind: "reconciliation")
26+
end
2227
end
2328

29+
valuation_entry.date = date
2430
valuation_entry.amount = balance
2531
valuation_entry.currency = currency if currency.present?
2632
valuation_entry.name = Valuation.build_reconciliation_name(account.accountable_type)
@@ -37,7 +43,7 @@ def update
3743
end
3844

3945
private
40-
attr_reader :account, :balance, :currency, :date, :notes
46+
attr_reader :account, :balance, :currency, :date, :notes, :existing_valuation_id
4147

4248
Result = Struct.new(:success?, :updated?, :error_message)
4349

app/models/investment.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,19 @@ def icon
2828
"line-chart"
2929
end
3030
end
31+
32+
def holdings_value_for_date(date)
33+
# Find the most recent holding for each security on or before the given date
34+
# Using a subquery to get the max date for each security
35+
account.holdings
36+
.where(currency: account.currency)
37+
.where("date <= ?", date)
38+
.where("(security_id, date) IN (
39+
SELECT security_id, MAX(date) as max_date
40+
FROM holdings
41+
WHERE account_id = ? AND date <= ?
42+
GROUP BY security_id
43+
)", account.id, date)
44+
.sum(:amount)
45+
end
3146
end

app/views/entries/_selection_bar.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
</div>
77

88
<div class="flex items-center gap-1 text-secondary">
9-
<%= form_with url: transactions_bulk_deletion_path, data: { turbo_confirm: true, turbo_frame: "_top" } do %>
9+
<%= form_with url: transactions_bulk_deletion_path, data: { turbo_confirm: CustomConfirm.for_resource_deletion("entry").to_data_attribute, turbo_frame: "_top" } do %>
1010
<button type="button" data-bulk-select-scope-param="bulk_delete" data-action="bulk-select#submitBulkRequest" class="p-1.5 group hover:bg-inverse flex items-center justify-center rounded-md" title="Delete">
1111
<%= icon "trash-2", class: "group-hover:text-inverse" %>
1212
</button>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<div class="space-y-4 text-sm text-secondary">
2+
<% if account.investment? %>
3+
<% holdings_value = account.investment.holdings_value_for_date(entry.date) %>
4+
<% brokerage_cash = entry.amount - holdings_value %>
5+
6+
<p>This will <%= action_verb %> the account value on <span class="font-medium text-primary"><%= entry.date.strftime("%B %d, %Y") %></span> to:</p>
7+
8+
<div class="bg-container rounded-lg p-4 space-y-2 border border-primary">
9+
<div class="flex justify-between">
10+
<span>Total account value</span>
11+
<span class="font-medium text-primary"><%= entry.amount_money.format %></span>
12+
</div>
13+
<div class="flex justify-between text-xs">
14+
<span>Holdings value</span>
15+
<span><%= Money.new(holdings_value, account.currency).format %></span>
16+
</div>
17+
<div class="flex justify-between text-xs">
18+
<span>Brokerage cash</span>
19+
<span class="<%= brokerage_cash.negative? ? "text-red-500" : "text-green-500" %>"><%= Money.new(brokerage_cash, account.currency).format %></span>
20+
</div>
21+
</div>
22+
<% else %>
23+
<p><%= action_verb.capitalize %>
24+
<% if account.depository? %>
25+
account balance
26+
<% elsif account.credit_card? %>
27+
credit card balance
28+
<% elsif account.loan? %>
29+
loan balance
30+
<% elsif account.property? %>
31+
property value
32+
<% elsif account.vehicle? %>
33+
vehicle value
34+
<% elsif account.crypto? %>
35+
crypto balance
36+
<% elsif account.other_asset? %>
37+
asset value
38+
<% elsif account.other_liability? %>
39+
liability balance
40+
<% else %>
41+
balance
42+
<% end %>
43+
on <span class="font-medium text-primary"><%= entry.date.strftime("%B %d, %Y") %></span> to
44+
<span class="font-medium text-primary"><%= entry.amount_money.format %></span>.
45+
</p>
46+
<% end %>
47+
48+
<p>All future transactions and balances will be recalculated based on this <%= is_update ? "change" : "update" %>.</p>
49+
</div>

app/views/valuations/_form.html.erb

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<%= render DialogComponent.new do |dialog| %>
2+
<% dialog.with_header(title: "Confirm new balance") %>
3+
<% dialog.with_body do %>
4+
<%= styled_form_with model: @entry, url: valuations_path, class: "space-y-4", data: { turbo: false } do |form| %>
5+
<%= form.hidden_field :account_id %>
6+
<%= form.hidden_field :date %>
7+
<%= form.hidden_field :amount %>
8+
<%= form.hidden_field :currency %>
9+
<%= form.hidden_field :notes %>
10+
11+
<%= render "confirmation_contents",
12+
account: @account,
13+
entry: @entry,
14+
action_verb: "set",
15+
is_update: false %>
16+
17+
<%= form.submit "Confirm" %>
18+
<% end %>
19+
<% end %>
20+
<% end %>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<%= render DialogComponent.new do |dialog| %>
2+
<% dialog.with_header(title: "Update balance") %>
3+
<% dialog.with_body do %>
4+
<%= styled_form_with model: @entry, url: valuation_path(@entry), method: :patch, class: "space-y-4", data: { turbo: false } do |form| %>
5+
<%= form.hidden_field :date %>
6+
<%= form.hidden_field :amount %>
7+
<%= form.hidden_field :currency %>
8+
<%= form.hidden_field :notes %>
9+
10+
<%= render "confirmation_contents",
11+
account: @account,
12+
entry: @entry,
13+
action_verb: "update",
14+
is_update: true %>
15+
16+
<%= form.submit "Update" %>
17+
<% end %>
18+
<% end %>
19+
<% end %>

app/views/valuations/new.html.erb

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
<%= render DialogComponent.new do |dialog| %>
22
<% dialog.with_header(title: t(".title")) %>
33
<% dialog.with_body do %>
4-
<%= render "form", entry: @entry, error_message: @error_message %>
4+
<%= styled_form_with model: @entry, url: confirm_create_valuations_path, class: "space-y-4" do |form| %>
5+
<%= form.hidden_field :account_id %>
6+
7+
<% if @error_message.present? %>
8+
<%= render AlertComponent.new(message: @error_message, variant: :error) %>
9+
<% end %>
10+
11+
<div class="space-y-3">
12+
<%= form.date_field :date, label: true, required: true, value: Date.current, min: Entry.min_supported_date, max: Date.current %>
13+
<%= form.money_field :amount, label: t(".amount"), required: true, disable_currency: true %>
14+
</div>
15+
16+
<%= form.submit t(".submit") %>
17+
<% end %>
518
<% end %>
619
<% end %>

0 commit comments

Comments
 (0)