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