Compare commits
5
Commits
a4a678d11e
...
3e9cd39859
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e9cd39859 | ||
|
|
9bdd90c073 | ||
|
|
e50f423304 | ||
|
|
ef219e00ba | ||
|
|
30f3ffaded |
No files matched your search
+46
@@ -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
@@ -22,7 +22,7 @@ from src.config import (
|
||||
get_available_templates,
|
||||
)
|
||||
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
|
||||
|
||||
|
||||
@@ -70,6 +70,18 @@ def main():
|
||||
total = tpl_result.info.get("total_fields", "?")
|
||||
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 ──────────────────────────────────────────────────
|
||||
print()
|
||||
print("[INFO] Parse Input-Datei…")
|
||||
|
||||
@@ -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": "Stellenanteil in %", "pdf_field": "Stellenanteil_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": "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-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}
|
||||
]
|
||||
@@ -81,6 +81,49 @@ def validate_input_file(
|
||||
info["sheets_with_data"] = sheets_with_data
|
||||
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:
|
||||
errors.append("Kein Sheet mit den erwarteten Spalten gefunden.")
|
||||
|
||||
|
||||
+35
-3
@@ -169,6 +169,12 @@ def _fill_pdf_pages(
|
||||
field_info: dict[str, dict],
|
||||
) -> None:
|
||||
"""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:
|
||||
if m.pdf_field is None:
|
||||
continue
|
||||
@@ -188,12 +194,38 @@ def _fill_pdf_pages(
|
||||
value = _get_field_value(record, employee_name, m.source_field)
|
||||
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.apply_redactions()
|
||||
|
||||
# Neuen Text einfügen
|
||||
if formatted:
|
||||
if not 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(
|
||||
rect.bl,
|
||||
formatted,
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user