A build-to-order Odoo module that turns a posted customer invoice into a validated OIOUBL document and delivers it over Nemhandel/Peppol, with rejection handling, a correction workflow and a compliant archive. ECOSIRE builds, installs and supports it for your Odoo 17.0, 18.0 or 19.0 database. Built to order by ECOSIRE for Odoo 17, 18, 19 — indicative price from $499.00 USD; request a quote for a scoped proposal.
Illustrative previewA build-to-order Odoo module that turns a posted customer invoice into a validated OIOUBL document and delivers
it over Nemhandel/Peppol, with rejection handling, a correction workflow and a compliant archive. ECOSIRE builds, installs and supports it for your Odoo 17.0, 18.0 or 19.0 database.
No DIY setup — a working app, built, installed and supported by ECOSIRE.
Start with a one-time build price. We scope it with you at kickoff.
ECOSIRE builds, configures and installs it on your Odoo.
You go live in about 2–4 weeks, with a post-launch support window.
Selling to Danish public bodies means the invoice has to arrive as a structured OIOUBL document through Nemhandel, not as a PDF attached to an email. Most finance teams bridge that gap by hand: export the invoice, log into a portal or a third-party web client, retype the EAN/GLN receiver number, the requisitioner and order reference, upload, then hope. When something is rejected days later the accountant is reconciling an ERP that says posted against a network that says nothing arrived. Odoo out of the box gives you a UBL-shaped account.edi layer and a generic EN 16931 export, but it does not implement the Danish OIOUBL profile's identifiers and business rules, does not talk to a Nemhandel access point, and gives you nowhere to record a rejection against the invoice it belongs to.
We build a Danish localisation layer that sits on top of account.move rather than beside it. A models/ extension adds the fields the profile actually needs — receiver EAN/GLN endpoint and scheme, ContractDocumentReference, requisitioner and order reference, AccountingCost — with @api.depends computes that derive whatever can be derived from master data on res.partner and res.company, so users type as little as possible. Tax mapping is data, not code: your Danish VAT codes, reverse-charge and zero-rated cases map to the correct TaxCategory/TaxScheme identifiers through configurable records, so a new tax next year is a configuration change rather than a patch. On post, the document is generated once and rendered two ways — the machine-readable OIOUBL XML and a QWeb report humans and auditors read — from the same posting, so the two can never disagree.
Delivery runs as a queued send. The XML is schema- and Schematron-validated before it ever leaves the database, then handed to your chosen Nemhandel/Peppol access point over its API, with the transmission id, timestamp and payload hash written back onto the invoice. Failures are first-class: a rejection or negative message-level response lands on the invoice chatter with its reason codes parsed into readable lines, moves the record into a rejected state, and offers a guarded correction workflow producing a credit note plus a corrected invoice carrying the correct BillingReference back to the original — so the correction is legally linked, not an unrelated new document. An automated action retries transient transport errors on a backoff and escalates the rest as an activity on the responsible accountant. Access is scoped through ir.model.access.csv plus record rules so an invoicing clerk can send and read status while only a finance manager can force-resend or cancel, and every generated payload is retained as an immutable attachment with its hash — the archive and audit trail your retention rules expect. The same operations are reachable over XML-RPC/JSON-RPC, so an external portal or logistics system can trigger a send or read delivery status without a human opening Odoo.
The module runs on Community and Enterprise alike. Where Enterprise is present we hook into its accounting EDI screens instead of duplicating them; on Community we ship our own send and status views so nothing is Enterprise-gated. It is built for the exact series you run — a __manifest__.py targeting 17.0, 18.0 or 19.0 — against your real chart of accounts, tax configuration and partner data, not a generic demo database.
This is build-to-order, so there is nothing to download today. You request a quotation, we run a short scoping call to confirm your Odoo version and edition, hosting, access-point provider and which document flows are in scope, and we quote a fixed price against that written scope. Typical delivery is 2–4 weeks from confirmed scope. You get the full source installed and tested on a staging copy of your database with your own invoices, a UAT round with your finance team, then a supervised go-live with a rollback plan, a post-go-live support window, and the git repository handed over to you.
Answerable for whether public-sector invoices actually arrive and get paid. Needs one place in Odoo showing sent, acknowledged and rejected, and needs a rejection to become someone's task instead of an email nobody opens.
Runs one Odoo across several legal entities, only some of which invoice in Denmark. Needs the Danish rules applied per company with record-rule isolation, without changing how the other entities post.
Wants the localisation as clean, versioned, testable source in their own git repository — not a portal subscription or an untraceable in-database customisation — and wants it to survive the next Odoo upgrade.
Needs to demonstrate that the transmitted document, the printed document and the ledger entry come from the same posting, and to produce archived payloads with timestamps and hashes on request during an audit.
| Criterion | ECOSIRE | Custom Build | Competitor | Odoo Native |
|---|---|---|---|---|
| OIOUBL profile coverage | Built to the Danish profile your receivers actually require, and validated against it before sending | Whatever your developer had time to interpret from the specification | Usually generic Peppol/EN 16931 with partial or no Danish specifics | Generic UBL export; no OIOUBL profile logic |
| Nemhandel delivery | Integrated with your chosen access point's API through a swappable adapter | One hardcoded integration to whichever provider was in place at the time | Often locked to the vendor's own gateway or reseller agreement | No access-point integration — export the file and send it yourself |
| Rejection handling | Responses parsed onto the invoice, state change, activity assigned, guarded correction workflow with BillingReference | Commonly deferred to phase two and never built | Frequently a status field only, with no correction workflow | Nothing — rejections live in the portal or the provider's inbox |
| Tax mapping maintenance | Configurable mapping records, so a new Danish tax is a configuration change | Usually hardcoded in Python; every change is a developer ticket | Fixed mapping table you cannot extend without forking | No OIOUBL tax category mapping at all |
| Archiving and audit trail | Immutable payload attachments with hashes, timestamps and full status history kept for the retention period | Depends entirely on whether it was specified up front | Typically last payload only, overwritten on resend | Attachments only if someone prints and attaches them by hand |
| Source code ownership | Full git repository handed over, with commit history and tests | You own it, but usually with no tests and one person who understands it | Licence-gated or obfuscated; no repository, no right to modify | Core code you should not fork |
| Fit to your database | Built and UAT-tested against a staging copy of your real chart of accounts, taxes and partners | As good as the internal specification you managed to write | Fits a generic demo database; you adapt your process to it | Generic by design |
| Cost and timeline shape | Fixed quote against written scope, typically 2–4 weeks from confirmation | Open-ended day rate; the long tail is the rejection and correction work | Low upfront licence, cost reappears as customisation and gateway fees | Free, but the compliance gap stays with your team |
This is a build-to-order module, so there is nothing to download today. Typical delivery is 2–4 weeks from confirmed scope — that is, from the point where we have agreed in writing your Odoo version and edition, hosting, access-point provider and the document flows in scope. Complex tax mappings, unusual receiver profiles or extra document types can extend that, and we tell you before you commit rather than afterwards.
Delivery includes a post-go-live support window during which defects in the delivered scope are fixed at no extra cost. After that, support and version maintenance — moving the module to a newer Odoo series, or adapting it when the OIOUBL profile or your access point's API changes — is available as an ongoing agreement or per request. Because you receive the git repository, you are never locked in: your own team or another partner can maintain the code.
Yes. Nemhandel and Peppol delivery is performed through a certified access point, and the module integrates with the provider you choose or already use via their API. We are not an access point and do not resell one. If you have not selected a provider we will discuss the options during scoping, and the integration layer is written as an adapter so a later provider change is configuration and a new adapter rather than a rewrite.
Both. Odoo Enterprise ships accounting EDI screens we hook into rather than duplicate; on Community we ship our own send and status views, so no functionality is Enterprise-gated. The module is built and tested against the exact series you run — 17.0, 18.0 or 19.0.
Odoo core can produce a generic EN 16931 UBL file and, in some versions, send it through its own e-invoicing service. What it does not do is implement the Danish OIOUBL profile's specific identifiers and business rules, validate against them before sending, integrate with an arbitrary Nemhandel access point, or give you a place to record and act on a rejection. Those gaps are exactly what this module fills.
The rejection or negative message-level response is ingested, its reason codes are parsed into readable lines on the invoice chatter, the invoice moves into a rejected state, and an activity is assigned to the responsible accountant. From there the correction workflow produces a credit note and a corrected invoice carrying the correct BillingReference to the original, so the correction is legally linked rather than an unrelated new document.
The module is written as a proper addon — extending `account.move` and `res.partner` through the ORM, with mappings held in configurable records instead of hardcoded — which makes an upgrade a manageable port rather than a rebuild. Odoo's accounting internals do change between major series, so a move from 18.0 to 19.0 is a scoped upgrade engagement: we quote it, run it on staging first, and hand back the updated repository.

A build-to-order Odoo localization that loads Algeria's full wilaya and commune hierarchy with bilingual French and Arabic names, wired into partner addresses and reporting. ECOSIRE builds, installs, and supports it after your quotation.



A build-to-order Odoo module that lets you render any accounting report — P&L, Balance Sheet, aged reports, journals, trial balance — in a currency of your choice, with automatic conversion at the rate you define. ECOSIRE scopes, builds, installs and supports it for your Odoo 17/18/19 environment.
A build-to-order Odoo module that turns a posted customer invoice into a validated OIOUBL document and delivers it over Nemhandel/Peppol, with rejection handling, a correction workflow and a compliant archive. ECOSIRE builds, installs and supports it for your Odoo 17.0, 18.0 or 19.0 database.