feat(reconciliation): match migrated bank history against imported SIE verifikat (#1598)

* feat(reconciliation): match migrated bank history against imported SIE verifikat

A first-class Fortnox/SIE migrator path: after SIE import plus bank connect
or bank CSV upload, historical bank rows are auto-matched (>= 0.9) or
suggestion-matched (0.75-0.89, persisted for review) against the imported
verifikat, with a guided review surface, instead of landing as anonymous
"Att bokfora" rows.

Phase 0: per-cash-account unattended sweep (fixes #1298 cross-account
pooling); widen payment_match_log action CHECK with
linked_to_existing_voucher (silently unlogged since March).
Phase 1: potential_journal_entry_id/method/confidence on transactions with
CHECK + invalidation triggers; persistSuggestions in runReconciliation;
sweep after bank CSV import with SIE overlap (suppressing
auto-categorization); sweep summaries stamped on bank_connections and
bank_file_imports; POST /api/reconciliation/bank/confirm-suggestions with
per-pair server-side revalidation (voucher consumption + bank-leg amount
and direction).
Phase 2: "Granska forslag" review tab on Transactions with chunked bulk
confirm, per-row fallbacks, "Kor matchning igen" (all_accounts sweep mode,
mutually exclusive with dry_run), attn line, pre-migration row marker.
Phase 3: ImportResultStep dual CTA (bank connect + CSV), migrator variant
of the account-picker #917 nudge, sweep outcome on the onboarding
checklist bank step.

Non-selection apply runs on /api/reconciliation/bank/run now floor at 0.9
and persist the review band instead of auto-committing fuzzy matches.
Migrations already applied to staging under the same versions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(reconciliation): resolve PR review findings in one pass

Swedish accounting review (both previously-deferred holes closed):
- runReconciliation's >= 0.9 auto-apply now writes 'matched' to
  payment_match_log (behandlingshistorik, BFNAR 2013:2 kap 8); the bus
  event alone lands in the 30-day event_log and is not an audit record.
- The three match-route storno-conflict branches detach reconciliation
  links via unlinkReconciliation instead of storno-reversing the linked
  verifikat: a reconciliation link points at an independent verifikat
  that may evidence other affarshandelser, and a wholesale reversal is
  an over-broad rattelse (BFL 5 kap 5 §).
- Historical gap quantified on prod (read-only, recorded in DECISIONS):
  762 unlogged manual links across 52 companies since 2026-03-23.

CodeRabbit:
- confirm-suggestions route: maxDuration 300 for full 500-item batches.
- AccountPickerDialog: migrator-nudge buttons set lookbackTouched so the
  async gap-fill probe cannot override an explicit choice.
- enable-banking post-backfill sweep: persistSuggestions so the review
  band is not dropped.
- bank-file execute: sie_sweep stamp errors are logged, not swallowed.
- ImportResultStep: sandbox keeps the CSV CTA (file import works there).
- payment_match_log CHECK swap: NOT VALID + VALIDATE, no table scan
  under ACCESS EXCLUSIVE.
- logMatchEvent calls awaited (serverless can freeze unawaited work).
- DECISIONS.md stale version reference annotated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(reconciliation): defer reconciliation-link detach until the match commits

Round-2 review findings:
- CodeRabbit: the eager unlinkReconciliation call could orphan a
  transaction if the match flow failed after it. All three match routes
  now persist NOTHING up front: the final transaction update overwrites
  journal_entry_id and clears reconciliation_method in the same write,
  so any failure in between leaves the existing link intact. The release
  is logged as 'unmatched' after the commit.
- Swedish review: the auto_suggested logMatchEvent in runReconciliation
  is now awaited like every other audit write.
- DECISIONS entry split into compliance/CodeRabbit lines and updated to
  describe the deferred detach.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(reconciliation): literal reconciliation_method payloads for the phantom-column scanner

The conditional spreads introduced with the deferred detach pushed the
scanner's unresolvable-expression count past its ceiling (380 > 378).
reconciliation_method: null is correct unconditionally on a confirmed
invoice/supplier match (null is already the value on every row that was
not reconciliation-linked), so the payloads become plain literals the
guard can verify. No behavior change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Mattsson
2026-08-13 23:12:27 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent 07e89d9b52
commit 08440fed94
35 changed files with 3223 additions and 99 deletions
+36
View File
@@ -1451,6 +1451,7 @@
"step_books_sie": "+ SIE",
"step_bank_title": "Koppla banken",
"step_bank_description": "Hämta transaktioner automatiskt, eller importera ett kontoutdrag.",
"step_bank_sweep_note": "{matched, plural, one {# matchad mot importen} other {# matchade mot importen}}, {toReview, plural, one {# att granska} other {# att granska}}",
"step_bank_action": "Koppla banken",
"step_assistant_title": "Bygg din bokföringsassistent",
"step_assistant_beta": "Beta",
@@ -2702,6 +2703,7 @@
"delete_aria": "Ta bort transaktion",
"edit_title_aria": "Ändra titel",
"edited_badge": "redigerad",
"pre_migration_marker": "från perioden före din migrering",
"original_name_tooltip": "Bankens originalnamn: {name}",
"edit_title_dialog_title": "Ändra transaktionens titel",
"edit_title_warning": "Är du säker på att du vill ändra titeln på den här transaktionen?",
@@ -3291,6 +3293,23 @@
"saving": "Sparar...",
"save": "Spara transaktion"
},
"tx_review": {
"intro": "{count, plural, one {# banktransaktion matchar ett verifikat i din importerade bokföring. Bekräfta kopplingen så bokförs ingenting dubbelt.} other {# banktransaktioner matchar verifikat i din importerade bokföring. Bekräfta kopplingarna så bokförs ingenting dubbelt.}}",
"rerun": "Kör matchning igen",
"confirm_all": "Bekräfta alla ({count})",
"empty_title": "Inga förslag att granska",
"empty_description": "Alla matchförslag är hanterade. Kvarvarande transaktioner hittar du under Att bokföra.",
"th_date": "Datum",
"th_description": "Beskrivning",
"th_amount": "Belopp",
"th_suggestion": "Föreslaget verifikat",
"th_actions": "Åtgärder",
"suggestion_gone": "Förslaget är inte längre giltigt",
"confirm": "Bekräfta",
"row_menu": "Fler åtgärder",
"open_match_dialog": "Välj annat verifikat…",
"reject": "Avvisa förslaget"
},
"tx_history": {
"search_placeholder": "Sök transaktioner...",
"filter_all": "Alla",
@@ -5463,6 +5482,17 @@
"skv_err_commit_failed": "utkast skapat, ej bokfört",
"skv_err_other": "misslyckades",
"mode_all": "Alla",
"mode_review": "Granska förslag",
"review_attn_body": "{count, plural, one {# historisk banktransaktion matchar} other {# historiska banktransaktioner matchar}} din importerade bokföring.",
"review_attn_cta": "Granska förslagen",
"review_confirm_failed": "Kunde inte koppla förslagen",
"review_confirm_done_title": "{count, plural, one {# transaktion kopplad} other {# transaktioner kopplade}}",
"review_confirm_done_description": "Kopplade till befintliga verifikat. Ingen ny bokföring skapades.",
"review_confirm_done_skipped": "{count, plural, one {# hoppades över} other {# hoppades över}}: förslaget hade ändrats sedan det skapades.",
"review_reject_failed": "Kunde inte avvisa förslaget",
"review_rerun_failed": "Matchningen kunde inte köras",
"review_rerun_done_title": "Matchning klar",
"review_rerun_done_description": "{applied, plural, one {# transaktion kopplades automatiskt} other {# transaktioner kopplades automatiskt}}, {suggested, plural, one {# nytt förslag att granska} other {# nya förslag att granska}}.",
"footer_to_handle": "{count, plural, =0 {Inget att hantera} =1 {1 att hantera} other {# att hantera}}",
"recon_attn": "{count, plural, one {1 obokförd banktransaktion: bankavstämningen kan hitta automatiska träffar mot befintliga verifikationer.} other {# obokförda banktransaktioner: bankavstämningen kan hitta automatiska träffar mot befintliga verifikationer.}}",
"recon_attn_action": "Förhandsgranska träffar"
@@ -6888,7 +6918,13 @@
"reveal_skipped": "{count, plural, one {# verifikat hoppades över, se detaljer nedan.} other {# verifikat hoppades över, se detaljer nedan.}}",
"reveal_bridge": "Historiken är på plats. Det som saknas är nuet: banken.",
"reveal_cta_bank": "Koppla banken",
"reveal_cta_csv": "Ladda upp kontoutdrag (CSV)",
"reveal_cta_open": "Öppna Accounted",
"next_steps_title": "Nästa steg: hämta bankhistoriken",
"next_steps_match_copy": "Koppla din bank eller ladda upp kontoutdrag, så matchar vi bankhistoriken mot det du just importerade.",
"next_steps_cta_bank": "Koppla din bank",
"next_steps_cta_csv": "Ladda upp kontoutdrag (CSV)",
"next_steps_window_hint": "Bankkopplingen hämtar den senaste tidens historik (de flesta banker stannar vid ca 90 dagar). För äldre perioder laddar du upp kontoutdrag som CSV, matchningen fungerar likadant.",
"vat_review_title": "{count, plural, one {# konto saknar momskod} other {# konton saknar momskod}}",
"vat_review_description": "Sätt momskod på importerade intäkts- och inköpskonton så att momsdeklarationen hamnar i rätt ruta.",
"vat_review_action": "Granska momskoder i kontoplanen",