Files
sufw-anweisenheiten/README.md
T

8.7 KiB

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: false fü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/.

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

Sonderfall P = Praktikum

Wenn im Excel-Sheet für einen Tag P steht, bleibt der Status im PDF P, aber die Stundenfelder werden mit 6 Stunden befüllt:

Feld Wert
anwesend.N P
ist.N 6,00
plan.N 6
pauschale_5.N 1

P = Praktikum wird damit wie ein regulärer Anwesenheitstag mit 6 Stunden abgerechnet und zählt auch für die 5-Euro-Pauschale.


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:

  1. config.json vorhanden und vollständig
  2. Excel-Datei existiert und ist eine .xlsx/.xlsm-Datei
  3. Jahr aus Dateiname extrahierbar (z. B. Anwesenheitsliste 2026.xlsx)
  4. Sheet-Name bekannt (in month_mapping der config.json)
  5. Sheet existiert in der Arbeitsmappe
  6. Spalte A (lfd. Nummer) enthält Daten
  7. Headerzeile mit Name, Vorname gefunden
  8. Tagesspalten ab Spaltenindex 9 vorhanden
  9. PDF-Template unter template/sab.pdf erreichbar

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:

  1. Abhängigkeiten installieren (pip install)
  2. Version aktualisieren (update_version.py)
  3. Programm packen (PyInstaller → dist\AnwesenheitPDF\)
  4. 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