diff --git a/app/controllers/concerns/accountable_resource.rb b/app/controllers/concerns/accountable_resource.rb
index bc1d636f4..2d994d45c 100644
--- a/app/controllers/concerns/accountable_resource.rb
+++ b/app/controllers/concerns/accountable_resource.rb
@@ -86,6 +86,7 @@ module AccountableResource
def account_params
params.require(:account).permit(
:name, :balance, :subtype, :currency, :accountable_type, :return_to,
+ :institution_name, :institution_domain, :notes,
accountable_attributes: self.class.permitted_accountable_attributes
)
end
diff --git a/app/controllers/properties_controller.rb b/app/controllers/properties_controller.rb
index f1df28d3d..c222d5a74 100644
--- a/app/controllers/properties_controller.rb
+++ b/app/controllers/properties_controller.rb
@@ -89,7 +89,14 @@ class PropertiesController < ApplicationController
def property_params
params.require(:account)
- .permit(:name, :accountable_type, accountable_attributes: [ :id, :subtype, :year_built, :area_unit, :area_value ])
+ .permit(
+ :name,
+ :accountable_type,
+ :institution_name,
+ :institution_domain,
+ :notes,
+ accountable_attributes: [ :id, :subtype, :year_built, :area_unit, :area_value ]
+ )
end
def set_property
diff --git a/app/models/account.rb b/app/models/account.rb
index 2f1508494..4f96782b4 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -188,8 +188,12 @@ class Account < ApplicationRecord
end
end
+ def institution_name
+ read_attribute(:institution_name).presence || provider&.institution_name
+ end
+
def institution_domain
- provider&.institution_domain
+ read_attribute(:institution_domain).presence || provider&.institution_domain
end
def destroy_later
diff --git a/app/views/accounts/_account.html.erb b/app/views/accounts/_account.html.erb
index b4651c64f..cd8dade0e 100644
--- a/app/views/accounts/_account.html.erb
+++ b/app/views/accounts/_account.html.erb
@@ -18,8 +18,8 @@
<% else %>
<%= link_to account.name, account, class: [(account.active? ? "text-primary" : "text-subdued"), "text-sm font-medium hover:underline"], data: { turbo_frame: "_top" } %>
- <% if account.simplefin_account&.org_data&.dig('name') %>
- • <%= account.simplefin_account.org_data["name"] %>
+ <% if account.institution_name %>
+ • <%= account.institution_name %>
<% end %>
<% if account.long_subtype_label %>
diff --git a/app/views/accounts/_form.html.erb b/app/views/accounts/_form.html.erb
index d2139b56a..7f3ede919 100644
--- a/app/views/accounts/_form.html.erb
+++ b/app/views/accounts/_form.html.erb
@@ -16,6 +16,28 @@
<% end %>
<%= yield form %>
+
+
+
+ <%= icon "chevron-right", size: "sm", class: "group-open:rotate-90 transition-transform" %>
+ <%= t(".additional_details") %>
+
+
+
+ <%= form.text_field :institution_name,
+ label: t(".institution_name_label"),
+ placeholder: account.provider&.institution_name || t(".institution_name_placeholder") %>
+
+ <%= form.text_field :institution_domain,
+ label: t(".institution_domain_label"),
+ placeholder: account.provider&.institution_domain || t(".institution_domain_placeholder") %>
+
+ <%= form.text_area :notes,
+ label: t(".notes_label"),
+ placeholder: t(".notes_placeholder"),
+ rows: 4 %>
+
+
<%= form.submit %>
diff --git a/app/views/accounts/_logo.html.erb b/app/views/accounts/_logo.html.erb
index 364e7ccb5..52be912f8 100644
--- a/app/views/accounts/_logo.html.erb
+++ b/app/views/accounts/_logo.html.erb
@@ -7,7 +7,7 @@
"full" => "w-full h-full"
} %>
-<% if account.linked? && account.institution_domain.present? && Setting.brand_fetch_client_id.present? %>
+<% if account.institution_domain.present? && Setting.brand_fetch_client_id.present? %>
<%= image_tag "https://cdn.brandfetch.io/#{account.institution_domain}/icon/fallback/lettermark/w/40/h/40?c=#{Setting.brand_fetch_client_id}", class: "shrink-0 rounded-full #{size_classes[size]}" %>
<% elsif account.logo.attached? %>
<%= image_tag account.logo, class: "shrink-0 rounded-full #{size_classes[size]}" %>
diff --git a/config/locales/views/accounts/en.yml b/config/locales/views/accounts/en.yml
index e3911b1ab..517ff6825 100644
--- a/config/locales/views/accounts/en.yml
+++ b/config/locales/views/accounts/en.yml
@@ -21,6 +21,13 @@ en:
balance: Current balance
name_label: Account name
name_placeholder: Example account name
+ additional_details: Additional details
+ institution_name_label: Institution name
+ institution_name_placeholder: e.g., Chase Bank
+ institution_domain_label: Institution domain
+ institution_domain_placeholder: e.g., chase.com
+ notes_label: Notes
+ notes_placeholder: Store additional information like account numbers, sort codes, IBAN, routing numbers, etc.
index:
accounts: Accounts
manual_accounts:
diff --git a/db/migrate/20251116010421_add_institution_fields_to_accounts.rb b/db/migrate/20251116010421_add_institution_fields_to_accounts.rb
new file mode 100644
index 000000000..b98763923
--- /dev/null
+++ b/db/migrate/20251116010421_add_institution_fields_to_accounts.rb
@@ -0,0 +1,12 @@
+class AddInstitutionFieldsToAccounts < ActiveRecord::Migration[7.2]
+ def change
+ add_column :accounts, :institution_name, :string
+ add_column :accounts, :institution_domain, :string
+ add_column :accounts, :notes, :text
+
+ # Touch all accounts to invalidate cached queries that depend on accounts.maximum(:updated_at)
+ # Without this, the following error would occur post-update and prevent page loads:
+ # "undefined method 'institution_domain' for an instance of BalanceSheet::AccountTotals::AccountRow"
+ Account.in_batches.update_all(updated_at: Time.current)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 06bc242de..27adfde1e 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -46,6 +46,9 @@ ActiveRecord::Schema[7.2].define(version: 2025_12_15_100443) do
t.jsonb "locked_attributes", default: {}
t.string "status", default: "active"
t.uuid "simplefin_account_id"
+ t.string "institution_name"
+ t.string "institution_domain"
+ t.text "notes"
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 ["currency"], name: "index_accounts_on_currency"
diff --git a/test/controllers/credit_cards_controller_test.rb b/test/controllers/credit_cards_controller_test.rb
index d19db6512..1b11ce558 100644
--- a/test/controllers/credit_cards_controller_test.rb
+++ b/test/controllers/credit_cards_controller_test.rb
@@ -18,6 +18,9 @@ class CreditCardsControllerTest < ActionDispatch::IntegrationTest
name: "New Credit Card",
balance: 1000,
currency: "USD",
+ institution_name: "Amex",
+ institution_domain: "americanexpress.com",
+ notes: "Primary card",
accountable_type: "CreditCard",
accountable_attributes: {
available_credit: 5000,
@@ -35,6 +38,9 @@ class CreditCardsControllerTest < ActionDispatch::IntegrationTest
assert_equal "New Credit Card", created_account.name
assert_equal 1000, created_account.balance
assert_equal "USD", created_account.currency
+ assert_equal "Amex", created_account[:institution_name]
+ assert_equal "americanexpress.com", created_account[:institution_domain]
+ assert_equal "Primary card", created_account[:notes]
assert_equal 5000, created_account.accountable.available_credit
assert_equal 25.51, created_account.accountable.minimum_payment
assert_equal 15.99, created_account.accountable.apr
@@ -53,6 +59,9 @@ class CreditCardsControllerTest < ActionDispatch::IntegrationTest
name: "Updated Credit Card",
balance: 2000,
currency: "USD",
+ institution_name: "Chase",
+ institution_domain: "chase.com",
+ notes: "Updated notes",
accountable_type: "CreditCard",
accountable_attributes: {
id: @account.accountable_id,
@@ -70,6 +79,9 @@ class CreditCardsControllerTest < ActionDispatch::IntegrationTest
assert_equal "Updated Credit Card", @account.name
assert_equal 2000, @account.balance
+ assert_equal "Chase", @account[:institution_name]
+ assert_equal "chase.com", @account[:institution_domain]
+ assert_equal "Updated notes", @account[:notes]
assert_equal 6000, @account.accountable.available_credit
assert_equal 50, @account.accountable.minimum_payment
assert_equal 14.99, @account.accountable.apr
diff --git a/test/controllers/loans_controller_test.rb b/test/controllers/loans_controller_test.rb
index 47809400c..094933642 100644
--- a/test/controllers/loans_controller_test.rb
+++ b/test/controllers/loans_controller_test.rb
@@ -18,6 +18,9 @@ class LoansControllerTest < ActionDispatch::IntegrationTest
name: "New Loan",
balance: 50000,
currency: "USD",
+ institution_name: "Local Bank",
+ institution_domain: "localbank.example",
+ notes: "Mortgage notes",
accountable_type: "Loan",
accountable_attributes: {
interest_rate: 5.5,
@@ -34,6 +37,9 @@ class LoansControllerTest < ActionDispatch::IntegrationTest
assert_equal "New Loan", created_account.name
assert_equal 50000, created_account.balance
assert_equal "USD", created_account.currency
+ assert_equal "Local Bank", created_account[:institution_name]
+ assert_equal "localbank.example", created_account[:institution_domain]
+ assert_equal "Mortgage notes", created_account[:notes]
assert_equal 5.5, created_account.accountable.interest_rate
assert_equal 60, created_account.accountable.term_months
assert_equal "fixed", created_account.accountable.rate_type
@@ -51,6 +57,9 @@ class LoansControllerTest < ActionDispatch::IntegrationTest
name: "Updated Loan",
balance: 45000,
currency: "USD",
+ institution_name: "Updated Bank",
+ institution_domain: "updatedbank.example",
+ notes: "Updated loan notes",
accountable_type: "Loan",
accountable_attributes: {
id: @account.accountable_id,
@@ -67,6 +76,9 @@ class LoansControllerTest < ActionDispatch::IntegrationTest
assert_equal "Updated Loan", @account.name
assert_equal 45000, @account.balance
+ assert_equal "Updated Bank", @account[:institution_name]
+ assert_equal "updatedbank.example", @account[:institution_domain]
+ assert_equal "Updated loan notes", @account[:notes]
assert_equal 4.5, @account.accountable.interest_rate
assert_equal 48, @account.accountable.term_months
assert_equal "fixed", @account.accountable.rate_type
diff --git a/test/controllers/properties_controller_test.rb b/test/controllers/properties_controller_test.rb
index 872579b13..890f8b17e 100644
--- a/test/controllers/properties_controller_test.rb
+++ b/test/controllers/properties_controller_test.rb
@@ -14,6 +14,9 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
account: {
name: "New Property",
subtype: "house",
+ institution_name: "Property Lender",
+ institution_domain: "propertylender.example",
+ notes: "Property notes",
accountable_type: "Property",
accountable_attributes: {
year_built: 1990,
@@ -28,6 +31,9 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
assert created_account.accountable.is_a?(Property)
assert_equal "draft", created_account.status
assert_equal 0, created_account.balance
+ assert_equal "Property Lender", created_account[:institution_name]
+ assert_equal "propertylender.example", created_account[:institution_domain]
+ assert_equal "Property notes", created_account[:notes]
assert_equal 1990, created_account.accountable.year_built
assert_equal 1200, created_account.accountable.area_value
assert_equal "sqft", created_account.accountable.area_unit
@@ -39,6 +45,9 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
patch property_path(@account), params: {
account: {
name: "Updated Property",
+ institution_name: "Updated Lender",
+ institution_domain: "updatedlender.example",
+ notes: "Updated property notes",
accountable_attributes: {
id: @account.accountable.id,
subtype: "condominium"
@@ -50,6 +59,9 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
@account.reload
assert_equal "Updated Property", @account.name
assert_equal "condominium", @account.subtype
+ assert_equal "Updated Lender", @account[:institution_name]
+ assert_equal "updatedlender.example", @account[:institution_domain]
+ assert_equal "Updated property notes", @account[:notes]
# If account is active, it renders edit view; otherwise redirects to balances
if @account.active?
diff --git a/test/controllers/vehicles_controller_test.rb b/test/controllers/vehicles_controller_test.rb
index 55aa89cff..d4896230e 100644
--- a/test/controllers/vehicles_controller_test.rb
+++ b/test/controllers/vehicles_controller_test.rb
@@ -18,6 +18,9 @@ class VehiclesControllerTest < ActionDispatch::IntegrationTest
name: "Vehicle",
balance: 30000,
currency: "USD",
+ institution_name: "Auto Lender",
+ institution_domain: "autolender.example",
+ notes: "Lease notes",
accountable_type: "Vehicle",
accountable_attributes: {
make: "Toyota",
@@ -32,6 +35,12 @@ class VehiclesControllerTest < ActionDispatch::IntegrationTest
created_account = Account.order(:created_at).last
+ assert_equal "Vehicle", created_account.name
+ assert_equal 30000, created_account.balance
+ assert_equal "USD", created_account.currency
+ assert_equal "Auto Lender", created_account[:institution_name]
+ assert_equal "autolender.example", created_account[:institution_domain]
+ assert_equal "Lease notes", created_account[:notes]
assert_equal "Toyota", created_account.accountable.make
assert_equal "Camry", created_account.accountable.model
assert_equal 2020, created_account.accountable.year
@@ -50,6 +59,9 @@ class VehiclesControllerTest < ActionDispatch::IntegrationTest
name: "Updated Vehicle",
balance: 28000,
currency: "USD",
+ institution_name: "Updated Lender",
+ institution_domain: "updatedlender.example",
+ notes: "Updated lease notes",
accountable_type: "Vehicle",
accountable_attributes: {
id: @account.accountable_id,
@@ -64,6 +76,13 @@ class VehiclesControllerTest < ActionDispatch::IntegrationTest
}
end
+ @account.reload
+ assert_equal "Updated Vehicle", @account.name
+ assert_equal 28000, @account.balance
+ assert_equal "Updated Lender", @account[:institution_name]
+ assert_equal "updatedlender.example", @account[:institution_domain]
+ assert_equal "Updated lease notes", @account[:notes]
+
assert_redirected_to account_path(@account)
assert_equal "Vehicle account updated", flash[:notice]
assert_enqueued_with(job: SyncJob)
diff --git a/test/system/accounts_test.rb b/test/system/accounts_test.rb
index da1b59384..555959190 100644
--- a/test/system/accounts_test.rb
+++ b/test/system/accounts_test.rb
@@ -109,9 +109,16 @@ class AccountsTest < ApplicationSystemTestCase
click_link "Enter account balance" if accountable_type.in?(%w[Depository Investment Crypto Loan CreditCard])
account_name = "[system test] #{accountable_type} Account"
+ institution_name = "[system test] Institution"
+ institution_domain = "example.com"
+ notes = "Test notes for #{accountable_type}"
fill_in "Account name*", with: account_name
fill_in "account[balance]", with: 100.99
+ find("summary", text: "Additional details").click
+ fill_in "Institution name", with: institution_name
+ fill_in "Institution domain", with: institution_domain
+ fill_in "Notes", with: notes
yield if block_given?
@@ -127,6 +134,9 @@ class AccountsTest < ApplicationSystemTestCase
assert_text account_name
created_account = Account.order(:created_at).last
+ assert_equal institution_name, created_account[:institution_name]
+ assert_equal institution_domain, created_account[:institution_domain]
+ assert_equal notes, created_account[:notes]
visit account_url(created_account)
@@ -135,9 +145,22 @@ class AccountsTest < ApplicationSystemTestCase
click_on "Edit"
end
+ updated_institution_name = "[system test] Updated Institution"
+ updated_institution_domain = "updated.example.com"
+ updated_notes = "Updated notes for #{accountable_type}"
+
fill_in "Account name", with: "Updated account name"
+ find("summary", text: "Additional details").click
+ fill_in "Institution name", with: updated_institution_name
+ fill_in "Institution domain", with: updated_institution_domain
+ fill_in "Notes", with: updated_notes
click_button "Update Account"
assert_selector "h2", text: "Updated account name"
+
+ created_account.reload
+ assert_equal updated_institution_name, created_account[:institution_name]
+ assert_equal updated_institution_domain, created_account[:institution_domain]
+ assert_equal updated_notes, created_account[:notes]
end
def humanized_accountable(accountable_type)