B-05 · Review a journal batch (approve / edit / reject / escalate)
SOP:
SOP_AI_Bookkeeping_Automation.md§4.5 (Human review gate)Actors: Bookkeeper (BOOKKEEPERrole). Senior Accountant for escalations. Pre-state: AJournalBatchexists for the client × period, inDRAFT. At least oneJournalEntryis present in any non-terminal status. Post-state: Every entry is inAPPROVEDorREJECTED. Batch can advance toAPPROVEDwhen noDRAFT/FLAGGEDentries remain.
The seeded ZENITH client has a draft April 2026 batch with entries spanning every status × confidence combination, so this flow can be exercised against the seed without any prior steps.
0. Prerequisites
- ZENITH seeded (preferred) or at least one entry produced via B-03.
- Logged in as
BOOKKEEPER/SENIOR_ACCOUNTANT/PLATFORM_ADMIN.
1. Steps
1.1 Find the batch
Web: /dashboard/bookkeeping/batches. ZENITH should show one batch, period 2026-04-01 → 2026-04-30, status DRAFT, platform XERO, 4 entries.
Or via API:
GET /ops/bookkeeping/batches?clientId=<zenithId>1.2 Open the batch
Click into /dashboard/bookkeeping/batches/<batchId>. The page renders a table with:
| Reference | Status | Classification | Flags |
|---|---|---|---|
INV-SPG-202603 | APPROVED | vendor-master EXACT (HIGH) | none |
UBER-20260408-0001 | FLAGGED | keyword route (MEDIUM) | KEYWORD_MATCH_ONLY |
FIGMA-SUB-202604 | DRAFT | vendor match (HIGH) | NON_BASE_CURRENCY |
IRAS-GST-Q1-2026 | FLAGGED | UNCLASSIFIED | REVIEWER_ATTENTION_REQUIRED, NO_VENDOR_MATCH |
API:
GET /ops/bookkeeping/batches/<batchId>/entriesEach row exposes the source document (click to view the original PDF), debit/credit accounts, FX, flags, classification confidence, and reviewer history.
1.3 Edit an entry
Click Review on the Uber entry → side panel / inline editor. Reassign the debit account from 6500 (Travel) to 6400 (Software) (just for testing), then save.
PATCH /ops/bookkeeping/journal-entries/<entryId>
{
"debitAccount": "6400",
"flags": ["RECLASSIFIED_BY_REVIEWER"],
"reviewNotes": "Was actually a Slack bill, not Uber"
}The status flips FLAGGED → DRAFT (removing the auto-flag while still keeping it pending). Audit event bookkeeping.journal_entry.edited.
1.4 Approve
Click Approve on the Uber entry.
POST /ops/bookkeeping/journal-entries/<entryId>/approveServer:
- Validates the entry is
DRAFTorFLAGGED. - Validates the debit + credit accounts exist on the client's CoA.
- Stamps
reviewedBy = <staff-jwt-email>,reviewedAt = now(). - Transitions
DRAFT/FLAGGED → APPROVED. - Writes
bookkeeping.journal_entry.approvedaudit event.
The reviewer email + timestamp now show on the row. The seeded SP Group entry already shows this.
1.5 Reject
POST /ops/bookkeeping/journal-entries/<entryId>/reject
{ "reason": "Duplicate of INV-SPG-202602; already posted last month." }Status → REJECTED. The entry is excluded from the upload file but stays in the batch for audit.
1.6 Escalate
POST /ops/bookkeeping/journal-entries/<entryId>/escalate
{ "comment": "Materiality threshold question — please advise on tax treatment." }Status → ESCALATED. A notification is dispatched to the SENIOR_ACCOUNTANT queue. The entry stays open until a senior approves / rejects it. The batch cannot advance to APPROVED while any entry is ESCALATED.
1.7 Clear a flag
For the IRAS entry, after assigning a debit account:
Click "Clear flag & reclassify"This is a UI affordance for the same PATCH — it removes REVIEWER_ATTENTION_REQUIRED / NO_VENDOR_MATCH and sets classificationConfidence based on the new debit account.
1.8 Inspect the multi-currency entry (Figma)
Open the Figma entry. The FX rate cell shows 1.35 with source SEED. Total renders in both USD (origin) and SGD (base). Confirm 75 × 1.35 = 101.25 SGD matches the persisted totalAmount (within rounding).
2. Verification
Database
SELECT reference, status, classification_confidence, reviewed_by, flags
FROM journal_entries WHERE batch_id = '<batchId>';Audit log
| Action | Event |
|---|---|
| Edit | bookkeeping.journal_entry.edited |
| Approve | bookkeeping.journal_entry.approved |
| Reject | bookkeeping.journal_entry.rejected |
| Escalate | bookkeeping.journal_entry.escalated |
| Clear flag | bookkeeping.journal_entry.flag_cleared |
Web UI
- The batch summary header shows live counts (
1 APPROVED, 2 FLAGGED, 1 DRAFTinitially). - Once every entry is
APPROVEDorREJECTED, the batch's Approve batch button enables.
3. Negative & edge cases
- Approve while
ESCALATED→ 400"Escalated entries must be resolved by SENIOR_ACCOUNTANT first." - Approve a
REJECTEDentry → 400; rejected is terminal. Use a re-ingest to start over. - Edit an
APPROVEDentry → 400"Approved entries are immutable. Reject and re-ingest if a correction is needed." - Approve with a debit/credit account not on the client's CoA → 400 with the bad code listed.
- Reject without a reason → 400
"Rejection reason is required." - Approve when the batch is in
EXPORTED→ 400"Batch has already been exported and cannot be modified." - Override a flag (e.g.
NON_BASE_CURRENCYon an entry without an FX rate) — onlySENIOR_ACCOUNTANT/PLATFORM_ADMIN(Action.OVERRIDE_JOURNAL_FLAG).BOOKKEEPERcannot bypass.
Next
When every entry is APPROVED (or REJECTED), proceed to B-06 · Generate upload file.