Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
7c4356d
SimpleFin: metadata + merge fixes; holdings (incl. crypto) + Day Chan…
Oct 30, 2025
d9ce476
fix testing
Oct 31, 2025
32c1081
fix linting
Oct 31, 2025
5632619
xfix linting x2
Oct 31, 2025
94b5b98
Review PR #267 on we-promise/sure (SimpleFin enhancements v2). Addres…
Oct 31, 2025
3766d79
SimpleFin: address CodeRabbit comments (batch 1)
Oct 31, 2025
e32345e
Linter noise
jjmata Oct 31, 2025
3966909
removed filed commited by mistake.
Oct 31, 2025
64e7328
manual relink flow and tighten composite matching
Oct 31, 2025
81f6568
enforce manual relink UI; fix adapter keywords; guarantee extra.simp…
Oct 31, 2025
d5d534a
refactor(simplefin): extract relink service; enforce manual relink UI…
Oct 31, 2025
ee939d9
add provider date parser; refactor rake; move view queries; partial r…
Oct 31, 2025
8d1efca
run balances-only import in background job. make update flow enqueue …
Oct 31, 2025
9fc755a
persists across all update redirects and initialize
Oct 31, 2025
2a971d6
SimpleFin: metadata + merge fixes; holdings (incl. crypto) + Day Chan…
Oct 30, 2025
5142630
Fixed failed test after rebase.
Nov 2, 2025
6f4d2f3
scan_ruby fix
Nov 2, 2025
c6e5908
Calming the rabbit:
Nov 2, 2025
10eceb8
FIX SimpleFIN new account modal
sokie Nov 3, 2025
ff72c1b
Remove SimpleFin legacy UI components, migrate schema, and refine lin…
Nov 5, 2025
e446621
Extract SimpleFin-related logic to `prepare_show_context` helper and …
Nov 5, 2025
789ca42
Remove unused SimpleFin maps from prepare_show_context; select IDs to…
Nov 6, 2025
6d8707c
Remove unnecessary blank lines
DeathCamel58 Nov 6, 2025
b6b1abb
Reduce unnecessary changes
DeathCamel58 Nov 6, 2025
2c3e512
Simplefin Account Setup: Display in modal
DeathCamel58 Nov 6, 2025
01ef5d2
Removed unnecessary comment.
Nov 7, 2025
84cf949
removed unnecessary function.
Nov 7, 2025
cd1868c
fixed broken links
Nov 7, 2025
ca3902d
Removed unnecessary file
Nov 7, 2025
d51700f
changed to database query
Nov 7, 2025
9f79d2b
set to use UTC and gaurd against null
Nov 7, 2025
0c99819
set dry_run=true
Nov 7, 2025
ca92e3a
Fixed comment
Nov 7, 2025
6982322
Changed to use a database-level query
Nov 7, 2025
097fe30
matched test name to test behavior.
Nov 7, 2025
1af9b12
Eliminate code duplication and Time.zone dependency
Nov 7, 2025
2d30a6d
make final summary surface failures
Nov 7, 2025
f3ea9bd
lint fix
Nov 7, 2025
310f317
Revised timezone comment. better handle missing selectors.
Nov 7, 2025
2019e33
sanitized LIKE wildcards
Nov 7, 2025
7d67e47
Fixed SimpleFin import to avoid “Currency can’t be blank” validation …
Nov 7, 2025
0ab8f21
Added helper methods for admin and self-hosted checks
Nov 8, 2025
b34e1ac
Specify exception types in rescue clauses.
Nov 8, 2025
bc13100
Refined logic to determine transaction dates for credit accounts.
Nov 8, 2025
c26f2d2
Refined stats calculation for `total_accounts` to track the maximum u…
Nov 8, 2025
0443b2c
Moved `unlink_all!` logic to `SimplefinItem::Unlinking` concern and d…
Nov 8, 2025
b8b840a
Refined legacy unlinking logic, improved `current_holdings` formattin…
Nov 8, 2025
f9b8d7a
Enhanced `unlink_all!` with explicit error handling, improved transac…
Nov 8, 2025
d4e216d
Improved currency assignment logic by adding fallback to `current_acc…
Nov 8, 2025
919dac7
Enhanced error tracking during SimpleFin account imports by adding ca…
Nov 8, 2025
d2dac04
typo fix
Nov 8, 2025
3b07f98
Didn't realize rabbit was still mad...
Nov 9, 2025
65beaa5
Dang rabbit never stops... Centralized SimpleFin maps logic into `Map…
Nov 9, 2025
daa1a5c
Persistent rabbit. Optimized SimpleFin maps logic by implementing bat…
Nov 9, 2025
0ab481e
Lost a commit somehow, resolved here. Refactored transaction extra de…
Nov 11, 2025
033bbec
Refactored sensitive data redaction in `simplefin_unlink` task for re…
Nov 11, 2025
bcc979c
Lint fix
Nov 11, 2025
1ec19b2
Removed per PR comments.
jjmata Nov 13, 2025
07b28c8
Also removing per PR comment.
jjmata Nov 13, 2025
3f3c62c
git commit -m "SimpleFIN polish: preserve #manual-accounts wrapper, u…
Nov 16, 2025
297670a
Extend unlinked account check to include "Investment" type
Nov 16, 2025
a7a081d
set SimpleFIN item for `balances`, remove redundant unpacking, and im…
Nov 16, 2025
344f3d6
SimpleFIN: add `errors` action + modal; do not reintroduce legacy rel…
Nov 16, 2025
eb089cd
FIX simpleFIN linking
sokie Nov 17, 2025
27a0d90
Merge branch 'main' into simplefin-enhancements-v2
sokie Nov 17, 2025
6b44808
Add delay back, tests benefit from it
jjmata Nov 17, 2025
485b3e3
Put cache back in
jjmata Nov 17, 2025
afbc140
Remove empty `rake` task
jjmata Nov 17, 2025
1b8b364
Small spelling fixes.
jjmata Nov 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 46 additions & 2 deletions app/controllers/accounts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,55 @@ class AccountsController < ApplicationController
include Periodable

