Fix: En-Dash in PDF durch Bindestrich ersetzen + _s() Sanitizer für alle Texteingaben (SW by-v982)
This commit is contained in:
parent
1a8716b0b2
commit
68fd9c0e38
7 changed files with 28 additions and 16 deletions
|
|
@ -408,7 +408,7 @@ async def serve_media(path: str, request: _Request):
|
|||
raise _HE(404, "Nicht gefunden.")
|
||||
return _media_response(filepath)
|
||||
|
||||
APP_VER = "981" # muss mit APP_VER in app.js übereinstimmen
|
||||
APP_VER = "982" # muss mit APP_VER in app.js übereinstimmen
|
||||
|
||||
@app.get("/.well-known/assetlinks.json")
|
||||
async def assetlinks():
|
||||
|
|
|
|||
|
|
@ -1339,7 +1339,7 @@ async def _handle_upgrade_invoices(req: dict, new_tier_label: str):
|
|||
price = {"pro": 29.00, "breeder": 49.00}.get(tier, 29.00)
|
||||
today = datetime.now(_TZ).date()
|
||||
end_date = today.replace(year=today.year + 1) - timedelta(days=1)
|
||||
period = f"{today.strftime('%d.%m.%Y')} – {end_date.strftime('%d.%m.%Y')}"
|
||||
period = f"{today.strftime('%d.%m.%Y')} - {end_date.strftime('%d.%m.%Y')}"
|
||||
description = f"{new_tier_label} Jahresabo"
|
||||
|
||||
billing = conn.execute(
|
||||
|
|
@ -1486,7 +1486,7 @@ async def send_quarterly_report(data: QuarterlyReportBody, user=Depends(require_
|
|||
count_sent = sum(1 for r in rows if r["status"] == "sent")
|
||||
|
||||
subject_stb = (
|
||||
f"Ban Yaro – Rechnungen Q{data.quarter}/{data.year} "
|
||||
f"Ban Yaro - Rechnungen Q{data.quarter}/{data.year} "
|
||||
f"({start} bis {end})"
|
||||
)
|
||||
body_stb = (
|
||||
|
|
@ -1559,7 +1559,7 @@ async def send_quarterly_report(data: QuarterlyReportBody, user=Depends(require_
|
|||
"sent_to": data.email,
|
||||
"year": data.year,
|
||||
"quarter": data.quarter,
|
||||
"period": f"{start} – {end}",
|
||||
"period": f"{start} - {end}",
|
||||
"count": len(rows),
|
||||
"count_paid": count_paid,
|
||||
"count_sent": count_sent,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ async def _create_renewal_invoice_draft(user: dict, expires: date, tier_label: s
|
|||
# Verlängerungszeitraum: Folgetag nach Ablauf bis +1 Jahr
|
||||
start = expires + timedelta(days=1)
|
||||
end = start.replace(year=start.year + 1) - timedelta(days=1)
|
||||
period = f"{start.strftime('%d.%m.%Y')} – {end.strftime('%d.%m.%Y')}"
|
||||
period = f"{start.strftime('%d.%m.%Y')} - {end.strftime('%d.%m.%Y')}"
|
||||
|
||||
with db() as conn:
|
||||
# Nur anlegen wenn noch kein Entwurf/offener Eintrag für diesen User + Zeitraum
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Router, State-Management, Navigation, Initialisierung.
|
||||
============================================================ */
|
||||
|
||||
const APP_VER = '981'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
const APP_VER = '982'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt
|
||||
const IS_STAGING = location.hostname === 'staging.banyaro.app';
|
||||
// Cache-Bust-Parameter nach Update-Reload sofort entfernen
|
||||
|
|
|
|||
|
|
@ -3646,7 +3646,7 @@ window.Page_admin = (() => {
|
|||
const _now = new Date();
|
||||
const _end = new Date(_now.getFullYear() + 1, _now.getMonth(), _now.getDate() - 1);
|
||||
const _fmt = d => `${String(d.getDate()).padStart(2,'0')}.${String(d.getMonth()+1).padStart(2,'0')}.${d.getFullYear()}`;
|
||||
const _period = `${_fmt(_now)} – ${_fmt(_end)}`;
|
||||
const _period = `${_fmt(_now)} - ${_fmt(_end)}`;
|
||||
|
||||
function _discountNote(reason, count, pct, tierLabel) {
|
||||
const agb = 'Jahresbeitrag gem. AGB. Bei vorzeitiger Kündigung keine anteilige Rückerstattung; Zugang bleibt bis Laufzeitende bestehen.';
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Offline-Cache + Push Notifications + Tile-Cache
|
||||
============================================================ */
|
||||
|
||||
const CACHE_VERSION = 'by-v981';
|
||||
const CACHE_VERSION = 'by-v982';
|
||||
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
||||
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
||||
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue