template mit 36 Monaten erzeugt. Denn es werden keine sheets selber erstellt.

This commit is contained in:
trnq1ll0 committed 2026-06-09 10:14:35 +02:00
1 parent 56d605c0cb
commit 39d3ee8baf
2 files changed
+63 -59

No files matched your search

+63 -59
View File
@@ -1,11 +1,15 @@
"""Template-basierte Activity-Report-Generierung."""
"""Template-basierte Activity-Report-Generierung.
Verwendet ein Template mit bereits vor-duplizierten Sheets.
Es werden KEINE Sheets programmatisch erzeugt oder kopiert –
ausschließlich die vorhandenen Sheets mit Daten befüllt.
"""
from datetime import date
from pathlib import Path
from openpyxl import load_workbook
from openpyxl.cell.cell import MergedCell
from openpyxl.utils import column_index_from_string
from .models import EmployeeRecord, OutputMapping
@@ -30,7 +34,6 @@ def _format_value(value, fmt: str | None) -> str:
return ""
if fmt is None:
return str(value)
# Numerische Formatierung (z.B. "%.2f")
if "%" in fmt:
try:
return fmt % float(value)
@@ -44,7 +47,6 @@ def _set_cell_value(ws, target_cell: str, value) -> None:
Behandelt MergedCells, indem in die Top-Left-Zelle des Merge-Bereichs geschrieben wird."""
try:
cell = ws[target_cell]
# Wenn es eine MergedCell ist, finde die Top-Left-Zelle des Merge-Bereichs
if isinstance(cell, MergedCell):
for merged_range in ws.merged_cells.ranges:
if cell.coordinate in merged_range:
@@ -54,11 +56,11 @@ def _set_cell_value(ws, target_cell: str, value) -> None:
else:
cell.value = value
except (ValueError, KeyError):
pass # Zelle existiert nicht
pass
def _format_month_for_template(monat_str: str) -> str:
"""Konvertiert 'MM.JJ' zu 'MM.JJJJ' (z.B. '01.2025' → '01.2025', '01.25' → '01.2025')."""
"""Konvertiert 'MM.JJ' zu 'MM.JJJJ'."""
monat_str = monat_str.strip()
parts = monat_str.split(".")
if len(parts) == 2:
@@ -70,6 +72,42 @@ def _format_month_for_template(monat_str: str) -> str:
return monat_str
def _month_name(mi: int) -> str:
names = ["", "Januar", "Februar", "März", "April", "Mai", "Juni",
"Juli", "August", "September", "Oktober", "November", "Dezember"]
return names[mi] if 1 <= mi <= 12 else ""
def _fill_sheet(ws, record: EmployeeRecord, employee_name: str, field_map: dict) -> None:
"""Befüllt ein einzelnes Sheet mit den Daten eines EmployeeRecords."""
for source_field, m in field_map.items():
if source_field == "Monat (MM.JJ)":
value = _format_month_for_template(record.monat)
elif source_field == "Stellenanteil in %":
value = record.stellenanteil
elif source_field == "Name":
value = employee_name
elif source_field == "Antragsnummer":
value = record.antragsnummer
elif source_field == "Qualifikation":
value = record.qualifikation
elif source_field == "Funktion":
value = record.funktion
elif source_field == "Tätigkeit":
value = record.taetigkeit
elif source_field == "Ort":
value = "Dresden"
elif source_field == "Datum":
value = date.today().strftime("%d.%m.%Y")
else:
continue
formatted = _format_value(value, m.format)
_set_cell_value(ws, m.target_cell, formatted)
# Datum zusätzlich ins Unterschriftsfeld
_set_cell_value(ws, "J95", date.today().strftime("%d.%m.%Y"))
def generate_activity_reports(
employee_records: dict[str, list[EmployeeRecord]],
template_path: Path,
@@ -78,13 +116,13 @@ def generate_activity_reports(
filename_pattern: str,
) -> list[Path]:
"""
Generiert für jeden Mitarbeiter eine Excel-Datei.
Jeder Monat wird als eigenes Sheet im Template-Stil erstellt.
Befüllt vor-duplizierte Template-Sheets mit Mitarbeiterdaten.
Erzeugt KEINE neuen Sheets – verwendet ausschließlich die im Template
bereits vorhandenen Sheet-Kopien.
"""
output_dir.mkdir(parents=True, exist_ok=True)
generated_files: list[Path] = []
# Build source_field → mapping lookup
field_map: dict[str, OutputMapping] = {m.source_field: m for m in mapping}
for employee_name, records in employee_records.items():
@@ -95,65 +133,31 @@ def generate_activity_reports(
safe_first = first_name.replace(" ", "_").replace("/", "_")
safe_last = last_name.replace(" ", "_").replace("/", "_")
filename = filename_pattern.format(
first_name=safe_first,
last_name=safe_last,
)
filename = filename_pattern.format(first_name=safe_first, last_name=safe_last)
output_path = output_dir / filename
# Lade Template (bleibt unverändert als Kopiervorlage)
wb = load_workbook(template_path)
template_sheet = wb.worksheets[0]
# Sortiere Records nach Monat
sorted_records = sorted(records, key=lambda r: r.month_index)
total_needed = len(sorted_records)
available_sheets = wb.worksheets
if len(available_sheets) < total_needed:
wb.close()
raise RuntimeError(
f"Template hat nur {len(available_sheets)} Sheets, "
f"aber {total_needed} werden benötigt für '{employee_name}'."
)
for idx, record in enumerate(sorted_records):
# Immer vom sauberen Template kopieren – nie das Original beschreiben
sheet = wb.copy_worksheet(template_sheet)
# Sheet-Name: Monatsname + Funktionskürzel (erste 3 Buchstaben)
month_names = [
"", "Januar", "Februar", "März", "April", "Mai", "Juni",
"Juli", "August", "September", "Oktober", "November", "Dezember"
]
sheet = available_sheets[idx]
mi = record.month_index
func_abbr = record.funktion[:3] if record.funktion else ""
sheet_name = f"{mi:02d}_{month_names[mi]}_{func_abbr}" if 1 <= mi <= 12 else f"Monat_{idx + 1}"
sheet.title = sheet_name[:31]
sheet.title = f"{mi:02d}_{_month_name(mi)}_{func_abbr}"[:31]
_fill_sheet(sheet, record, employee_name, field_map)
# Befülle Zielfelder
for source_field, m in field_map.items():
if source_field == "Monat (MM.JJ)":
value = _format_month_for_template(record.monat)
elif source_field == "Stellenanteil in %":
value = record.stellenanteil
elif source_field == "Name":
value = employee_name
elif source_field == "Antragsnummer":
value = record.antragsnummer
elif source_field == "Qualifikation":
value = record.qualifikation
elif source_field == "Funktion":
value = record.funktion
elif source_field == "Tätigkeit":
value = record.taetigkeit
elif source_field == "Ort":
value = "Dresden"
elif source_field == "Datum":
value = date.today().strftime("%d.%m.%Y")
else:
continue
formatted = _format_value(value, m.format)
_set_cell_value(sheet, m.target_cell, formatted)
# Setze aktuelles Datum auch ins Unterschriftsfeld
today = date.today().strftime("%d.%m.%Y")
_set_cell_value(sheet, "J95", today)
# Entferne das leere Original-Template-Sheet
wb.remove(template_sheet)
# Lösche überzählige Sheets (von hinten nach vorne)
for extra in reversed(available_sheets[total_needed:]):
wb.remove(extra)
wb.save(output_path)
wb.close()