# 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 ```bash # Abhängigkeiten installieren pip install -r requirements.txt ``` Für Entwicklung inkl. Tests: ```bash 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. ```json "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 ```bash 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) ```bash 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 ```bash 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: ```bash 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** ```bash 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.* ```bash 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`: ```bat winget install --id JRSoftware.InnoSetup -e -s winget -i ``` Alternativ manueller Download: ### Installer bauen ```bat 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 |