Skip to content

Bank-Reconciliation Settlement — confirming a match now posts the accounting

Date: 2026-06-27 Status: shipped Audit finding: #16 (confirm_match recorded match metadata but never settled the matched document or posted to the ledger).

Problem / motivation

The bank-statement importer parsed statements, suggested matches, and let a user confirm a transaction against an Invoice or Expense — but confirming had no accounting effect. The invoice's balance never moved and no ledger entry was posted, so the workflow the UI implied (reconcile the bank, settle the customer) never actually happened.

What shipped

When a bank-statement transaction match is confirmed (BankStatementService.confirm_match), the settlement is now posted:

  • CREDIT matched to an Invoice (a customer payment): a real Receipt is created via ReceiptService.create_receipt — debiting the bank account, crediting accounts receivable, and lowering the invoice's outstanding balance. Because it's a genuine Receipt, the payment also shows on the client statement and receipt lists. The created receipt id is recorded on the transaction (settlement_receipt_id) for traceability and to prevent double-settlement.
  • DEBIT matched to an Expense: no new posting. An Expense books its cash leg (DR expense, CR cash/bank) when it is recorded, so the bank line is that payment — confirming simply reconciles it.

The cash side posts to a default BANK GL account (AccountMutationRepository.get_or_create_account(AccountType.BANK)); see Future additions for per-bank-account mapping.

Files

Data model changes

  • bank_statement_transactions.settlement_receipt_id (nullable UUID) — the Receipt created when an invoice match is confirmed. Migration add_bank_tx_settlement_receipt (down_revision tax_rate_store_percent), both upgrade and downgrade implemented.

GraphQL surface

No new query/mutation/type. confirm_match keeps its signature; only its behaviour (and the underlying column) changed.

Frontend impact

  • Confirming a CREDIT→Invoice match now creates a Receipt and settles the invoice — the invoice's paid/outstanding balance updates and the payment appears on the client statement. No API change; the frontend simply gets correct balances after confirming a match.

Future additions

  • Per-bank-account GL mapping (audit #19): today settlements post to a single default BANK account. Add a GL-account FK to BankAccount so each bank account posts to its own cash/bank account, then use it here instead of the default.
  • Un-confirm / reversal: there is no un-confirm path yet; if one is added, it should delete/reverse the settlement_receipt_id receipt.
  • Supplier-invoice matches: the matcher only produces INVOICES (CREDIT) and EXPENSES (DEBIT) suggestions today; if SUPPLIER_INVOICES matching is added, a supplier Payment should be created on confirm.