Skip to Content
FeaturesFinanceBank reconciliation

Bank statement reconciliation and follow-up

APIs

EndpointPurpose
POST /finance/reconcile/previewBatch: parse all statements[], persist each file, aggregate match, return periods[] + followUp
POST /finance/reconcile/applyImport missing, delete extra, fix amounts
POST /finance/reconcile/periods/{id}/syncMark one period synced (allowMismatch optional)
POST /finance/reconcile/periods/sync-batchMark many periods synced in one call
POST /finance/reconcile/periods/{id}/replace-from-statementDelete all app txs in period range, import stored extracto lines
GET /finance/reconcile/periods?accountId=List periods and sync status
GET /finance/reconcile/periods/{id}/statementLoad stored extracto text

Legacy compare-only: POST /finance/reconciliation/preview (no period tracking).

Batch preview request

{ "strategy": "BANK_STATEMENT_SYNC", "accountId": "...", "bankBalanceToday": 1234567.89, "statements": [ { "text": "...", "fileName": "abril.txt" }, { "text": "...", "fileName": "mayo.txt" } ] }
  • bankBalanceToday (required for batch): user-entered real balance now; compared to app currentAccountBalance.
  • statements: every file is persisted to bank_statement_periods (statement_text, statement_file_name) before matching.
  • Period bounds are detected per file; same calendar month overwrites the same period row (last upload wins).
  • Legacy single-file fields (text, periodStart, periodEnd, expectedBalance) still work when statements is empty.

Balance semantics

FieldMeaning (batch flow)
report.statementClosingBalancebankBalanceToday from request
report.currentAccountBalanceApp balance now (checkpoints)
report.projectedBalanceFromStatementSame as current app balance (compat field)
report.balanceDifferencebankBalanceToday - currentAccountBalance

Follow-up (ReconcileFollowUp)

Built server-side via ReconcileFollowUpBuilder on each preview. Shown when any of:

  • Missing, extra, or amount-mismatch transactions
  • Any period in periods[] is OUT_OF_SYNC (stored status)

Not blocking sync status: |balanceDifference| > 1 (bank balance today vs app now). When statement lines already match (0 pending/extra), the analysis screen still shows Align bank balance with Add balance adjustment; balance summary chips read Lines synced + Balance pending instead of implying the balance is done.

Auto-sync: After preview, each period with no pending, extra, or amount mismatch in its date range is marked SYNCED in the DB (allowMismatch for balance). Balance-only gaps do not prevent this.

Steps: import missing → fix amounts → review extras → (optional) resolve balance gap → replace month hint → mark periods synced (often already complete).

Optional actions (UI):

  • Import all from statements — adds every statement line not yet in the app (onlyInStatement); does not delete “extra” app transactions (alternative to delete-all-extras).
  • Add balance adjustment — when the balance gap remains and there are no extras left to delete: creates one income/expense dated the day before combinedPeriodStart for |balanceDifference| so bank-today vs app-now aligns.

Auto OUT_OF_SYNC

  • Any create/update/delete/import on a transaction whose date falls inside a synced period’s range → that period becomes OUT_OF_SYNC (except reconcile balance adjustment txs — see below).
  • Re-uploading stored statement text on a synced period → OUT_OF_SYNC only when the extracto content or file name changed; analyzing the same stored file again does not flip status.
  • Replace month from statement → period set to OUT_OF_SYNC after replace.

Balance adjustment: Bank reconciliation balance adjustment on the day before combinedPeriodStart does not invalidate periods. Re-applying updates the existing adjustment for that account and calendar day (no duplicate).

Storage

Postgres bank_statement_periods (migration V21): statement_text, statement_file_name, statement_uploaded_at. One row per (account, periodStart, periodEnd).

Wire dates

Calendar dates use dd/MM/yyyy. Web date inputs convert via formatDateForAPI before calling the API.

Opening from the finance banner

The finance tab banner counts only OUT_OF_SYNC periods (all accounts) — not merely NEVER_SYNCED waiting for first mark-synced. Review opens reconcile with saved periods listed for the account that needs attention. Use the checkboxes to pick which months to include (defaults: all with stored extracto). Enter bank balance today, then Analyze selected (N) to reload only those stored statements and run batch preview without re-uploading files.

UI flow (shared Compose)

  1. Setup (ReconcileSetupScreen) — account, bank balance today, new uploads, pending periods, history.
  2. Analysis (ReconcileAnalysisScreen) — opens automatically when preview completes; summary, next steps, per-period stats, results. Back returns to setup and clears the preview.

Host: StatementReconciliationScreen switches steps; platforms wire onClearAnalysis to clear preview state.

Platforms

  • Desktop: ReconcileScreen — setup → analysis navigation in shared UI
  • Android: StatementReconciliationDestination — same two-step flow
  • Web: /finance/reconcile — multi-file, batch sync, replace month (single page for now)

Manual repair

If running balances drift: POST /finance/accounts/{id}/rebuild-checkpoints (see accounts.md).