Journal Entry Guide
Journal Entry β Bookkeeper: Full Walkthrough & Demo Guide
Section titled βJournal Entry β Bookkeeper: Full Walkthrough & Demo GuideβWhat Actually Happens When You Click βSubmitβ
Section titled βWhat Actually Happens When You Click βSubmitββHere is the precise chain of events, from browser click to committed GL entry:
[Browser] ββ POST /api/v1/workflow/JournalEntry/{uuid}/transition { action: "Submit" } β[workflow_router.py] ββ Loads JournalEntry from DB via GenericRepository ββ Sets doc.docstatus = DocStatus.SUBMITTED ββ Calls repo.save(session, doc) β[GenericRepository.save()] ββ Detects docstatus changed β SUBMITTED ββ Instantiates JournalEntryController(doc) ββ Calls controller.on_submit() β[JournalEntryController.on_submit()] ββ Validates: total_debit > 0, β₯ 2 legs ββ Validates: posting date is within an open AccountingPeriod ββ Builds GL payload (legs, company, narration, date) ββ Creates OutboxEntry β written to "outboxentry" table ββ Sets doc.status = "Pending GL" β visible in UI ββ Updates BudgetUtilization for each debit line β[get_session() auto-commits the transaction] ββ journalentry row: docstatus="1", status="Pending GL" ββ outboxentry row: operation="post_journal_entry", status="Pending" β[relay_outbox worker β runs separately] ββ Scans outboxentry table for status="Pending" ββ POSTs to Book-Keeper: /journal-entries/compound ββ Book-Keeper returns: { transfer_id: "compound_entry_xxxx", status: "posted" } ββ Updates OutboxEntry.status = "Sent" ββ Updates JournalEntry.gl_batch_id = "compound_entry_xxxx"Step-by-Step: Verify Manually After Clicking Submit
Section titled βStep-by-Step: Verify Manually After Clicking SubmitβStep 1 β Confirm the UI response
Section titled βStep 1 β Confirm the UI responseβAfter clicking Submit, the action handler receives:
{ "success": true, "old_state": "Draft", "new_state": "Submitted", "action": "Submit", "message": "Transitioned from Draft to Submitted"}The form should reload and status should now show βPending GLβ.
Step 2 β Check the journal entry in Postgres
Section titled βStep 2 β Check the journal entry in PostgresβConnect to the finance database and verify:
# Connect to postgrespsql postgresql://postgres:secret@localhost:5432/postgres-- Confirm docstatus and status using the internal name identifier (e.g. JOUR-XXXXXXXX)SELECT name, entry_number, status, docstatus, gl_batch_idFROM journalentryWHERE name = 'JOUR-53CBEC53';
-- Expected:-- status = "Pending GL"-- docstatus = "1" (SUBMITTED)-- gl_batch_id = NULL (not yet relayed)Step 3 β Check the Outbox queue
Section titled βStep 3 β Check the Outbox queueβ-- See the pending outbox entry (reference_id holds the entry_number)SELECT id, operation, status, reference_id, error, retry_countFROM outboxentryWHERE reference_doctype = 'JournalEntry'ORDER BY created_at DESCLIMIT 5;
-- Expected:-- operation = "post_journal_entry"-- status = "Pending"-- reference_id = "0017" (or your equivalent entry_number)Step 4 β Ensure the Background Worker is Running
Section titled βStep 4 β Ensure the Background Worker is RunningβThe relay worker is the bridge to Book-Keeper. In development, it automatically runs alongside your server if you start it with the worker flag:
uv run m dev --env-file monolith.env --enable-workerIf the worker is running, it will automatically pick up the pending outbox entry within a few seconds and log the success to the console.
Step 5 β Confirm the GL batch was posted
Section titled βStep 5 β Confirm the GL batch was postedβAfter relay, check that gl_batch_id is now populated and outbox status is βSentβ:
-- Outbox should now be SentSELECT operation, status, errorFROM outboxentryWHERE reference_id = '0017'; -- using the entry_number
-- Expected: status = "Sent"
-- JournalEntry should now have gl_batch_idSELECT name, entry_number, status, gl_batch_idFROM journalentryWHERE name = 'JOUR-53CBEC53';
-- Expected:-- status = "Submitted" (updated by relay after GL confirmation)-- gl_batch_id = "compound_entry_xxxxxxxx"Step 6 β Query Book-Keeper directly (Event Sourcing)
Section titled βStep 6 β Query Book-Keeper directly (Event Sourcing)βBecause Book-Keeper uses an Event Sourced Architecture, there are no static journal_entries or gl_entries tables. Instead, all changes are written as immutable events!
Verify the event in the Book-Keeper database:
psql postgresql://postgres:secret@localhost:5432/book_keeper-- The gl_batch_id corresponds directly to the stream_id in the events tableSELECT tenant_id, stream_id, event_type, data, timestampFROM eventsWHERE stream_id = 'compound_entry_c29b04e5'; -- use your actual gl_batch_idWhat you will see in the data JSON blob:
The JSON data payload will contain the exact debits, credits, and the Book-Keeper internal transfer_id (e.g. 26) which maps to the internal journal_transactions table.
Architecture Summary
Section titled βArchitecture SummaryβFinance Module (Business-M) Book-Keeper Serviceβββββββββββββββββββββββββββ βββββββββββββββββββββ Journal Entry (Draft) β β ββ β Submit β β Events (Ledger) ββ Journal Entry β relay β ββββββββββββββ ββ (Pending GL) + OutboxβββΌββworkerβββΆβ β stream_id β ββ β β β β compound_xxβ ββ Journal Entry βββbatch_idββ ββββββββββββββ ββ (Submitted, gl_batch) β β ββββββββββββββββββββββββββββ ββββββββββββββββββββ PostgreSQL PostgreSQL (postgres DB) (book_keeper DB)Key points:
- Journal entries are immutable after submission (docstatus=1 prevents edits)
- The Outbox pattern ensures GL posting is reliable even if Book-Keeper is temporarily offline
- If relay fails, entries retry automatically (up to
MAX_RETRIES) before goingDead - Every transition is auditable β creation, submission, GL batch ID all stored
- Budget utilization is tracked in real-time against budget allocations per cost center
Quick Reference: Status States
Section titled βQuick Reference: Status Statesβ| docstatus | status | What it means |
|---|---|---|
0 |
Draft | Being drafted, editable |
1 |
Pending GL | Submitted, waiting for GL relay |
1 |
Submitted | Relay complete, GL batch confirmed |
2 |
Cancelled | Reversed in both Finance and Book-Keeper |