DEV Community

Palks Studio
Palks Studio

Posted on

Building a Factur-X EN16931 engine from scratch — what actually took time

Factur-X EN16931 pipeline — structured invoicing engine

When I started working on B2B e-invoicing, I thought the technical part would be straightforward. Generate a PDF, embed an XML, follow a standard. On paper, it seemed linear.

In practice, it's something else entirely.

Here's what actually took time — not the concepts, but the details that break everything in production.

The Comfort profile is not declared — it is earned

EN16931 defines several profiles. The Comfort profile, the one that guarantees real interoperability with accounting systems, requires strict identifier completeness.

SIREN alone is not enough. SIRET alone is not enough. The combination of SIREN + SIRET + VAT number determines the profile declared in the XML. If one identifier is missing, the profile changes. And an incorrect profile means a technically invalid invoice — even if it looks perfectly fine visually.

I implemented conditional validation logic before generation. No silent correction, no approximation. Either the data is complete, or the invoice does not go out.

Multi-period invoicing is a state problem, not a calculation problem

Invoicing across multiple months in a single document seems simple. It is not.

The real issue: a period included in an issued invoice is permanently closed. It cannot be re-invoiced, partially modified, or cancelled. This is not a software constraint — it is a legal and accounting one.

I had to model an explicit state system per period and per client, using immutable archiving flags. No database — only locked, deterministic and traceable files.

Deduplication without a database

The classic question: how do you prevent issuing the same invoice twice if the process is restarted?

My answer: file-based locks. Each execution cycle checks for the existence of a lock file before generating anything. No race condition, no distributed transaction, no external dependency. One execution = one deterministic cycle. If the file exists, skip. Otherwise, generate and lock.

Simple, traceable, reliable.

The pipeline runs on cron — no background process, no service to keep alive

No persistent process running in the background, no service to maintain active, no SaaS dependency. The engine triggers, processes, archives and stops. Each execution is independent and reproducible.

This architectural choice has a direct consequence: if something goes wrong, there is nothing to restart, nothing to debug live. Logs are exhaustive, states are explicit, generated files are evidence.

What I take from this

Factur-X is not a PDF generation problem. It is a structured pipeline problem — controlled states and normalized data before production.
Tools that "generate Factur-X" are plentiful. Systems that guarantee end-to-end compliance, full traceability and zero implicit correction are far less common.

Top comments (0)