diff --git a/app/controllers/simplefin_items_controller.rb b/app/controllers/simplefin_items_controller.rb index 9a3bf087e..fc7a211a5 100644 --- a/app/controllers/simplefin_items_controller.rb +++ b/app/controllers/simplefin_items_controller.rb @@ -81,32 +81,32 @@ class SimplefinItemsController < ApplicationController def setup_accounts @simplefin_accounts = @simplefin_item.simplefin_accounts @account_type_options = [ - ['Checking or Savings Account', 'Depository'], - ['Credit Card', 'CreditCard'], - ['Investment Account', 'Investment'], - ['Loan or Mortgage', 'Loan'], - ['Other Asset', 'OtherAsset'] + [ "Checking or Savings Account", "Depository" ], + [ "Credit Card", "CreditCard" ], + [ "Investment Account", "Investment" ], + [ "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] } + @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 and subtype account = Account.create_from_simplefin_account_with_type_and_subtype( - simplefin_account, - selected_type, + simplefin_account, + selected_type, selected_subtype ) simplefin_account.update!(account: account) @@ -114,10 +114,10 @@ class SimplefinItemsController < ApplicationController # Clear pending status and mark as complete @simplefin_item.update!(pending_account_setup: false) - + # Schedule account syncs for the newly created accounts @simplefin_item.schedule_account_syncs - + redirect_to simplefin_items_path, notice: "SimpleFin accounts have been set up successfully!" end diff --git a/app/models/account.rb b/app/models/account.rb index 20bbafd5f..a59024ccd 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -118,37 +118,37 @@ class Account < ApplicationRecord 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 - when "checking", "savings" + # First try to map by explicit type if provided + case simplefin_type&.downcase + when "checking", "savings" + return "Depository" + when "credit", "credit card" + return "CreditCard" + when "investment", "brokerage" + return "Investment" + when "loan", "mortgage" + return "Loan" + end + + # If type is unknown, try to infer from account name + if account_name.present? + name_lower = account_name.downcase + case name_lower + when /checking|chk/ return "Depository" - when "credit", "credit card" + when /savings|save/ + return "Depository" + when /credit|card/ return "CreditCard" - when "investment", "brokerage" + when /investment|invest|brokerage|401k|ira/ return "Investment" - when "loan", "mortgage" + when /loan|mortgage|auto|personal/ return "Loan" end + end - # If type is unknown, try to infer from account name - if account_name.present? - name_lower = account_name.downcase - case name_lower - when /checking|chk/ - return "Depository" - when /savings|save/ - return "Depository" - when /credit|card/ - return "CreditCard" - when /investment|invest|brokerage|401k|ira/ - return "Investment" - when /loan|mortgage|auto|personal/ - return "Loan" - end - end - - # Default to OtherAsset if we can't determine type - "OtherAsset" + # Default to OtherAsset if we can't determine type + "OtherAsset" end private @@ -164,11 +164,11 @@ class Account < ApplicationRecord 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 diff --git a/app/models/simplefin_account.rb b/app/models/simplefin_account.rb index a84e64c99..a123ed9a8 100644 --- a/app/models/simplefin_account.rb +++ b/app/models/simplefin_account.rb @@ -7,19 +7,20 @@ class SimplefinAccount < ApplicationRecord validate :has_balance def upsert_simplefin_snapshot!(account_snapshot) + # Convert to symbol keys or handle both string and symbol keys + snapshot = account_snapshot.with_indifferent_access + # Map SimpleFin field names to our field names - assign_attributes( - current_balance: parse_balance(account_snapshot[:balance]), - available_balance: parse_balance(account_snapshot[:"available-balance"]), - currency: parse_currency(account_snapshot[:currency]), - account_type: account_snapshot[:type] || "unknown", - account_subtype: account_snapshot[:subtype], - name: account_snapshot[:name], - account_id: account_snapshot[:id], + update!( + current_balance: parse_balance(snapshot[:balance]), + available_balance: parse_balance(snapshot[:"available-balance"]), + currency: parse_currency(snapshot[:currency]), + account_type: snapshot[:type] || "unknown", + account_subtype: snapshot[:subtype], + name: snapshot[:name], + account_id: snapshot[:id], raw_payload: account_snapshot ) - - save! end def upsert_simplefin_transactions_snapshot!(transactions_snapshot) diff --git a/app/models/simplefin_item/provided.rb b/app/models/simplefin_item/provided.rb index 570571af9..b79c13b97 100644 --- a/app/models/simplefin_item/provided.rb +++ b/app/models/simplefin_item/provided.rb @@ -4,4 +4,4 @@ module SimplefinItem::Provided def simplefin_provider @simplefin_provider ||= Provider::Simplefin.new end -end \ No newline at end of file +end diff --git a/app/views/accounts/show/_header.html.erb b/app/views/accounts/show/_header.html.erb index 760c201f8..e34236e64 100644 --- a/app/views/accounts/show/_header.html.erb +++ b/app/views/accounts/show/_header.html.erb @@ -32,7 +32,7 @@ "refresh-cw", as_button: true, size: "sm", - href: account.plaid_account_id.present? ? sync_plaid_item_path(account.plaid_account.plaid_item) : + href: account.plaid_account_id.present? ? sync_plaid_item_path(account.plaid_account.plaid_item) : account.simplefin_account_id.present? ? sync_simplefin_item_path(account.simplefin_account.simplefin_item) : sync_account_path(account), disabled: account.syncing?, diff --git a/app/views/simplefin_items/_simplefin_item.html.erb b/app/views/simplefin_items/_simplefin_item.html.erb index 128edaf3e..552be10d0 100644 --- a/app/views/simplefin_items/_simplefin_item.html.erb +++ b/app/views/simplefin_items/_simplefin_item.html.erb @@ -92,4 +92,4 @@ <% end %> -<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/simplefin_items/index.html.erb b/app/views/simplefin_items/index.html.erb index 01ff66d16..929981873 100644 --- a/app/views/simplefin_items/index.html.erb +++ b/app/views/simplefin_items/index.html.erb @@ -6,7 +6,7 @@

SimpleFin Connections

Manage your SimpleFin bank account connections

- + <%= render DS::Link.new( text: "Add Connection", icon: "plus", @@ -35,4 +35,4 @@ ) %> <% end %> - \ No newline at end of file + diff --git a/app/views/simplefin_items/new.html.erb b/app/views/simplefin_items/new.html.erb index 6b7fbb332..8d1f72d7d 100644 --- a/app/views/simplefin_items/new.html.erb +++ b/app/views/simplefin_items/new.html.erb @@ -8,16 +8,16 @@ <% end %> <%= styled_form_with model: @simplefin_item, local: true, data: { turbo: false }, class: "flex flex-col gap-4 justify-between grow text-primary" do |form| %>
- <%= form.text_area :setup_token, + <%= form.text_area :setup_token, label: "SimpleFin Setup Token", placeholder: "Paste your SimpleFin setup token here...", rows: 4, required: true %> - +

- Get your setup token from - <%= link_to "SimpleFin Bridge", "https://bridge.simplefin.org/simplefin/create", - target: "_blank", + Get your setup token from + <%= link_to "SimpleFin Bridge", "https://bridge.simplefin.org/simplefin/create", + target: "_blank", class: "text-link underline" %>

@@ -43,4 +43,4 @@ <%= form.submit "Add Connection" %> <% end %> <% end %> -<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/simplefin_items/setup_accounts.html.erb b/app/views/simplefin_items/setup_accounts.html.erb index 6493be9d7..7ee45c377 100644 --- a/app/views/simplefin_items/setup_accounts.html.erb +++ b/app/views/simplefin_items/setup_accounts.html.erb @@ -7,14 +7,14 @@ Choose the correct account types for your imported accounts
<% end %> - + <% dialog.with_body do %> - <%= form_with url: complete_account_setup_simplefin_item_path(@simplefin_item), - method: :post, - local: true, - data: { turbo: false }, + <%= form_with url: complete_account_setup_simplefin_item_path(@simplefin_item), + method: :post, + local: true, + data: { turbo: false }, class: "space-y-6" do |form| %> - +
@@ -33,7 +33,7 @@
- + <% @simplefin_accounts.each do |simplefin_account| %>
@@ -44,56 +44,56 @@

- +
- <%= label_tag "account_types[#{simplefin_account.id}]", "Account Type:", + <%= 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)), + <%= 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-600 focus:border-blue-600", - data: { + data: { action: "change->account-type-selector#updateSubtype" } } %>
- +
- + - + - + - + <% end %>
- +
- <%= form.submit "Create Accounts", + <%= form.submit "Create Accounts", class: "flex-1 bg-blue-600 text-white px-4 py-2 rounded-lg font-medium hover:bg-blue-700 focus:ring-2 focus:ring-blue-600 focus:ring-offset-2" %> - <%= link_to "Cancel", - simplefin_items_path, + <%= link_to "Cancel", + simplefin_items_path, class: "px-4 py-2 text-secondary hover:text-primary" %>
<% end %> <% end %> -<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/simplefin_items/show.html.erb b/app/views/simplefin_items/show.html.erb new file mode 100644 index 000000000..5b16e58de --- /dev/null +++ b/app/views/simplefin_items/show.html.erb @@ -0,0 +1,67 @@ +<% content_for :title, @simplefin_item.name %> + +
+ <%= link_to simplefin_items_path, class: "text-gray-700 hover:text-gray-900" do %> + ← Back to SimpleFin Connections + <% end %> +

