Skip to content

Sequences (Record Numbering)

The Sequence model generates gapless, formatted, auto-incrementing reference numbers — invoice numbers, order references, lot numbers, and so on. You seed a Sequence as a data record with a prefix/suffix pattern and padding, then ask for the next value in code.

Not the view ordering field. This page is about the Sequence model for numbering records. It is unrelated to the integer sequence field used to order rows in list/kanban views — that one is just a sort key, not a number generator.

The entry point is the classmethod Sequence.get_next:

async def get_next(cls, identifier=None, date=None) -> str: ...
  • identifier — the unique identifier of the Sequence record (required; raises a UserError if missing or unknown).
  • date — the date used to fill date tokens in the pattern and to bucket per-period resets. Defaults to the current date/time when omitted.

It increments the sequence and returns the formatted string. Typical use is inside a create override, when the record has no name yet:

class SaleOrder(Model):
async def create(cls, records):
Sequence = get_model("Sequence")
for record in records:
if not record.get("name") or record.get("name") == "Draft":
record_date = record.get("date") or date.today()
if isinstance(record_date, str):
record_date = date.fromisoformat(record_date)
record["name"] = await Sequence.get_next("sale_order_sequence", record_date)
return await super().create(records)

Always call it as a classmethod on the model — await Sequence.get_next("...") — never by instantiating it.

The prefix and suffix are Python percent-format patterns. The following tokens are substituted from the date you pass (or now). A pattern with no tokens is used verbatim.

TokenMeaningExample
%(year)s4-digit year2026
%(y)s2-digit year26
%(month)sZero-padded month06
%(day)sZero-padded day of month25
%(doy)sDay of year176
%(woy)sWeek of year25
%(weekday)sWeekday number (0 = Sunday)4
%(h24)sHour (24h)14
%(h12)sHour (12h)02
%(min)sMinute09
%(sec)sSecond30

The final value is prefix + zero-padded-number + suffix.

size controls the zero-padding of the numeric portion (default 5). With size: 5 and the counter at 42, the number renders as 00042. So a sequence with prefix: "INV" and size: 5 produces INV00042; one with prefix: "PKG/%(year)s/" produces PKG/2026/00042.

reset_frequency controls when the counter resets back to 1. Choices:

Never, Daily, Weekly, Monthly, Yearly (default Never).

  • With Never, the sequence uses a single global counter (next_number on the Sequence).

  • With any other value, the counter is tracked per period in SequenceValue rows keyed by a date key derived from the date you pass:

    FrequencyDate key formatExample
    Yearly%Y2026
    Monthly%Y-%m2026-06
    Weekly%Y-W%U2026-W25
    Daily%Y-%m-%d2026-06-25

    The first call within a new period starts that period’s counter at 1. Combine a reset with a matching date token in the pattern (e.g. prefix: "INV/%(year)s/" + reset_frequency: Yearly) so the visible number and the reset bucket line up: INV/2026/00001, INV/2026/00002, …, then INV/2027/00001 next year.

Ship data_type: Sequence records in your module’s data/ directory.

modules/inventory/data/sequences.yaml
- data_type: Sequence
identifier: package_sequence
name: Package Sequence
prefix: PKG/%(year)s/
next_number: 1
size: 5
- data_type: Sequence
identifier: serial_number_sequence
name: Serial Number Sequence
prefix: "SN/"
next_number: 1
size: 5
FieldTypeMeaning
identifierCharUnique handle passed to get_next (snake_case).
nameCharHuman-readable label (required).
prefixCharPattern prepended to the number.
suffixCharPattern appended to the number.
next_numberIntegerNext value for the global counter (default 1; used when reset_frequency is Never).
sizeIntegerZero-padding width of the number (default 5).
reset_frequencySelectionNever / Daily / Weekly / Monthly / Yearly (default Never).
modelManyToOne (ModelRegistry)Optional. The model this sequence numbers (informational/grouping).

The invoicing module seeds one sequence per document kind and resolves the right one when a document is created:

modules/invoicing/data/sequences.yaml
- data_type: Sequence
name: Invoices
identifier: customer_invoice_seq
model: FinancialDocument
prefix: INV
- data_type: Sequence
name: Credit Notes
identifier: credit_note_seq
model: FinancialDocument
prefix: CN

In the model’s create override, the document’s number is filled by calling Sequence.get_next("<identifier>", document_date) for the matching kind — the same pattern shown above for SaleOrder. get_next runs under elevated privileges, reads the current counter, formats the value, and writes the incremented counter back within the caller’s transaction.