diff --git a/app/controllers/simplefin_items_controller.rb b/app/controllers/simplefin_items_controller.rb index 5a02eb723..9a3bf087e 100644 --- a/app/controllers/simplefin_items_controller.rb +++ b/app/controllers/simplefin_items_controller.rb @@ -42,7 +42,7 @@ class SimplefinItemsController < ApplicationController # Immediately sync to get account and institution data @simplefin_item.sync_later - redirect_back_or_to root_path, notice: "SimpleFin connection added successfully! Your accounts will appear shortly as they sync in the background." + redirect_to simplefin_items_path, notice: "SimpleFin connection added successfully! Your accounts will appear shortly as they sync in the background." else @error_message = @simplefin_item.errors.full_messages.join(", ") render :new, status: :unprocessable_entity @@ -87,16 +87,28 @@ class SimplefinItemsController < ApplicationController ['Loan or Mortgage', 'Loan'], ['Other Asset', 'OtherAsset'] ] + + # Subtype options for each account type + @depository_subtypes = Depository::SUBTYPES.map { |k, v| [v[:long], k] } + @credit_card_subtypes = CreditCard::SUBTYPES.map { |k, v| [v[:long], k] } + @investment_subtypes = Investment::SUBTYPES.map { |k, v| [v[:long], k] } + @loan_subtypes = Loan::SUBTYPES.map { |k, v| [v[:long], k] } end def complete_account_setup account_types = params[:account_types] || {} + account_subtypes = params[:account_subtypes] || {} account_types.each do |simplefin_account_id, selected_type| simplefin_account = @simplefin_item.simplefin_accounts.find(simplefin_account_id) + selected_subtype = account_subtypes[simplefin_account_id] - # Create account with user-selected type - account = Account.create_from_simplefin_account_with_type(simplefin_account, selected_type) + # Create account with user-selected type and subtype + account = Account.create_from_simplefin_account_with_type_and_subtype( + simplefin_account, + selected_type, + selected_subtype + ) simplefin_account.update!(account: account) end diff --git a/app/javascript/controllers/account_type_selector_controller.js b/app/javascript/controllers/account_type_selector_controller.js new file mode 100644 index 000000000..db4dc39ce --- /dev/null +++ b/app/javascript/controllers/account_type_selector_controller.js @@ -0,0 +1,43 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["subtypeContainer"] + static values = { accountId: String } + + connect() { + console.log('Account type selector connected for account:', this.accountIdValue) + // Show initial subtype dropdown based on current selection + this.updateSubtype() + } + + updateSubtype(event) { + const selectElement = this.element.querySelector('select[name^="account_types"]') + const selectedType = selectElement ? selectElement.value : '' + const container = this.subtypeContainerTarget + const accountId = this.accountIdValue + + console.log('Updating subtype for account:', accountId, 'Selected type:', selectedType) + + // Hide all subtype selects + const subtypeSelects = container.querySelectorAll('.subtype-select') + subtypeSelects.forEach(select => { + select.style.display = 'none' + // Clear the name attribute so it doesn't get submitted + const selectElement = select.querySelector('select') + if (selectElement) { + selectElement.removeAttribute('name') + } + }) + + // Show the relevant subtype select + const relevantSubtype = container.querySelector(`[data-type="${selectedType}"]`) + if (relevantSubtype) { + relevantSubtype.style.display = 'block' + // Re-add the name attribute so it gets submitted + const selectElement = relevantSubtype.querySelector('select') + if (selectElement) { + selectElement.setAttribute('name', `account_subtypes[${accountId}]`) + } + } + } +} \ No newline at end of file diff --git a/app/models/account.rb b/app/models/account.rb index e76371586..20bbafd5f 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -27,6 +27,7 @@ class Account < ApplicationRecord has_one_attached :logo delegated_type :accountable, types: Accountable::TYPES, dependent: :destroy + delegate :subtype, to: :accountable, allow_nil: true accepts_nested_attributes_for :accountable, update_only: true @@ -102,6 +103,20 @@ class Account < ApplicationRecord create_and_sync(attributes) end + def create_from_simplefin_account_with_type_and_subtype(simplefin_account, account_type, subtype) + attributes = { + family: simplefin_account.simplefin_item.family, + name: simplefin_account.name, + balance: simplefin_account.current_balance || simplefin_account.available_balance || 0, + currency: simplefin_account.currency, + accountable_type: account_type, + accountable_attributes: build_accountable_attributes_with_subtype(simplefin_account, account_type, subtype), + simplefin_account_id: simplefin_account.id + } + + create_and_sync(attributes) + end + def map_simplefin_type_to_accountable_type(simplefin_type, account_name: nil) # First try to map by explicit type if provided case simplefin_type&.downcase @@ -146,6 +161,16 @@ class Account < ApplicationRecord {} end end + + def build_accountable_attributes_with_subtype(simplefin_account, account_type, subtype) + base_attributes = build_accountable_attributes(simplefin_account, account_type) + + if subtype.present? + base_attributes[:subtype] = subtype + end + + base_attributes + end end def institution_domain diff --git a/app/views/simplefin_items/setup_accounts.html.erb b/app/views/simplefin_items/setup_accounts.html.erb index 5213304ac..02ebb6d51 100644 --- a/app/views/simplefin_items/setup_accounts.html.erb +++ b/app/views/simplefin_items/setup_accounts.html.erb @@ -45,13 +45,60 @@ -
- <%= label_tag "account_types[#{simplefin_account.id}]", "Account Type:", - class: "block text-sm font-medium text-primary mb-2" %> - <%= select_tag "account_types[#{simplefin_account.id}]", - options_for_select(@account_type_options, - Account.map_simplefin_type_to_accountable_type(simplefin_account.account_type, account_name: simplefin_account.name)), - { class: "w-full px-3 py-2 border border-primary rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" } %> +
+
+ <%= label_tag "account_types[#{simplefin_account.id}]", "Account Type:", + class: "block text-sm font-medium text-primary mb-2" %> + <%= select_tag "account_types[#{simplefin_account.id}]", + options_for_select(@account_type_options, + Account.map_simplefin_type_to_accountable_type(simplefin_account.account_type, account_name: simplefin_account.name)), + { class: "w-full px-3 py-2 border border-primary rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500", + data: { + action: "change->account-type-selector#updateSubtype" + } } %> +
+ + +
+ + + + + + + + + + +
<% end %> diff --git a/db/migrate/20250807170943_add_subtype_to_accountables.rb b/db/migrate/20250807170943_add_subtype_to_accountables.rb new file mode 100644 index 000000000..854d5a79f --- /dev/null +++ b/db/migrate/20250807170943_add_subtype_to_accountables.rb @@ -0,0 +1,13 @@ +class AddSubtypeToAccountables < ActiveRecord::Migration[7.2] + def change + add_column :depositories, :subtype, :string + add_column :investments, :subtype, :string + add_column :loans, :subtype, :string + add_column :credit_cards, :subtype, :string + add_column :other_assets, :subtype, :string + add_column :other_liabilities, :subtype, :string + add_column :properties, :subtype, :string + add_column :vehicles, :subtype, :string + add_column :cryptos, :subtype, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 1969c4c20..b5cc8ae4e 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: 2025_08_07_163541) do +ActiveRecord::Schema[7.2].define(version: 2025_08_07_170943) do create_schema "sure_dev_schema" # These are extensions that must be enabled in order to support this database @@ -195,12 +195,14 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_07_163541) do t.date "expiration_date" t.decimal "annual_fee", precision: 10, scale: 2 t.jsonb "locked_attributes", default: {} + t.string "subtype" end create_table "cryptos", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false t.jsonb "locked_attributes", default: {} + t.string "subtype" end create_table "data_enrichments", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| @@ -220,6 +222,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_07_163541) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.jsonb "locked_attributes", default: {} + t.string "subtype" end create_table "entries", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| @@ -391,6 +394,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_07_163541) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.jsonb "locked_attributes", default: {} + t.string "subtype" end create_table "invitations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| @@ -425,6 +429,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_07_163541) do t.integer "term_months" t.decimal "initial_balance", precision: 19, scale: 4 t.jsonb "locked_attributes", default: {} + t.string "subtype" end create_table "merchants", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| @@ -523,12 +528,14 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_07_163541) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.jsonb "locked_attributes", default: {} + t.string "subtype" end create_table "other_liabilities", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false t.jsonb "locked_attributes", default: {} + t.string "subtype" end create_table "plaid_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| @@ -580,6 +587,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_07_163541) do t.integer "area_value" t.string "area_unit" t.jsonb "locked_attributes", default: {} + t.string "subtype" end create_table "rejected_transfers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| @@ -863,6 +871,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_07_163541) do t.string "make" t.string "model" t.jsonb "locked_attributes", default: {} + t.string "subtype" end add_foreign_key "accounts", "families"