def index
@manual_accounts = family.accounts.manual.alphabetically
@manual_accounts = family.accounts
.visible_manual
.order(:name)
@plaid_items = family.plaid_items.ordered
@simplefin_items = family.simplefin_items.ordered
@simplefin_items = family.simplefin_items.ordered.includes(:syncs)
@lunchflow_items = family.lunchflow_items.ordered

# Precompute per-item maps to avoid queries in the view
@simplefin_sync_stats_map = {}
@simplefin_has_unlinked_map = {}

@simplefin_items.each do |item|
latest_sync = item.syncs.ordered.first
@simplefin_sync_stats_map[item.id] = (latest_sync&.sync_stats || {})
@simplefin_has_unlinked_map[item.id] = item.family.accounts
.visible_manual
.exists?
end

# Count of SimpleFin accounts that are not linked (no legacy account and no AccountProvider)
@simplefin_unlinked_count_map = {}
@simplefin_items.each do |item|
count = item.simplefin_accounts
.left_joins(:account, :account_provider)
.where(accounts: { id: nil }, account_providers: { id: nil })
.count
@simplefin_unlinked_count_map[item.id] = count
end

# Compute CTA visibility map used by the simplefin_item partial
@simplefin_show_relink_map = {}
@simplefin_items.each do |item|
begin
unlinked_count = @simplefin_unlinked_count_map[item.id] || 0
manuals_exist = @simplefin_has_unlinked_map[item.id]
sfa_any = if item.simplefin_accounts.loaded?
item.simplefin_accounts.any?
else
item.simplefin_accounts.exists?
end
@simplefin_show_relink_map[item.id] = (unlinked_count.to_i == 0 && manuals_exist && sfa_any)
rescue => e
Rails.logger.warn("SimpleFin card: CTA computation failed for item #{item.id}: #{e.class} - #{e.message}")
@simplefin_show_relink_map[item.id] = false
end
end

# Prevent Turbo Drive from caching this page to ensure fresh account lists
expires_now
render layout: "settings"
end

Expand Down
94 changes: 94 additions & 0 deletions app/controllers/concerns/simplefin_items/maps_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# frozen_string_literal: true

module SimplefinItems
module MapsHelper
extend ActiveSupport::Concern

# Build per-item maps consumed by the simplefin_item partial.
# Accepts a single SimplefinItem or a collection.
def build_simplefin_maps_for(items)
items = Array(items).compact
return if items.empty?

@simplefin_sync_stats_map ||= {}
@simplefin_has_unlinked_map ||= {}
@simplefin_unlinked_count_map ||= {}
@simplefin_duplicate_only_map ||= {}
@simplefin_show_relink_map ||= {}

# Batch-check if ANY family has manual accounts (same result for all items from same family)
family_ids = items.map { |i| i.family_id }.uniq
families_with_manuals = Account
.visible_manual
.where(family_id: family_ids)
.distinct
.pluck(:family_id)
.to_set

