yeeraf/laravel-document-numberer

A Laravel package for create document running number

Maintainers

Package info

github.com/yeeraf/laravel-document-numberer

pkg:composer/yeeraf/laravel-document-numberer

Transparency log

Statistics

Installs: 1 566

Dependents: 0

Suggesters: 0

Stars: 2

Open Issues: 0

v1.4.0 2026-06-27 23:14 UTC

This package is auto-updated.

Last update: 2026-06-27 23:21:49 UTC


README

Test

Laravel Document Running Number Generator

A Laravel package for generating sequential document numbers with a customisable prefix, suffix, and padding. Ideal for invoices, receipts, quotations, and any document that requires a unique running number.

Examples:

  • INV-000001
  • INV2101-0001
  • REC-0000001
  • QT #####1

Concurrent requests are safe — the package uses database row-level locking and a unique constraint to guarantee no two requests ever receive the same number.

Installation

composer require yeeraf/laravel-document-numberer

Then run the migration to create the document_numbers table:

php artisan migrate

Basic Usage

$documentNumberer = new \Yeeraf\DocumentNumberer\DocumentNumberer;
$docNumber = $documentNumberer->generate();
// "2107000001"

By default the number is prefixed with the last 2 digits of the current year and the 2-digit month — e.g. for 20/07/2021 the prefix is 2107 — followed by a 6-digit zero-padded sequence starting at 1: 2107000001.

Options

name() — separate counters per document type

Use this when two document types share the same format but must have independent sequences.

// Invoice counter
$documentNumberer = new \Yeeraf\DocumentNumberer\DocumentNumberer;
$docNumber = $documentNumberer->name("invoice")->generate();
// "2107000001"

// Receipt counter (independent from invoice)
$documentNumberer = new \Yeeraf\DocumentNumberer\DocumentNumberer;
$docNumber = $documentNumberer->name("receipt")->generate();
// "2107000001"

prefix() — custom prefix

$documentNumberer = new \Yeeraf\DocumentNumberer\DocumentNumberer;
$docNumber = $documentNumberer->prefix("INV-")->generate();
// "INV-000001"

suffix() — custom suffix

$documentNumberer = new \Yeeraf\DocumentNumberer\DocumentNumberer;
$docNumber = $documentNumberer->suffix("-X")->generate();
// "2107000001-X"

padLength(int) — number of digits in the sequence

$documentNumberer = new \Yeeraf\DocumentNumberer\DocumentNumberer;
$docNumber = $documentNumberer->padLength(3)->generate();
// "2107001"

padString() — character used for padding

$documentNumberer = new \Yeeraf\DocumentNumberer\DocumentNumberer;
$docNumber = $documentNumberer->padString("#")->generate();
// "2107#####1"

padType() — padding direction (left or right)

Default is left (digits appear on the right).

$documentNumberer = new \Yeeraf\DocumentNumberer\DocumentNumberer;
$docNumber = $documentNumberer->padType('right')->generate();
// "2107100000"

teamId() — separate counters per team

Use in multi-tenant applications where each team needs its own independent sequence.

// Team 1
$documentNumberer = new \Yeeraf\DocumentNumberer\DocumentNumberer;
$docNumber = $documentNumberer->teamId(1)->generate();
// "2107000001"

// Team 2 (independent counter)
$documentNumberer = new \Yeeraf\DocumentNumberer\DocumentNumberer;
$docNumber = $documentNumberer->teamId(2)->generate();
// "2107000001"

autoExtend() — prevent the sequence from exceeding padLength

Default is true (the sequence grows beyond padLength automatically).

Set to false to throw an exception once the sequence would exceed the configured length:

$documentNumberer = new \Yeeraf\DocumentNumberer\DocumentNumberer;
$docNumber = $documentNumberer
                ->padLength(3)
                ->autoExtend(false)
                ->generate();
// throws Exception("running number length go over pad length") once the counter exceeds 999

Combined Example

$documentNumberer = new \Yeeraf\DocumentNumberer\DocumentNumberer;
$docNumber = $documentNumberer
                ->name("invoice")
                ->prefix("INV-")
                ->suffix("-X")
                ->padLength(4)
                ->padString("_")
                ->padType("left")
                ->teamId(1)
                ->generate();
// "INV-___1-X"

Concurrency

The package uses SELECT ... FOR UPDATE (row-level locking) together with a unique composite index on the document_numbers table to prevent race conditions when multiple requests call generate() simultaneously.

  • Unique index — prevents duplicate rows from being created when two requests initialise the same counter at the same time.
  • Deadlock retry — if MySQL raises a deadlock error, the transaction is automatically retried up to 3 times.

Note: SELECT ... FOR UPDATE is supported by MySQL and PostgreSQL only. SQLite (used in the test suite) silently ignores this clause. Run the test suite against MySQL to validate concurrency behaviour.

License

The MIT License (MIT)