Skip to content

B-13 · Complete reconciliation & generate report

SOP: SOP_AI_Bookkeeping_Automation.md §5.4 (Reconciliation Completion and Reporting)Actors: Bookkeeper. Pre-state: Every UnreconciledItem for the run is in RESOLVED. Every ReconciliationMatch is confirmed. Post-state: ReconciliationRun.status = COMPLETE. CSV + PDF reconciliation report stored as a File and linked via reportFileId. Email summary dispatched to reviewerEmail.

0. Prerequisites

  • All matches confirmed (B-09).
  • All unreconciled items resolved (via B-11 and/or B-12).

1. Steps

1.1 Verify completeness

http
GET /ops/bookkeeping/reconciliations/<runId>

Response includes counters:

json
{
  "status": "AWAITING_CLIENT",
  "totals": {
    "matched": 3,
    "unreconciled": { "PENDING": 0, "CLIENT_RESPONDED": 0, "RESOLVED": 3 }
  }
}

If any non-RESOLVED items remain, the Complete button is disabled.

1.2 Click Complete

Web: /dashboard/bookkeeping/reconciliations/<runId>Complete reconciliation.

http
POST /ops/bookkeeping/reconciliations/<runId>/complete

Server:

  1. Re-checks every match is confirmed and every unreconciled item is RESOLVED. Otherwise 400 with the offending ids.
  2. Transitions status = COMPLETE, stamps completedAt.
  3. Enqueues bookkeeping.generate-reconciliation-report.

1.3 Worker generates the report

bookkeeping.generate-reconciliation-report handler:

  1. Loads the run + statement + every match + every unreconciled item.
  2. Renders a CSV (one row per bank transaction with match status, journal entry reference, amount) and a PDF summary:
    • Opening balance per statement.
    • Closing balance per statement.
    • List of matched transactions with journal entry references.
    • List of resolved-via-client items (uploaded vs no-supporting).
  3. Uploads both to S3 at <clientCode>/bookkeeping/<period>/reconciliation/v<n>/report.{csv,pdf}.
  4. Persists File rows; links the PDF via ReconciliationRun.reportFileId.
  5. Sends an email to bookkeepingConfig.reviewerEmail with the report attached + summary text.
  6. Writes bookkeeping.reconciliation.report_generated audit event.

1.4 Download the report

/dashboard/bookkeeping/reconciliations/<runId>Download report (CSV) and Download report (PDF) buttons. Both resolve presigned S3 URLs.

2. Verification

Database

sql
SELECT status, completed_at, report_file_id
  FROM reconciliation_runs WHERE id = '<runId>';
-- COMPLETE, completed_at set, report_file_id not null

S3 / MinIO

<clientCode>/bookkeeping/<period>/reconciliation/v1/report.pdf and .csv.

Mailpit

A summary email with attachments to reviewerEmail. Subject: Reconciliation report — {clientName} {period}.

Audit log

bookkeeping.reconciliation.completed       runId=<id>
bookkeeping.reconciliation.report_generated  runId=<id> fileId=<id>

3. Negative & edge cases

  • Open items remain → 400 with the offending unreconciled item ids and / or proposed match ids.
  • Run already COMPLETE → 409 "Run is already complete."
  • Report render failure — handler logs and rethrows; pg-boss retries. The run stays COMPLETE (it transitioned before the render kicked off); the report can be re-rendered manually via POST /ops/bookkeeping/reconciliations/<runId>/regenerate-report.
  • No approval round / sign-off — bookkeeping does not yet have a client-side approval round equivalent to payroll. The reviewer's POST is terminal. If the engagement requires a second client sign-off, that's out of scope for the MVP — track via the integration plan.

Next

The run is closed. Future statements for the same client + bank account follow the same flow. To exercise the annual variant (large bulk + multiple statements at once), see B-14 · Annual bookkeeping.

Internal use only — BreezyCorp