# Batch-fetch unlinked counts for all items in one query
unlinked_counts = SimplefinAccount
.where(simplefin_item_id: items.map(&:id))
.left_joins(:account, :account_provider)
.where(accounts: { id: nil }, account_providers: { id: nil })
.group(:simplefin_item_id)
.count

items.each do |item|
# Latest sync stats (avoid N+1; rely on includes(:syncs) where appropriate)
latest_sync = if item.syncs.loaded?
item.syncs.max_by(&:created_at)
else
item.syncs.ordered.first
end
stats = (latest_sync&.sync_stats || {})
@simplefin_sync_stats_map[item.id] = stats

# Whether the family has any manual accounts available to link (from batch query)
@simplefin_has_unlinked_map[item.id] = families_with_manuals.include?(item.family_id)

# Count from batch query (defaults to 0 if not found)
@simplefin_unlinked_count_map[item.id] = unlinked_counts[item.id] || 0

# Whether all reported errors for this item are duplicate-account warnings
@simplefin_duplicate_only_map[item.id] = compute_duplicate_only_flag(stats)

# Compute CTA visibility: show relink only when there are zero unlinked SFAs,
# there exist manual accounts to link, and the item has at least one SFA
begin
unlinked_count = @simplefin_unlinked_count_map[item.id] || 0
manuals_exist = @simplefin_has_unlinked_map[item.id]
sfa_any = if item.simplefin_accounts.loaded?
item.simplefin_accounts.any?
else
item.simplefin_accounts.exists?
end
@simplefin_show_relink_map[item.id] = (unlinked_count.to_i == 0 && manuals_exist && sfa_any)
rescue StandardError => e
Rails.logger.warn("SimpleFin card: CTA computation failed for item #{item.id}: #{e.class} - #{e.message}")
@simplefin_show_relink_map[item.id] = false
end
end

# Ensure maps are hashes even when items empty
@simplefin_sync_stats_map ||= {}
@simplefin_has_unlinked_map ||= {}
@simplefin_unlinked_count_map ||= {}
@simplefin_duplicate_only_map ||= {}
@simplefin_show_relink_map ||= {}
end

private
def compute_duplicate_only_flag(stats)
errs = Array(stats && stats["errors"]).map do |e|
if e.is_a?(Hash)
e["message"] || e[:message]
else
e.to_s
end
end
errs.present? && errs.all? { |m| m.to_s.downcase.include?("duplicate upstream account detected") }
rescue
false
end
end
end
8 changes: 5 additions & 3 deletions app/controllers/settings/bank_sync_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ def show
rel: "noopener noreferrer"
},
{
name: "SimpleFin",
description: "US & Canada connections via SimpleFin protocol.",
path: simplefin_items_path
name: "SimpleFIN",
description: "US & Canada connections via SimpleFIN protocol.",
path: "https://beta-bridge.simplefin.org",
target: "_blank",
rel: "noopener noreferrer"
}
]
end
Expand Down
18 changes: 12 additions & 6 deletions app/controllers/settings/providers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ def show
[ "Bank Sync Providers", nil ]
]

# Load all provider configurations
Provider::Factory.ensure_adapters_loaded
@provider_configurations = Provider::ConfigurationRegistry.all
prepare_show_context
end

def update
Expand Down Expand Up @@ -74,9 +72,7 @@ def update
rescue => error
Rails.logger.error("Failed to update provider settings: #{error.message}")
flash.now[:alert] = "Failed to update provider settings: #{error.message}"
# Set @provider_configurations so the view can render properly
Provider::Factory.ensure_adapters_loaded
@provider_configurations = Provider::ConfigurationRegistry.all
prepare_show_context
render :show, status: :unprocessable_entity
end

Expand Down Expand Up @@ -121,4 +117,14 @@ def reload_provider_configs(updated_fields)
adapter_class&.reload_configuration
end
end

# Prepares instance vars needed by the show view and partials
def prepare_show_context
# Load all provider configurations (exclude SimpleFin, which has its own unified panel below)
Provider::Factory.ensure_adapters_loaded
@provider_configurations = Provider::ConfigurationRegistry.all.reject { |config| config.provider_key.to_s.casecmp("simplefin").zero? }

# Providers page only needs to know whether any SimpleFin connections exist
@simplefin_items = Current.family.simplefin_items.ordered.select(:id)
end
end
Loading