[19.0] Migrate account_invoice_en16931 and the einvoicing stack to 19.0 - #34
Draft
njeudy wants to merge 44 commits into
Draft
[19.0] Migrate account_invoice_en16931 and the einvoicing stack to 19.0#34njeudy wants to merge 44 commits into
njeudy wants to merge 44 commits into
Conversation
Fix crash when no last sync date on partner Fix criteria to create flow on customer invoices
Add support for invoice attachment generation in Factur-X as attachement to PDF. Simplify proto of method generate_en16931_xml() Change 'variant' by 'invoice_format' to unifiy vocabulary [FIX] account_invoice_en16931_py3o: add support for context key- 'regular_pdf_invoice' [IMP] l10n_fr_einvoicing: add checks on attachments for Chorus Pro Re-organize invoice form view: all fields are now in the dedicated tab- "e-Invoicing"
Contributor
Author
Move France-specific code for BT-23 from account_invoice_en16931 to l10n_fr_account_invoice_en16931
…ction_confirm When sale_subscription is installed, action_confirm() splits self into recurring/non-recurring subsets and calls super().action_confirm() on each, including empty ones. self.ensure_one() at the top of _action_confirm() then raises on the empty subset, blocking confirmation of any non-subscription order. Moving ensure_one() inside the loop lets empty recordsets pass through as a no-op, matching how the rest of the method already handles batches. Fixes akretion#40
Bump manifest to 19.0.1.0.0. Core l10n_fr renamed the partner-level flag from res.partner.is_france_country to res.partner.l10n_fr_is_french in 19.0 (res.company.is_france_country is unchanged). Adapt the buyer-side check accordingly.
…ework report_py3o removed the py3o.report model in 19.0 and moved rendering into ir.actions.report._render_py3o. The _postprocess_report hook this glue overrides no longer exists. Bump the manifest to 19.0.1.0.0 but keep the module uninstallable until the hook is reimplemented against the new flow.
Bump manifest to 19.0.1.2.0 and adapt to the 19.0 breaking changes: - _sql_constraints is no longer supported by the ORM (it only logs a warning and the constraints are never created): declare models.Constraint instead. - Core l10n_fr renamed the partner-level flag is_france_country to l10n_fr_is_french (res.company.is_france_country is unchanged). - l10n_fr_siret dropped the stored siren/siret/nic fields in favor of the core company_registry field plus the _get_siren()/_get_siret()/_get_nic() getters. Adapt the compute dependencies, the partner search domains and the demo data accordingly. Searching a partner by SIREN now uses the upstream pattern company_registry =like '<siren>%'. - l10n_fr_siret_account no longer exists in 19.0 (it only added tracking on the dropped siren/nic fields): depend on l10n_fr_siret directly. - Demo partner Burger Queen had a VAT number inconsistent with its SIREN, which 19.0 now rejects in _check_siret: align the VAT on the SIREN.
Bump the five layered modules to 19.0.1.0.0: dashboard_banner, import, payment_batch_oca, purchase and sale. - l10n_fr_einvoicing_sale: partner-level is_france_country was renamed to l10n_fr_is_french in core l10n_fr 19.0. - res.company._sql_constraints is no longer supported by the ORM: declare a models.Constraint instead. All overridden core methods (_prepare_invoice, _get_invoice_grouping_keys, _action_confirm, generated2uploaded, _prepare_create_invoice_vals) keep the same signature in 19.0.
19.0 merged the base partner form's warning_company banner into warning_tax: anchor the directory warnings on the remaining one.
19.0 replaced the account.journal form's group_alias_ro/group_alias_ro_general groups by a single group_email_alias in the Advanced Settings page.
sale_commercial_partner dropped its sale.order form view in 19.0 and only keeps the search filter, so inherit sale.view_order_form directly. The 'done' state of sale.order no longer exists either: the order is locked instead.
…_invoice_id sale_commercial_partner no longer provides commercial_partner_invoice_id in 19.0. Declare the related invoicing entity in the module's own namespace and expose it in the form view, so the directory line domain keeps working.
…egistry _fr_directory_check_siren_siret_vat() was written against the 18.0 layout where l10n_fr_siret provided three fields: siren and nic (stored) plus a siret computed from them. In 19.0 these fields are gone: the value lives in the native company_registry field of res.partner (9 digits for a SIREN, 14 for a SIRET) and l10n_fr_siret only exposes the _get_siren()/_get_siret()/ _get_nic() getters, which the rest of the module already uses. Rewrite the cleanup around that single field. The siren/nic cross-checks collapse into a length + checksum check, split out into _fr_check_company_registry() to keep the method readable, and an invalid SIRET now falls back to its SIREN instead of the '<siren>*****' placeholder that 18.0 used to render. Fix the search domain of the settings button accordingly and rewrite the tests on company_registry.
The 19.0 branch was opened with every manifest set to installable: False. The port is complete for the seven deliverable modules, so mark them installable. Two stay out: account_invoice_en16931_py3o (report_py3o dropped the py3o.report model in 19.0) and l10n_fr_einvoicing_dashboard_banner (its account_dashboard_banner dependency has no 19.0 host repository yet).
… 19.0 Odoo 19 dropped the mobile field from res.partner and kept phone alone (odoo/addons/base/models/res_partner.py). Both remaining readers of it raised an AttributeError as soon as an EN16931 document was generated, which is why the install-only recipe never caught them: _en16931_partner_data() feeding the seller and buyer blocks, and BT-42 for the sales contact.
…hook Importing facturx died on "Can't open orphan path" and, since the failure was only logged at debug level, the module loaded fine and blew up much later with a NameError on generate_xml that pointed nowhere. The cause is in Odoo itself: odoo/_monkeypatches registers an import hook on stdnum and replaces the package loader with a SimpleNamespace carrying only create_module and exec_module. That object has no get_resource_reader, so importlib.resources can no longer open the .dat files stdnum.iban and its siblings read at import time, and every library importing them breaks. The patch this hook installs is a documented no-op from python-stdnum 2.0 on, so dropping the hook and importing again is safe. Still present on odoo master. Also raise the final failure to warning: a silent debug on this import costs hours to trace back from the NameError it produces.
…ines The line VAT rate was only emitted for categories S, K and G, so an exempt line (E), a zero-rated one (Z) or a reverse charge (AE) went out without BT-152 at all. BR-E-05 and its siblings require the rate to be there and to be 0; only category O, out of scope, may omit it (BR-O-05). The knock-on effect is worse than the missing element: with no rate on the line, the schematron matches no line against the E VAT breakdown, so BR-FXEXT-E-08 (and BR-FREXT-E-08 on UBL) compares a 300 taxable amount against a sum of zero lines and fails too. An invoice carrying a single exempt line was rejected by the base schematron in both CII and UBL. Present in 18.0 as well: not a migration regression.
njeudy
force-pushed
the
MIG-19.0-account_invoice_en16931
branch
from
August 7, 2026 09:05
3f46186 to
8dfdd03
Compare
decimal.precision is database-global, so this check forces every company of a multi-company database down to 2 decimals on discounts, and it does not protect the generated document: the discount percentage is never exported, and disc_prec has a single consumer, the float_compare deciding whether a line carries a discount. At 2 decimals a 0.001 discount compares equal to zero and BT-147 is silently omitted, so lowering the precision produces a less faithful document, not a more compliant one. Reported by @almumu in akretion#33. Same change proposed upstream on 18.0 for akretion#43; to be dropped from this branch if it is merged there.
…ng companies _post() runs the company-level configuration check for every sale document, with no condition, so every company of the database has to be UNECE-coded before it can post a customer invoice -- including companies that will never emit an EN16931 document. Measured on a clean 19.0 database with the whole stack installed: the unconditional check raises "The following errors have been detected in company ... that block EN16931 e-invoicing" in **390 standard Odoo tests** (account 287, sale 45, account_edi_ubl_cii 18, account_payment 8), which all create their own test company and post invoices on it. With this commit, that count drops to zero. The document itself is not at risk: the same checks run from _en16931_checks_upon_invoice_generation(), so an incomplete configuration still cannot produce an EN16931 document. Add en16931_issuer on res.company, unset by default, gate the _post() check on it, and keep the configuration verifiable on demand from the accounting settings. Reported by @almumu in akretion#33. Same change proposed upstream on 18.0 for akretion#42; to be dropped from this branch if it is merged there.
(cherry picked from commit 7ff630e)
(cherry picked from commit 449ba64)
(cherry picked from commit 6a3767a)
(cherry picked from commit e118dd1)
(cherry picked from commit 8855c91)
…rocess_type (cherry picked from commit 95d1fab)
…server is down Fixes bug akretion#44 the schematron validation is silently skipped when no Saxon server is reachable Update and improve fr translation (cherry picked from commit f8a72c6)
…ve (new method dedicated to the check on the ivoice lines) Inspired by bug akretion#38 (cherry picked from commit 1757cae)
Improve display_name of flows: add syntax (cherry picked from commit b7df7da)
…rectory status FIX l10n_fr_einvoicing_sale: activate checks only for VAT registered companies Mutualize code between l10n_fr_einvoicing_sale and l10n_fr_einvoicing: new method _fr_directory_should_sync_upon_confirmation() on res.partner Block back to draft when the invoice is not linked to a flow but would have to be sent to AP after re-confirmation (cherry picked from commit fcf1cad)
… line Fixes issue akretion#38 (cherry picked from commit 5fc122f)
…rmation for unittests (cherry picked from commit 7399781)
…ny directory lines This new feature require PR 1386 from OCA/edi Remove field company_partner_id from account.journal because it is a native field! (cherry picked from commit aba1448)
(cherry picked from commit 9f97c4e)
The module was missing from the 19.0 branch: it lives on 18.0 and on the 16.0 backport, but was never forward-ported. Almost nothing had to change. The two 19.0 traps this module could have hit do not apply: it never touches res.partner.siren/siret/nic -- dropped by l10n_fr_siret 19.0 -- because it goes through the _get_siren() getter and builds its matching index by iterating over partners rather than searching that field; and it declares no _sql_constraints, which 19.0 would have ignored silently. tools.create_index, used to index account_move.commercial_partner_id, is unchanged in 19.0. Ported from the 18.0 branch, whose view syntax is already the 19.0 one, plus the tests carried on the 16.0 branch (they are not on 18.0).
…_registry l10n_fr_siret 19.0 no longer exposes siren/nic on res.partner -- the value lives in the native company_registry field. The six partners created by the test suite still used the 16.0 fields, so both setUpClass raised "ValueError: Invalid field 'siren' in 'res.partner'" and the whole suite errored out. The module code itself needed nothing: it reads SIREN through the _get_siren() getter. Only the fixtures did. Note that fr.directory.line keeps its own siren field, which is unrelated and left as is.
…rtners On 19.0, _get_siren() goes through _common_get_siren_siret(), which returns None when the partner has no country_id or a country that is not France. The 16.0 version read the siren field directly, so the fixtures never needed a country and the whole export/import suite found no SIREN at all.
…e instance 19.0 installs its own guard by patching commit/rollback/close on the cursor *instance* (odoo/tests/common.py), and an instance attribute shadows the class one, so patching type(self.env.cr) no longer has any effect: the nine import tests all died on the AssertionError raised by that guard. Patch the instance instead. The intent is unchanged -- the batched commits of _directory_import_csv() must not release the savepoint TransactionCase rolls back to.
factur-x writes the pdfaid:part=3 XMP but converts nothing: the PDF still comes from wkhtmltopdf, without sRGB OutputIntent, with glyph widths inconsistent with the embedded fonts and a pre-1.7 header. veraPDF therefore rejects a file that claims PDF/A-3 — measured on 19.0: FAIL 3b. Run the already-Factur-X PDF through OdooPdfFileWriter.convert_to_pdfa(), which account_edi_ubl_cii uses for the same purpose; cloneReaderDocumentRoot keeps the embedded XML and the XMP. Same treatment for the UBL-in-PDF flavour. fonttools is declared: convert_to_pdfa() needs it to rebuild the width arrays, and only logs a warning when it is missing.
alexis-via
force-pushed
the
19.0
branch
2 times, most recently
from
August 18, 2026 21:44
fd33528 to
ceb2f36
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Migration of the
fr-einvoicingstack to 19.0, rebased onupstream/19.0.Ports the modules to 19.0 API changes (
res.users.groups_id→group_ids, view adaptations, droppedcommercial_partner_invoice_id, journal/sale views…) and adapts the SIREN/SIRET data cleanup to the newl10n_fr_siretlayout.SIREN/SIRET on
company_registryl10n_fr_siret19.0 no longer provides thesiren/nic/siretfields: the value lives in the nativecompany_registryfield (9 digits for a SIREN, 14 for a SIRET) and the module only exposes the_get_siren()/_get_siret()/_get_nic()getters. The stack already went through those getters everywhere, so the only caller left to port was_fr_directory_check_siren_siret_vat(), added in 3e3f1ba/5b0f0ff and written against the 18.0 fields.It is rewritten around the single field: the SIREN+NIC vs SIRET arbitration collapses into a length + checksum check (extracted into
_fr_check_company_registry()to keep the method readable). One deliberate behaviour change: when the SIRET checksum is wrong but its SIREN is valid, 18.0 left the computed field rendering792377731*****; 19.0 has no such intermediate representation, so it falls back to the 9-digit SIREN. The search domain offr_ctc_check_siren_siret_vat_buttonis fixed accordingly (it still queried the two removed fields), and the tests are rewritten oncompany_registry.Modules — migration status
account_invoice_en16931— no code change needed, the 18.0 tax base-lines computation API is unchanged in 19.0l10n_fr_account_invoice_en16931l10n_fr_einvoicingl10n_fr_einvoicing_importl10n_fr_einvoicing_purchasel10n_fr_einvoicing_salel10n_fr_einvoicing_payment_batch_ocal10n_fr_einvoicing_dashboard_banner— blocked:account_dashboard_bannerhas no 19.0 host repository yet. The port is a two-hunk patch (theaccount.DashboardKanbanRendererOWL renderer is identical in 18.0 and 19.0), but it needs a home first. Leftinstallable: False, as you did in 74f3e9b.account_invoice_en16931_py3o— out of scope,report_py3odropped thepy3o.reportmodel in 19.0The seven ported modules are back to
installable: True.Runtime fixes found while generating a real invoice
Installation alone did not exercise the generation path. Producing an actual EN16931 document surfaced three bugs:
res.partner.mobileno longer exists in 19.0 (543636a). Two readers were left:_en16931_partner_data()— hence the seller and buyer blocks of every document — and BT-42. Invisible at install time.stdnum(4a1a117).odoo/_monkeypatches/__init__.pyinstalls aPatchImportHookwhose replacement loader has noget_resource_reader, soimportlib.resources.files('stdnum')raisesFileNotFoundErrorand any stdnum submodule reading a.datat import time (iban,cfi,gs1_ai…) becomes unimportable inside the Odoo process —facturx.generate_xmlamong them. Ironically the patch itself is a no-op withpython-stdnum≥ 2.0, but it breaks the loader anyway. Still unfixed upstream onmaster; the workaround lives in the module for now and would be better placed elsewhere.Also included:
self._context→self.env.context(8dfdd03), deprecated in 19.0.Rebase on
upstream/19.0The branch is rebased on the seven commits carried onto
19.0(send/receive configuration,invoice_attachment_ids,business_process_type, the Chorus old-syntax option, two fixes). The local fix for #36 is dropped: upstream's[FIX] don't store fr_einvoicing_requiredremoves the mass recompute at the root.Two fixes carried temporarily
#46 (#43, Discount precision) and #47 (#42, EN16931 config scope) are applied here and proposed upstream on 18.0 where they belong. They will be dropped from this branch once merged there, or adjusted if a different shape is preferred.
The second one matters for this branch specifically. Running the full suite on a clean 19.0 database, the unconditional
_en16931_checks()in_post()raises... that block EN16931 e-invoicingin 390 standard Odoo tests (account 287, sale 45, account_edi_ubl_cii 18, account_payment 8) — every test that creates its own company and posts an invoice on it. With the fix, the run goes from41 failed, 418 errors of 3902 teststo39 failed, 51 errors of 3976 tests. Same code on 18.0.Testing
Installed and upgraded on a fresh 19.0 database (Odoo 19.0-20260723, no demo data), cascade install of the full dependency chain: 81 modules loaded, no warning.
Unit tests:
0 failed, 0 error(s) of 15 testsonl10n_fr_einvoicing, the only module of the stack carrying tests. This includes the two edge cases only reachable through raw SQL — the SUPERPDP sandbox partner and an invalid VAT containing a valid SIREN — sincebase_vatrefuses to write them through the ORM.fr_ctc_check_siren_siret_vat_buttonwas additionally exercised over nine deliberately corrupted registries — valid SIRET, wrong SIRET checksum, wrong SIREN checksum, non-digit registry, truncated registry, spaces only, inconsistent FR VAT, non-FR VAT, spaced valid VAT — all nine behave as expected.End-to-end generation validated, on an invoice mixing a 20 % line (category S) and an exempt line (category E,
VATEX-FR-FRANCHISE), with a Saxon server actually answering:XSD plus the
baseandfr-ctcschematrons, both formats. Worth noting for anyone reproducing this: without theen16931.saxon_server_urlparameter, the schematrons are silently skipped and the log still says "validated".Test coverage remains partial: only VAT categories S and E were exercised end to end; Z, AE, K, G, refunds and down payments are not.
Two pre-existing failures surfaced by the full test run
Neither is caused by the migration, but both are worth knowing:
account_invoice_en16931makescountry_idrequired in the partner view (en16931.country.required.on.partner), which breaks three standardbasetests:test_create_res_partner,test_lang_computation_form_viewandtest_onchange_parent_sync_user, all of which fill a partner form without a country. The view is identical in 18.0, so 18.0 fails the same way — it looks like the suite had simply never been run against it. Reporting it rather than changing it: the trade-off is deliberate and documented in the view's own comment.account_invoice_import.tests.test_invoice_importfails insetUpClasson 19.0:NotNullViolationonaccount_tax.tax_group_id, anat_installtest running against a partial registry. That is a third-party dependency, out of scope here.Provisioning notes
Two transitive dependencies are invisible in the manifests and only surface at install time:
account_payment_base_ocaandpdf_xml_attachment(OCA/reporting-engine).account_payment_batch_ocalives inOCA/bank-payment-alternative, notbank-payment.account_invoice_importonly exists as an open PR, OCA/edi#1363, consumed as-is for the test run.account_tax_unece19.0.2.0.0 (withunece_vatex_idand the 88 VATEX codes) is required and available on the official 19.0 branch since OCA/community-data-files#284 was merged.Follows up on #7 and #35.