Skip to content

Odoo ir.sequence Numbering Guide: Auto-Numbering Patterns

DeployMonkey Team · March 24, 2026 10 min read

What is ir.sequence?

Odoo's ir.sequence model generates sequential numbers for documents — invoice numbers, purchase order references, employee IDs, ticket numbers, and any other record that needs auto-incrementing identifiers. Understanding how to configure and use sequences correctly prevents numbering gaps, duplicate numbers, and regulatory compliance issues.

Basic Usage

Getting the Next Number

# In a model's create method
reference = self.env['ir.sequence'].next_by_code('my.model')

# Or with a specific sequence record
sequence = self.env['ir.sequence'].browse(sequence_id)
reference = sequence.next_by_id()

Defining a Sequence in XML

<record id="seq_purchase_request" model="ir.sequence">
    <field name="name">Purchase Request</field>
    <field name="code">purchase.request</field>
    <field name="prefix">PR/%(year)s/</field>
    <field name="padding">5</field>
    <field name="number_increment">1</field>
    <field name="number_next_actual">1</field>
</record>

This generates numbers like: PR/2026/00001, PR/2026/00002, etc.

Prefix and Suffix Patterns

Available Variables

VariableOutputExample
%(year)s4-digit year2026
%(y)s2-digit year26
%(month)s2-digit month03
%(day)s2-digit day24
%(doy)sDay of year083
%(woy)sWeek of year13
%(h24)sHour (24h)14
%(min)sMinutes30
%(sec)sSeconds45

Common Patterns

<!-- INV/2026/00001 -->
<field name="prefix">INV/%(year)s/</field>

<!-- PO-2026-03-00001 -->
<field name="prefix">PO-%(year)s-%(month)s-</field>

<!-- 00001/2026 (number first, then year suffix) -->
<field name="suffix">/%(year)s</field>

<!-- WO-00001 (no date component) -->
<field name="prefix">WO-</field>

Year Reset

Reset the counter to 1 at the start of each year, month, or other period:

<record id="seq_invoice" model="ir.sequence">
    <field name="name">Invoice</field>
    <field name="code">account.invoice</field>
    <field name="prefix">INV/%(year)s/</field>
    <field name="padding">4</field>
    <field name="use_date_range">True</field>
</record>

With use_date_range=True, Odoo creates separate counters per date range. The first invoice of 2027 will be INV/2027/0001 regardless of how many 2026 invoices exist.

No-Gap Sequences

Some regulations require no gaps in numbering (especially for invoices). Odoo supports this:

<field name="implementation">no_gap</field>

Key differences between standard and no-gap:

  • Standard — Uses PostgreSQL SEQUENCE objects. Fast but may have gaps if transactions roll back.
  • No-gap — Uses table-level locks. Slower but guarantees consecutive numbers. No gaps even on rollback.

Use no-gap only when required by regulation. It creates a performance bottleneck under high concurrency because it locks the sequence row.

Company-Specific Sequences

In multi-company setups, create per-company sequences:

<record id="seq_invoice_company_1" model="ir.sequence">
    <field name="name">Invoice - Company A</field>
    <field name="code">account.invoice</field>
    <field name="company_id" ref="base.main_company"/>
    <field name="prefix">COMP-A/%(year)s/</field>
    <field name="padding">5</field>
</record>

When next_by_code() is called, Odoo automatically selects the sequence matching the current user's company.

Using in Model Create

class PurchaseRequest(models.Model):
    _name = 'purchase.request'

    name = fields.Char(
        string='Reference',
        readonly=True,
        copy=False,
        default='New',
    )

    @api.model_create_multi
    def create(self, vals_list):
        for vals in vals_list:
            if vals.get('name', 'New') == 'New':
                vals['name'] = self.env['ir.sequence'].next_by_code(
                    'purchase.request'
                ) or 'New'
        return super().create(vals_list)

Common Pitfalls

  • Sequence not foundnext_by_code() returns False if no sequence exists with that code. Always handle the fallback.
  • Gaps in no-gap — If records are deleted, gaps appear. No-gap prevents gaps from rollbacks, not deletions.
  • Date range initialization — First call in a new year auto-creates the date range entry. Verify padding carries over.
  • Concurrency with no-gap — Under high load, no-gap sequences become a bottleneck. Use standard sequences unless regulation requires no-gap.