mirror of
https://github.com/complexcaresolutions/dak.c2s.git
synced 2026-03-17 23:03:41 +00:00
When an admin creates an invitation with an email address, an email is now automatically sent containing the registration link and an attached PDF guide (3 pages: registration steps, feature overview, contact info). - Add fpdf2 for PDF generation with Unicode font support - Add PDF guide generator (backend/app/services/pdf_guide.py) - Extend send_email() to support file attachments - Fire-and-forget email in create_invitation endpoint Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
239 lines
7.6 KiB
Python
239 lines
7.6 KiB
Python
"""PDF guide generator for DAK onboarding invitations."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from fpdf import FPDF
|
|
|
|
ASSETS_DIR = Path(__file__).resolve().parent.parent / "assets"
|
|
LOGO_PATH = ASSETS_DIR / "dak_logo.png"
|
|
FONT_DIR = ASSETS_DIR
|
|
|
|
# Font family name used throughout the PDF
|
|
_FONT = "LiberationSans"
|
|
|
|
# Colours (DAK blue tones)
|
|
DAK_BLUE = (0, 82, 147)
|
|
DAK_DARK = (33, 37, 41)
|
|
DAK_GREY = (108, 117, 125)
|
|
DAK_LIGHT_BG = (240, 244, 248)
|
|
|
|
|
|
class _GuidePDF(FPDF):
|
|
"""Custom FPDF subclass with shared header/footer styling."""
|
|
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
# Register Liberation Sans (Unicode TTF) in Regular, Bold, Italic
|
|
self.add_font(_FONT, "", str(FONT_DIR / "LiberationSans-Regular.ttf"))
|
|
self.add_font(_FONT, "B", str(FONT_DIR / "LiberationSans-Bold.ttf"))
|
|
self.add_font(_FONT, "I", str(FONT_DIR / "LiberationSans-Italic.ttf"))
|
|
|
|
def header(self):
|
|
if self.page_no() == 1:
|
|
return # Page 1 has its own header with logo
|
|
self.set_font(_FONT, "I", 8)
|
|
self.set_text_color(*DAK_GREY)
|
|
self.cell(0, 8, "DAK Zweitmeinungs-Portal \u2014 Anleitung", align="R")
|
|
self.ln(12)
|
|
|
|
def footer(self):
|
|
self.set_y(-15)
|
|
self.set_font(_FONT, "I", 8)
|
|
self.set_text_color(*DAK_GREY)
|
|
self.cell(0, 10, f"Seite {self.page_no()}/{{nb}}", align="C")
|
|
|
|
# -- helpers ---------------------------------------------------------------
|
|
|
|
def section_title(self, text: str) -> None:
|
|
self.set_font(_FONT, "B", 14)
|
|
self.set_text_color(*DAK_BLUE)
|
|
self.cell(0, 10, text)
|
|
self.ln(8)
|
|
# blue underline
|
|
self.set_draw_color(*DAK_BLUE)
|
|
self.set_line_width(0.5)
|
|
self.line(self.l_margin, self.get_y(), self.w - self.r_margin, self.get_y())
|
|
self.ln(6)
|
|
|
|
def body_text(self, text: str) -> None:
|
|
self.set_font(_FONT, "", 10)
|
|
self.set_text_color(*DAK_DARK)
|
|
self.multi_cell(0, 6, text)
|
|
self.ln(2)
|
|
|
|
def numbered_step(self, number: int, text: str) -> None:
|
|
self.set_font(_FONT, "B", 10)
|
|
self.set_text_color(*DAK_BLUE)
|
|
self.cell(8, 6, f"{number}.")
|
|
self.set_font(_FONT, "", 10)
|
|
self.set_text_color(*DAK_DARK)
|
|
self.multi_cell(0, 6, text)
|
|
self.ln(1)
|
|
|
|
def bullet_item(self, title: str, description: str) -> None:
|
|
self.set_font(_FONT, "B", 10)
|
|
self.set_text_color(*DAK_DARK)
|
|
self.cell(4, 6, "\u2022 ")
|
|
self.cell(0, 6, title)
|
|
self.ln(6)
|
|
if description:
|
|
self.set_font(_FONT, "", 9)
|
|
self.set_text_color(*DAK_GREY)
|
|
self.set_x(self.l_margin + 6)
|
|
self.multi_cell(0, 5, description)
|
|
self.ln(2)
|
|
|
|
def info_box(self, text: str) -> None:
|
|
self.set_fill_color(*DAK_LIGHT_BG)
|
|
self.set_draw_color(*DAK_BLUE)
|
|
x = self.l_margin
|
|
y = self.get_y()
|
|
w = self.w - self.l_margin - self.r_margin
|
|
self.set_font(_FONT, "", 9)
|
|
self.set_text_color(*DAK_DARK)
|
|
# calculate height
|
|
self.set_xy(x + 4, y + 4)
|
|
self.multi_cell(w - 8, 5, text)
|
|
h = self.get_y() - y + 4
|
|
# draw box behind
|
|
self.rect(x, y, w, h, style="DF")
|
|
# re-render text on top
|
|
self.set_xy(x + 4, y + 4)
|
|
self.multi_cell(w - 8, 5, text)
|
|
self.ln(4)
|
|
|
|
|
|
def generate_guide_pdf(invite_url: str) -> bytes:
|
|
"""Generate the DAK onboarding guide as PDF bytes.
|
|
|
|
Parameters
|
|
----------
|
|
invite_url:
|
|
The personalised invitation/registration URL to embed in the document.
|
|
|
|
Returns
|
|
-------
|
|
bytes
|
|
The raw PDF content (no disk I/O).
|
|
"""
|
|
pdf = _GuidePDF(orientation="P", unit="mm", format="A4")
|
|
pdf.alias_nb_pages()
|
|
pdf.set_auto_page_break(auto=True, margin=20)
|
|
pdf.set_left_margin(20)
|
|
pdf.set_right_margin(20)
|
|
|
|
# ── Page 1: Welcome & Registration ────────────────────────────────────
|
|
pdf.add_page()
|
|
|
|
# Logo
|
|
if LOGO_PATH.exists():
|
|
pdf.image(str(LOGO_PATH), x=75, y=15, w=60)
|
|
pdf.ln(50)
|
|
else:
|
|
pdf.ln(15)
|
|
|
|
# Title
|
|
pdf.set_font(_FONT, "B", 20)
|
|
pdf.set_text_color(*DAK_BLUE)
|
|
pdf.cell(0, 12, "Willkommen im", align="C")
|
|
pdf.ln(12)
|
|
pdf.cell(0, 12, "DAK Zweitmeinungs-Portal", align="C")
|
|
pdf.ln(20)
|
|
|
|
# Registration section
|
|
pdf.section_title("Konto erstellen")
|
|
pdf.body_text(
|
|
"Um Ihr Konto zu erstellen, folgen Sie bitte diesen Schritten:"
|
|
)
|
|
|
|
pdf.numbered_step(1, "Registrierungslink im Browser öffnen (siehe unten)")
|
|
pdf.numbered_step(2, "Benutzername festlegen (mindestens 3 Zeichen)")
|
|
pdf.numbered_step(3, "E-Mail-Adresse eingeben")
|
|
pdf.numbered_step(4, "Passwort setzen (mindestens 8 Zeichen)")
|
|
pdf.numbered_step(5, "\u201eRegistrieren\u201c anklicken")
|
|
pdf.ln(2)
|
|
|
|
# Invite URL box
|
|
pdf.set_font(_FONT, "B", 10)
|
|
pdf.set_text_color(*DAK_BLUE)
|
|
pdf.cell(0, 6, "Ihr Registrierungslink:")
|
|
pdf.ln(7)
|
|
pdf.info_box(invite_url)
|
|
|
|
# First login
|
|
pdf.section_title("Erste Anmeldung")
|
|
pdf.body_text(
|
|
"Melden Sie sich mit Ihrem Benutzernamen (oder E-Mail-Adresse) "
|
|
"und dem gewählten Passwort an."
|
|
)
|
|
pdf.body_text(
|
|
"Empfehlung: Aktivieren Sie die Zwei-Faktor-Authentifizierung "
|
|
"unter Kontoverwaltung für zusätzliche Sicherheit."
|
|
)
|
|
|
|
# ── Page 2: Feature overview ──────────────────────────────────────────
|
|
pdf.add_page()
|
|
pdf.section_title("Funktionsübersicht")
|
|
|
|
pdf.body_text(
|
|
"Das DAK Zweitmeinungs-Portal bietet Ihnen folgende Funktionen:"
|
|
)
|
|
pdf.ln(2)
|
|
|
|
features = [
|
|
("Dashboard", "Übersicht über KPIs, Diagramme und Jahresvergleich."),
|
|
("Fälle", "Fallübersicht mit Such- und Filterfunktionen."),
|
|
("ICD-Eingabe", "ICD-Codes für Fälle erfassen und bearbeiten."),
|
|
("Berichte", "Wochenberichte als Excel-Datei herunterladen."),
|
|
(
|
|
"Wochenübersicht",
|
|
"Datenübersicht pro Kalenderwoche und ICD-Upload per Excel.",
|
|
),
|
|
(
|
|
"Freigaben",
|
|
"Personenbezogene Daten bei Bedarf anfordern (24 Stunden Gültigkeit).",
|
|
),
|
|
(
|
|
"Kontoverwaltung",
|
|
"Profil bearbeiten, Passwort ändern, Zwei-Faktor-Authentifizierung.",
|
|
),
|
|
]
|
|
for title, desc in features:
|
|
pdf.bullet_item(title, desc)
|
|
|
|
# ── Page 3: Closing notes ─────────────────────────────────────────────
|
|
pdf.add_page()
|
|
pdf.section_title("Weitere Informationen")
|
|
|
|
pdf.body_text(
|
|
"Ausführliche Anleitungen zu allen Funktionen finden Sie direkt "
|
|
"im Portal unter dem Menüpunkt \u201eAnleitung\u201c."
|
|
)
|
|
pdf.ln(4)
|
|
|
|
pdf.body_text(
|
|
"Bei Fragen oder Problemen wenden Sie sich bitte an Ihren "
|
|
"Ansprechpartner bei Complex Care Solutions:"
|
|
)
|
|
pdf.ln(2)
|
|
|
|
pdf.set_font(_FONT, "B", 10)
|
|
pdf.set_text_color(*DAK_DARK)
|
|
pdf.cell(0, 6, "Complex Care Solutions GmbH")
|
|
pdf.ln(6)
|
|
pdf.set_font(_FONT, "", 10)
|
|
pdf.cell(0, 6, "E-Mail: info@complexcaresolutions.de")
|
|
pdf.ln(6)
|
|
pdf.cell(0, 6, "Portal: https://dak.complexcaresolutions.de")
|
|
pdf.ln(16)
|
|
|
|
# Final info box
|
|
pdf.info_box(
|
|
"Dieses Dokument wurde automatisch generiert und enthält "
|
|
"Ihren persönlichen Registrierungslink. "
|
|
"Bitte geben Sie diesen Link nicht an Dritte weiter."
|
|
)
|
|
|
|
return pdf.output()
|