Bank Reconciliation
At a glance
Bank Reconciliation imports CAMT.053, OFX, SIE4 and bank-specific CSVs (Nordea, SEB, Handelsbanken, Swedbank, BNP Paribas, Credit Agricole), runs rule-based and heuristic auto-matching against invoices and payments, surfaces unmatched lines in a colour-coded admin inbox, generates double-entry AccountingEntry rows on match and locks the period only when the closing balance reconciles.
How it works
Bank statements arrive in any of four formats: CAMT.053 (ISO 20022), CSV (generic and bank-specific), OFX or SIE4. Format detection is automatic and deduplication uses a SHA-256 hash of the file contents so the same statement re-uploaded never creates duplicate rows. Auto-matching runs in two passes.
First, the club treasurer's own rules (counterparty-name regex, amount range, OCR prefix) execute in priority order; supported actions are book_to_account (post directly to a configured account), match_to_invoice_by_ocr (look up the invoice by OCR reference) and ignore. Second, heuristic matching applies: an exact reference or OCR match against an existing Payment scores 1.0 confidence, while fuzzy name plus amount plus date within 3 days uses Jaro-Winkler with threshold 0.85; anything below 0.6 is left unmatched. Unmatched lines feed the manual inbox, colour-coded green (high confidence), yellow (medium) and red (low or no match) with three actions per row: match against invoice, post as new entry or ignore.
Split matching is supported for one bank line that should clear several invoices. On every match the engine writes a balanced AccountingEntry voucher: incoming lines book debit bank, credit accounts receivable; outgoing lines book debit accounts payable, credit bank; new revenue posts as debit bank, credit revenue account. Balance reconciliation is the gate to locking: closing_balance must equal opening_balance plus the sum of all lines AND every line must be matched before status flips to locked.
The balance-check endpoint returns expected, actual and diff so the operator can spot a missing line before attempting to lock. The implementation under PL-T076 covers parser, matcher, admin inbox, AccountingEntry generation and the lock flow.
Key capabilities
- Multi-format import: CAMT.053, OFX, SIE4 and bank-specific CSVs from Nordea, SEB, Handelsbanken, Swedbank, BNP Paribas and Credit Agricole
- SHA-256 file deduplication so re-uploaded statements never duplicate rows
- Rule-based auto-matching with counterparty regex, amount range and OCR prefix in priority order
- Heuristic matching: 1.0 confidence on exact reference/OCR, Jaro-Winkler 0.85 on fuzzy name plus amount plus date
- Colour-coded manual inbox with three-button flow and split-matching support
- Automatic double-entry AccountingEntry generation on match (incoming, outgoing and new-revenue patterns)
- Balance reconciliation gating with expected vs actual diff before period lock
In practice
On Monday the treasurer uploads last week's CAMT.053 from Swedbank. The platform deduplicates by SHA-256 (the file is new), parses 47 lines, and auto-matches 38 — 31 exact OCR matches against open invoices, four fuzzy matches at 0.91 confidence on member names, three rule-driven postings (regular hall-rental refund) booked direct to account 6210. Nine lines remain in the inbox: six green (likely matches the treasurer just confirms), two yellow (wrong amount, she splits one across two invoices), one red (unknown payer that turns out to be a forgotten sponsor — she posts as new revenue).
She runs balance check; expected matches actual, every line is matched, status flips to locked, the corresponding AccountingEntry vouchers are visible in /accounting-entries/.
Features in this subsystem
7| ID | Status | Features |
|---|---|---|
| F08.12.07 | Planned | Avstämningen: payout-reconciliation, avgiftens reverse charge och bankavstämning (BILL m-10 t-1, 4e608d85.m-10) — migration 00232 med payout_reconciliation (mottagningens durabla register, unique (provider, provider_payout_id), items[] med klass och skäl per transaktion), bank_statement (unique (tenant_id, sha256)) och bank_statement_line (unique (tenant_id, external_id) + partiellt unikt (tenant_id, matched_entry_id) = A-6:s en-rad-per-verifikat), källordet bank_statement additivt i liggarens vokabulär, platform_invoice.ocr_reference (Luhn mod-10, backfilld) och BAS 2999 i varje bok. Payout-porten PayoutProvider är en valfri, additiv utvidgning (F1-11:s Event-form oförändrad) med sin deterministiska fejk; mottagningen går genom InstallPayoutReconciler på den CENTRALA processorn och persisterar bara (A-3 — ett periodlås får aldrig blockera railen). Svepet accounting_payout_reconcile_tick bokför kvittningen i serie B: tvåklass-regeln läser den bokförda radens debetkonto (1580 ⇒ kvitta, 1930 ⇒ redan på banken, dras AV från bankdebeten), avgiften kommer ur porten aldrig ur en prislista (A-4), och B4:s reverse charge har två verkligt olika grenar (med avdragsrätt: nollparet; utan: momsen är en KOSTNAD på 6570 och bara det utgående benet krediteras) med kontona ur bookkeeping.VATAccountFor — ett land utan mappning ger failed:vat_account_unmapped, aldrig ett svenskt konto i en fransk bok. Residualen göms aldrig: den bokförs synligt på 2999, raden blir booked_with_residual och notisen till accounting:admin-bärarna bär MÄTT utfall på raden. B5 mekaniskt: payouten bokförs i kontots bok — en klubbs payout ger noll rader i PL:s liggare. Bankavstämningen: CAMT.053-/CSV-import med dubbel dedup, husets ENDA matchningsheuristik (samma rena funktion i importen, svepet accounting_bank_match_tick och :auto-match), inboxens knappar med If-Match/Idempotency-Key, låsningen bakom accounting:admin och SettlePlatformInvoice som partially_paid-ordets enda skrivare (beloppet ackumuleras, statusen HÄRLEDS; en bankreglering har ingen payments-rad — bankraden ÄR händelsen). Sys-läsvyerna GET /v1/sys/billing/{bank-lines,payout-reconciliation} häver ⊘-korten unmatched_payments och provider_reconciliation i samma leverans och är LÄS-ONLY (A-9). Fixtur: seed-bokforings m-10-delta (ingen ny CLI-rad; båda payouts drivna genom den riktiga railen och svepets egen söm, båda utdragen genom import-sömmen). Kontrakt: specs/api/endpoints/accounting-reconciliation.md; mönstret: docs/domain/accounting-flows.md §1h. ⊘ med ägare: regelmotor + split-matchning → egen BILL-uppföljning · bankdialekter/OFX/BGMax/SFTP/PSD2 och den riktiga Stripe-payout-adaptern → go-live · PayPal-payouts → PAY-3 · momsperioder/EC-listan → m-11 · dashboards/runway → m-12 · sys-mutationer → ägarbeslut. |
| F08.12.01 | Shipped | Import av kontoutdrag — stöd för CAMT.053 (ISO 20022), CSV (generisk + bankspecifik: Nordea, SEB, Handelsbanken, Swedbank, BNP Paribas, Crédit Agricole), OFX, SIE4. Formatdetektering automatisk. SHA-256-deduplicering. ✅ PL-T076 |
| F08.12.02 | Shipped | Regelbaserad auto-matchning — klubbkassörens egna regler (motpartsnamn-regex, beloppsintervall, OCR-prefix). Prioritetsordning. Stöder book_to_account, match_to_invoice_by_ocr och ignore. ✅ PL-T076 |
| F08.12.03 | Shipped | Heuristisk matchning — referens/OCR-matchning mot Payment (1.0 conf), fuzzy namn+belopp+datum ±3d (Jaro-Winkler ≥ 0.85). Under 0.6 → unmatched. ✅ PL-T076 |
| F08.12.04 | Shipped | Manuell inbox — obematchade rader med färgkodning (grön/gul/röd). Tre-knapps-flöde: matcha mot faktura, bokför som ny post, ignorera. Split-matchning stöds. ✅ PL-T076 |
| F08.12.05 | Shipped | AccountingEntry-generering — automatisk dubbelbokning vid matchning: debet bank/kredit kundfordran (incoming), debet leverantörsskuld/kredit bank (outgoing), debet bank/kredit intäktskonto (new). ✅ PL-T076 |
| F08.12.06 | Shipped | Saldo-avstämning och lock — closing_balance == opening_balance + sum(lines) OCH alla lines matchade ⇒ status=locked. Balance-check-endpoint returnerar expected/actual/diff. ✅ PL-T076 |
Stakeholders who need this subsystem
Surfaces in 2 stakeholder analyses