fix(enable-banking): preserve claimed pending date on subsequent syncs (#1797)

After the first sync claims a pending entry (setting auto_claimed_pending_ids),
subsequent syncs find the entry by booked external_id as an existing record.
pending_match is never entered so pending_entry_date stays nil, causing
`nil || date` to silently overwrite the preserved pending date with the
booked settlement date.

Fix by checking auto_claimed_pending_ids on the existing entry — its presence
signals a prior auto-claim, so entry.date (the original pending date) is kept.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
CrossDrain
2026-05-14 19:33:22 +00:00
committed by GitHub
parent 81e66870d7
commit c106aaf10d
2 changed files with 63 additions and 1 deletions

View File

@@ -137,10 +137,25 @@ class Account::ProviderImportAdapter
# Track if this is a new posted transaction (for fuzzy suggestion after save)
is_new_posted = entry.new_record? && !incoming_pending
# Preserve the original pending date across all syncs:
# - First claim: pending_entry_date is captured from the pending match above
# - Subsequent syncs: entry already exists (no pending_match found), so check
# auto_claimed_pending_ids which signals it was previously auto-claimed and
# keep entry.date (the pending date stored on first claim) unchanged
effective_date = if pending_entry_date
pending_entry_date
elsif !entry.new_record? &&
entry.entryable.is_a?(Transaction) &&
entry.transaction.extra&.key?("auto_claimed_pending_ids")
entry.date
else
date
end
entry.assign_attributes(
amount: amount,
currency: currency,
date: pending_entry_date || date
date: effective_date
)
# Use enrichment pattern to respect user overrides

View File

@@ -926,6 +926,53 @@ class Account::ProviderImportAdapterTest < ActiveSupport::TestCase
end
end
test "preserves pending date on subsequent syncs after auto-claim" do
# On the first sync the pending date is captured from the pending match.
# On subsequent syncs the entry already exists (found by booked external_id),
# so pending_match is nil. auto_claimed_pending_ids signals the prior claim
# and the stored date must not be overwritten with the booked date.
pending_date = Date.today - 3.days
booked_date = Date.today
@adapter.import_transaction(
external_id: "eb_pending_subseq",
amount: 75.00,
currency: "EUR",
date: pending_date,
name: "Coffee Shop",
source: "enable_banking",
extra: { "enable_banking" => { "pending" => true } }
)
# First sync: claim the pending entry
@adapter.import_transaction(
external_id: "eb_booked_subseq",
amount: 75.00,
currency: "EUR",
date: booked_date,
name: "Coffee Shop Posted",
source: "enable_banking",
extra: nil
)
# Simulate a subsequent sync: same booked transaction arrives again
assert_no_difference "@account.entries.count" do
re_synced = @adapter.import_transaction(
external_id: "eb_booked_subseq",
amount: 75.00,
currency: "EUR",
date: booked_date,
name: "Coffee Shop Posted",
source: "enable_banking",
extra: nil
)
re_synced.reload
assert_equal pending_date, re_synced.date,
"pending date must be preserved on subsequent syncs, not overwritten with booked date"
end
end
test "does not reconcile when posted transaction has same external_id as pending" do
# When external_id matches, normal dedup should handle it
pending_entry = @adapter.import_transaction(