feat: create validated ZUGFeRD invoices in one console command

This commit is contained in:
admin committed 2026-10-16 09:29:08 +02:00
1 parent e0c15fc7f2
commit a643cf5227
30 files changed
+1509 -207

No files matched your search

+19 -5
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
from pathlib import Path
import subprocess
import xml.etree.ElementTree as ET
class MustangError(RuntimeError):
pass
@@ -10,7 +11,7 @@ def _run(jar: Path, args: list[str], logger) -> subprocess.CompletedProcess[str]
raise MustangError(f"Mustang jar not found: {jar}")
cmd = ["java", "-Xmx1G", "-Dfile.encoding=UTF-8", "-jar", str(jar)] + args
logger.debug("Running Mustang: %s", " ".join(cmd))
proc = subprocess.run(cmd, capture_output=True, text=True)
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=180)
logger.debug("Mustang stdout:\n%s", proc.stdout.strip())
logger.debug("Mustang stderr:\n%s", proc.stderr.strip())
return proc
@@ -22,10 +23,13 @@ def combine_facturx(jar: Path, source_pdf: Path, source_xml: Path, out_pdf: Path
"--source", str(source_pdf),
"--source-xml", str(source_xml),
"--out", str(out_pdf),
"--profile", profile
"--format", "fx", "--version", "1",
"--profile", profile, "--no-additional-attachments"
], logger)
if proc.returncode != 0:
raise MustangError(f"Mustang combine failed (rc={proc.returncode}). See logs for details.")
if not out_pdf.is_file() or out_pdf.stat().st_size == 0:
raise MustangError("Mustang produced no PDF")
return out_pdf
def validate(jar: Path, source: Path, out_report: Path, logger, no_notices: bool = True) -> Path:
@@ -35,8 +39,18 @@ def validate(jar: Path, source: Path, out_report: Path, logger, no_notices: bool
args += ["--no-notices"]
args += ["--action", "validate", "--source", str(source)]
proc = _run(jar, args, logger)
if proc.returncode != 0:
raise MustangError(f"Mustang validate failed (rc={proc.returncode}). See logs for details.")
content = proc.stdout.strip() or "<report>empty stdout from Mustang; check logs</report>"
# Persist reports even on failure; a zero exit code alone is insufficient.
content = proc.stdout.strip()
out_report.write_text(content, encoding="utf-8")
try:
root = ET.fromstring(content)
summaries = root.findall(".//summary")
valid = (root.tag == "validation"
and root.find("xml/summary") is not None
and root.find("summary") is not None
and all(s.get("status") == "valid" for s in summaries))
except ET.ParseError:
valid = False
if proc.returncode != 0 or not valid:
raise MustangError(f"Validation failed; see {out_report} and logs")
return out_report