Fetch transaction counts during provider setup

Add transaction count validation for all banking providers (SimpleFIN,
Lunch Flow, and Enable Banking) during the account setup process.

This change fetches transaction data for each bank account immediately
after provider credentials are configured, allowing users to see
warnings about accounts with no transaction history before completing
the setup.

Key changes:
- SimpleFIN: Fetch accounts and check transaction counts after token setup
- Lunch Flow: Check transaction availability after API key configuration
- Enable Banking: Validate transaction data after OAuth authorization
- Display warning messages in provider panels when issues are detected
- Warnings show accounts with 0 transactions in the last 90 days

The warnings appear in the /settings/providers screen before the
"Configured and ready to use" message, giving users early visibility
into potential data availability issues.
This commit is contained in:
Claude
2025-11-30 21:27:32 +00:00
parent 4a29d030af
commit ddab962d76
5 changed files with 175 additions and 2 deletions

View File

@@ -190,9 +190,17 @@ class EnableBankingItemsController < ApplicationController
begin
enable_banking_item.complete_authorization(code: code)
# Fetch transaction counts for validation
transaction_warnings = fetch_transaction_counts(enable_banking_item)
# Trigger sync to process accounts
enable_banking_item.sync_later
if transaction_warnings.any?
# Store warnings in flash for display on accounts page
flash[:warning] = "Connected successfully, but some issues were found: #{transaction_warnings.join('; ')}"
end
redirect_to accounts_path, notice: t(".success", default: "Successfully connected to your bank. Your accounts are being synced.")
rescue Provider::EnableBanking::EnableBankingError => e
Rails.logger.error "Enable Banking session creation error: #{e.message}"
@@ -419,6 +427,53 @@ class EnableBankingItemsController < ApplicationController
)
end
# Fetch transaction counts for all accounts in the Enable Banking item
# Returns an array of warning messages if any accounts have issues
def fetch_transaction_counts(enable_banking_item)
warnings = []
begin
provider = enable_banking_item.enable_banking_provider
return warnings unless provider
accounts = enable_banking_item.enable_banking_accounts
if accounts.empty?
warnings << "No bank accounts found after authorization."
else
# Check transaction counts for each account (last 90 days)
accounts.each do |enable_banking_account|
account_name = enable_banking_account.name || "Unknown Account"
account_uid = enable_banking_account.uid
begin
transactions_data = provider.get_account_transactions(
account_id: account_uid,
date_from: 90.days.ago,
date_to: Date.today
)
transactions = transactions_data[:transactions] || []
if transactions.empty?
warnings << "Account '#{account_name}' has 0 transactions available in the last 90 days."
end
rescue Provider::EnableBanking::EnableBankingError => e
Rails.logger.warn("Enable Banking transaction count check failed for account #{account_uid}: #{e.message}")
warnings << "Unable to fetch transactions for '#{account_name}': #{e.message}"
end
end
end
rescue Provider::EnableBanking::EnableBankingError => e
Rails.logger.warn("Enable Banking accounts fetch failed: #{e.message}")
warnings << "Unable to fetch account information: #{e.message}"
rescue => e
Rails.logger.warn("Unexpected error checking Enable Banking transactions: #{e.message}")
warnings << "Unable to verify transaction availability."
end
warnings
end
# Generate the callback URL for Enable Banking OAuth
# In production, uses the standard Rails route
# In development, uses DEV_WEBHOOKS_URL if set (e.g., ngrok URL)

View File

@@ -411,6 +411,9 @@ class LunchflowItemsController < ApplicationController
# Trigger initial sync to fetch accounts
@lunchflow_item.sync_later
# Fetch transaction counts for validation
@transaction_warnings = fetch_transaction_counts(@lunchflow_item)
if turbo_frame_request?
flash.now[:notice] = t(".success")
@lunchflow_items = Current.family.lunchflow_items.ordered
@@ -418,7 +421,7 @@ class LunchflowItemsController < ApplicationController
turbo_stream.replace(
"lunchflow-providers-panel",
partial: "settings/providers/lunchflow_panel",
locals: { lunchflow_items: @lunchflow_items }
locals: { lunchflow_items: @lunchflow_items, transaction_warnings: @transaction_warnings }
),
*flash_notification_stream_items
]
@@ -737,6 +740,54 @@ class LunchflowItemsController < ApplicationController
params.require(:lunchflow_item).permit(:name, :sync_start_date, :api_key, :base_url)
end
# Fetch transaction counts for all accounts in the Lunchflow item
# Returns an array of warning messages if any accounts have issues
def fetch_transaction_counts(lunchflow_item)
warnings = []
begin
provider = lunchflow_item.lunchflow_provider
return warnings unless provider
accounts_data = provider.get_accounts
accounts = accounts_data[:accounts] || []
if accounts.empty?
warnings << "No bank accounts found. Please check your Lunch Flow configuration."
else
# Check transaction counts for each account (last 90 days)
accounts.each do |account_data|
account_name = account_data[:name] || "Unknown Account"
account_id = account_data[:id]
begin
transactions_data = provider.get_account_transactions(
account_id,
start_date: 90.days.ago,
end_date: Date.today
)
transactions = transactions_data[:transactions] || []
if transactions.empty?
warnings << "Account '#{account_name}' has 0 transactions available in the last 90 days."
end
rescue Provider::Lunchflow::LunchflowError => e
Rails.logger.warn("Lunchflow transaction count check failed for account #{account_id}: #{e.message}")
warnings << "Unable to fetch transactions for '#{account_name}': #{e.message}"
end
end
end
rescue Provider::Lunchflow::LunchflowError => e
Rails.logger.warn("Lunchflow accounts fetch failed: #{e.message}")
warnings << "Unable to fetch account information: #{e.message}"
rescue => e
Rails.logger.warn("Unexpected error checking Lunchflow transactions: #{e.message}")
warnings << "Unable to verify transaction availability."
end
warnings
end
# Sanitize return_to parameter to prevent XSS attacks
# Only allow internal paths, reject external URLs and javascript: URIs
def safe_return_to_path

