Initial version of the E-Invoice solution of JR IT Services

This commit is contained in:
2026-02-16 17:02:03 +01:00
commit e0c15fc7f2
36 changed files with 1407 additions and 0 deletions

80
jr_einvoice/render.py Normal file
View File

@@ -0,0 +1,80 @@
from __future__ import annotations
from pathlib import Path
import subprocess
import shutil
from jinja2 import Environment, FileSystemLoader, select_autoescape
from .models import Invoice
from .calc import compute_totals
from .types import RenderResult
from .utils import money_fmt
def render_html(inv: Invoice, template_path: Path) -> str:
env = Environment(
loader=FileSystemLoader(str(template_path.parent)),
autoescape=select_autoescape(["html", "xml"]),
)
env.globals["money"] = lambda x: money_fmt(x, inv.meta.currency)
tpl = env.get_template(template_path.name)
totals = compute_totals(inv)
items = []
for it in inv.items:
d = it.model_dump()
d["line_total_net"] = float(it.quantity) * float(it.unit_price_net)
items.append(type("Obj", (), d))
ctx = {
"meta": inv.meta.model_dump(),
"seller": inv.seller.model_dump(),
"buyer": inv.buyer.model_dump(),
"payment": inv.payment.model_dump(),
"items": items,
"totals": totals,
}
return tpl.render(**ctx)
def _weasyprint_available() -> bool:
try:
import weasyprint # noqa
return True
except Exception:
return False
def render_pdf(html: str, out_pdf: Path, logger, prefer_weasyprint: bool = True, base_url: str | None = None) -> RenderResult:
out_pdf.parent.mkdir(parents=True, exist_ok=True)
if prefer_weasyprint and _weasyprint_available():
logger.info("Rendering PDF using WeasyPrint")
try:
from weasyprint import HTML # type: ignore
HTML(string=html, base_url=base_url).write_pdf(str(out_pdf))
return RenderResult(ok=True, backend="weasyprint", path=out_pdf)
except Exception as e:
logger.exception("WeasyPrint rendering failed; trying wkhtmltopdf if available: %s", e)
wk = shutil.which("wkhtmltopdf")
if wk:
logger.info("Rendering PDF using wkhtmltopdf (%s)", wk)
tmp_html = out_pdf.with_suffix(".tmp.html")
tmp_html.write_text(html, encoding="utf-8")
try:
proc = subprocess.run(
[wk, "--quiet", "--enable-local-file-access", str(tmp_html), str(out_pdf)],
capture_output=True,
text=True,
check=False,
)
if proc.returncode != 0:
logger.error("wkhtmltopdf failed (rc=%s). stderr=%s", proc.returncode, proc.stderr.strip())
return RenderResult(ok=False, backend="wkhtmltopdf", path=None, error="wkhtmltopdf failed")
return RenderResult(ok=True, backend="wkhtmltopdf", path=out_pdf)
finally:
try:
tmp_html.unlink(missing_ok=True)
except Exception:
pass
return RenderResult(ok=False, backend="none", path=None, error="No PDF backend available (install weasyprint or wkhtmltopdf).")