<%= @simplefin_item.name %>

+
+ <%= button_to "Sync", sync_simplefin_item_path(@simplefin_item), + method: :post, + class: "btn btn-secondary" %> + <%= button_to "Delete", simplefin_item_path(@simplefin_item), + method: :delete, + class: "btn btn-red", + data: { confirm: "Are you sure?" } %> +
+
+ +
+ <% if @simplefin_item.syncing? %> +
+
+ <%= icon "loader-2", class: "w-5 h-5 text-blue-600 animate-spin mr-2" %> +

Syncing accounts...

+
+
+ <% end %> + + <% if @simplefin_item.simplefin_accounts.any? %> +
+
+

Connected Accounts

+
+
    + <% @simplefin_item.simplefin_accounts.each do |simplefin_account| %> +
  • +
    +

    <%= simplefin_account.name %>

    +

    <%= simplefin_account.account_type.humanize %>

    +

    + Balance: <%= number_to_currency(simplefin_account.current_balance || 0) %> + <% if simplefin_account.currency != "USD" %> + <%= simplefin_account.currency %> + <% end %> +

    +
    +
    + <% if simplefin_account.account %> + <%= link_to "View Account", account_path(simplefin_account.account), + class: "text-blue-600 hover:text-blue-800" %> + <% else %> + Setting up... + <% end %> +
    +
  • + <% end %> +
