Shipmind Labs

An invoice carrying taxable lines under a regime where the seller charges no VAT is not a rounding problem. It is two parts of the same document disagreeing about who owes the tax.

We ran into this while building euinvoice, our open-source invoicing library. The seller's country, the buyer's status and the kind of supply already decide the VAT regime. The lines then carry their own VAT categories. Nothing stopped the two from contradicting each other, and the contradiction only shows up later, at the tax authority rather than at construction time.

So the regime check runs inside the invoice's own construction, not as a separate validation pass somebody has to remember to call.

The ordering matters more than the rule does. Currency gets checked first. Then the rule that reverse-charge lines cannot sit beside taxable ones. Then the regime check. Only after that do we look for required fields. Each step assumes the previous one held, so no step has to defend against a state an earlier step already rejected.

The check returns immediately when the seller sits outside the EU. A seller with no Union regime has nothing to measure the lines against, and inventing a default there would probably be worse than declining to answer.

Inside the Union, the decision function gets the seller, the buyer and the supply kind, and returns a regime that knows one thing about itself: whether it charges seller VAT. Only domestic supplies and private buyers do. Under every other regime, taxable lines are a contradiction, so we walk the lines, collect the taxable categories once each in the order they first appear, and raise.

The part worth copying is the error. It keeps the decision object and the offending categories, and writes the regime, the categories and the decision's own explanation into the message. The legal reference travels with the traceback. Whoever reads the stack trace gets the facts that produced the verdict, not just the verdict.

And the rule is narrow on purpose. Exempt and zero-rated lines are not taxable, so a domestic invoice can still carry them, provided the notes field says why. Zero is a rate. Exempt is a reason. Neither is the same as a regime that shifts the tax to the buyer.

The general shape: when one part of a document decides the legal frame and another part fills in the amounts, the frame has to be computed before the amounts are accepted, and the error has to carry enough context that nobody re-derives the reasoning from scratch.

For those of you modelling tax, compliance or contractual rules in code, the question sitting underneath all of this is whether you let the frame validate the contents at construction time, or keep validation as a separate pass you run before submission.

Was this useful?

Building something similar?

or email hello@shipmindlabs.com