Pending detection, FX metadata, Pending UI badge. (#374)

* - Add support for `SIMPLEFIN_INCLUDE_PENDING` to control pending behavior via ENV.
- Enhance debug logging for SimpleFin API requests and raw payloads.
- Refine pending flag handling in `SimplefinEntry::Processor` based on provider data and inferred conditions.
- Improve FX metadata processing for transactions with currency mismatches.
- Add new tests for pending detection, FX metadata, and edge cases involving `posted` values.
- Add pending indicator UI to transaction view.

* Document pending transaction detection, storage, and UI behavior for SimpleFIN and Plaid integrations. Add debug flags for troubleshooting.

* Add `pending?` method to `Transaction` model, refactor UI indicator, and centralize SimpleFIN configuration

- Introduced `pending?` method in `Transaction` for unified pending state detection.
- Refactored transaction pending indicator in the UI to use `pending?` method.
- Centralized SimpleFIN configuration in initializer with ENV-backed toggles.
- Updated tests for `pending?` behavior and clarified docs for pending detection logic

* Add SimpleFIN debug and runtime flags to `.env.local.example` and `.env.test.example`

- Introduced `SIMPLEFIN_INCLUDE_PENDING` and `SIMPLEFIN_DEBUG_RAW` flags for controlling pending behavior and debugging.
- Updated example environment files with descriptions for new configuration options.

* Normalize formatting for `SIMPLEFIN_INCLUDE_PENDING` and `SIMPLEFIN_DEBUG_RAW` flags in `.env.local.example` and `.env.test.example`.

---------

Co-authored-by: Josh Waldrep <joshua.waldrep5+github@gmail.com>
This commit is contained in:
LPW
2025-12-19 17:24:48 -05:00
committed by GitHub
parent febbf42e1b
commit 664c6c2b7c
13 changed files with 229 additions and 3 deletions

View File

@@ -15,7 +15,12 @@ class PlaidEntry::Processor
name: name,
source: "plaid",
category_id: matched_category&.id,
merchant: merchant
merchant: merchant,
extra: {
plaid: {
pending: plaid_transaction["pending"]
}
}
)
end

View File

@@ -1,4 +1,9 @@
class Provider::Simplefin
# Pending: some institutions do not return pending transactions even with `pending=1`.
# This is provider variability (not a bug). For troubleshooting, you can set
# `SIMPLEFIN_INCLUDE_PENDING=1` and/or `SIMPLEFIN_DEBUG_RAW=1` (both default-off).
# These are centralized in `Rails.configuration.x.simplefin.*` via
# `config/initializers/simplefin.rb`.
include HTTParty
headers "User-Agent" => "Sure Finance SimpleFin Client"

View File

@@ -34,6 +34,24 @@ class SimplefinEntry::Processor
# Include provider-supplied extra hash if present
sf["extra"] = data[:extra] if data[:extra].is_a?(Hash)
# Pending detection: honor provider flag or infer from missing/zero posted with present transacted_at
posted_val = data[:posted]
posted_missing = posted_val.blank? || posted_val == 0 || posted_val == "0"
if ActiveModel::Type::Boolean.new.cast(data[:pending]) || (posted_missing && data[:transacted_at].present?)
sf["pending"] = true
Rails.logger.debug("SimpleFIN: flagged pending transaction #{external_id}")
end
# FX metadata: when tx currency differs from account currency
tx_currency = parse_currency(data[:currency])
acct_currency = account.currency
if tx_currency.present? && acct_currency.present? && tx_currency != acct_currency
sf["fx_from"] = tx_currency
# Prefer transacted_at for fx date, fallback to posted
fx_d = transacted_date || posted_date
sf["fx_date"] = fx_d&.to_s
end
return nil if sf.empty?
{ "simplefin" => sf }
end
@@ -124,6 +142,8 @@ class SimplefinEntry::Processor
def posted_date
val = data[:posted]
# Treat 0 / "0" as missing to avoid Unix epoch 1970-01-01 for pendings
return nil if val == 0 || val == "0"
Simplefin::DateUtils.parse_provider_date(val)
end

View File

@@ -403,6 +403,10 @@ class SimplefinItem::Importer
# Returns a Hash payload with keys like :accounts, or nil when an error is
# handled internally via `handle_errors`.
def fetch_accounts_data(start_date:, end_date: nil, pending: nil)
# Determine whether to include pending based on explicit arg or global config.
# `Rails.configuration.x.simplefin.include_pending` is ENV-backed.
effective_pending = pending.nil? ? Rails.configuration.x.simplefin.include_pending : pending
# Debug logging to track exactly what's being sent to SimpleFin API
start_str = start_date.respond_to?(:strftime) ? start_date.strftime("%Y-%m-%d") : "none"
end_str = end_date.respond_to?(:strftime) ? end_date.strftime("%Y-%m-%d") : "current"
@@ -411,7 +415,7 @@ class SimplefinItem::Importer
else
"unknown"
end
Rails.logger.info "SimplefinItem::Importer - API Request: #{start_str} to #{end_str} (#{days_requested} days)"
Rails.logger.info "SimplefinItem::Importer - API Request: #{start_str} to #{end_str} (#{days_requested} days) pending=#{effective_pending ? 1 : 0}"
begin
# Track API request count for quota awareness
@@ -420,7 +424,7 @@ class SimplefinItem::Importer
simplefin_item.access_url,
start_date: start_date,
end_date: end_date,
pending: pending
pending: effective_pending
)
# Soft warning when approaching SimpleFin daily refresh guidance
if stats["api_requests"].to_i >= 20
@@ -436,6 +440,11 @@ class SimplefinItem::Importer
end
end
# Optional raw payload debug logging (guarded by ENV to avoid spam)
if Rails.configuration.x.simplefin.debug_raw
Rails.logger.debug("SimpleFIN raw: #{accounts_data.inspect}")
end
# Handle errors if present in response
if accounts_data[:errors] && accounts_data[:errors].any?
if accounts_data[:accounts].to_a.any?

View File

@@ -31,4 +31,12 @@ class Transaction < ApplicationRecord
update!(category: category)
end
def pending?
extra_data = extra.is_a?(Hash) ? extra : {}
ActiveModel::Type::Boolean.new.cast(extra_data.dig("simplefin", "pending")) ||
ActiveModel::Type::Boolean.new.cast(extra_data.dig("plaid", "pending"))
rescue
false
end
end