Bank statement reconciliation and follow-up
APIs
| Endpoint | Purpose |
|---|---|
POST /finance/reconcile/preview | Batch: parse all statements[], persist each file, aggregate match, return periods[] + followUp |
POST /finance/reconcile/apply | Import missing, delete extra, fix amounts |
POST /finance/reconcile/periods/{id}/sync | Mark one period synced (allowMismatch optional) |
POST /finance/reconcile/periods/sync-batch | Mark many periods synced in one call |
POST /finance/reconcile/periods/{id}/replace-from-statement | Delete 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}/statement | Load 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 appcurrentAccountBalance.statements: every file is persisted tobank_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 whenstatementsis empty.
Balance semantics
| Field | Meaning (batch flow) |
|---|---|
report.statementClosingBalance | bankBalanceToday from request |
report.currentAccountBalance | App balance now (checkpoints) |
report.projectedBalanceFromStatement | Same as current app balance (compat field) |
report.balanceDifference | bankBalanceToday - 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[]isOUT_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
combinedPeriodStartfor|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_SYNConly 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_SYNCafter 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)
- Setup (
ReconcileSetupScreen) — account, bank balance today, new uploads, pending periods, history. - 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).