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 withbookkeepingConfig.serviceType = 'ANNUAL'andengagementStartDate/engagementEndDateset. Post-state: Documents distributed across the engagement window into per-month or per-quarterJournalBatchrows. 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:
httpPATCH /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:
POST /ops/bookkeeping/ingest-bulk
{
"clientId": "<clientId>",
"fileIds": ["<id1>", "<id2>", ...] // 100+ allowed
}The route:
- Iterates each file, creates a
BookkeepingDocument, and enqueuesbookkeeping.ingest-document. - 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:
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:
POST /ops/bookkeeping/batches/<batchId>/generate-upload-fileAnnual 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:
POST /ops/bookkeeping/clients/<clientId>/engagements/<engagementId>/closeThe route:
- Verifies every batch in the engagement window is
EXPORTED(orAPPROVEDwith no entries). - Verifies every reconciliation run for an uploaded statement is
COMPLETE. - 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.
- Stamps
engagementClosedAton the client.
This step is manual in the MVP — there is no scheduled close. Reviewer initiates.
2. Verification
Database
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-313. Negative & edge cases
- Document dated before
engagementStartDateor afterengagementEndDate→ flaggedOUT_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 = 0to 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.