The tax officer asked why invoice 41 was missing
In several countries an invoice series must be unbroken. A plain database counter leaves holes the moment a draft is abandoned. Here is what we built instead.
In several countries an invoice series has to be unbroken. Not mostly in order, and not sorted by date: unbroken, so a tax officer looking at your books can see that nothing was removed. If invoice forty-one does not exist, you are the one explaining yourself.
The obvious way to number documents is to use the counter a database provides, which is fast and safe under heavy use, and that counter does not go backwards. If somebody starts an invoice, takes number forty-one, and the save is cancelled or fails, number forty-one is gone for good.
The second obvious way is to look up the highest number and add one. That works until two people issue an invoice in the same second and both get the same number.
What was actually going on
There were two separate problems, and the second is the one people get wrong.
The first is the counter itself. The database’s built-in counter moves forward outside the save that used it. That is a deliberate feature, and it is exactly what rules it out for anything a tax authority counts.
The second is timing. An invoice starts as a draft. It is edited, corrected, and sometimes abandoned, and drafts are abandoned constantly, because that is what a draft is for. If a number is given out when the draft is created, every abandoned draft leaves a permanent hole in the series.
What we changed
Each customer of the system, for each kind of document and each financial year, has its own counter. It is increased in the same step as the save that uses it, so if the save is cancelled, the increase is cancelled with it and the number is available again. One customer’s activity never moves another’s series, and orders, dispatches, invoices, credit notes and receipts each count on their own.
A draft invoice has no number at all. The number is given at the moment of issue, together with the tax lines and the issue date. From then on the invoice is frozen: trying to edit it, or issue it again, is refused. A number on a record therefore means the document exists in the world, and no number means it does not yet. There is no half-issued state, and no status to cross-check against the number.
The rule is not always to number late. It is to number at the first moment the record is real. A dispatch is numbered when it is created, because it exists only because something is being shipped. A receipt is numbered when a payment is recorded. An order is numbered at creation.
The uniqueness rule ignores drafts, which have no number, and ignores deleted records. That last part needs care, because a deleted issued invoice would free its number for reuse, which is worse than a gap. The answer is that issued invoices are never deleted. They are voided, which is a change of status, so the number stays taken. That is a promise from one layer of the system to another, and it is written down where someone inspecting the table will see it.
The financial year is part of the number, and financial years do not start in January everywhere, so the start month is a setting per customer. The counter and the year-end reports both read that same setting. If they did not, an invoice numbered for one year would appear in the totals for another.
What it did not fix
Everything issued within one series queues up behind one counter. Two people issuing at the same moment are served one after the other. That is the price of the guarantee: a system with no queue has quietly given up being gapless.
We have not needed it to work any faster than that. If a customer ever issues invoices faster than this allows, the honest answer is a conversation about whether they actually need unbroken numbering, not a cleverer counter.
The pattern, for anyone who issues invoices
Ask your software three questions. Does a draft invoice already carry a number? If yes, every abandoned draft is a hole. What happens to a number when a save fails? And can an issued invoice be deleted, or only voided?
Then look at your own books. If a gap exists, the useful thing to know is when the number was given out, not which invoice it was meant for.
Where this ends up
That counter, and the rule that a number is given out only at issue, is where document numbering lives in Sazinga Field, which runs orders, dispatch and invoicing in one system.