Document Sequences & Numbering

Developer guide for the Enezaframework. Part of the Developer documentation.

Table of Contents

  1. Overview
  2. How SequenceMixin Works
  3. SequenceConfig Reference
  4. Using a Sequence-Enabled Base
  5. Failure Modes
  6. Change History
  7. Related Guides

Overview

Master and document records get human-readable business numbers (CUST-0001, INV-00001) from a SequenceMixin that reads a SequenceConfig row and increments its counter on the first save.

Base models that include the mixin: EnezaMaster and EnezaDocument. Base models that do not: EnezaTransactionalTable, EnezaSetup, EnezaParameter, EnezaGeneral, EnezaMasterDetails, and EnezaDocumentLines.

sequenceDiagram
    participant App
    participant Model as Model.save()
    participant Mixin as SequenceMixin
    participant Config as SequenceConfig

    App->>Model: save() new instance
    Model->>Mixin: generate_sequence()
    Mixin->>Config: filter(documents=document_name, active=True).first()
    Config-->>Mixin: config (prefix, delimiter, number_length, nextnumber)
    Mixin->>Config: nextnumber += 1; save()
    Mixin-->>Model: "PREFIX-0001"
    Model->>Model: set sequence_field_name; save()

How SequenceMixin Works

Defined in enezaframework/services/Mixin.py. On the first save of a new instance (no existing row), it fills the sequence field if it is empty:

python
class SequenceMixin:
    def generate_sequence(self):
        sequence_field_name = self.sequence_field_name
        document_name = self.document_name
        config = SequenceConfig.objects.filter(documents=document_name, active=True).first()
        ...
        return f"{config.prefix}{config.delimiter}{formatted_number}"

    def save(self, *args, **kwargs):
        sequence_field_name = self.sequence_field_name
        currentobject = self.__class__.objects.filter(pk=self.pk)
        if not currentobject:
            if not getattr(self, sequence_field_name, None):
                setattr(self, sequence_field_name, self.generate_sequence())
        super().save(*args, **kwargs)

Two attributes are required on the concrete model:

Attribute Purpose
sequence_field_name Name of the field that stores the generated number (for example "vendorno").
document_name Value matched against SequenceConfig.documents to select the configuration.

number_length is applied with zero-padding, and the result is prefix + delimiter + zero-padded number.


SequenceConfig Reference

Defined in enezaframework/models/enezasequence.py. It is a concrete EnezaModel, so it has the standard UUID key, audit fields, and history.

Field Type Description
documents CharField The document/model key; matched against the model's document_name.
prefix CharField Prefix such as "IV" or "CR".
delimiter CharField Separator, default "-".
number_length PositiveIntegerField Zero-pad width, default 4.
active BooleanField Whether this config is used.
nextnumber PositiveIntegerField The next number to issue, default 1.
modules CharField Module/choices grouping for the configuration.
entity ForeignKey to Entity Entity the configuration belongs to, nullable.

Constraint: UniqueConstraint(fields=["documents", "active"]) named unique_model_name_active. In practice, only one active config per document key can exist.

SequenceConfig can be maintained through the admin (see the System Administrator module) or created programmatically:

python
from enezaframework.models.enezasequence import SequenceConfig


config = SequenceConfig.objects.create(
    documents="VendorTable",
    prefix="VEND",
    delimiter="-",
    number_length=4,
    active=True,
    nextnumber=1,
    modules="accountpayable",
)

Using a Sequence-Enabled Base

Declare the two mixin attributes on the model, plus the field that stores the number.

python
# accountpayable/models/vendortable.py
from django.db import models

from enezaframework.models import EnezaMaster


class VendorTable(EnezaMaster):
    sequence_field_name = "vendorno"
    document_name = "VendorTable"

    vendorno = models.CharField(
        max_length=50,
        unique=True,
        blank=True,
        verbose_name="Vendor No.",
    )
    searchname = models.CharField(
        max_length=255,
        verbose_name="Search Name",
    )

    class Meta:
        verbose_name = "Vendor"
        verbose_name_plural = "Vendors"

    def __str__(self):
        return self.searchname

Leave the sequence field empty (or blank=True, not required in forms) so the mixin fills it on the first save.


Failure Modes

Symptom Cause
ValueError: No Sequence configuration found for model '...' No active SequenceConfig with documents == self.document_name.
AttributeError: 'X' object has no attribute 'sequence_field_name' The model inherits a sequence base but does not declare sequence_field_name.
Number not generated The model is not using a sequence base, or the field already has a value, or the row is not new.

Always create and activate the matching SequenceConfig before saving the first record.

The user is solely responsible for testing and validating sequence generation in their environment; the developer/agent does not run the application.


Change History

Dated changes that affect this guide, taken from docs/Release note/CHANGELOG.md and the release notes. For the complete history, see the changelog.

Date Version What changed
2026-06-27 — HistoricalSequenceConfig model gained document relationships and updated field choices (release note "Workflow Type Registry Developer Guide and various migrations").

No dated changes to SequenceMixin or SequenceConfig's core fields appear in the changelog for the 0.2.7–0.2.19 window.