diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index 033ed3b8..21419365 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -79,6 +79,6 @@ class AccountsController < ApplicationController
end
def account_params
- params.require(:account).permit(:name, :accountable_type, :balance, :start_date, :start_balance, :currency, :subtype, :is_active, :institution_id)
+ params.require(:account).permit(:name, :accountable_type, :mode, :balance, :start_date, :start_balance, :currency, :subtype, :is_active, :institution_id)
end
end
diff --git a/app/controllers/credit_cards_controller.rb b/app/controllers/credit_cards_controller.rb
index 5854c9fa..bb416ce7 100644
--- a/app/controllers/credit_cards_controller.rb
+++ b/app/controllers/credit_cards_controller.rb
@@ -27,7 +27,7 @@ class CreditCardsController < ApplicationController
def account_params
params.require(:account)
.permit(
- :name, :balance, :institution_id, :start_date, :start_balance, :currency, :accountable_type,
+ :name, :balance, :institution_id, :mode, :start_date, :start_balance, :currency, :accountable_type,
accountable_attributes: [
:id,
:available_credit,
diff --git a/app/controllers/loans_controller.rb b/app/controllers/loans_controller.rb
index b084e566..1b704290 100644
--- a/app/controllers/loans_controller.rb
+++ b/app/controllers/loans_controller.rb
@@ -27,7 +27,7 @@ class LoansController < ApplicationController
def account_params
params.require(:account)
.permit(
- :name, :balance, :institution_id, :start_date, :start_balance, :currency, :accountable_type,
+ :name, :balance, :institution_id, :start_date, :mode, :start_balance, :currency, :accountable_type,
accountable_attributes: [
:id,
:rate_type,
diff --git a/app/controllers/properties_controller.rb b/app/controllers/properties_controller.rb
index 71d46f0e..e37344d4 100644
--- a/app/controllers/properties_controller.rb
+++ b/app/controllers/properties_controller.rb
@@ -27,7 +27,7 @@ class PropertiesController < ApplicationController
def account_params
params.require(:account)
.permit(
- :name, :balance, :institution_id, :start_date, :start_balance, :currency, :accountable_type,
+ :name, :balance, :institution_id, :start_date, :mode, :start_balance, :currency, :accountable_type,
accountable_attributes: [
:id,
:year_built,
diff --git a/app/controllers/vehicles_controller.rb b/app/controllers/vehicles_controller.rb
index e9822b9b..fe41a42f 100644
--- a/app/controllers/vehicles_controller.rb
+++ b/app/controllers/vehicles_controller.rb
@@ -27,7 +27,7 @@ class VehiclesController < ApplicationController
def account_params
params.require(:account)
.permit(
- :name, :balance, :institution_id, :start_date, :start_balance, :currency, :accountable_type,
+ :name, :balance, :institution_id, :start_date, :mode, :start_balance, :currency, :accountable_type,
accountable_attributes: [
:id,
:make,
diff --git a/app/models/account.rb b/app/models/account.rb
index 9dd49d60..c2c91f37 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -1,7 +1,10 @@
class Account < ApplicationRecord
+ VALUE_MODES = %w[balance transactions]
+
include Syncable, Monetizable, Issuable
validates :name, :balance, :currency, presence: true
+ validates :mode, inclusion: { in: VALUE_MODES }, allow_nil: true
belongs_to :family
belongs_to :institution, optional: true
diff --git a/app/models/account/balance/calculator.rb b/app/models/account/balance/calculator.rb
index 68d85853..04dfb381 100644
--- a/app/models/account/balance/calculator.rb
+++ b/app/models/account/balance/calculator.rb
@@ -23,11 +23,11 @@ class Account::Balance::Calculator
attr_reader :account, :sync_start_date
def find_start_balance_for_partial_sync
- account.balances.find_by(currency: account.currency, date: sync_start_date - 1.day).balance
+ account.balances.find_by(currency: account.currency, date: sync_start_date - 1.day)&.balance
end
def find_start_balance_for_full_sync(cached_entries)
- account.balance + net_entry_flows(cached_entries)
+ account.balance + net_entry_flows(cached_entries.select { |e| e.account_transaction? })
end
def calculate_balance_for_date(date, entries:, prior_balance:)
diff --git a/app/views/accounts/_form.html.erb b/app/views/accounts/_form.html.erb
index e85448e0..65dee8df 100644
--- a/app/views/accounts/_form.html.erb
+++ b/app/views/accounts/_form.html.erb
@@ -2,6 +2,10 @@
<%= styled_form_with model: account, url: url, scope: :account, class: "flex flex-col gap-4 justify-between grow", data: { turbo: false } do |f| %>
+ <% unless account.new_record? %>
+ <%= f.select :mode, Account::VALUE_MODES.map { |mode| [mode.titleize, mode] }, { label: t(".mode"), prompt: t(".mode_prompt") } %>
+ <% end %>
+
<%= f.select :accountable_type, Accountable::TYPES.map { |type| [type.titleize, type] }, { label: t(".accountable_type"), prompt: t(".type_prompt") }, required: true, autofocus: true %>
<%= f.text_field :name, placeholder: t(".name_placeholder"), required: "required", label: t(".name_label") %>
diff --git a/app/views/accounts/_new_account_setup_bar.html.erb b/app/views/accounts/_new_account_setup_bar.html.erb
deleted file mode 100644
index 16b6e430..00000000
--- a/app/views/accounts/_new_account_setup_bar.html.erb
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
Setup your new account
-
-
- <%= link_to "Track balances only", new_account_valuation_path(@account), class: "btn btn--ghost", data: { turbo_frame: dom_id(@account.entries.account_valuations.new) } %>
- <%= link_to "Add your first transaction", new_transaction_path(account_id: @account.id), class: "btn btn--primary", data: { turbo_frame: :modal } %>
-
-
diff --git a/app/views/accounts/accountables/_default_tabs.html.erb b/app/views/accounts/accountables/_default_tabs.html.erb
index 0a2f355a..9b6274ef 100644
--- a/app/views/accounts/accountables/_default_tabs.html.erb
+++ b/app/views/accounts/accountables/_default_tabs.html.erb
@@ -1,7 +1,13 @@
-<%# locals: (account:) %>
+<%# locals: (account:, selected_tab:) %>
-<% if account.transactions.any? %>
- <%= render "accounts/accountables/transactions", account: account %>
+<% if account.mode.nil? %>
+ <%= render "accounts/accountables/value_onboarding", account: account %>
<% else %>
- <%= render "accounts/accountables/valuations", account: account %>
+
+ <% if account.mode == "transactions" %>
+ <%= render "accounts/accountables/transactions", account: account %>
+ <% else %>
+ <%= render "accounts/accountables/valuations", account: account %>
+ <% end %>
+
<% end %>
diff --git a/app/views/accounts/accountables/_value_onboarding.html.erb b/app/views/accounts/accountables/_value_onboarding.html.erb
new file mode 100644
index 00000000..26b20b6a
--- /dev/null
+++ b/app/views/accounts/accountables/_value_onboarding.html.erb
@@ -0,0 +1,16 @@
+<%# locals: (account:) %>
+
+
+
How would you like to track value for this account?
+
We will use this to determine what data to show for this account.
+
+ <%= button_to account_path(account, { account: { mode: "balance" } }), method: :put, class: "btn btn--outline", data: { controller: "tooltip", turbo: false } do %>
+ <%= render partial: "shared/text_tooltip", locals: { tooltip_text: "Choose this if you only need to track the historical value of this account over time and do not plan on importing any transactions." } %>
+ Balance only
+ <% end %>
+ <%= button_to account_path(account, { account: { mode: "transactions" } }), method: :put, class: "btn btn--primary", data: { controller: "tooltip", turbo: false } do %>
+ <%= render partial: "shared/text_tooltip", locals: { tooltip_text: "Choose this if you plan on importing transactions into this account for budgeting and other analytics." } %>
+ Transactions
+ <% end %>
+
+
\ No newline at end of file
diff --git a/app/views/accounts/accountables/credit_card/_tabs.html.erb b/app/views/accounts/accountables/credit_card/_tabs.html.erb
index 781cc6d0..d08ff8e2 100644
--- a/app/views/accounts/accountables/credit_card/_tabs.html.erb
+++ b/app/views/accounts/accountables/credit_card/_tabs.html.erb
@@ -1,15 +1,26 @@
<%# locals: (account:, selected_tab:) %>
-
- <%= render "accounts/accountables/tab", account: account, key: "overview", is_selected: selected_tab.in?([nil, "overview"]) %>
- <%= render "accounts/accountables/tab", account: account, key: "transactions", is_selected: selected_tab == "transactions" %>
-
+<% if account.mode.nil? %>
+ <%= render "accounts/accountables/value_onboarding", account: account %>
+<% else %>
+
+ <%= render "accounts/accountables/tab", account: account, key: "overview", is_selected: selected_tab.in?([nil, "overview"]) %>
-
- <% case selected_tab %>
- <% when nil, "overview" %>
- <%= render "accounts/accountables/credit_card/overview", account: account %>
- <% when "transactions" %>
- <%= render "accounts/accountables/transactions", account: account %>
- <% end %>
-
+ <% if account.mode == "transactions" %>
+ <%= render "accounts/accountables/tab", account: account, key: "transactions", is_selected: selected_tab == "transactions" %>
+ <% else %>
+ <%= render "accounts/accountables/tab", account: account, key: "value", is_selected: selected_tab == "value" %>
+ <% end %>
+
+
+
+ <% case selected_tab %>
+ <% when nil, "overview" %>
+ <%= render "accounts/accountables/credit_card/overview", account: account %>
+ <% when "transactions" %>
+ <%= render "accounts/accountables/transactions", account: account %>
+ <% when "value" %>
+ <%= render "accounts/accountables/valuations", account: account %>
+ <% end %>
+
+<% end %>
diff --git a/app/views/accounts/accountables/crypto/_tabs.html.erb b/app/views/accounts/accountables/crypto/_tabs.html.erb
index 381f7273..be74694e 100644
--- a/app/views/accounts/accountables/crypto/_tabs.html.erb
+++ b/app/views/accounts/accountables/crypto/_tabs.html.erb
@@ -1 +1 @@
-<%= render "accounts/accountables/default_tabs", account: account %>
+<%= render "accounts/accountables/default_tabs", account: account, selected_tab: selected_tab %>
diff --git a/app/views/accounts/accountables/depository/_tabs.html.erb b/app/views/accounts/accountables/depository/_tabs.html.erb
index 381f7273..be74694e 100644
--- a/app/views/accounts/accountables/depository/_tabs.html.erb
+++ b/app/views/accounts/accountables/depository/_tabs.html.erb
@@ -1 +1 @@
-<%= render "accounts/accountables/default_tabs", account: account %>
+<%= render "accounts/accountables/default_tabs", account: account, selected_tab: selected_tab %>
diff --git a/app/views/accounts/accountables/investment/_tabs.html.erb b/app/views/accounts/accountables/investment/_tabs.html.erb
index 8da4905d..bff1d3ce 100644
--- a/app/views/accounts/accountables/investment/_tabs.html.erb
+++ b/app/views/accounts/accountables/investment/_tabs.html.erb
@@ -1,28 +1,34 @@
<%# locals: (account:, selected_tab:) %>
-<% if account.entries.account_trades.any? || account.entries.account_transactions.any? %>
+<% if account.mode.nil? %>
+ <%= render "accounts/accountables/value_onboarding", account: account %>
+<% else %>
- <%= render "accounts/accountables/tab", account: account, key: "holdings", is_selected: selected_tab.in?([nil, "holdings"]) %>
- <%= render "accounts/accountables/tab", account: account, key: "cash", is_selected: selected_tab == "cash" %>
- <%= render "accounts/accountables/tab", account: account, key: "transactions", is_selected: selected_tab == "transactions" %>
+ <% if account.mode == "transactions" %>
+ <%= render "accounts/accountables/tab", account: account, key: "holdings", is_selected: selected_tab.in?([nil, "holdings"]) %>
+ <%= render "accounts/accountables/tab", account: account, key: "cash", is_selected: selected_tab == "cash" %>
+ <%= render "accounts/accountables/tab", account: account, key: "transactions", is_selected: selected_tab == "transactions" %>
+ <% end %>
- <% case selected_tab %>
- <% when nil, "holdings" %>
- <%= turbo_frame_tag dom_id(account, :holdings), src: account_holdings_path(account) do %>
- <%= render "account/entries/loading" %>
- <% end %>
- <% when "cash" %>
- <%= turbo_frame_tag dom_id(account, :cash), src: account_cashes_path(account) do %>
- <%= render "account/entries/loading" %>
- <% end %>
- <% when "transactions" %>
- <%= turbo_frame_tag dom_id(account, :trades), src: account_trades_path(account) do %>
- <%= render "account/entries/loading" %>
+ <% if account.mode == "transactions" %>
+ <% case selected_tab %>
+ <% when nil, "holdings" %>
+ <%= turbo_frame_tag dom_id(account, :holdings), src: account_holdings_path(account) do %>
+ <%= render "account/entries/loading" %>
+ <% end %>
+ <% when "cash" %>
+ <%= turbo_frame_tag dom_id(account, :cash), src: account_cashes_path(account) do %>
+ <%= render "account/entries/loading" %>
+ <% end %>
+ <% when "transactions" %>
+ <%= turbo_frame_tag dom_id(account, :trades), src: account_trades_path(account) do %>
+ <%= render "account/entries/loading" %>
+ <% end %>
<% end %>
+ <% else %>
+ <%= render "accounts/accountables/valuations", account: account %>
<% end %>
-<% else %>
- <%= render "accounts/accountables/valuations", account: account %>
<% end %>
diff --git a/app/views/accounts/accountables/loan/_tabs.html.erb b/app/views/accounts/accountables/loan/_tabs.html.erb
index 3ed1efcf..8c5ca4fe 100644
--- a/app/views/accounts/accountables/loan/_tabs.html.erb
+++ b/app/views/accounts/accountables/loan/_tabs.html.erb
@@ -1,15 +1,25 @@
<%# locals: (account:, selected_tab:) %>
-
- <%= render "accounts/accountables/tab", account: account, key: "overview", is_selected: selected_tab.in?([nil, "overview"]) %>
- <%= render "accounts/accountables/tab", account: account, key: "value", is_selected: selected_tab == "value" %>
-
+<% if account.mode.nil? %>
+ <%= render "accounts/accountables/value_onboarding", account: account %>
+<% else %>
+
+ <%= render "accounts/accountables/tab", account: account, key: "overview", is_selected: selected_tab.in?([nil, "overview"]) %>
+ <% if account.mode == "transactions" %>
+ <%= render "accounts/accountables/tab", account: account, key: "transactions", is_selected: selected_tab == "transactions" %>
+ <% else %>
+ <%= render "accounts/accountables/tab", account: account, key: "value", is_selected: selected_tab == "value" %>
+ <% end %>
+
-
- <% case selected_tab %>
- <% when nil, "overview" %>
- <%= render "accounts/accountables/loan/overview", account: account %>
- <% when "value" %>
- <%= render "accounts/accountables/valuations", account: account %>
- <% end %>
-
+
+ <% case selected_tab %>
+ <% when nil, "overview" %>
+ <%= render "accounts/accountables/loan/overview", account: account %>
+ <% when "transactions" %>
+ <%= render "accounts/accountables/transactions", account: account %>
+ <% when "value" %>
+ <%= render "accounts/accountables/valuations", account: account %>
+ <% end %>
+
+<% end %>
diff --git a/app/views/accounts/show.html.erb b/app/views/accounts/show.html.erb
index c49b64e2..d250a38f 100644
--- a/app/views/accounts/show.html.erb
+++ b/app/views/accounts/show.html.erb
@@ -16,10 +16,6 @@
- <% if @account.entries.empty? && @account.depository? %>
- <%= render "accounts/new_account_setup_bar", account: @account %>
- <% end %>
-
<% if @account.highest_priority_issue %>
<%= render partial: "issues/issue", locals: { issue: @account.highest_priority_issue } %>
<% end %>
diff --git a/app/views/shared/_text_tooltip.erb b/app/views/shared/_text_tooltip.erb
index 53f93dd3..f6823c80 100644
--- a/app/views/shared/_text_tooltip.erb
+++ b/app/views/shared/_text_tooltip.erb
@@ -1,5 +1,5 @@
-
diff --git a/config/locales/views/accounts/en.yml b/config/locales/views/accounts/en.yml
index 0e12a0f5..4dff4381 100644
--- a/config/locales/views/accounts/en.yml
+++ b/config/locales/views/accounts/en.yml
@@ -19,6 +19,8 @@ en:
ungrouped: "(none)"
balance: Today's balance
accountable_type: Account type
+ mode: Value tracking mode
+ mode_prompt: Select a mode
type_prompt: Select a type
header:
accounts: Accounts
diff --git a/db/migrate/20241018201653_add_account_mode.rb b/db/migrate/20241018201653_add_account_mode.rb
new file mode 100644
index 00000000..54a273d2
--- /dev/null
+++ b/db/migrate/20241018201653_add_account_mode.rb
@@ -0,0 +1,5 @@
+class AddAccountMode < ActiveRecord::Migration[7.2]
+ def change
+ add_column :accounts, :mode, :string
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index c5a313ea..09baa1b2 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.2].define(version: 2024_10_17_204250) do
+ActiveRecord::Schema[7.2].define(version: 2024_10_18_201653) do
# These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto"
enable_extension "plpgsql"
@@ -19,7 +19,6 @@ ActiveRecord::Schema[7.2].define(version: 2024_10_17_204250) do
# Note that some types may not work with other database engines. Be careful if changing database.
create_enum "account_status", ["ok", "syncing", "error"]
create_enum "import_status", ["pending", "importing", "complete", "failed"]
- create_enum "user_role", ["admin", "member", "super_admin"]
create_table "account_balances", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "account_id", null: false
@@ -122,6 +121,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_10_17_204250) do
t.uuid "institution_id"
t.virtual "classification", type: :string, as: "\nCASE\n WHEN ((accountable_type)::text = ANY (ARRAY[('Loan'::character varying)::text, ('CreditCard'::character varying)::text, ('OtherLiability'::character varying)::text])) THEN 'liability'::text\n ELSE 'asset'::text\nEND", stored: true
t.uuid "import_id"
+ t.string "mode"
t.index ["accountable_id", "accountable_type"], name: "index_accounts_on_accountable_id_and_accountable_type"
t.index ["accountable_type"], name: "index_accounts_on_accountable_type"
t.index ["family_id", "accountable_type"], name: "index_accounts_on_family_id_and_accountable_type"
@@ -535,7 +535,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_10_17_204250) do
t.datetime "updated_at", null: false
t.string "last_prompted_upgrade_commit_sha"
t.string "last_alerted_upgrade_commit_sha"
- t.enum "role", default: "member", null: false, enum_type: "user_role"
+ t.string "role", default: "member", null: false
t.boolean "active", default: true, null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["family_id"], name: "index_users_on_family_id"
diff --git a/test/fixtures/accounts.yml b/test/fixtures/accounts.yml
index eeb616f3..9afbc8a1 100644
--- a/test/fixtures/accounts.yml
+++ b/test/fixtures/accounts.yml
@@ -5,6 +5,7 @@ other_asset:
currency: USD
accountable_type: OtherAsset
accountable: one
+ mode: balance
other_liability:
family: dylan_family
@@ -13,6 +14,7 @@ other_liability:
currency: USD
accountable_type: OtherLiability
accountable: one
+ mode: balance
depository:
family: dylan_family
@@ -22,6 +24,7 @@ depository:
accountable_type: Depository
accountable: one
institution: chase
+ mode: transactions
credit_card:
family: dylan_family
@@ -31,6 +34,7 @@ credit_card:
accountable_type: CreditCard
accountable: one
institution: chase
+ mode: transactions
investment:
family: dylan_family
@@ -39,6 +43,7 @@ investment:
currency: USD
accountable_type: Investment
accountable: one
+ mode: transactions
loan:
family: dylan_family
@@ -47,6 +52,7 @@ loan:
currency: USD
accountable_type: Loan
accountable: one
+ mode: transactions
property:
family: dylan_family
@@ -55,6 +61,7 @@ property:
currency: USD
accountable_type: Property
accountable: one
+ mode: transactions
vehicle:
family: dylan_family
@@ -63,3 +70,4 @@ vehicle:
currency: USD
accountable_type: Vehicle
accountable: one
+ mode: transactions
diff --git a/test/models/account/balance/syncer_test.rb b/test/models/account/balance/syncer_test.rb
index 748b3e6a..528863be 100644
--- a/test/models/account/balance/syncer_test.rb
+++ b/test/models/account/balance/syncer_test.rb
@@ -35,7 +35,7 @@ class Account::Balance::SyncerTest < ActiveSupport::TestCase
assert_equal [ 19600, 19500, 19500, 20000, 20000, 20000 ], @account.balances.chronological.map(&:balance)
end
- test "syncs account with valuations and transactions" do
+ test "syncs account with valuations and transactions when valuation starts" do
create_valuation(account: @account, date: 5.days.ago.to_date, amount: 20000)
create_transaction(account: @account, date: 3.days.ago.to_date, amount: -500)
create_transaction(account: @account, date: 2.days.ago.to_date, amount: 100)
@@ -47,6 +47,17 @@ class Account::Balance::SyncerTest < ActiveSupport::TestCase
assert_equal [ 20000, 20000, 20500, 20400, 25000, 25000 ], @account.balances.chronological.map(&:balance)
end
+ test "syncs account with valuations and transactions when transaction starts" do
+ new_account = families(:empty).accounts.create!(name: "Test Account", balance: 1000, currency: "USD", accountable: Depository.new)
+ create_transaction(account: new_account, date: 2.days.ago.to_date, amount: 250)
+ create_valuation(account: new_account, date: Date.current, amount: 1000)
+
+ run_sync_for(new_account)
+
+ assert_equal 1000, new_account.balance
+ assert_equal [ 1250, 1000, 1000, 1000 ], new_account.balances.chronological.map(&:balance)
+ end
+
test "syncs account with transactions in multiple currencies" do
ExchangeRate.create! date: 1.day.ago.to_date, from_currency: "EUR", to_currency: "USD", rate: 1.2
diff --git a/test/system/accounts_test.rb b/test/system/accounts_test.rb
index 97f3fd91..b7b6d18e 100644
--- a/test/system/accounts_test.rb
+++ b/test/system/accounts_test.rb
@@ -96,7 +96,10 @@ class AccountsTest < ApplicationSystemTestCase
visit accounts_url
assert_text account_name
- visit account_url(Account.order(:created_at).last)
+ created_account = Account.order(:created_at).last
+ created_account.update!(mode: "transactions")
+
+ visit account_url(created_account)
within "header" do
find('button[data-menu-target="button"]').click