Skip to main content

The problem

An invoice number sequence must be gapless and non-duplicated to pass a fiscal audit. That is hard when:
  • The mobile app is in airplane mode or has a weak signal and still needs to issue a receipt.
  • The same firm has a web user who could emit an invoice on the same series between the time the phone reserves a number and the time it actually syncs.
Contazen solves both with a device-bound lock on the invoice series plus pre-allocated reservations carried on the device.

How it works

Lock a series

POST /invoice-series/{id}/lock binds the series to the calling mobile device. While locked:
  • Only that device can call POST /invoices with series_id equal to this series.
  • Only that device can request reservations.
  • Web / API-key callers that try to emit get series_locked_to_device (403).
Unlocking requires either the same device or force: true from an admin.

Pre-allocate numbers

The mobile app keeps a small pool (10 by default) of reservations in local storage. It tops the pool up whenever online and usage drops below 3:
The response carries {token, number} pairs. The app stores them in SQLite alongside each token’s expiry. When the user creates an invoice while offline, the app:
  1. Pops the next usable reservation and saves a draft with that number visible (e.g. “Factură #101”).
  2. Enqueues a POST /invoices mutation in its outbox with reservation_token and force_chronology: false.
  3. The outbox drains on the next network/foreground transition.

Emit an invoice from a draft

The server verifies the reservation: On success, the invoice is created with number_idx equal to the reservation’s pre-allocated slot. The reservation flips to consumed and records the bill_id.

Discarded drafts

If the user deletes an offline draft before it syncs, the app calls POST /invoice-series/reservations/{token}/release. The reservation flips to released — the number itself is still gone (documented gap), but the audit trail captures intent.

Gap handling

The series counter advances the moment a reservation is created, not when the invoice is actually emitted. That means expired / released reservations can become gaps in the sequence. Romanian fiscal regulations accept gaps when a firm can document why they occurred — keep the reservation TTL generous (default 30 days) and have the device refresh the pool often enough that expiry is rare in practice. If your jurisdiction does not accept gaps, disable the reservation feature entirely: leave series unlocked and require the mobile device to be online when emitting. The existing online-only path in POST /invoices still works without any changes.

When to use the lock

Error reference (invoice creation)