Skip to content
Last updated: Sep 25, 2026

Document numbering convention ​

OwnerClassificationVersionEffectiveNext reviewStatus
Platform EngineeringInternal0.12026-09-132026-12-13Draft

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:

typeprefixroundoff (pad width)example
journalJV6JV-009254
invoiceINV6INV-004120
remittanceREM6REM-000317
EmailDOC6DOC-010233
collections-replyCR6CR-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-009254 may be followed by JV-009261 because other document types took the ids in between. This is expected and consistent across the platform.
  • type matches documentreceipt.documenttype (canonical lowercase, e.g. journal). If no row matches, the convention falls back to the Email prefix (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:

  1. Insert the documentreceipt without a documentnumber (so you get its id).
  2. Look up coresystem.receiptprefixes by type = <documenttype> (fallback Email).
  3. Format `${prefix}-${String(receipt.id).padStart(roundoff, '0')}`.
  4. 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) — sets MJV-<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.

Finaisse Internal — Confidential. Access-restricted; not for external distribution.