diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index ce3d120693..ac4b54926c 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -53,10 +53,13 @@ def update
end
def create
- @account = Current.family.accounts.build(account_params.except(:accountable_type))
+ @account = Current.family.accounts.build(account_params.except(:accountable_type, :start_date))
@account.accountable = Accountable.from_type(account_params[:accountable_type])&.new
if @account.save
+ @valuation = @account.valuations.new(date: account_params[:start_date] || Date.today, value: @account.balance, currency: @account.currency)
+ @valuation.save!
+
redirect_to accounts_path, notice: t(".success")
else
render "new", status: :unprocessable_entity
@@ -86,6 +89,6 @@ def set_account
end
def account_params
- params.require(:account).permit(:name, :accountable_type, :balance, :currency, :subtype, :is_active)
+ params.require(:account).permit(:name, :accountable_type, :balance, :start_date, :currency, :subtype, :is_active)
end
end
diff --git a/app/models/account/balance/calculator.rb b/app/models/account/balance/calculator.rb
index eaa597348b..8d086d562e 100644
--- a/app/models/account/balance/calculator.rb
+++ b/app/models/account/balance/calculator.rb
@@ -13,7 +13,7 @@ def initialize(account, options = {})
def calculate
prior_balance = implied_start_balance
- calculated_balances = ((@calc_start_date + 1.day)...Date.current).map do |date|
+ calculated_balances = ((@calc_start_date + 1.day)..Date.current).map do |date|
valuation = normalized_valuations.find { |v| v["date"] == date }
if valuation
@@ -30,8 +30,7 @@ def calculate
@daily_balances = [
{ date: @calc_start_date, balance: implied_start_balance, currency: @account.currency, updated_at: Time.current },
- *calculated_balances,
- { date: Date.current, balance: @account.balance, currency: @account.currency, updated_at: Time.current } # Last balance must always match "source of truth"
+ *calculated_balances
]
if @account.foreign_currency?
@@ -66,10 +65,7 @@ def normalize_entries_to_account_currency(entries, value_key)
value = entry.send(value_key)
if currency != @account.currency
- rate = ExchangeRate.find_by(base_currency: currency, converted_currency: @account.currency, date: date)
- raise "Rate for #{currency} to #{@account.currency} not found" unless rate
-
- value *= rate.rate
+ value = ExchangeRate.convert(value:, from: currency, to: @account.currency, date:)
currency = @account.currency
end
diff --git a/app/models/account/syncable.rb b/app/models/account/syncable.rb
index 1afd18c8f3..e295b8ea7b 100644
--- a/app/models/account/syncable.rb
+++ b/app/models/account/syncable.rb
@@ -16,6 +16,10 @@ def sync(start_date = nil)
calculator.calculate
self.balances.upsert_all(calculator.daily_balances, unique_by: :index_account_balances_on_account_id_date_currency_unique)
self.balances.where("date < ?", effective_start_date).delete_all
+ new_balance = calculator.daily_balances.select { |b| b[:currency] == self.currency }.last[:balance]
+ self.balance = new_balance
+ self.save!
+
update!(status: "ok", last_sync_date: Date.today)
rescue => e
update!(status: "error")
diff --git a/app/models/exchange_rate.rb b/app/models/exchange_rate.rb
index 9f4d04fc30..5dc04eccfa 100644
--- a/app/models/exchange_rate.rb
+++ b/app/models/exchange_rate.rb
@@ -18,5 +18,12 @@ def find_rate_or_fetch(from:, to:, date:)
def get_rate_series(from, to, date_range)
where(base_currency: from, converted_currency: to, date: date_range).order(:date)
end
+
+ def convert(value:, from:, to:, date:)
+ rate = ExchangeRate.find_by(base_currency: from, converted_currency: to, date:)
+ raise "Conversion from: #{from} to: #{to} on: #{date} not found" unless rate
+
+ value * rate.rate
+ end
end
end
diff --git a/app/views/accounts/new.html.erb b/app/views/accounts/new.html.erb
index e7e000f5f6..52c64ce593 100644
--- a/app/views/accounts/new.html.erb
+++ b/app/views/accounts/new.html.erb
@@ -67,9 +67,10 @@
<%= form_with model: @account, url: accounts_path, scope: :account, html: { class: "m-5 mt-1 flex flex-col justify-between grow", data: { turbo: false } } do |f| %>
<%= f.hidden_field :accountable_type %>
- <%= f.text_field :name, placeholder: t("accounts.new.name.placeholder"), required: "required", label: t("accounts.new.name.label"), autofocus: true %>
+ <%= f.text_field :name, placeholder: t(".name.placeholder"), required: "required", label: t(".name.label"), autofocus: true %>
<%= render "accounts/#{permitted_accountable_partial(@account.accountable_type)}", f: f %>
- <%= f.money_field :balance_money, label: "Balance", required: "required" %>
+ <%= f.money_field :balance_money, label: t(".balance.label"), required: "required" %>
+ <%= f.date_field :date, label: t(".start_date.label"), required: true, max: Date.today, value: Date.today %>
<%= f.submit "Add #{@account.accountable.model_name.human.downcase}" %>
<% end %>
diff --git a/app/views/transactions/_form.html.erb b/app/views/transactions/_form.html.erb
index 2db4d837e5..1741b1501a 100644
--- a/app/views/transactions/_form.html.erb
+++ b/app/views/transactions/_form.html.erb
@@ -12,7 +12,7 @@
<%= f.collection_select :account_id, Current.family.accounts.alphabetically, :id, :name, { prompt: t(".account_prompt"), label: t(".account") }, required: true %>
<%= f.money_field :amount_money, label: t(".amount"), required: true %>
<%= f.collection_select :category_id, Current.family.transaction_categories.alphabetically, :id, :name, { prompt: t(".category_prompt"), label: t(".category") }, required: true %>
- <%= f.date_field :date, label: t(".date"), required: true %>
+ <%= f.date_field :date, label: t(".date"), required: true, max: Date.today %>
diff --git a/app/views/transactions/show.html.erb b/app/views/transactions/show.html.erb
index 2311657bb5..ad34804b2c 100644
--- a/app/views/transactions/show.html.erb
+++ b/app/views/transactions/show.html.erb
@@ -13,7 +13,7 @@
<%= lucide_icon("chevron-right", class: "group-open:hidden text-gray-500 w-5 h-5") %>
- <%= f.date_field :date, label: "Date", "data-auto-submit-form-target": "auto" %>
+ <%= f.date_field :date, label: "Date", max: Date.today, "data-auto-submit-form-target": "auto" %>
<%= f.collection_select :account_id, Current.family.accounts, :id, :name, { prompt: "Select an Account", label: "Account", class: "text-gray-400" }, {class: "form-field__input cursor-not-allowed text-gray-400", disabled: "disabled"} %>
diff --git a/app/views/valuations/_form_row.html.erb b/app/views/valuations/_form_row.html.erb
index 3f99be4df3..5ef34fc9bc 100644
--- a/app/views/valuations/_form_row.html.erb
+++ b/app/views/valuations/_form_row.html.erb
@@ -6,7 +6,7 @@
- <%= f.date_field :date, required: "required", class: "border border-alpha-black-200 bg-white rounded-lg shadow-xs min-w-[200px] px-3 py-1.5 text-gray-900 text-sm" %>
+ <%= f.date_field :date, required: "required", max: Date.today, class: "border border-alpha-black-200 bg-white rounded-lg shadow-xs min-w-[200px] px-3 py-1.5 text-gray-900 text-sm" %>
<%= f.number_field :value, required: "required", placeholder: "0.00", step: "0.01", class: "bg-white border border-alpha-black-200 rounded-lg shadow-xs text-gray-900 text-sm px-3 py-1.5 text-right" %>
diff --git a/config/locales/views/account/en.yml b/config/locales/views/account/en.yml
index 82caf8a8d6..b36025b8f0 100644
--- a/config/locales/views/account/en.yml
+++ b/config/locales/views/account/en.yml
@@ -15,6 +15,8 @@ en:
index:
new_account: New account
new:
+ balance:
+ label: Balance
currency:
all_others: All Others
popular: Popular
@@ -22,6 +24,8 @@ en:
label: Account name
placeholder: Example account name
select_accountable_type: What would you like to add?
+ start_date:
+ label: Start date
title: Add an account
show:
confirm_accept: Delete "%{name}"
diff --git a/test/controllers/accounts_controller_test.rb b/test/controllers/accounts_controller_test.rb
index 8ef6db201e..d84369c913 100644
--- a/test/controllers/accounts_controller_test.rb
+++ b/test/controllers/accounts_controller_test.rb
@@ -16,10 +16,20 @@ class AccountsControllerTest < ActionDispatch::IntegrationTest
assert_response :ok
end
- test "create" do
+ test "should create account" do
assert_difference -> { Account.count }, +1 do
post accounts_path, params: { account: { accountable_type: "Account::Credit" } }
assert_redirected_to accounts_url
end
end
+
+ test "should create a valuation together with account" do
+ balance = 700
+ start_date = 3.days.ago.to_date
+ post accounts_path, params: { account: { accountable_type: "Account::Credit", balance:, start_date: } }
+
+ new_valuation = Valuation.order(:created_at).last
+ assert new_valuation.value == balance
+ assert new_valuation.date == start_date
+ end
end
diff --git a/test/fixtures/account/expected_balances.csv b/test/fixtures/account/expected_balances.csv
index 32f0c152fa..c72f02c461 100644
--- a/test/fixtures/account/expected_balances.csv
+++ b/test/fixtures/account/expected_balances.csv
@@ -29,4 +29,4 @@ date_offset,collectable,checking,savings_with_valuation_overrides,credit_card,eu
-3,550,5000,20500,1000,12000,13046.4,10000
-2,550,5000,20500,1000,12000,12982.8,10000
-1,550,5000,20500,1000,12000,13014,10000
-0,550,5000,20000,1000,12000,13000.8,10000
\ No newline at end of file
+0,550,5000,20500,1000,12000,13000.8,10000
\ No newline at end of file
diff --git a/test/fixtures/family/expected_snapshots.csv b/test/fixtures/family/expected_snapshots.csv
index 5f5e6d485b..fb4a6b0100 100644
--- a/test/fixtures/family/expected_snapshots.csv
+++ b/test/fixtures/family/expected_snapshots.csv
@@ -29,4 +29,4 @@ date_offset,net_worth,assets,liabilities,depositories,investments,loans,credits,
-3,48096.40,49096.40,1000.00,48546.40,0.00,0.00,1000.00,0.00,0.00,550.00,0.00,0,0,3214.48,2447.715,-0.3132574667
-2,48032.80,49032.80,1000.00,48482.80,0.00,0.00,1000.00,0.00,0.00,550.00,0.00,0,0,3214.48,2447.715,-0.3132574667
-1,48064.00,49064.00,1000.00,48514.00,0.00,0.00,1000.00,0.00,0.00,550.00,0.00,0,0,3214.48,2447.715,-0.3132574667
-0,47550.80,48550.80,1000.00,48000.80,0.00,0.00,1000.00,0.00,0.00,550.00,0.00,0,0,3214.48,2447.715,-0.3132574667
\ No newline at end of file
+0,48050.80,49050.80,1000.00,48514.00,0.00,0.00,1000.00,0.00,0.00,550.00,0.00,0,0,3214.48,2447.715,-0.3132574667
\ No newline at end of file
diff --git a/test/models/account/syncable_test.rb b/test/models/account/syncable_test.rb
index 67dafe6d85..786083e83b 100644
--- a/test/models/account/syncable_test.rb
+++ b/test/models/account/syncable_test.rb
@@ -78,4 +78,12 @@ class Account::SyncableTest < ActiveSupport::TestCase
assert_equal 31, account.balances.count
end
+
+ test "account balance is updated after sync" do
+ account = accounts(:savings_with_valuation_overrides)
+
+ assert_changes -> { account.balance }, to: 20500 do
+ account.sync
+ end
+ end
end