The Number That Cannot Go Backwards
A revenue authority identifies your invoice by a number you choose. Three kinds of document in one system were each choosing from their own sequence, which meant three documents claiming to be the same filing — and the fix is a sequence with a lock on it.
Most integration mistakes cost you a retry. A few cost you a conversation with a regulator. This one was in the second category and it had the shape those always have: entirely reasonable code, correct in isolation, wrong because of something true about the other end that nobody had written down.
The authority identifies a document by three things
Not by a reference it issues. By the taxpayer, the branch, and a number the submitter supplies. Those three together are the identity of a filing.
The first two are fixed for a given workspace. So within one taxpayer and one branch, the number you send is the whole of the identity — and it is yours to allocate, which means it is yours to get wrong.
Being allowed to choose an identifier is not a convenience. It is a responsibility that arrives without any warning that it has.
What went wrong, and why it was invisible
This system files more than one kind of document: sales invoices, point-of-sale receipts, and the credit notes that reverse them. Each is a different record with its own table and its own numbering.
Each conversion did the obvious thing and sent the record's own identifier as the filing number. Reasonable in isolation — it is unique within its own table, it is stable, and it is right there.
Three tables, three sequences, all filed under one taxpayer. Sales invoice number five and receipt number five are two different documents claiming the same identity with the authority.
And it is invisible from inside. Every record looks correct. Every submission is well formed. Both filings may even be accepted. The contradiction exists only in the authority's records, and it surfaces as a question about your books rather than as an error in your software.
One pool, with a lock on it
The fix is a single counter per workspace that every document type draws from, and the way it is drawn from matters as much as the fact of it.
Taking a number happens inside a transaction that locks the counter row. Two filings starting at the same instant queue rather than race, and each gets a number nobody else has.
The naive version — read the current value, add one, write it back — is fine in testing and wrong under concurrency, in exactly the way that matters here. Two processes read the same value, both use it, and two documents are filed under one number. That is the original defect, reintroduced by the fix for it.
It is worth noticing that a lock is acceptable here for a reason that is not always true: allocations are rare and brief. A counter taken once per document, held for microseconds, is not a contention problem. The same pattern on something taken thousands of times a second would be.
Allocated once, kept on the record
The second half of the design, and the one that makes retries safe.
A number is taken when a document is first filed and written onto that document. Asking again returns what is already there rather than taking another.
That matters because filings fail. A network problem, a validation refusal, a temporary outage at the authority — and the filing is retried.
| On a retry | Allocating each time | Allocating once |
|---|---|---|
| The number used | A new one, every attempt | The one it first claimed |
| After three failures | Three numbers consumed | One number, still held |
| If an earlier attempt did land | A second filing of the same sale | The same identity — a repeat, not a duplicate |
| The sequence | Gaps nobody can explain | Continuous |
The third row is the important one. An attempt that timed out may have been received. Retrying under the same number is a repeat of the same filing; retrying under a new one is a second, different document for the same sale.
Starting a counter safely
A detail that only exists because of the history, and is a good example of what a careful migration looks like.
A workspace that has been filing already has numbers with the authority. A new counter starting from one would hand out numbers that have already been used — and the authority has issued receipts against them.
So a counter starts above the highest number that workspace could possibly have filed, calculated from both what was actually filed and what could have been under the old scheme. It gives up a range of numbers that will never be used, and it guarantees no collision with anything already in the authority's records.
Wasting numbers is free. Reusing one is not. That asymmetry decides the design, and it is worth recognising the shape — a lot of migration decisions come down to finding which direction of error is cheap.
Reversal is a filing of its own
The last piece, and the one that changes how people think about crediting a customer.
A filed document cannot be withdrawn. What reverses it is a credit note, and a credit note is itself a document filed with the authority — with its own number from the same pool, and a reference to the number it reverses.
So a refund is not an edit. It is a second filing, permanently linked to the first, and both remain. Which is what a tax record should be: an append-only history in which nothing that happened stops having happened.
The connector refuses to raise one against a document that was never filed. There is nothing to reverse, the reference would point at nothing, and the refusal names that rather than producing a credit note that hangs in the air.
One place where the reversal is less detailed
Worth stating plainly, because these posts are written off the code and the caveat is written in the code.
Reversing a sale reverses its lines, because a sale has lines. A credit note raised on its own is stored as a value rather than as a basket of items, so it files as a single line describing itself rather than as an itemised reversal.
The total is right and the link to the original is right. What it does not carry is a line-by-line account of what was credited. Whether that matters depends on your circumstances, and it is the kind of thing worth knowing in advance rather than at an audit — which is why it is here rather than in a comment nobody outside the codebase reads.
What is in place, layer by layer
Filing identity, end to end
One number pool per workspace
Every document type draws from a single sequence, because the authority identifies a filing by the taxpayer, the branch and that number together.
Allocation under a lock
Numbers are taken inside a transaction that locks the counter, so two simultaneous filings queue rather than both taking the same value.
Allocated once, kept on the record
A retry files under the number it first claimed, so a failed attempt that was actually received is a repeat rather than a second document.
A counter seeded above history
A workspace already filing starts above anything it could have used before, so no number is ever reused against a receipt already issued.
Reversal as its own filing
A credit note carries its own number from the same pool and a reference to the number it reverses, so nothing is edited and nothing disappears.
No reversal of an unfiled document
Refused with a reason, because a credit note referencing a filing that never happened points at nothing.
Reversals sent to the same endpoint
A credit note is a sales document with a reversal marker rather than a separate kind of call, which is how the authority models it.
Itemised where the source is itemised
Reversing a sale reverses its lines. A standalone credit note is stored as a value and files as one line describing itself.
The number pool exists because three document types were each filing under their own primary key — three sequences under one taxpayer, and two different documents claiming one identity.
Three positions held on purpose
- A number is allocated once and held, rather than taken fresh on each attempt. An attempt that timed out may have been received, and retrying under a new number turns one uncertain filing into two certain ones.
- The counter is seeded above history rather than from one. Skipping a range of numbers costs nothing; reusing a number against a receipt the authority has already issued is a conversation with a regulator.
- A reversal is a new filing rather than an edit. A tax record is append-only, and a system that could quietly amend a submitted document would be the wrong shape for the obligation it is serving.
Five questions about identifiers you supply yourself
Where does the filing number come from?
A good answer sounds like
One pool, named.
What ours actually is
A single sequence per workspace that every document type draws from.
What stops two filings taking the same number?
A good answer sounds like
A lock.
What ours actually is
Allocation happens inside a transaction that locks the counter row.
What happens on a retry?
A good answer sounds like
The same number.
What ours actually is
It is kept on the record, so a repeat is a repeat rather than a second document.
How did you start counting for existing workspaces?
A good answer sounds like
Above what they used.
What ours actually is
Seeded past anything that could already have been filed, so no number is reused.
How is a filed invoice corrected?
A good answer sounds like
A credit note.
What ours actually is
Its own filing with its own number, referencing the original. Nothing is edited or withdrawn.
Our take
When an external system lets you choose an identifier, it has handed you a responsibility without mentioning it, and the natural thing to reach for — the record's own primary key — is right until there is a second kind of record. Nothing warns you. Every filing is well formed, every record is correct, and the contradiction lives entirely in somebody else's database where you will not see it until they ask. The habit worth forming is to notice the moment you are choosing an identifier for an external system, and to ask immediately what else, now or later, will be choosing from the same space.
One taxpayer, one sequence
If you file more than one kind of document under one tax identity, the numbering is not an implementation detail — it is the identity the authority holds you to.
Talk through eTIMS filingFrequently asked questions
Why do our filing numbers not match our invoice numbers?
Because they are a different sequence. The authority identifies a filing by the taxpayer, branch and a number the submitter supplies, and every document type filed under one tax identity has to draw from a single pool. Your own invoice numbering is untouched and appears on the invoice as it always did.
What happens if a filing fails and is retried?
It goes back under the number it first claimed, because the number is allocated once and kept on the record. That matters because an attempt that timed out may actually have been received — retrying under the same number is a repeat of one filing, while a new number would be a second document for the same sale.
Can a filed invoice be corrected?
Not by editing it. A credit note is raised, which is itself a filing with its own number and a reference to the one it reverses. Both remain in the record, which is what a tax history should be — nothing that happened stops having happened.
Why can we not reverse an invoice that was never filed?
Because the credit note would reference a filing that does not exist. The connector refuses with a reason rather than producing a document that points at nothing. If the original was never filed, there is nothing at the authority to correct.
Are credit notes itemised?
When a sale is reversed, its lines are reversed, because the sale has lines. A credit note raised on its own is stored as a value rather than a basket, so it files as a single line describing itself. The total and the link to the original are correct; the line-by-line detail is not carried in that case.