82 lines
3.1 KiB
Python
82 lines
3.1 KiB
Python
from __future__ import annotations
|
|
from pathlib import Path
|
|
import re
|
|
import shutil
|
|
from dataclasses import dataclass
|
|
from typing import Optional
|
|
|
|
from .io import load_invoice_yaml
|
|
from .render import render_html, render_pdf
|
|
from .facturx import generate_facturx_xml
|
|
from .mustang import combine_facturx, validate
|
|
|
|
@dataclass
|
|
class BuildResult:
|
|
invoice_dir: Path
|
|
out_dir: Path
|
|
invoice_pdf: Path
|
|
facturx_xml: Path
|
|
facturx_pdf: Optional[Path] = None
|
|
validate_report: Optional[Path] = None
|
|
|
|
def build_in_folder(
|
|
invoice_dir: Path,
|
|
out_subdir: str,
|
|
template_path: Path,
|
|
logger,
|
|
mustang_jar: Optional[Path] = None,
|
|
combine: bool = False,
|
|
do_validate: bool = False,
|
|
prefer_weasyprint: bool = True,
|
|
invoice_filename: str = "invoice.yaml",
|
|
) -> BuildResult:
|
|
invoice_dir = invoice_dir.resolve()
|
|
invoice_yaml = invoice_dir / invoice_filename
|
|
if not invoice_yaml.exists():
|
|
raise FileNotFoundError(f"Missing {invoice_filename} in invoice folder: {invoice_dir}")
|
|
|
|
inv = load_invoice_yaml(invoice_yaml)
|
|
out_dir = invoice_dir / out_subdir
|
|
(out_dir / "logs").mkdir(parents=True, exist_ok=True)
|
|
|
|
attachment = invoice_dir / ('Rechnung_' + re.sub(r'[^A-Za-z0-9_-]', '_', inv.meta.invoice_number) + '.pdf')
|
|
# A new build invalidates old delivery drafts and prior validation results.
|
|
for stale in [attachment, out_dir / "invoice.eml", out_dir / "manifest.json",
|
|
out_dir / "invoice.facturx.pdf", out_dir / "validation/validate_report.xml"]:
|
|
stale.unlink(missing_ok=True)
|
|
|
|
logger.info("Loaded invoice: %s (folder=%s)", inv.meta.invoice_number, invoice_dir)
|
|
logger.info("Artifacts directory: %s", out_dir)
|
|
|
|
html = render_html(inv, template_path)
|
|
|
|
pdf_path = out_dir / "invoice.pdf"
|
|
pdf_res = render_pdf(html, pdf_path, logger, prefer_weasyprint=prefer_weasyprint, base_url=str(invoice_dir))
|
|
if not pdf_res.ok or not pdf_res.path:
|
|
raise RuntimeError(f"PDF rendering failed: {pdf_res.error}")
|
|
|
|
xml_path = out_dir / "factur-x.xml"
|
|
generate_facturx_xml(inv, xml_path, logger)
|
|
|
|
result = BuildResult(invoice_dir=invoice_dir, out_dir=out_dir, invoice_pdf=pdf_path, facturx_xml=xml_path)
|
|
|
|
if combine or do_validate:
|
|
if not mustang_jar:
|
|
raise RuntimeError("Mustang jar required for --combine/--validate. Provide --mustang-jar path.")
|
|
facturx_pdf = out_dir / "invoice.facturx.pdf"
|
|
logger.info("Combining PDF + XML -> Factur-X PDF via Mustang")
|
|
result.facturx_pdf = combine_facturx(mustang_jar, pdf_path, xml_path, facturx_pdf, logger, profile="E")
|
|
|
|
if do_validate:
|
|
logger.info("Validating Factur-X PDF via Mustang")
|
|
report = out_dir / "validation" / "validate_report.xml"
|
|
try:
|
|
result.validate_report = validate(mustang_jar, result.facturx_pdf, report, logger)
|
|
except Exception:
|
|
# Failed output must not be mistaken for a ready-to-send invoice.
|
|
result.facturx_pdf.unlink(missing_ok=True)
|
|
raise
|
|
shutil.copy2(result.facturx_pdf, attachment)
|
|
|
|
return result
|