Skip to content

B-14 · Annual bookkeeping batch flow

SOP: SOP_AI_Bookkeeping_Automation.md §6 (Annual Bookkeeping)Actors: Senior Accountant (preferred — annual engagements often involve write-offs / suspense decisions). Pre-state: Bookkeeping client onboarded with bookkeepingConfig.serviceType = 'ANNUAL' and engagementStartDate / engagementEndDate set. Post-state: Documents distributed across the engagement window into per-month or per-quarter JournalBatch rows. One reconciliation run per uploaded statement. Cumulative reconciliation report.

The annual engagement reuses the same monthly Phase 1 + Phase 2 pipelines, just at a higher volume. This flow exists to call out the variations, not duplicate the steps.

0. Prerequisites

  • A bookkeeping-enabled client. Ideally a fresh client for the annual demo (e.g. duplicate the B-01 flow with clientCode: ANNUALCO).

  • Set the engagement window:

    http
    PATCH /admin/clients/<clientId>
    {
      "bookkeepingConfig": {
        "serviceType": "ANNUAL",
        "platform": "XERO",
        "baseCurrency": "SGD",
        "reviewerEmail": "reviewer@spade.sg",
        "batchingCadence": "MONTHLY"   // or "QUARTERLY"
      },
      "engagementStartDate": "2025-04-01",
      "engagementEndDate": "2026-03-31"
    }

1. Steps

1.1 Bulk ingest documents

Annual clients drop a year's worth of documents in one go via the folder channel (preferred).

API:

http
POST /ops/bookkeeping/ingest-bulk

{
  "clientId": "<clientId>",
  "fileIds": ["<id1>", "<id2>", ...]    // 100+ allowed
}

The route:

  1. Iterates each file, creates a BookkeepingDocument, and enqueues bookkeeping.ingest-document.
  2. Returns 202 with { ingested: <count>, skipped: <count> } (skipped = duplicates by SHA-256).

Worker pipeline is identical to B-03 per document. The classify-and-draft handler resolves the right batch based on the document date:

  • batchingCadence: MONTHLY → one batch per (clientId, document_date.month, platform).
  • batchingCadence: QUARTERLY → one batch per (clientId, document_date.quarter, platform).

Documents with dates outside the engagement window are flagged OUT_OF_ENGAGEMENT_WINDOW and not added to any batch — they appear in the exception queue for reviewer judgement.

1.2 Bulk batch packaging (optional)

Once everything is ingested:

http
POST /ops/bookkeeping/batches/package

{
  "clientId": "<clientId>",
  "engagementStart": "2025-04-01",
  "engagementEnd": "2026-03-31"
}

Useful for verifying that every document was bucketed correctly. Returns a per-period summary of batch counts + total entry counts + OUT_OF_ENGAGEMENT_WINDOW count.

1.3 Review batches

/dashboard/bookkeeping/batches?clientId=<id> lists every batch for the engagement, sortable by period, with per-status entry counts. Walk each batch through the B-05 flow; entries can be approved in bulk if the classification confidence is uniformly HIGH.

1.4 Generate one upload file per period

For each APPROVED batch:

http
POST /ops/bookkeeping/batches/<batchId>/generate-upload-file

Annual engagements typically prefer one upload per month (or per quarter, depending on batchingCadence) so the reviewer can post into the platform-of-record at a manageable cadence.

1.5 Upload all bank statements

Each month's bank statement is uploaded individually (B-07). Annual clients typically deliver 12 monthly statements. The statements are processed independently — there is no "annual statement" concept.

1.6 Run reconciliation per month

Per B-08. Each statement → one run. Confirm matches per B-09, dispatch unreconciled items per B-10.

1.7 Annual portal experience

The unreconciled portal (per B-11) supports filtering by month and sorting by amount and date — essential when the unreconciled list spans 12 months.

The magic link for annual clients defaults to a 60-day expiry (configurable via bookkeepingConfig.magicLinkTtlDays). Per SOP §6.3, the longer expiry reflects the longer cycle.

1.8 Final close-out

Once every month's run is COMPLETE:

http
POST /ops/bookkeeping/clients/<clientId>/engagements/<engagementId>/close

The route:

  1. Verifies every batch in the engagement window is EXPORTED (or APPROVED with no entries).
  2. Verifies every reconciliation run for an uploaded statement is COMPLETE.
  3. Generates an annual summary report aggregating:
    • Total entries per batch.
    • Total reconciled / unreconciled value per month.
    • List of write-offs / suspense entries with reviewer reasons.
  4. Stamps engagementClosedAt on the client.

This step is manual in the MVP — there is no scheduled close. Reviewer initiates.

2. Verification

Database

sql
SELECT period_start, period_end, status, accounting_platform,
       (SELECT COUNT(*) FROM journal_entries WHERE batch_id = jb.id AND status='APPROVED') AS approved
  FROM journal_batches jb WHERE client_id = '<clientId>'
  ORDER BY period_start;

SELECT period_start, period_end, status FROM bank_statements WHERE client_id = '<clientId>';
SELECT id, status FROM reconciliation_runs WHERE client_id = '<clientId>';

Audit log

bookkeeping.bulk_ingest.requested  fileCount=120 ingested=118 skipped=2
bookkeeping.engagement.closed      clientId=<id> from=2025-04-01 to=2026-03-31

3. Negative & edge cases

  • Document dated before engagementStartDate or after engagementEndDate → flagged OUT_OF_ENGAGEMENT_WINDOW. Not added to a batch. Reviewer either includes via override (creates a one-off batch) or excludes from the engagement.
  • Bulk ingest exceeds size cap (default 500 documents) → 400 "Bulk ingest cap exceeded. Split into smaller drops.". Configurable per client.
  • Pre-screen no-supporting declarations for materiality — annual engagements should pre-screen because volume is high. Reviewer can use the exception queue with minAmount = 0 to sweep low-value items in bulk.
  • Engagement close while a run is open → 409 with the open run ids.
  • Re-open a closed engagement — there is no re-open. Open a follow-on engagement for the next year.

Next

Audit / records management — see B-15 · Audit export.

Internal use only — BreezyCorp