Document numbering convention
| Owner | Classification | Version | Effective | Next review | Status |
|---|---|---|---|---|---|
| Platform Engineering | Internal | 0.1 | 2026-09-13 | 2026-12-13 | Draft |
Purpose. How every finance document (invoice, journal, remittance, receipt, …) gets its human-facing number, so new document-creating code reuses the one convention instead of inventing prefixes (or shipping raw UUIDs). Read this before adding a code path that creates a documentreceipt or assigns a documentnumber.
The convention
A document number is {prefix}-{zero-padded documentreceipt.id}. The prefix and pad width are configured per document type in coresystem.receiptprefixes:
| type | prefix | roundoff (pad width) | example |
|---|---|---|---|
| journal | JV | 6 | JV-009254 |
| invoice | INV | 6 | INV-004120 |
| remittance | REM | 6 | REM-000317 |
DOC | 6 | DOC-010233 | |
| collections-reply | CR | 6 | CR-000042 |
Two consequences to know:
- The running number is the global
documentreceipt.id, not a per-type sequence. So numbers are not contiguous within a type —JV-009254may be followed byJV-009261because other document types took the ids in between. This is expected and consistent across the platform. typematchesdocumentreceipt.documenttype(canonical lowercase, e.g.journal). If no row matches, the convention falls back to theEmailprefix (DOC).
Where it is applied (and where it is NOT)
The convention is implemented once, in DocumentReceiptService.create() (coresystem/coresystema/services/coresystem/documentreceipt.service.ts): when a receipt is created without a documentnumber, it looks up receiptprefixes by documenttype and stamps {prefix}-{padded id}.
The trap: this numbering lives in the service method, not in a DB trigger. Any code that creates a documentreceipt through the generic CRUD path (InsertObjectActivity / prisma.documentreceipt.create) bypasses it and gets no number — so historically the programmatic journal paths shipped MJV-<uuid>, MJV-<timestamp>, or an empty number. There is no applicationsettings numbering field and no DB sequence for this; receiptprefixes is the single source.
How to number correctly when creating a document programmatically
If you create the receipt via the generic activity/CRUD (e.g. inside a Temporal workflow), replicate the convention explicitly:
- Insert the
documentreceiptwithout adocumentnumber(so you get itsid). - Look up
coresystem.receiptprefixesbytype = <documenttype>(fallbackEmail). - Format
`${prefix}-${String(receipt.id).padStart(roundoff, '0')}`. - Update the receipt's
documentnumber, and use the same value on any downstream rows (extractedjournalvoucher, line items, the promoted header, etc.).
Reference implementation (journal): createsourcedjournal (journal/journalwf/src/workflows/createsourcedjournal/workflow.ts) — the shared primitive every journal generator (AP-cutoff accrual, recon adjustment, recurring) routes through. It does steps 1–4 above and only falls back to a UUID if the prefix config is missing. A caller that owns its own numbering can pass an explicit header.documentnumber, which always wins.
Assign the number at draft creation, not at post — the whole approval chain (draft → submit → approve → post) should reference one stable human number, so a UUID never reaches a user or a downstream module projecting the document.
Adding a new document type
Insert a row into coresystem.receiptprefixes (type, prefix, roundoff) — no code change if the creating path already follows the pattern above. Keep roundoff at 6 unless a type needs a wider range. Match type to the documenttype the creating code sets on the receipt.
Known non-conforming paths (2026-09-13)
createManualJv(manual UI create) — setsMJV-<timestamp>; should adopt the convention.uploadmanualjournal— inserts via the generic activity, so its receipt lands with an empty number; should adopt the convention.
New code should not add to this list — route document creation through a primitive that applies the convention, or apply steps 1–4 inline.