Skip to Content

Odoo interfaces in Germany: DATEV, shop, e-invoice

What the standard image brings along, what it does not, and where the seams tear
November 5, 2026 by
Odoo interfaces in Germany: DATEV, shop, e-invoice

A German request for an Odoo interface almost always names three counterparts: the tax advisor with DATEV, the shop on Shopware or JTL, and, since last year, the e-invoice. The technical part of connecting them is rarely the problem. The problem is that each of the three expects the data in a shape that depends on decisions Odoo cannot make for you, and that nobody says so at the start.

So here is what the standard image actually contains, what it does not, and which seams I have had to sew by hand.

What Odoo brings for Germany, counted rather than assumed

The running image of Odoo 19 Community carries 659 modules. Exactly one of them is German: l10n_de, the chart of accounts. Modules with DATEV in the name: none at all. The European one-stop-shop module l10n_eu_oss is there, and so is the format list for electronic invoices, which already knows XRechnung.

That single count decides the shape of a German project on Community. Accounting exports are your own work or somebody else's product; Enterprise ships one. The audit trail arrives with the German chart of accounts, while the hash on posted entries is a setting you have to find. The e-invoice is closer than most people expect. And whatever the shop needs is entirely up to the connector.

DATEV: Enterprise ships the export, Community does not, and the format is the smaller half

Odoo Enterprise has the module l10n_de_reports, which adds two exports to the general ledger: DATEV DATA, CSV files with customers, vendors and journal items that your advisor imports, and DATEV ATCH, the invoice files, which go through DATEV Belegtransfer. Community has neither, only the field l10n_de_datev_code on taxes, filled for 4 of the 49 taxes in the SKR03 template. There the question is who writes the export: a paid module, a partner, or me. Whichever edition you run, the hard part is not the CSV. DATEV wants postings, not documents: account and contra account, a tax key, the document number your advisor reconciles by, and a date that lands in the right period. Odoo happily gives you all four - once somebody has decided which Odoo account maps to which number in the advisor's chart, and what happens to the cases that have no number at all.

The cases without a number are where the month ends badly: a refund that crosses a period, a payment that arrives before the invoice, a marketplace payout that is one line for two hundred orders. I ask for last month's postings from the advisor before writing a single line of export code, because that file says more about the mapping than any meeting.

GoBD: immutability is a switch, and it has to be flicked early

Odoo has two mechanisms here, and they start differently. The audit trail is already on: the German chart of accounts sets restrictive_audit_trail when it is installed, and in Odoo 19 a German company cannot switch it off, so the log of journal items cannot be pruned. The hash is off by default. Journals carry restrict_mode_hash_table; switch it on and each posted entry gets an inalterable_hash chained to the previous one, so an altered row can be detected.

Two traps. The first: switching the hash on late does not leave old entries outside the chain for good. The next posting hashes back through its sequence to the last hashed entry, and the Secure Entries wizard seals entries on demand. But a hash only seals an entry as it stands that day and says nothing about what happened to it before, and a gap in the sequence stops the posting with an error. has_unhashed_entries on the journal tells you whether anything is still open. Switch it on at go-live, not when the audit is announced. The second is the interface itself: a connector that “corrects” a posted invoice because the shop changed the order breaks exactly the property the hash is there to prove. Corrections are new documents - a credit note, a reversal - and the connector has to be written that way from the first day.

Shop: Shopware or JTL, and the question is ownership

Both talk fluently enough - Shopware through its admin API, JTL through its own database and connector - and neither of those is the reason projects go sideways. The reason is that stock and price exist on both sides, and both sides believe they are right.

One table settles it before any code: every field that crosses the border, and next to it the single system that may change it. Stock is owned by Odoo, the shop is told. Prices usually belong to the shop, because that is where campaigns happen. Whatever field gets the answer “both” will produce a support ticket within the quarter.

Then the German detail that costs the most time: shops think in gross prices, Odoo thinks in net. Round nineteen percent off €49.99 and back again, a few thousand times, and the cent differences show up in the accounts as an endless list of tiny discrepancies nobody can explain. Decide once which side is authoritative for the number the customer sees, carry it as that, and let the other side derive.

E-invoice: XRechnung is already in the box

Since the beginning of 2025 German companies must be able to receive a structured invoice; the duty to send them arrives in stages. The good news is that Odoo 19 already offers the format: on the customer, the invoice format list includes “Germany (XRechnung)” next to Factur-X and Peppol BIS 3.0. Peppol access itself lives in separate modules.

For an interface this means one thing: the invoice that leaves your system is a document with fields, not a PDF with a picture of them. Everything the XML needs - the buyer reference, the tax breakdown, the unit of measure - has to be in the record before the export runs, which usually sends me back to the shop connector to collect fields nobody mapped.

How I start such a project

One call, and three questions that decide the estimate: which chart of accounts your advisor uses and whether the export has to match an existing posting practice; whether the hash mode is on and since when; which system owns stock, price and the customer record. The answers fit on one page, and that page is the interface specification.

If you already have a connector and it runs, the useful question is a different one: what does it do when it is run twice, and how do you find out that last night it did not run at all? That is the part I am usually called about.

Paddle and Odoo 19: what a subscription webhook has to survive