mirror of
https://github.com/we-promise/sure.git
synced 2026-04-19 12:04:08 +00:00
Complete subtype selection for SimpleFin accounts
- Add subtype database columns to all accountable models - Create Stimulus controller for dynamic subtype dropdown interaction - Add delegation from Account to accountable subtype for clean API access - Update SimpleFin account setup form with working subtype selection - Fix account display to show proper subtype labels instead of generic "Cash" Users can now select both account type and subtype during SimpleFin import, and the selected subtypes are properly saved and displayed in the UI.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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}]`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -45,13 +45,60 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<%= 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" } %>
|
||||
<div class="space-y-3" data-controller="account-type-selector" data-account-type-selector-account-id-value="<%= simplefin_account.id %>">
|
||||
<div>
|
||||
<%= 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"
|
||||
} } %>
|
||||
</div>
|
||||
|
||||
<!-- Subtype dropdowns (shown/hidden based on account type) -->
|
||||
<div data-account-type-selector-target="subtypeContainer">
|
||||
<div class="subtype-select" data-type="Depository" style="display: none;">
|
||||
<%= label_tag "account_subtypes[#{simplefin_account.id}]", "Account Subtype:",
|
||||
class: "block text-sm font-medium text-primary mb-2" %>
|
||||
<%= select_tag "account_subtypes[#{simplefin_account.id}]",
|
||||
options_for_select([["Select subtype", ""]] + @depository_subtypes,
|
||||
simplefin_account.name.downcase.include?("checking") ? "checking" :
|
||||
simplefin_account.name.downcase.include?("savings") ? "savings" : ""),
|
||||
{ class: "w-full px-3 py-2 border border-primary rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" } %>
|
||||
</div>
|
||||
|
||||
<div class="subtype-select" data-type="CreditCard" style="display: none;">
|
||||
<%= label_tag "account_subtypes[#{simplefin_account.id}]", "Credit Card Type:",
|
||||
class: "block text-sm font-medium text-primary mb-2" %>
|
||||
<%= select_tag "account_subtypes[#{simplefin_account.id}]",
|
||||
options_for_select([["Select type", ""]] + @credit_card_subtypes),
|
||||
{ class: "w-full px-3 py-2 border border-primary rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" } %>
|
||||
</div>
|
||||
|
||||
<div class="subtype-select" data-type="Investment" style="display: none;">
|
||||
<%= label_tag "account_subtypes[#{simplefin_account.id}]", "Investment Type:",
|
||||
class: "block text-sm font-medium text-primary mb-2" %>
|
||||
<%= select_tag "account_subtypes[#{simplefin_account.id}]",
|
||||
options_for_select([["Select type", ""]] + @investment_subtypes),
|
||||
{ class: "w-full px-3 py-2 border border-primary rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" } %>
|
||||
</div>
|
||||
|
||||
<div class="subtype-select" data-type="Loan" style="display: none;">
|
||||
<%= label_tag "account_subtypes[#{simplefin_account.id}]", "Loan Type:",
|
||||
class: "block text-sm font-medium text-primary mb-2" %>
|
||||
<%= select_tag "account_subtypes[#{simplefin_account.id}]",
|
||||
options_for_select([["Select type", ""]] + @loan_subtypes),
|
||||
{ class: "w-full px-3 py-2 border border-primary rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" } %>
|
||||
</div>
|
||||
|
||||
<!-- OtherAsset doesn't need subtype selection -->
|
||||
<div class="subtype-select" data-type="OtherAsset" style="display: none;">
|
||||
<p class="text-sm text-secondary">No additional options needed for Other Assets.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
Reference in New Issue
Block a user