feat(enable-banking): enhance transaction import, metadata handling, and UI (#1406)

* feat(enable-banking): enhance transaction import, metadata handling, and UI

* fix(enable-banking): address security, sync edge cases and PR feedback

* fix(enable-banking): resolve silent failures, auth overrides, and sync logic bugs

* fix(enable-banking): resolve sync logic bugs, trailing whitespaces, and apply safe_psu_headers

* test(enable-banking): mock set_current_balance to return success result

* fix(budget): properly filter pending transactions and classify synced loan payments

* style: fix trailing whitespace detected by rubocop

* refactor: address code review feedback for Enable Banking sync and reporting

---------

Signed-off-by: Louis <contact@boul2gom.com>
Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
This commit is contained in:
Louis
2026-04-10 23:19:48 +02:00
committed by GitHub
parent d6d7df12fd
commit e96fb0c23f
28 changed files with 1118 additions and 160 deletions

View File

@@ -1,4 +1,5 @@
class EnableBankingAccount::Processor
class ProcessingError < StandardError; end
include CurrencyNormalizable
attr_reader :enable_banking_account
@@ -37,23 +38,47 @@ class EnableBankingAccount::Processor
account = enable_banking_account.current_account
balance = enable_banking_account.current_balance || 0
available_credit = nil
# For credit cards, compute balance based on credit limit
if account.accountable_type == "CreditCard"
available_credit = account.accountable.available_credit || 0
balance = available_credit - balance
# For liability accounts, ensure positive balances
elsif account.accountable_type == "Loan"
balance = -balance
# For liability accounts, ensure balance sign is correct.
# DELIBERATE UX DECISION: For CreditCards, we display the available credit (credit_limit - outstanding debt)
# rather than the raw outstanding debt. Do not revert this behavior, as future maintainers should understand
# users expect to see how much credit they have left rather than their debt balance.
# The 'available_credit' calculation overrides the 'balance' variable.
if account.accountable_type == "Loan"
balance = balance.abs
elsif account.accountable_type == "CreditCard"
if enable_banking_account.credit_limit.present?
available = enable_banking_account.credit_limit - balance.abs
available_credit = [ available, 0 ].max
balance = available_credit
unless account.accountable.present?
Rails.logger.warn "EnableBankingAccount::Processor - CreditCard accountable missing for account #{account.id}"
end
else
# Fallback: no credit_limit from API — display raw outstanding balance
# We cannot derive available credit without knowing the limit; leave balance unchanged.
end
end
currency = parse_currency(enable_banking_account.currency) || account.currency || "EUR"
account.update!(
balance: balance,
cash_balance: balance,
currency: currency
)
# Wrap both writes in a transaction so a failure on either rolls back both.
ActiveRecord::Base.transaction do
if account.accountable.present? && account.accountable.respond_to?(:available_credit=)
account.accountable.update!(available_credit: available_credit)
end
account.update!(currency: currency, cash_balance: balance)
# Use set_current_balance to create a current_anchor valuation entry.
# This enables Balance::ReverseCalculator, which works backward from the
# bank-reported balance — eliminating spurious cash adjustment spikes.
result = account.set_current_balance(balance)
raise ProcessingError, "Failed to set current balance: #{result.error}" unless result.success?
end
# TODO: pass explicit window_start_date to sync_later to avoid full history recalculation on every sync
# Currently relies on set_current_balance's implicit sync trigger; window params would require refactor
end
def process_transactions