Fix: En-Dash in PDF durch Bindestrich ersetzen + _s() Sanitizer für alle Texteingaben (SW by-v982)

This commit is contained in:
rene 2026-05-15 15:50:02 +02:00
parent 1a8716b0b2
commit 68fd9c0e38
7 changed files with 28 additions and 16 deletions

View file

@ -82,6 +82,18 @@ def _generate_pdf(invoice, items) -> bytes:
LG = (245, 245, 245)
WH = (255, 255, 255)
def _s(text) -> str:
"""Nicht-Latin1-Zeichen ersetzen bevor sie an fpdf Helvetica übergeben werden."""
if not text:
return ""
return (str(text)
.replace("", "-").replace("", "-") # En/Em-Dash
.replace("", "'").replace("", "'") # Typogr. Anf.zeichen
.replace("", '"').replace("", '"')
.replace("", "...").replace("·", ".")
.replace("", "EUR") # € falls doch
)
def eur(v: float) -> str:
s = f"{v:,.2f}".replace(",", "X").replace(".", ",").replace("X", ".")
return f"{s} EUR"
@ -144,17 +156,17 @@ def _generate_pdf(invoice, items) -> bytes:
pdf.set_xy(20, 52)
pdf.set_font("Helvetica", "B", 10)
pdf.set_text_color(*DK)
pdf.cell(85, 5.5, invoice["recipient_name"], new_x="LMARGIN", new_y="NEXT")
pdf.cell(85, 5.5, _s(invoice["recipient_name"]), new_x="LMARGIN", new_y="NEXT")
pdf.set_font("Helvetica", "", 10)
if invoice.get("recipient_address"):
for line in str(invoice["recipient_address"]).split("\n"):
if line.strip():
pdf.set_x(20)
pdf.cell(85, 5, line.strip(), new_x="LMARGIN", new_y="NEXT")
pdf.cell(85, 5, _s(line.strip()), new_x="LMARGIN", new_y="NEXT")
pdf.set_x(20)
pdf.set_font("Helvetica", "", 8.5)
pdf.set_text_color(*GY)
pdf.cell(85, 5, invoice["recipient_email"])
pdf.cell(85, 5, _s(invoice["recipient_email"]))
# ── Info-Block rechts ─────────────────────────────────────────
info_rows = [
@ -163,7 +175,7 @@ def _generate_pdf(invoice, items) -> bytes:
("Fällig bis", due_date),
]
if invoice.get("service_period"):
info_rows.append(("Leistungszeitraum", invoice["service_period"]))
info_rows.append(("Leistungszeitraum", _s(invoice["service_period"])))
y_info = 52
for lbl, val in info_rows:
@ -204,7 +216,7 @@ def _generate_pdf(invoice, items) -> bytes:
for i, item in enumerate(items):
pdf.set_fill_color(*(LG if i % 2 == 0 else WH))
qty = f"{item['quantity']:.2f}".rstrip("0").rstrip(".")
pdf.cell(CW[0], 7, f" {str(item['description'])[:64]}", border="B", fill=True)
pdf.cell(CW[0], 7, f" {_s(str(item['description']))[:64]}", border="B", fill=True)
pdf.cell(CW[1], 7, qty, border="B", fill=True, align="C")
pdf.cell(CW[2], 7, eur(item["unit_price"]), border="B", fill=True, align="R")
pdf.cell(CW[3], 7, eur(item["total"]), border="B", fill=True, align="R",
@ -240,7 +252,7 @@ def _generate_pdf(invoice, items) -> bytes:
pdf.set_x(20)
pdf.set_font("Helvetica", "I", 8.5)
pdf.set_text_color(*GY)
pdf.multi_cell(W, 5, "Hinweis: Gem. § 19 UStG wird keine Umsatzsteuer berechnet.")
pdf.multi_cell(W, 5, _s("Hinweis: Gem. § 19 UStG wird keine Umsatzsteuer berechnet.")
# ── Zahlungsinfo-Box ──────────────────────────────────────────
pdf.ln(5)
@ -276,7 +288,7 @@ def _generate_pdf(invoice, items) -> bytes:
pdf.set_x(20)
pdf.set_font("Helvetica", "I", 9)
pdf.set_text_color(*GY)
pdf.multi_cell(W, 5, str(invoice["notes"]))
pdf.multi_cell(W, 5, _s(str(invoice["notes"])))
# ── Footer (fixiert auf Seite 1, kein auto-break) ─────────────
pdf.set_auto_page_break(False)
@ -410,7 +422,7 @@ def get_quarterly(year: int, q: int, admin=Depends(require_admin)):
labels = {1: "01.01.", 2: "01.04.", 3: "01.07.", 4: "01.10."}
ends = {1: "31.03.", 2: "30.06.", 3: "30.09.", 4: "31.12."}
period = f"Q{q} {year} ({labels[q]} {ends[q]})"
period = f"Q{q} {year} ({labels[q]} - {ends[q]})"
with db() as conn:
# Alle Rechnungen außer Entwürfe im Quartal (nach Ausstellungsdatum)