+
+ <% else %> +
+

No accounts found. Try syncing again.

+ <%= button_to "Sync Now", sync_simplefin_item_path(@simplefin_item), + method: :post, + class: "mt-4 btn btn--secondary" %> +
+ <% end %> +
diff --git a/app/views/transactions/show.html.erb b/app/views/transactions/show.html.erb index 9554fabe6..770f1482b 100644 --- a/app/views/transactions/show.html.erb +++ b/app/views/transactions/show.html.erb @@ -84,8 +84,7 @@ label: t(".tags_label"), container_class: "h-40" }, - { "data-controller": "multi-select", "data-auto-submit-form-target": "auto" } - %> + { "data-controller": "multi-select", "data-auto-submit-form-target": "auto" } %> <% end %> <% end %> diff --git a/config/routes.rb b/config/routes.rb index 042ae1571..9a5a66d53 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -251,7 +251,7 @@ Rails.application.routes.draw do end end - resources :simplefin_items, only: %i[index new create destroy] do + resources :simplefin_items, only: %i[index new create show destroy] do member do post :sync get :setup_accounts diff --git a/test/controllers/simplefin_items_controller_test.rb b/test/controllers/simplefin_items_controller_test.rb index 9f395a243..1ba03fd4a 100644 --- a/test/controllers/simplefin_items_controller_test.rb +++ b/test/controllers/simplefin_items_controller_test.rb @@ -2,7 +2,7 @@ require "test_helper" class SimplefinItemsControllerTest < ActionDispatch::IntegrationTest setup do - sign_in users(:dylan) + sign_in users(:family_admin) @family = families(:dylan_family) @simplefin_item = SimplefinItem.create!( family: @family, diff --git a/test/models/simplefin_account_test.rb b/test/models/simplefin_account_test.rb index 012366152..43b7f60c5 100644 --- a/test/models/simplefin_account_test.rb +++ b/test/models/simplefin_account_test.rb @@ -46,19 +46,19 @@ class SimplefinAccountTest < ActiveSupport::TestCase test "can upsert snapshot data" do snapshot = { - balance: 2000.00, - available_balance: 1800.00, - currency: "USD", - type: "savings", - subtype: "savings", - name: "Updated Savings Account", - id: "updated_123" + "balance" => 2000.0, + "available-balance" => 1800.0, + "currency" => "USD", + "type" => "savings", + "subtype" => "savings", + "name" => "Updated Savings Account", + "id" => "updated_123" } @simplefin_account.upsert_simplefin_snapshot!(snapshot) - assert_equal 2000.00, @simplefin_account.current_balance - assert_equal 1800.00, @simplefin_account.available_balance + assert_equal BigDecimal("2000.0"), @simplefin_account.current_balance + assert_equal BigDecimal("1800.0"), @simplefin_account.available_balance assert_equal "savings", @simplefin_account.account_type assert_equal "Updated Savings Account", @simplefin_account.name assert_equal snapshot, @simplefin_account.raw_payload @@ -66,8 +66,8 @@ class SimplefinAccountTest < ActiveSupport::TestCase test "can upsert transactions" do transactions = [ - { id: "txn_1", amount: -50.00, description: "Coffee Shop", posted: "2024-01-01" }, - { id: "txn_2", amount: 1000.00, description: "Paycheck", posted: "2024-01-02" } + { "id" => "txn_1", "amount" => -50.00, "description" => "Coffee Shop", "posted" => "2024-01-01" }, + { "id" => "txn_2", "amount" => 1000.00, "description" => "Paycheck", "posted" => "2024-01-02" } ] @simplefin_account.upsert_simplefin_transactions_snapshot!(transactions)