v5.39.0 4 June 2026 Feature Fix

An Invoice Issued in SpeyBooks Changes the Books

The journal posting contract ships. Until today, SpeyBooks invoices were documents with a tamper-evident audit trail — but issuing one did not touch the ledger. From this release, an invoice issued in SpeyBooks changes the books, which is, after all, the product.

Issue journals (new)

Issuing a sales invoice — whether by creating it directly as sent, transitioning a draft via the status endpoint, or emailing a draft — posts one immutable double-entry journal atomically with the status change: Trade Debtors debited the gross total, each line’s revenue account credited its net amount, VAT on Sales credited the VAT total. Purchase invoices mirror through Trade Creditors and VAT on Purchases. The journal is dated at the invoice’s issue date (the tax point), so VAT lands in the correct return period.

Enforcement is Class M throughout: every journal is proven to balance to zero by the database itself at commit (migration 082 family), is immutable once posted (migration 069), and is linked to its invoice in both directions — a bijection verified against the live database at every deploy (AX-INV-004, a dedicated deploy gate). Because the lifecycle state machine makes sent reachable exactly once (AX-INV-001, migration 091), a duplicate issue journal has no event to ride: the property holds by construction and is proven by the gate.

Every line must carry an account assignment before an invoice can be issued; issuing with unmapped lines fails with a 400 and changes nothing.

Write-off journals (new)

Writing off an invoice posts DR Bad Debts / CR Trade Debtors at the outstanding amount (total minus payments received). VAT bad-debt relief is deliberately NOT posted at write-off: under HMRC Notice 700/18 the relief is a VAT-return-level claim whose six-month condition has usually not matured when a business writes off — and this journal is itself the relief’s precondition (the transfer of the debt to a separate bad debt account). Recording a payment still posts no journal: cash recognition belongs to bank reconciliation, by design.

Report scale correction (fix)

Ledger amounts are stored as integer pence, enforced by the database’s monetary type (Class M, migration 071 family). The reporting layer was converting those pence sums as if they were pounds, inflating every figure on the P&L, balance sheet, trial balance, and VAT return by 100x for ledger-written data. Every conversion site is corrected: pence now passes through unconverted. The VAT liability probe also moved from a legacy account code to the TAX_LIABILITY system account.

Two further report defects were found and fixed by the contract’s own verification: date-ranged P&L and balance-sheet figures were in fact all-time totals (a query construction defect meant the date and posted-status filters never excluded anything — draft entries included), and the trial balance omitted archived accounts’ balances, making it structurally unable to balance. Both corrected; the trial balance now balances to the penny and every report figure is verified pence-exact against the ledger. A tenant-isolation data defect from the import era (bank-import lines referencing another organisation’s account) was also surfaced and repaired, with the engine fix tracked separately.

Pre-launch, test data only — no customer figures were ever affected.

Invoice line envelope (Class M promotion)

Invoice lines are now non-negative by database constraint (migration 093): quantity strictly positive; unit price, net, VAT, and gross floors at zero. Credit is expressed through credit notes (coming in a future release), never negative lines — so every journal leg inherits the document grammar. Previously an application-level safeguard; now Class M (AX-INV-003).

Ledger truth begins at this release

Invoices issued before this release predate the posting contract and carry no issue journals; their revenue was recognised through existing transaction workflows, and imported invoices carry their migration clearing journals as before. The cutover is recorded mechanically — the provenance gate reads migration 092’s own application timestamp and enforces the boundary in both directions, forever.