Author SHA1 Message Date
Rob 3e9cd39859 unnötiges Feld entfernt 2026-06-23 11:51:00 +02:00
Rob 9bdd90c073 ouput pdf: text styling 2026-06-23 11:50:44 +02:00
Rob e50f423304 input validierung für Qualifikationsfeld. Wenn keine Daten -> Platzhalter im template löschen 2026-06-23 11:49:54 +02:00
Rob ef219e00ba add; coldrun.bat 2026-06-23 11:00:36 +02:00
Rob 30f3ffaded neues cleanes template.pdf 2026-06-23 09:58:32 +02:00
8 changed files with 138 additions and 8 deletions

No files matched your search

+46
View File
@@ -0,0 +1,46 @@
@echo off
title Tätigkeitsnachweis-Generator – Cold Run
echo ========================================
echo Cold Run – Headless-Generierung
echo ========================================
echo.
:: Prüfe ob Python verfügbar ist
where python >nul 2>nul
if %ERRORLEVEL% neq 0 (
echo [FEHLER] Python wurde nicht gefunden.
echo Bitte installiere Python 3.9 oder neuer von https://python.org
pause
exit /b 1
)
echo [INFO] Python gefunden:
python --version
:: Prüfe ob venv existiert, sonst erstelle
if not exist ".venv\Scripts\python.exe" (
echo [FEHLER] Virtuelle Umgebung nicht gefunden.
echo Bitte zuerst start.bat ausführen, um das Projekt einzurichten.
pause
exit /b 1
)
:: Aktiviere venv und installiere Abhängigkeiten
call .venv\Scripts\activate.bat
echo [INFO] Stelle sicher, dass Abhängigkeiten installiert sind...
pip install -r requirements.txt --quiet
if %ERRORLEVEL% neq 0 (
echo [FEHLER] Konnte Abhängigkeiten nicht installieren.
pause
exit /b 1
)
:: Starte Cold Run
echo.
echo [INFO] Starte Cold Run – generiere Tätigkeitsnachweis für erste Person...
echo.
python coldrun.py
echo.
echo [INFO] Cold Run abgeschlossen.
pause
+13 -1
View File
@@ -22,7 +22,7 @@ from src.config import (
get_available_templates, get_available_templates,
) )
from src.parser import parse_input_file, group_by_employee, get_unique_employees from src.parser import parse_input_file, group_by_employee, get_unique_employees
from src.validator import validate_template from src.validator import validate_input_file, validate_template
from src.writer import generate_activity_reports from src.writer import generate_activity_reports
@@ -70,6 +70,18 @@ def main():
total = tpl_result.info.get("total_fields", "?") total = tpl_result.info.get("total_fields", "?")
print(f"[OK] {found}/{total} Platzhalter im Template gefunden.") print(f"[OK] {found}/{total} Platzhalter im Template gefunden.")
# ── Input validieren ──────────────────────────────────────────────
print()
print("[INFO] Validiere Input-Datei…")
inp_result = validate_input_file(input_path, config.input_schema, config.month_sheet_pattern)
if not inp_result.is_valid:
for err in inp_result.errors:
print(f"[FEHLER] {err}")
return 1
if inp_result.warnings:
for warn in inp_result.warnings:
print(f"[WARN] {warn}")
# ── Input parsen ────────────────────────────────────────────────── # ── Input parsen ──────────────────────────────────────────────────
print() print()
print("[INFO] Parse Input-Datei…") print("[INFO] Parse Input-Datei…")
+1 -4
View File
@@ -1,13 +1,10 @@
[ [
{"source_field": "Antragsnummer", "pdf_field": "Auftragsnummer_Field", "format": null}, {"source_field": "Antragsnummer", "pdf_field": "Antragsnummer_Field", "format": null},
{"source_field": "Name", "pdf_field": "Name_Field", "format": null}, {"source_field": "Name", "pdf_field": "Name_Field", "format": null},
{"source_field": "Stellenanteil in %", "pdf_field": "Stellenanteil_Field", "format": null}, {"source_field": "Stellenanteil in %", "pdf_field": "Stellenanteil_Field", "format": null},
{"source_field": "Qualifikation", "pdf_field": "Qualifikation_Field", "format": null}, {"source_field": "Qualifikation", "pdf_field": "Qualifikation_Field", "format": null},
{"source_field": "Monat (MM.JJ)", "pdf_field": "Monat_Field", "format": null}, {"source_field": "Monat (MM.JJ)", "pdf_field": "Monat_Field", "format": null},
{"source_field": "Funktion", "pdf_field": "Funktion_Field", "format": null}, {"source_field": "Funktion", "pdf_field": "Funktion_Field", "format": null},
{"source_field": "Tätigkeit", "pdf_field": "Tätigkeiten_Field", "format": null}, {"source_field": "Tätigkeit", "pdf_field": "Tätigkeiten_Field", "format": null},
{"source_field": "Tätigkeit", "pdf_field": "Tätigkeiten-inkl-Reisen_Field","format": null},
{"source_field": "Ort", "pdf_field": "Ort_Field", "format": null},
{"source_field": "Ort 2", "pdf_field": "Ort2_Field", "format": null},
{"source_field": "Datum", "pdf_field": "Datum_Field", "format": null} {"source_field": "Datum", "pdf_field": "Datum_Field", "format": null}
] ]
+43
View File
@@ -81,6 +81,49 @@ def validate_input_file(
info["sheets_with_data"] = sheets_with_data info["sheets_with_data"] = sheets_with_data
wb.close() wb.close()
# ── Dateninhalts-Prüfung: leere Pflichtfelder ───────────────────
required_fields = [f for f in schema if f.required]
if required_fields and sheets_with_data > 0:
# Mapping field_name → column index
field_col_map: dict[str, int] = {}
for f in required_fields:
field_col_map[f.field_name] = column_index_from_string(f.column) - 1
max_col = max(field_col_map.values()) + 1
# {field_name: {name1, name2, ...}} für leere Pflichtwerte
empty_by_field: dict[str, set[str]] = {f.field_name: set() for f in required_fields}
wb2 = load_workbook(filepath, data_only=True)
for sheet_name in wb2.sheetnames:
if not is_month_sheet(sheet_name, month_sheet_pattern):
continue
ws = wb2[sheet_name]
if ws.max_row < 2:
continue
for row in ws.iter_rows(min_row=2, max_row=ws.max_row, min_col=1, max_col=max_col, values_only=True):
name_val = str(row[field_col_map.get("Name", 0)] or "").strip() if len(row) > field_col_map.get("Name", 0) else ""
if not name_val:
continue
for f in required_fields:
col_idx = field_col_map[f.field_name]
if col_idx < len(row):
cell_val = str(row[col_idx] or "").strip() if row[col_idx] is not None else ""
if not cell_val:
empty_by_field[f.field_name].add(name_val)
wb2.close()
for field_name, names in empty_by_field.items():
if names:
sorted_names = sorted(names)
total = len(sorted_names)
preview = sorted_names[:5]
suffix = " …" if total > 5 else ""
warnings.append(
f"Feld '{field_name}' ist leer bei {total} Person(en): "
f"{', '.join(preview)}{suffix}"
)
# ──────────────────────────────────────────────────────────────────
if sheets_with_data == 0: if sheets_with_data == 0:
errors.append("Kein Sheet mit den erwarteten Spalten gefunden.") errors.append("Kein Sheet mit den erwarteten Spalten gefunden.")
+35 -3
View File
@@ -169,6 +169,12 @@ def _fill_pdf_pages(
field_info: dict[str, dict], field_info: dict[str, dict],
) -> None: ) -> None:
"""Befüllt alle Template-Seiten: redacted _Field-Platzhalter und fügt echte Daten ein.""" """Befüllt alle Template-Seiten: redacted _Field-Platzhalter und fügt echte Daten ein."""
# Mehrzeilige Felder, die als Textbox (mit Zeilenumbruch) behandelt werden
MULTILINE_FIELDS = {"Tätigkeiten_Field", "Dienstreisen_Field"}
TEXTBOX_FONT_SCALE = 1 # Schriftgrößen-Faktor für Textboxen
TEXTBOX_WIDTH_PT = 400 # Breite der Textbox in Punkt
TEXTBOX_HEIGHT_PT = 120 # Höhe der Textbox in Punkt
for m in mapping: for m in mapping:
if m.pdf_field is None: if m.pdf_field is None:
continue continue
@@ -188,12 +194,38 @@ def _fill_pdf_pages(
value = _get_field_value(record, employee_name, m.source_field) value = _get_field_value(record, employee_name, m.source_field)
formatted = _format_value(value, m.format) formatted = _format_value(value, m.format)
# Platzhalter redacten # Platzhalter immer redacten, auch wenn kein Wert zum Einsetzen da ist
page.add_redact_annot(rect) page.add_redact_annot(rect)
page.apply_redactions() page.apply_redactions()
# Neuen Text einfügen if not formatted:
if formatted: continue
# Mehrzeilige Felder: insert_textbox mit Zeilenumbruch
if m.pdf_field in MULTILINE_FIELDS:
# Doppelte Leerzeilen entfernen
clean_text = "\n".join(
line for line in formatted.split("\n") if line.strip()
)
if not clean_text:
continue
# Rechteck für die Textbox: ausgehend vom Marker, nach rechts & unten erweitert
textbox_rect = fitz.Rect(
rect.x0, # links
rect.y0, # oben
rect.x0 + TEXTBOX_WIDTH_PT, # rechts
rect.y0 + TEXTBOX_HEIGHT_PT, # unten
)
page.insert_textbox(
textbox_rect,
clean_text,
fontname="helv",
fontsize=fontsize * TEXTBOX_FONT_SCALE,
lineheight=1.5,
align=fitz.TEXT_ALIGN_LEFT,
)
else:
page.insert_text( page.insert_text(
rect.bl, rect.bl,
formatted, formatted,
Binary file not shown.
Binary file not shown.