Skip to content

B-05 · Review a journal batch (approve / edit / reject / escalate)

SOP: SOP_AI_Bookkeeping_Automation.md §4.5 (Human review gate)Actors: Bookkeeper (BOOKKEEPER role). Senior Accountant for escalations. Pre-state: A JournalBatch exists for the client × period, in DRAFT. At least one JournalEntry is present in any non-terminal status. Post-state: Every entry is in APPROVED or REJECTED. Batch can advance to APPROVED when no DRAFT / FLAGGED entries 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:

http
GET /ops/bookkeeping/batches?clientId=<zenithId>

1.2 Open the batch

Click into /dashboard/bookkeeping/batches/<batchId>. The page renders a table with:

ReferenceStatusClassificationFlags
INV-SPG-202603APPROVEDvendor-master EXACT (HIGH)none
UBER-20260408-0001FLAGGEDkeyword route (MEDIUM)KEYWORD_MATCH_ONLY
FIGMA-SUB-202604DRAFTvendor match (HIGH)NON_BASE_CURRENCY
IRAS-GST-Q1-2026FLAGGEDUNCLASSIFIEDREVIEWER_ATTENTION_REQUIRED, NO_VENDOR_MATCH

API:

http
GET /ops/bookkeeping/batches/<batchId>/entries

Each 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.

http
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.

http
POST /ops/bookkeeping/journal-entries/<entryId>/approve

Server:

  1. Validates the entry is DRAFT or FLAGGED.
  2. Validates the debit + credit accounts exist on the client's CoA.
  3. Stamps reviewedBy = <staff-jwt-email>, reviewedAt = now().
  4. Transitions DRAFT/FLAGGED → APPROVED.
  5. Writes bookkeeping.journal_entry.approved audit event.

The reviewer email + timestamp now show on the row. The seeded SP Group entry already shows this.

1.5 Reject

http
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

http
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

sql
SELECT reference, status, classification_confidence, reviewed_by, flags
  FROM journal_entries WHERE batch_id = '<batchId>';

Audit log

ActionEvent
Editbookkeeping.journal_entry.edited
Approvebookkeeping.journal_entry.approved
Rejectbookkeeping.journal_entry.rejected
Escalatebookkeeping.journal_entry.escalated
Clear flagbookkeeping.journal_entry.flag_cleared

Web UI

  • The batch summary header shows live counts (1 APPROVED, 2 FLAGGED, 1 DRAFT initially).
  • Once every entry is APPROVED or REJECTED, 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 REJECTED entry → 400; rejected is terminal. Use a re-ingest to start over.
  • Edit an APPROVED entry → 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_CURRENCY on an entry without an FX rate) — only SENIOR_ACCOUNTANT / PLATFORM_ADMIN (Action.OVERRIDE_JOURNAL_FLAG). BOOKKEEPER cannot bypass.

Next

When every entry is APPROVED (or REJECTED), proceed to B-06 · Generate upload file.

Internal use only — BreezyCorp