Sequence Generation Best Practices

Developer guide for the Enezaframework sequence generation. Part of the Sequence Generation series.

Table of Contents

  1. Overview
  2. Choose the Right Base Model
  3. Declare Models Correctly
  4. Use Meaningful Prefixes
  5. Plan Number Lengths
  6. Test Roll-Over
  7. Handle Concurrency
  8. Back Up and Monitor
  9. Related Guides

Overview

These practices keep sequences predictable and safe to operate. They are about the model side (what to inherit and how to format) and the operations side (lengths, roll-over, concurrency, backups).


Choose the Right Base Model

Record type Inherit Why
Master data (Customer, Vendor, Product) EnezaMaster Includes SequenceMixin + entity.
Documents (Invoice, Order, Journal) EnezaDocument Includes SequenceMixin + entity.
Records without a business number EnezaGeneral No sequence support.

Declare Models Correctly

  1. Do not inherit SequenceMixin separately — it is already in EnezaMaster / EnezaDocument.
  2. Do define both sequence_field_name and document_name.
  3. Do make the sequence field nullable (null=True, blank=True).
  4. Do follow the project field conventions (one field per line, verbose_name, closing parenthesis aligned).
python
# Correct
class Product(EnezaMaster):
    product_code = models.CharField(
        max_length=50,
        null=True,
        blank=True,
        verbose_name="Product Code",
    )
    name = models.CharField(
        max_length=200,
        verbose_name="Name",
    )

    sequence_field_name = 'product_code'
    document_name = ProjectDocumentTypes.PRODUCT
python
# Incorrect — risk of MRO problems
class Product(SequenceMixin, EnezaMaster):
    ...

Use Meaningful Prefixes

Choose prefixes that are short but unambiguous and unique per document type:

Good Bad
CUST, INV, PO, SO, EMP C, I, P, S, E

Keep prefixes consistent across the system so users can recognize record types at a glance.


Plan Number Lengths

Length Range Capacity
4 0001–9999 10,000
5 00001–99999 100,000
6 000001–999999 1,000,000

Pick based on expected volume with headroom. Note that a number wider than number_length is not truncated — it simply grows, so the display width can change at roll-over.


Test Roll-Over

Test what happens at the boundary before it matters:

python
# With number_length=4, set nextnumber near 9999 and save records.
# Observe whether the value rolls to 10000 (wider) or needs a format change.

Decide in advance whether to widen number_length or introduce a year segment before the limit is reached.


Handle Concurrency

generate_sequence() performs a read (config.nextnumber) followed by a separate config.save(). That read-modify-write is not atomic:

  • For a single-server, low-concurrency deployment this is usually fine.
  • For concurrent writers, two saves can read the same nextnumber and produce duplicates.

Mitigations:

  • Serialize generation at the database level (row locks / select_for_update() in a custom generate_sequence()).
  • Generate sequences in a dedicated process or queue.
  • Consider a database sequence or an atomic F() expression update in a custom implementation.

See Using SequenceMixin in Models for overriding generate_sequence().


Back Up and Monitor

Backup: include SequenceConfig.nextnumber in your backup/restore strategy. Restoring an older value can re-issue numbers that were already handed out.

Monitor utilization: track how close active sequences are to their limit.

python
# Admin dashboard helper: warn when a sequence is >90% used
def sequence_health_check():
    configs = SequenceConfig.objects.filter(active=True)
    for config in configs:
        utilization = (config.nextnumber - 1) / (10 ** config.number_length)
        if utilization > 0.9:
            print(f"Warning: {config} is {utilization:.1%} utilized")
flowchart LR
    A["SequenceConfig active"] --> B["utilization =<br/>(nextnumber-1) / 10^length"]
    B --> C{"> 90%?"}
    C -->|Yes| D["Warn / widen length"]
    C -->|No| E["OK"]