Anwesenheit PDF Generator
Liest Anwesenheitsdaten aus einer Excel-Datei (.xlsx) und erzeugt für jeden Teilnehmer eine ausgefüllte PDF-Datei (SAB-Formular).
Voraussetzungen
- Python 3.10 oder höher
- pip
Installation
# Abhängigkeiten installieren
pip install -r requirements.txt
Für Entwicklung inkl. Tests:
pip install -r requirements-dev.txt
Konfiguration
Vor dem ersten Start müssen zwei Werte in config.json eingetragen werden:
| Feld | Beschreibung |
|---|---|
antragsnummer |
SAB-Antragsnummer der Einrichtung |
ort |
Ort für die Unterschriftszeile im PDF |
Alle anderen Felder sind vorbelegt und müssen nur bei abweichender Excel-Struktur angepasst werden.
Erweiterte PDF-Optionen
Diese Schalter steuern das interne Verhalten beim PDF-Befüllen und müssen in der Regel nicht verändert werden.
pdf_use_display_values (Standard: true)
Steuert, welche Werte in Dropdown- und Auswahlfelder geschrieben werden.
| Wert | Verhalten |
|---|---|
true |
Lesbare Anzeigewerte (z. B. "X = anwesend", "mind. 6 h", "8,60") werden ins PDF geschrieben. Der PDF-Viewer zeigt den Text direkt an. |
false |
Rohe Export-Werte (z. B. "X", "6", "1") werden verwendet — so wie es das JavaScript im Originalformular intern macht. Sinnvoll, wenn ein externer Viewer (z. B. Adobe Acrobat) die Werte selbst aufzulösen soll. |
pdf_need_appearances (Standard: false)
Steuert das NeedAppearances-Flag in der erzeugten PDF-Datei.
| Wert | Verhalten |
|---|---|
false |
Der PDF-Renderer nutzt die im PDF gespeicherten Appearance-Streams (AP) direkt. Buttons, Footer und Summenfelder werden korrekt angezeigt. Empfohlene Einstellung. |
true |
Der PDF-Viewer regeneriert alle Darstellungen aus den Felddefinitionen (/DA). Kann helfen, wenn ein externer Viewer einzelne Felder leer anzeigt — kann aber dazu führen, dass Buttons und Footer ausgeblendet werden. |
Hinweis: Die Summenfelder erhalten beim Generieren eigene Appearance-Streams (Zwei-Pass-Schreibstrategie), sodass
pdf_need_appearances: falsefür alle Felder funktioniert.
regression (Objekt)
Einstellungen für den automatisierten Regressions-Bildvergleich (pytest). Alle Unterfelder befinden sich in diesem Objekt.
"regression": {
"enabled": false,
"mask_footer": true
}
regression.enabled (Standard: false)
Aktiviert oder deaktiviert den Bildvergleich.
| Wert | Verhalten |
|---|---|
false |
Regression-Tests werden übersprungen (pytest.skip). Standard. |
true |
Bildvergleich wird ausgeführt. Erfordert Referenz-PDFs in tests/fixtures/. |
regression.mask_footer (Standard: true)
Steuert, ob der untere Bereich (Footer) beim Pixel-Vergleich ignoriert wird.
| Wert | Verhalten |
|---|---|
true |
Die untere ~20% jeder PDF-Seite wird ignoriert. Sinnvoll, da Template-PDFs je nach Vorlage unterschiedlich viele Footer-Zeilen (2 oder 3) haben können. |
false |
Vollständiger Pixel-by-Pixel-Vergleich inkl. Footer-Bereich. |
Verzeichnisstruktur
generate.py ← Einstiegspunkt
config.json ← Konfiguration
input/ ← Excel-Dateien ablegen
template/
sab.pdf ← PDF-Vorlage (muss vorhanden sein)
output/ ← Generierte PDFs (werden hier abgelegt)
Verwendung
Generierung starten
python generate.py generate --file "input/Anwesenheitsliste 2026.xlsx" --month Feb
Das Programm zeigt vor der Generierung eine Vorschau und fragt zur Bestätigung:
=== Vorschau der ermittelten Werte ===
Excel-Datei : Anwesenheitsliste 2026.xlsx
Jahr : 2026
Monat : Feb (02)
Antragsnummer : 12345
Ort : Leipzig
Erstelldatum : 27.03.2026
Teilnehmer : 49
Fehler : 0
Möchten Sie die PDF-Generierung starten? [j/N]
Nur bei Eingabe von j werden PDFs geschrieben.
Nur Validierung (keine PDFs erzeugen)
python generate.py validate --file "input/Anwesenheitsliste 2026.xlsx" --month Feb
Führt alle Prüfungen durch und zeigt die Vorschau — ohne Dateien zu schreiben.
PDF als Bild rendern
python generate.py render-image --pdf "output/(1) Mustermann, Max.pdf" --page 1 --out "preview.png"
Dieser Befehl rendert eine PDF-Seite als PNG-Bild. Er nutzt dieselbe Render-Funktion wie der Regressionstest, damit CLI-Vorschau und Testvergleich technisch identisch arbeiten.
Alle Seiten auf einmal rendern:
python generate.py render-image --pdf "output/(1) Mustermann, Max.pdf" --all-pages --out "render_out"
Optionen
generate
| Option | Pflicht | Beschreibung |
|---|---|---|
--file |
ja | Pfad zur Excel-Datei |
--month |
ja | Name des Monats-Sheets (z. B. Jan, Feb, Mrz, Apr …) |
--out |
nein | Ausgabeverzeichnis (Standard: output/ aus config.json) |
validate
| Option | Pflicht | Beschreibung |
|---|---|---|
--file |
ja | Pfad zur Excel-Datei |
--month |
ja | Name des Monats-Sheets |
render-image
| Option | Pflicht | Beschreibung |
|---|---|---|
--pdf |
ja | Pfad zur PDF-Datei |
--out |
nein | Zieldatei (Einzelseite) oder Zielordner (--all-pages) |
--dpi |
nein | Render-Auflösung in DPI (Standard: 150) |
--page |
nein | 1-basierte Seitennummer (Standard: 1) |
--all-pages |
nein | Rendert alle Seiten statt nur --page |
Sheet-Namen
Die deutschen Monatsabkürzungen entsprechen den Tabellenblatt-Namen in der Excel-Datei:
| Sheet | Monat |
|---|---|
Jan |
Januar |
Feb |
Februar |
Mrz |
März |
Apr |
April |
Mai |
Mai |
Jun |
Juni |
Jul |
Juli |
Aug |
August |
Sep |
September |
Okt |
Oktober |
Nov |
November |
Dez |
Dezember |
Ausgabe
Für jeden Teilnehmer wird eine PDF-Datei erstellt:
output/
(1) Mustermann, Max.pdf
(2) Beispiel, Anna.pdf
...
Die laufende Nummer entspricht der Nummerierung in Spalte A der Excel-Datei.
Tests ausführen
Alle Tests starten
pytest tests/ -v
Nur den Regression-Test. Der Test erwartet, dass im Ordner 2026_Feb die Excel-Datei und in reference/ die Ground-Truth-PDFs liegen. Er generiert PDFs in ein Temp-Verzeichnis und vergleicht sie pixelweise. Bei Abweichungen werden Diff-Bilder nach diff geschrieben.
python -m pytest tests/test_regression.py -v
Validierungsprüfungen
Das Programm prüft vor jeder Generierung:
config.jsonvorhanden und vollständig- Excel-Datei existiert und ist eine
.xlsx/.xlsm-Datei - Jahr aus Dateiname extrahierbar (z. B.
Anwesenheitsliste 2026.xlsx) - Sheet-Name bekannt (in
month_mappingderconfig.json) - Sheet existiert in der Arbeitsmappe
- Spalte A (lfd. Nummer) enthält Daten
- Headerzeile mit
Name, Vornamegefunden - Tagesspalten ab Spaltenindex 9 vorhanden
- PDF-Template unter
template/sab.pdferreichbar
Windows-Installer erstellen
Der Build-Prozess erzeugt neben dem Programm auch einen Windows-Installer (.exe), der vom SMB-Netzlaufwerk gestartet werden kann.
Voraussetzung: Inno Setup 6
Inno Setup 6 muss auf der Build-Maschine installiert sein. Installation über winget:
winget install --id JRSoftware.InnoSetup -e -s winget -i
Alternativ manueller Download: https://jrsoftware.org/isinfo.php
Installer bauen
build.bat
build.bat führt alle vier Schritte automatisch aus:
- Abhängigkeiten installieren (
pip install) - Version aktualisieren (
update_version.py) - Programm packen (
PyInstaller→dist\AnwesenheitPDF\) - Installer kompilieren (
ISCC.exe→dist\AnwesenheitPDF-Setup-x.y.z.exe)
Ist Inno Setup nicht installiert, wird Schritt 4 mit einer Warnung übersprungen — der PyInstaller-Output in dist\AnwesenheitPDF\ ist trotzdem nutzbar.
Installer verteilen
Die fertige Datei dist\AnwesenheitPDF-Setup-x.y.z.exe auf das SMB-Netzlaufwerk kopieren. Von dort kann sie direkt gestartet werden — ohne vorherige lokale Kopie.
Was der Installer macht
| Aktion | Details |
|---|---|
| Installationspfad | %ProgramFiles%\AnwesenheitPDF\ (Admin erforderlich) |
| Startmenü-Eintrag | AnwesenheitPDF |
| Desktop-Verknüpfung | Optional (Checkbox im Wizard, Standard: deaktiviert) |
| Uninstaller | Registriert unter Programme und Features |
| Windows-Version | Windows 10 oder höher |