Kompletter Umbau in einem Rutsch auf PDF als template Grundlage

This commit is contained in:
trnq1ll0 committed 2026-06-16 20:46:45 +02:00
1 parent dbe325d305
commit 169d2167e0
12 files changed
+421 -287

No files matched your search

+84 -77
View File
@@ -1,10 +1,10 @@
"""End-to-End-Test: Reale Input-Datei + reales Template → Excel-Output für erste Person."""
"""End-to-End-Test: Reale Input-Datei + reales PDF-Template → PDF-Output für erste Person."""
import sys
from pathlib import Path
import fitz # PyMuPDF
import pytest
from openpyxl import load_workbook
# Projekt-Root zum Python-Path hinzufügen
PROJECT_ROOT = Path(__file__).resolve().parent.parent
@@ -21,7 +21,7 @@ from src.writer import generate_activity_reports
CONFIG_DIR = PROJECT_ROOT / "config"
INPUT_FILE = PROJECT_ROOT / "input" / "2025 JM.xlsx"
TEMPLATE_FILE = PROJECT_ROOT / "template" / "Tätigkeitsnachweise Stellenförderung.xlsx"
TEMPLATE_FILE = PROJECT_ROOT / "template" / "Tätigkeitsnachweise Stellenförderung.pdf"
def _load_app_config():
@@ -62,20 +62,62 @@ def output_dir(tmp_path):
return out
# ──────────────────────────────────────────────
# Hilfsfunktion: Text aus PDF extrahieren
# ──────────────────────────────────────────────
def _get_pdf_text(pdf_path: Path) -> str:
"""Extrahiert den gesamten Text aus einem PDF."""
doc = fitz.open(pdf_path)
text = ""
for page in doc:
text += page.get_text()
doc.close()
return text
def _get_page_text(pdf_path: Path, page_num: int = 0) -> str:
"""Extrahiert Text von einer bestimmten PDF-Seite."""
doc = fitz.open(pdf_path)
text = doc[page_num].get_text() if page_num < doc.page_count else ""
doc.close()
return text
# ──────────────────────────────────────────────
# End-to-End Tests
# ──────────────────────────────────────────────
class TestEndToEndFirstPerson:
"""End-to-End: Reale Input-Datei + reales Template → nur erste Person."""
"""End-to-End: Reale Input-Datei + reales PDF-Template → nur erste Person."""
def test_input_file_exists(self):
"""Stellt sicher, dass die reale Input-Datei vorhanden ist."""
assert INPUT_FILE.exists(), f"Input-Datei nicht gefunden: {INPUT_FILE}"
def test_template_file_exists(self):
"""Stellt sicher, dass das reale Template vorhanden ist."""
assert TEMPLATE_FILE.exists(), f"Template nicht gefunden: {TEMPLATE_FILE}"
"""Stellt sicher, dass das reale PDF-Template vorhanden ist."""
assert TEMPLATE_FILE.exists(), f"PDF-Template nicht gefunden: {TEMPLATE_FILE}"
def test_template_has_field_markers(self, app_config):
"""Stellt sicher, dass alle _Field-Platzhalter im PDF-Template vorhanden sind."""
doc = fitz.open(TEMPLATE_FILE)
missing = []
for m in app_config.output_mapping:
if m.pdf_field is None:
continue
# Suche auf allen Seiten
found_any = False
for page_num in range(doc.page_count):
if doc[page_num].search_for(m.pdf_field):
found_any = True
break
if not found_any:
missing.append(m.pdf_field)
doc.close()
assert len(missing) == 0, (
f"Folgende _Field-Platzhalter fehlen im PDF: {missing}"
)
def test_parse_real_input(self, parsed_records):
"""Validiert das Parsen der realen Input-Datei."""
@@ -128,86 +170,51 @@ class TestEndToEndFirstPerson:
)
output_file = generated[0]
assert output_file.exists(), f"Output-Datei nicht gefunden: {output_file}"
assert output_file.suffix == ".xlsx"
assert output_file.suffix == ".pdf"
erwarteter_name = "Draßdo_Cornelia_Tätigkeitsnachweis.xlsx"
erwarteter_name = "Draßdo_Cornelia_Tätigkeitsnachweis.pdf"
assert output_file.name == erwarteter_name, (
f"Erwartet '{erwarteter_name}', aber '{output_file.name}'"
)
# 5. Output-Datei öffnen und Inhalt validieren
wb = load_workbook(output_file, data_only=True)
# 12 sichtbare Sheets, Rest (36 - 12 = 24) ausgeblendet
visible_sheets = [s for s in wb.worksheets if s.sheet_state == "visible"]
assert len(visible_sheets) == 12, (
f"Erwartet 12 sichtbare Sheets, aber {len(visible_sheets)}"
# 5. PDF öffnen und Inhalt validieren
doc = fitz.open(output_file)
assert doc.page_count == 24, (
f"Erwartet 24 Seiten (12 Monate × 2 Seiten), aber {doc.page_count}"
)
# Prüfe Sheet-Namen für alle 12 Monate
expected_titles = {
"01_Januar_Anl", "02_Februar_Anl", "03_März_Anl",
"04_April_Anl", "05_Mai_Anl", "06_Juni_Anl",
"07_Juli_Anl", "08_August_Anl", "09_September_Anl",
"10_Oktober_Anl", "11_November_Anl", "12_Dezember_Anl",
}
actual_titles = {ws.title for ws in visible_sheets}
assert actual_titles == expected_titles, (
f"Erwartet {expected_titles},\n aber {actual_titles}"
# 6. Prüfe, dass KEINE _Field-Platzhalter mehr im Output sind
full_text = ""
for page in doc:
full_text += page.get_text()
assert "_Field" not in full_text, (
"Es sind noch _Field-Platzhalter im Output-PDF vorhanden!"
)
# Prüfe exemplarisch das Januar-Sheet (01_Januar_Anl)
jan = wb["01_Januar_Anl"]
assert jan["B3"].value == "100740439", (
f"Erwartet '100740439' in B3, aber '{jan['B3'].value}'"
)
assert jan["B17"].value == "Draßdo, Cornelia", (
f"Erwartet 'Draßdo, Cornelia' in B17, aber '{jan['B17'].value}'"
)
# Stellenanteil: 0.5 → vom Parser zu "50" konvertiert (0 < x ≤ 1 → *100)
assert jan["R17"].value == "50", (
f"Erwartet '50' in R17, aber '{jan['R17'].value}'"
)
assert jan["B21"].value == "Meisterabschluß Küche", (
f"Falsche Qualifikation in B21: '{jan['B21'].value}'"
)
assert jan["R21"].value == "01.2025", (
f"Erwartet '01.2025' in R21, aber '{jan['R21'].value}'"
)
assert jan["B25"].value == "Anleiterin / Ausbilderin", (
f"Falsche Funktion in B25: '{jan['B25'].value}'"
)
# Tätigkeit beginnt mit "Tätigkeiten im Praxisbereich Garten…"
taetigkeit = jan["B31"].value or ""
assert taetigkeit.startswith("Tätigkeiten im Praxisbereich Garten"), (
f"Tätigkeit in B31 beginnt nicht wie erwartet: '{taetigkeit[:60]}…'"
)
# Ort wird hartkodiert auf "Dresden" gesetzt
assert jan["B95"].value == "Dresden", (
f"Erwartet 'Dresden' in B95, aber '{jan['B95'].value}'"
)
# Datum: letzter Tag des Monats Januar → 31.01.2025
assert jan["J95"].value == "31.01.2025", (
f"Erwartet '31.01.2025' in J95, aber '{jan['J95'].value}'"
)
# 7. Prüfe Seite 0 (Januar, Seite 1 – Stammdaten)
jan0_text = doc[0].get_text()
assert "100740439" in jan0_text, "Antragsnummer fehlt auf Januar-Seite 1"
assert "Draßdo, Cornelia" in jan0_text, "Name fehlt auf Januar-Seite 1"
assert "50" in jan0_text, "Stellenanteil fehlt auf Januar-Seite 1"
assert "Meisterabschluß Küche" in jan0_text, "Qualifikation fehlt auf Januar-Seite 1"
assert "01.2025" in jan0_text, "Monat fehlt auf Januar-Seite 1"
assert "Anleiterin / Ausbilderin" in jan0_text, "Funktion fehlt auf Januar-Seite 1"
assert "Tätigkeiten im Praxisbereich Garten" in jan0_text, "Tätigkeit fehlt auf Januar-Seite 1"
# Prüfe exemplarisch das Juli-Sheet (Stellenanteil wechselt auf 40%)
jul = wb["07_Juli_Anl"]
assert jul["R17"].value == "40", (
f"Erwartet '40' in R17 (Juli), aber '{jul['R17'].value}'"
)
assert jul["R21"].value == "07.2025", (
f"Erwartet '07.2025' in R21, aber '{jul['R21'].value}'"
)
# Datum Juli → 31.07.2025
assert jul["J95"].value == "31.07.2025", (
f"Erwartet '31.07.2025' in J95, aber '{jul['J95'].value}'"
)
# 8. Prüfe Seite 1 (Januar, Seite 2 – Unterschriftsblock mit Ort/Datum)
jan1_text = doc[1].get_text()
assert "100740439" not in jan1_text, "Antragsnummer sollte nicht auf Seite 2 sein"
assert "Dresden" in jan1_text, "Ort 'Dresden' fehlt auf Januar-Seite 2"
assert "31.01.2025" in jan1_text, "Datum '31.01.2025' fehlt auf Januar-Seite 2"
# Tätigkeit wiederholt auf Seite 2
assert "Tätigkeiten im Praxisbereich Garten" in jan1_text, "Tätigkeit fehlt auf Januar-Seite 2"
# Prüfe Dezember-Sheet (Datum: 31.12.2025)
dez = wb["12_Dezember_Anl"]
assert dez["J95"].value == "31.12.2025", (
f"Erwartet '31.12.2025' in J95, aber '{dez['J95'].value}'"
)
# 9. Prüfe Juli (Seite 12+13) – Stellenanteil wechselt auf 40%
jul0_text = doc[12].get_text()
assert "40" in jul0_text, "Stellenanteil 40% fehlt auf Juli-Seite 1"
assert "07.2025" in jul0_text, "Monat 07.2025 fehlt auf Juli-Seite 1"
wb.close()
jul1_text = doc[13].get_text()
assert "31.07.2025" in jul1_text, "Datum 31.07.2025 fehlt auf Juli-Seite 2"
doc.close()