Sequences
Sequences provide automatic document numbering across all modules. They generate sequential, formatted identifiers for journal entries, invoices, purchase orders, and any other document type. Each sequence defines a prefix, suffix, digit padding, and reset frequency. Counters are tracked in a separate table supporting per-branch and fiscal-period auto-reset.
Purpose
- Auto-generate unique, sequential document numbers
- Support configurable formats with prefix, suffix, year/month inclusion, and zero-padding
- Allow per-branch numbering for organizations that need branch-specific sequences
- Enable periodic reset (never, yearly, monthly) for numbering schemes
- Maintain separate counter rows per branch and fiscal period
Entity Attributes
Sequence (Definition)
| Field | Type | Description |
|---|---|---|
id | integer | Primary key |
company_id | integer | Foreign key to the company |
name | string | Sequence name (English) |
name_ar | string | Sequence name (Arabic) |
module | string | Target module (e.g., accounting, sales) |
entity | string | Target entity (e.g., journal_entry, invoice) |
prefix | string? | Text prepended to the number (e.g., JV-, INV-) |
suffix | string? | Text appended to the number |
include_year | boolean | Whether to include the year in the generated number |
include_month | boolean | Whether to include the month in the generated number |
digits | integer | Number of digits for zero-padding (e.g., 5 produces 00001) |
reset_frequency | enum | When to reset the counter: never, yearly, monthly |
per_branch | boolean | Whether numbering is separate per branch |
is_active | boolean | Whether the sequence is active |
created_at | datetime | Creation timestamp |
updated_at | datetime | Last update timestamp |
deleted_at | datetime? | Soft-delete timestamp |
Sequence Counter
Counters track the current number for each combination of sequence, branch, and fiscal period.
| Field | Type | Description |
|---|---|---|
id | integer | Primary key |
sequence_id | integer | FK to sequences |
branch_id | integer | Branch ID (0 = all branches) |
fiscal_year | integer | Fiscal year (0 = no yearly reset) |
fiscal_month | integer | Fiscal month (0 = no monthly reset) |
last_number | bigint | Last number issued |
created_at | datetime | Creation timestamp |
updated_at | datetime | Last update timestamp |
Unique constraint: (sequence_id, branch_id, fiscal_year, fiscal_month)
Generated Number Examples
Given a sequence with prefix: "JV-", include_year: true, digits: 5, counter at last_number: 42:
| Setting | Generated Number |
|---|---|
| Year only | JV-2026-00042 |
| Year + Month | JV-2026-02-00042 |
No year, with suffix -KW | JV-00042-KW |
| Minimal (no prefix/suffix/year) | 00042 |
Default Sequences
14 default sequences are seeded during installation:
| Module | Entity | Prefix | Reset | Per Branch |
|---|---|---|---|---|
| core | partner | BP- | never | no |
| core | product | PRD- | never | no |
| sales | quotation | QT- | yearly | no |
| sales | sales_order | SO- | yearly | no |
| sales | invoice | INV- | yearly | yes |
| sales | return | SR- | yearly | no |
| sales | credit_note | CN- | yearly | no |
| sales | receipt_voucher | RV- | yearly | yes |
| purchases | purchase_order | PO- | yearly | no |
| purchases | bill | BILL- | yearly | no |
| purchases | payment_voucher | PV- | yearly | yes |
| accounting | journal_entry | JV- | yearly | no |
| inventory | stock_movement | SM- | yearly | no |
| manufacturing | production_order | MO- | yearly | no |
How Counters Work
When a number is generated:
- The system finds the active sequence for the requested module + entity
- Determines the branch: if
per_branchis true and a branch ID is provided, uses that branch; otherwise uses 0 (all branches) - Resolves the fiscal period: if
reset_frequency = yearly, uses current year; ifmonthly, uses year + month; otherwise 0 - Finds or creates a counter row matching
(sequence_id, branch_id, fiscal_year, fiscal_month) - Locks the counter row, increments
last_number, and saves - Builds the formatted string from prefix + year + month + padded number + suffix
This means when a new fiscal year starts, a new counter row is automatically created starting from 1, effectively resetting the numbering.
Relationships
API Endpoints
| Method | Path | Description |
|---|---|---|
GET | /api/core/sequences | List all sequences (paginated) |
POST | /api/core/sequences | Create a new sequence |
GET | /api/core/sequences/{id} | Get a specific sequence |
PUT | /api/core/sequences/{id} | Update a sequence |
DELETE | /api/core/sequences/{id} | Soft-delete a sequence |
Internal Use
Number generation is handled internally by the SequenceService. There is no public API endpoint to generate a number -- it happens automatically when documents are created or posted (e.g., journal entries get their number on post, partners get their code on creation).
Request/Response Examples
GET /api/core/sequences
Retrieve a paginated list of sequences, ordered by module and entity.
curl -X GET https://moon-erp.test/api/core/sequences \
-H "Accept: application/json" \
-H "Authorization: Bearer {token}"final response = await http.get(
Uri.parse('https://moon-erp.test/api/core/sequences'),
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer $token',
},
);Response 200 OK
{
"data": [
{
"id": 1,
"name": "Journal Entry Sequence",
"name_en": "Journal Entry Sequence",
"name_ar": "تسلسل قيد اليومية",
"module": "accounting",
"entity": "journal_entry",
"prefix": "JV-",
"suffix": null,
"include_year": true,
"include_month": false,
"digits": 5,
"reset_frequency": "yearly",
"per_branch": false,
"is_active": true,
"created_at": "2026-01-01T00:00:00.000000Z",
"updated_at": "2026-01-01T00:00:00.000000Z"
}
],
"links": { "first": "...?page=1", "last": "...?page=1", "prev": null, "next": null },
"meta": { "current_page": 1, "from": 1, "last_page": 1, "per_page": 25, "to": 1, "total": 1 }
}POST /api/core/sequences
Create a new sequence.
curl -X POST https://moon-erp.test/api/core/sequences \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Authorization: Bearer {token}" \
-d '{
"name": "Purchase Order Sequence",
"name_ar": "تسلسل أمر الشراء",
"module": "purchasing",
"entity": "purchase_order",
"prefix": "PO-",
"suffix": null,
"include_year": true,
"include_month": false,
"digits": 5,
"reset_frequency": "yearly",
"per_branch": false,
"is_active": true
}'final response = await http.post(
Uri.parse('https://moon-erp.test/api/core/sequences'),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer $token',
},
body: jsonEncode({
'name': 'Purchase Order Sequence',
'name_ar': 'تسلسل أمر الشراء',
'module': 'purchasing',
'entity': 'purchase_order',
'prefix': 'PO-',
'include_year': true,
'include_month': false,
'digits': 5,
'reset_frequency': 'yearly',
'per_branch': false,
'is_active': true,
}),
);Response 201 Created
{
"data": {
"id": 3,
"name": "Purchase Order Sequence",
"name_en": "Purchase Order Sequence",
"name_ar": "تسلسل أمر الشراء",
"module": "purchasing",
"entity": "purchase_order",
"prefix": "PO-",
"suffix": null,
"include_year": true,
"include_month": false,
"digits": 5,
"reset_frequency": "yearly",
"per_branch": false,
"is_active": true,
"created_at": "2026-02-16T10:00:00.000000Z",
"updated_at": "2026-02-16T10:00:00.000000Z"
}
}Business Rules
- Company scoping -- sequences are scoped to the authenticated user's company.
- Module + Entity identification -- each sequence targets a specific module and entity combination (e.g.,
accounting/journal_entry). - Reset frequency -- controls when numbering resets:
never-- the counter increments indefinitely across all periods.yearly-- a new counter starts at 1 for each fiscal year.monthly-- a new counter starts at 1 for each month.
- Per-branch numbering -- when
per_branchistrue, each branch maintains its own independent counter for the sequence. - Digits (padding) -- the
digitsfield controls zero-padding. A value of 5 with counter at 42 produces00042. - Active flag -- only active sequences (
is_active: true) are used for number generation. Deactivating a sequence stops it from issuing new numbers. - Atomic locking -- counter rows are locked during increment to prevent duplicate numbers in concurrent requests.
- Auto-creation -- counter rows are created on-demand when the first number is generated for a new branch/period combination.