View File

@@ -92,6 +92,9 @@ class SimplefinItemsController < ApplicationController
item_name: "SimpleFIN Connection"
)
# Fetch transaction counts for validation
@transaction_warnings = fetch_transaction_counts(@simplefin_item)
if turbo_frame_request?
flash.now[:notice] = t(".success")
@simplefin_items = Current.family.simplefin_items.ordered
@@ -99,7 +102,7 @@ class SimplefinItemsController < ApplicationController
turbo_stream.replace(
"simplefin-providers-panel",
partial: "settings/providers/simplefin_panel",
locals: { simplefin_items: @simplefin_items }
locals: { simplefin_items: @simplefin_items, transaction_warnings: @transaction_warnings }
),
*flash_notification_stream_items
]
@@ -439,6 +442,45 @@ class SimplefinItemsController < ApplicationController
s.gsub(NAME_NORM_RE, " ")
end
# Fetch transaction counts for all accounts in the SimpleFIN item
# Returns an array of warning messages if any accounts have issues
def fetch_transaction_counts(simplefin_item)
warnings = []
begin
# Fetch accounts with a reasonable date range (last 90 days)
provider = simplefin_item.simplefin_provider
accounts_data = provider.get_accounts(
simplefin_item.access_url,
start_date: 90.days.ago,
end_date: Date.today
)
accounts = accounts_data[:accounts] || []
if accounts.empty?
warnings << "No bank accounts found. Please check your SimpleFIN Bridge setup."
else
accounts.each do |account_data|
account_name = account_data[:name] || "Unknown Account"
transactions = account_data[:transactions] || []
if transactions.empty?
warnings << "Account '#{account_name}' has 0 transactions available in the last 90 days."
end
end
end
rescue Provider::Simplefin::SimplefinError => e
Rails.logger.warn("SimpleFin transaction count check failed: #{e.message}")
warnings << "Unable to fetch transaction information: #{e.message}"
rescue => e
Rails.logger.warn("Unexpected error checking SimpleFin transactions: #{e.message}")
warnings << "Unable to verify transaction availability."
end
warnings
end
def compute_relink_candidates
# Best-effort dedup before building candidates
@simplefin_item.dedup_simplefin_accounts! rescue nil

View File

@@ -53,6 +53,19 @@
<% end %>
<% items = local_assigns[:lunchflow_items] || @lunchflow_items || Current.family.lunchflow_items.where.not(api_key: [nil, ""]) %>
<% transaction_warnings = local_assigns[:transaction_warnings] %>
<% if transaction_warnings&.any? %>
<div class="p-3 rounded-md bg-warning/10 border border-warning/20 text-warning text-sm space-y-1">
<p class="font-medium">Transaction Data Warnings:</p>
<ul class="list-disc list-inside text-xs space-y-0.5">
<% transaction_warnings.each do |warning| %>
<li><%= warning %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="flex items-center gap-2">
<% if items&.any? %>
<div class="w-2 h-2 bg-success rounded-full"></div>

View File

@@ -36,6 +36,18 @@
</div>
<% end %>
<% transaction_warnings = local_assigns[:transaction_warnings] %>
<% if transaction_warnings&.any? %>
<div class="p-3 rounded-md bg-warning/10 border border-warning/20 text-warning text-sm space-y-1">
<p class="font-medium">Transaction Data Warnings:</p>
<ul class="list-disc list-inside text-xs space-y-0.5">
<% transaction_warnings.each do |warning| %>
<li><%= warning %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="flex items-center gap-2">
<% if @simplefin_items&.any? %>
<div class="w-2 h-2 bg-success rounded-full"></div>