fix: prefer booked over available balance types in Enable Banking sync (#825)

The balance selection logic prioritized ITAV (Interim Available) which
includes overdraft capacity, inflating displayed balances. Reorder to
prefer booked types (CLBD > XPCD > ITBD) before available types
(CLAV > ITAV).

https://claude.ai/code/session_01EhxhgQqun8RuzfgkMxbp8x
This commit is contained in:
Claude
2026-04-07 17:11:13 +00:00
parent d6183be1ae
commit f08039d6a9
2 changed files with 102 additions and 6 deletions

View File

@@ -3,6 +3,10 @@ class EnableBankingItem::Importer
# Enable Banking typically returns ~100 transactions per page, so 100 pages = ~10,000 transactions
MAX_PAGINATION_PAGES = 100
# Balance type priority: prefer booked balances over available balances.
# Available types (CLAV, ITAV) include overdraft/credit capacity and inflate balances.
BALANCE_TYPE_PRIORITY = %w[CLBD XPCD ITBD CLAV ITAV].freeze
attr_reader :enable_banking_item, :enable_banking_provider
def initialize(enable_banking_item, enable_banking_provider:)
@@ -161,12 +165,8 @@ class EnableBankingItem::Importer
balances = balance_data[:balances] || []
return if balances.empty?
# Find the most relevant balance (prefer "ITAV" or "CLAV" types)
balance = balances.find { |b| b[:balance_type] == "ITAV" } ||
balances.find { |b| b[:balance_type] == "CLAV" } ||
balances.find { |b| b[:balance_type] == "ITBD" } ||
balances.find { |b| b[:balance_type] == "CLBD" } ||
balances.first
# Find the most relevant balance (prefer booked over available types)
balance = select_balance(balances)
if balance.present?
amount = balance.dig(:balance_amount, :amount) || balance[:amount]
@@ -183,6 +183,14 @@ class EnableBankingItem::Importer
Rails.logger.error "EnableBankingItem::Importer - Error fetching balance for account #{enable_banking_account.uid}: #{e.message}"
end
def select_balance(balances)
BALANCE_TYPE_PRIORITY.each do |type|
found = balances.find { |b| b[:balance_type] == type }
return found if found
end
balances.first
end
def fetch_and_store_transactions(enable_banking_account)
start_date = determine_sync_start_date(enable_banking_account)

View File

@@ -0,0 +1,88 @@
require "test_helper"
class EnableBankingItem::ImporterBalanceSelectionTest < ActiveSupport::TestCase
setup do
@family = families(:dylan_family)
@enable_banking_item = EnableBankingItem.create!(
family: @family,
name: "Test Enable Banking",
country_code: "AT",
application_id: "test_app_id",
client_certificate: "test_cert",
session_id: "test_session",
session_expires_at: 1.day.from_now
)
mock_provider = mock()
@importer = EnableBankingItem::Importer.new(@enable_banking_item, enable_banking_provider: mock_provider)
end
test "prefers CLBD when multiple balance types are present" do
balances = [
{ balance_type: "ITAV", balance_amount: { amount: "4332.81", currency: "EUR" } },
{ balance_type: "CLAV", balance_amount: { amount: "4332.81", currency: "EUR" } },
{ balance_type: "ITBD", balance_amount: { amount: "232.81", currency: "EUR" } },
{ balance_type: "CLBD", balance_amount: { amount: "232.81", currency: "EUR" } }
]
result = @importer.send(:select_balance, balances)
assert_equal "CLBD", result[:balance_type]
assert_equal "232.81", result[:balance_amount][:amount]
end
test "prefers XPCD over ITBD" do
balances = [
{ balance_type: "ITBD", balance_amount: { amount: "100.00", currency: "EUR" } },
{ balance_type: "XPCD", balance_amount: { amount: "150.00", currency: "EUR" } }
]
result = @importer.send(:select_balance, balances)
assert_equal "XPCD", result[:balance_type]
end
test "falls back through priority chain" do
balances = [
{ balance_type: "ITAV", balance_amount: { amount: "5000.00", currency: "EUR" } },
{ balance_type: "ITBD", balance_amount: { amount: "900.00", currency: "EUR" } }
]
result = @importer.send(:select_balance, balances)
assert_equal "ITBD", result[:balance_type]
end
test "CLAV is preferred over ITAV" do
balances = [
{ balance_type: "ITAV", balance_amount: { amount: "5000.00", currency: "EUR" } },
{ balance_type: "CLAV", balance_amount: { amount: "4800.00", currency: "EUR" } }
]
result = @importer.send(:select_balance, balances)
assert_equal "CLAV", result[:balance_type]
end
test "falls back to first balance when no known types present" do
balances = [
{ balance_type: "PRCD", balance_amount: { amount: "500.00", currency: "EUR" } },
{ balance_type: "INFO", balance_amount: { amount: "600.00", currency: "EUR" } }
]
result = @importer.send(:select_balance, balances)
assert_equal "PRCD", result[:balance_type]
assert_equal "500.00", result[:balance_amount][:amount]
end
test "returns single balance regardless of type" do
balances = [
{ balance_type: "ITAV", balance_amount: { amount: "1000.00", currency: "EUR" } }
]
result = @importer.send(:select_balance, balances)
assert_equal "ITAV", result[:balance_type]
end
end