Summary
When the Saxon server is not reachable, generate_en16931_xml() completes normally and the invoice is emitted as if it had passed the schematron. No exception, nothing visible to the user. On an instance where nobody deployed a Saxon server, every document would go out unvalidated, and the first thing to catch the errors would be the accredited platform rejecting them.
This is not an edge case: the validation is always attempted. check_schematron is set unconditionally, and the conditions below it only choose which schematron applies:
# account_invoice_en16931/models/account_move.py (18.0)
check_schematron = "base"
if (
hasattr(self, "fr_directory_partner_entity_type")
and self.fr_directory_company_entity_type == "private"
and not self.env.context.get("chorus_old_xml_syntax")
):
if self.fr_directory_partner_entity_type == "private":
check_schematron = "fr-ctc"
elif self.fr_directory_partner_entity_type == "public":
check_schematron = "fr-chorus"
Why it is silent
The module calls generate_xml() without raise_if_http_error:
# account_invoice_en16931/models/account_move.py:853-862 (18.0)
xml_bytes = generate_xml(
data_dict,
flavor=flavor,
level=level,
check_xsd=True,
check_schematron=check_schematron,
saxon_server_url=saxon_server_url,
saxon_server_codedb_base_url=saxon_server_codedb_base_url,
saxon_server_codedb_dir=saxon_server_codedb_dir,
)
raise_if_http_error does not appear anywhere in this repository (0 hits on 18.0 and on the 16.0 backport of #33), so the library default applies — facturx.xml_check_schematron(..., raise_if_http_error=False) in factur-x 6.6. With that default, a failed POST to the Saxon server is logged as a warning and the check is skipped rather than raising.
So the library offers the choice and the module currently takes the silent one. For a compliance module that seems like the wrong default.
Reproduction
Calling the check directly on a valid EN16931 XML with no Saxon server listening:
raise_if_http_error=False (what the module uses) → returns without raising, i.e. the document is treated as validated.
raise_if_http_error=True → RuntimeError: Check 'base' failed in the POST request to saxon server on http://localhost:5000/transform: ... Failed to establish a new connection.
With a server up, the controls behave correctly: a valid document passes, and the same document with a corrupted InvoiceCurrencyCode reports two errors citing the rule ([BR-CO-15]). The validation itself works — the only problem is what happens when the server is absent.
Second point: the server needs two undocumented adjustments
Even with willemvlh/saxon-server running (the one referenced in pyfrctc), the request fails with HTTP 400 out of the box:
{"statusCode":400,"exceptionType":"TransformationException",
"message":"Access to URI https://raw.githubusercontent.com/akretion/factur-x/refs/heads/master/src/facturx/xsd_and_schematron/facturx-en16931/FACTUR-X_EN16931_codedb.xml has been prohibited"}
The schematron stylesheet resolves an external URI for the code database, and Saxon blocks external access under its default configuration. Passing saxon_server_codedb_dir rewrites it to a local path, which is blocked as well (Access to URI file:/codedb/... has been prohibited). It only works once the server runs in permissive mode (--insecure for that image) and the codedb file is
made available to it.
Worth a line in the README, since a deployment that misses this ends up in exactly the silent-skip situation above — the validation looks enabled and never runs.
Proposal
- Pass
raise_if_http_error=True in the generate_xml() calls, or expose it as a setting that defaults to raising. Failing loudly when the validation cannot run is safer than emitting a document that was never checked.
- Document the Saxon server requirements (permissive configuration + codedb availability).
Seen on 18.0 (line numbers above) and on the 16.0 backport of #33.
Summary
When the Saxon server is not reachable,
generate_en16931_xml()completes normally and the invoice is emitted as if it had passed the schematron. No exception, nothing visible to the user. On an instance where nobody deployed a Saxon server, every document would go out unvalidated, and the first thing to catch the errors would be the accredited platform rejecting them.This is not an edge case: the validation is always attempted.
check_schematronis set unconditionally, and the conditions below it only choose which schematron applies:Why it is silent
The module calls
generate_xml()withoutraise_if_http_error:raise_if_http_errordoes not appear anywhere in this repository (0 hits on 18.0 and on the 16.0 backport of #33), so the library default applies —facturx.xml_check_schematron(..., raise_if_http_error=False)in factur-x 6.6. With that default, a failed POST to the Saxon server is logged as a warning and the check is skipped rather than raising.So the library offers the choice and the module currently takes the silent one. For a compliance module that seems like the wrong default.
Reproduction
Calling the check directly on a valid EN16931 XML with no Saxon server listening:
raise_if_http_error=False(what the module uses) → returns without raising, i.e. the document is treated as validated.raise_if_http_error=True→RuntimeError: Check 'base' failed in the POST request to saxon server on http://localhost:5000/transform: ... Failed to establish a new connection.With a server up, the controls behave correctly: a valid document passes, and the same document with a corrupted
InvoiceCurrencyCodereports two errors citing the rule ([BR-CO-15]). The validation itself works — the only problem is what happens when the server is absent.Second point: the server needs two undocumented adjustments
Even with
willemvlh/saxon-serverrunning (the one referenced inpyfrctc), the request fails with HTTP 400 out of the box:The schematron stylesheet resolves an external URI for the code database, and Saxon blocks external access under its default configuration. Passing
saxon_server_codedb_dirrewrites it to a local path, which is blocked as well (Access to URI file:/codedb/... has been prohibited). It only works once the server runs in permissive mode (--insecurefor that image) and the codedb file ismade available to it.
Worth a line in the README, since a deployment that misses this ends up in exactly the silent-skip situation above — the validation looks enabled and never runs.
Proposal
raise_if_http_error=Truein thegenerate_xml()calls, or expose it as a setting that defaults to raising. Failing loudly when the validation cannot run is safer than emitting a document that was never checked.Seen on 18.0 (line numbers above) and on the 16.0 backport of #33.