From c106aaf10d20caf51e5e2cc8b4fc7845ee57cdc4 Mon Sep 17 00:00:00 2001 From: CrossDrain <32982516+CrossDrain@users.noreply.github.com> Date: Thu, 14 May 2026 19:33:22 +0000 Subject: [PATCH] fix(enable-banking): preserve claimed pending date on subsequent syncs (#1797) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- app/models/account/provider_import_adapter.rb | 17 ++++++- .../account/provider_import_adapter_test.rb | 47 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/app/models/account/provider_import_adapter.rb b/app/models/account/provider_import_adapter.rb index 6b2dbf932..549423f10 100644 --- a/app/models/account/provider_import_adapter.rb +++ b/app/models/account/provider_import_adapter.rb @@ -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 diff --git a/test/models/account/provider_import_adapter_test.rb b/test/models/account/provider_import_adapter_test.rb index cff61d866..a62c30209 100644 --- a/test/models/account/provider_import_adapter_test.rb +++ b/test/models/account/provider_import_adapter_test.rb @@ -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(