Skip to content

Document Numbers per Workflow Stage

An order moves through quotation, order confirmation, delivery note and invoice. Out of the box all four carry the same number — the order number from Order → Number. The document type comes solely from the workflow stage of the status: the same record prints as a quotation while it sits on "Quotation", and as an invoice while it sits on "Invoice".

For many labs that is enough. If you need a separate invoice number — because accounting requires a gapless invoice series of its own — this is how you set it up.

What this page covers

This page describes the route via status rules. It needs no schema change and works on every installation. Its limits are listed under What this route cannot do — they are real, and worth reading before you move your invoice numbering onto it.


How it works

A status rule (Administration → Status Management → Status Rules) links a status to a field and a default. When an order moves into that status, the default is resolved and written to the field.

Three properties make that a usable number sequence:

  • The counter is real. [STATUS_COUNT] draws the counter of this status, reserved atomically. Two simultaneous status changes get two different numbers.
  • It assigns once. Only empty fields are filled. Stepping back and forward again assigns no second number, and neither does a second click on the same status.
  • The moment is the status change, not the printout. A quotation printed three times carries the same number three times.

The factory default

A freshly installed V1-compatible database (db:install --v1) ships four status rules:

Status Field Template Result
Quotation Quotation number (W4102) AN-[YYYY]-[STATUS_COUNT\|0000] AN-2026-0001
Order Order number (W4103) AB-[YYYY]-[STATUS_COUNT\|0000] AB-2026-0001
Delivery Delivery number (W4104) LS-[YYYY]-[STATUS_COUNT\|0000] LS-2026-0001
Invoice Invoice number (W4105) RE-[YYYY]-[STATUS_COUNT\|0000] RE-2026-0001

The record number (Number) stays alongside and does not change. It identifies the transaction; the document numbers identify the documents.

Existing installations do not get these rules

An update does not create them. If you run your invoice number off the order number today, you should not lose that by installing a new version — switching is a decision, not a side effect. The next section describes it.

No [STATUS_SHORT] in the templates

The obvious template would be [STATUS_SHORT]-[YYYY]-[STATUS_COUNT]. It misfires: the shipped booking statuses carry "AB" as the short title for Quotation and nothing at all for Order. The quotation would get AB-2026-0001, the order -2026-0001. The document type is fixed, so its prefix is fixed too — hence spelled-out prefixes.


Setting it up yourself

Using the invoice number as the example.

1. Create the status rule. Administration → Status Management → Status Rules, pick status Invoice, field Invoice number (W4105), default value:

RE-[YYYY]-[STATUS_COUNT|0000]

2. Set the counter. Administration → Number Sequences, row of the invoice status. If an invoice series is already running, set the counter to the last number issued — otherwise the sequence starts at 1 and collides with existing records.

3. Choose the reset period. Same row. The template has to distinguish whatever the period separates: a monthly reset needs [YYYY][MM] in the template, otherwise RE-2026-0001 appears in January and in February. The check reports this as reset_finer_than_template before saving.

4. Make the invoice number BT-1. Without this step the electronic invoice still carries the order number. Administration → Report Management → Variables, for the order document report type:

Variable Value
zugferd_invoice_number_field W4105

From then on BT-1 is the content of that field — even when it is empty. That is deliberate: silently falling back to the order number would produce a formally valid invoice carrying a number that means nothing to accounting. An empty field becomes a visible error instead.


Template building blocks

Token Yields
[YYYY] / [YY] Year, four digits / two digits
[MM] / [DD] Month / day
[STATUS_COUNT] Counter of this status, incremented atomically
[STATUS_COUNT\|0000] Same counter, padded to four digits
[STATUS_NAME] / [STATUS_SHORT] Title / short title of the status
[GLOBAL_COUNTER_1] … Global counter from the general settings

On padding: |0000 is a reading aid, not a limit. The 10,000th invoice becomes five digits rather than being truncated — a mutilated number would be the worse trade.

Do not reuse the same global counter

counter_1 is already drawn by the order number and the certificate number. A document number taken from it jumps whenever a calibration is created. Status counters are separate per status and therefore the right place.


What is checked

When a template is saved — on every route, including the MCP tool and the category package import:

Finding Meaning
unbalanced_brackets Error. Brackets do not match; the write is refused.
no_counter No counter token — every number would come out identical.
multiple_counters Several counters in one template: ambiguous when read back.
counter_adjacent_digit Digit right next to the counter: RE1 + 2 reads like RE + 12. A fixed width separates them.
reset_finer_than_template The counter resets more often than the template distinguishes.
empty_now_on_number Error. "Clear now" on the record number; the write is refused.

On that last row: the record number identifies the transaction and cannot be empty in the database. Clearing it on a status change to "make room for a new number" ends in a write error on the column — far away from the rule that caused it.


What this route cannot do

Honest limits. They follow from the number living in a field rather than in a document journal of its own:

  • No collision check. The document numbers live in custom_fields. The uniqueness check that exists for columns does not reach them — a duplicate invoice number would be stored.
  • No duplicate report. There is no view that finds two identical invoice numbers.
  • One number per document type and order. With partial invoices each carries the same number, because there is only one field. If you issue partial invoices, do not use this route for the invoice number.
  • Only the leading status assigns. If the installation runs two statuses of the same stage ("Invoice" and "Invoice issued"), only one carries the rule. Two rules with the same prefix would have two counters — and the same number would come out twice.
  • Skipped stages stay empty. If an order goes straight from "Order" to "Invoice", the delivery number stays empty. That is intended: a delivery note number for a delivery note that never existed would be a lie in the number sequence.

Field Management can bring the base template back

The four document fields no longer carry a default at base level — it would draw when the order is created, that is, before any document exists.

If such a field is edited and saved in Field Management, the sync with the V1 field configuration writes the default stored there back. The field then draws on creation again. You will notice because new orders immediately carry an invoice number. Workaround for now: clear the field's base default in Field Management again.


Checking that it works

docker exec calserver-api-v2 php artisan tinker
// Which status rules assign numbers?
DB::table('additional_field')
    ->where('type', 'status')
    ->whereNotNull('default_value')
    ->where('default_value', '!=', '')
    ->get(['object_uID', 'column_name', 'default_value']);

// Counter states of the booking statuses
DB::table('status')
    ->where('type', 'booking')
    ->orderBy('sort_order')
    ->get(['title', 'short_title', 'counter', 'counter_reset']);

An order pushed through the stages must end up carrying four different numbers — and must not get a new one when it passes the same stage a second time.