deepseek den plan umsetzen lassen
This commit is contained in:
1 parent
6c60a72e0f
commit
044e4df783
16 files changed
+971
-55
No files matched your search
+158
@@ -0,0 +1,158 @@
|
||||
"""Template-basierte Activity-Report-Generierung."""
|
||||
|
||||
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
|
||||
|
||||
|
||||
def _split_name_for_filename(name: str) -> tuple[str, str]:
|
||||
"""Teilt 'Nachname, Vorname' oder 'Vorname Nachname' in (Vorname, Nachname)."""
|
||||
cleaned = str(name or "").strip()
|
||||
if "," in cleaned:
|
||||
last_name, first_name = [p.strip() for p in cleaned.split(",", 1)]
|
||||
return first_name or "Unknown", last_name or "Unknown"
|
||||
parts = cleaned.split()
|
||||
if len(parts) >= 2:
|
||||
return parts[0], parts[-1]
|
||||
if len(parts) == 1:
|
||||
return parts[0], "Unknown"
|
||||
return "Unknown", "Unknown"
|
||||
|
||||
|
||||
def _format_value(value, fmt: str | None) -> str:
|
||||
"""Formatiert einen Wert gemäß Format-String."""
|
||||
if value is None:
|
||||
return ""
|
||||
if fmt is None:
|
||||
return str(value)
|
||||
# Numerische Formatierung (z.B. "%.2f")
|
||||
if "%" in fmt:
|
||||
try:
|
||||
return fmt % float(value)
|
||||
except (ValueError, TypeError):
|
||||
return str(value)
|
||||
return str(value)
|
||||
|
||||
|
||||
def _set_cell_value(ws, target_cell: str, value) -> None:
|
||||
"""Setzt einen Wert in einer Zelle des Worksheets.
|
||||
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:
|
||||
top_left = merged_range.min_row, merged_range.min_col
|
||||
ws.cell(row=top_left[0], column=top_left[1]).value = value
|
||||
return
|
||||
else:
|
||||
cell.value = value
|
||||
except (ValueError, KeyError):
|
||||
pass # Zelle existiert nicht
|
||||
|
||||
|
||||
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')."""
|
||||
monat_str = monat_str.strip()
|
||||
parts = monat_str.split(".")
|
||||
if len(parts) == 2:
|
||||
month = parts[0]
|
||||
year = parts[1]
|
||||
if len(year) == 2:
|
||||
year = "20" + year
|
||||
return f"{month}.{year}"
|
||||
return monat_str
|
||||
|
||||
|
||||
def generate_activity_reports(
|
||||
employee_records: dict[str, list[EmployeeRecord]],
|
||||
template_path: Path,
|
||||
mapping: list[OutputMapping],
|
||||
output_dir: Path,
|
||||
filename_pattern: str,
|
||||
) -> list[Path]:
|
||||
"""
|
||||
Generiert für jeden Mitarbeiter eine Excel-Datei.
|
||||
Jeder Monat wird als eigenes Sheet im Template-Stil erstellt.
|
||||
"""
|
||||
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():
|
||||
if not records:
|
||||
continue
|
||||
|
||||
first_name, last_name = _split_name_for_filename(employee_name)
|
||||
safe_first = first_name.replace(" ", "_").replace("/", "_")
|
||||
safe_last = last_name.replace(" ", "_").replace("/", "_")
|
||||
|
||||
filename = filename_pattern.format(
|
||||
first_name=safe_first,
|
||||
last_name=safe_last,
|
||||
)
|
||||
output_path = output_dir / filename
|
||||
|
||||
# Lade Template
|
||||
wb = load_workbook(template_path)
|
||||
template_sheet = wb.worksheets[0]
|
||||
|
||||
# Sortiere Records nach Monat
|
||||
sorted_records = sorted(records, key=lambda r: r.month_index)
|
||||
|
||||
for idx, record in enumerate(sorted_records):
|
||||
if idx == 0:
|
||||
sheet = template_sheet
|
||||
else:
|
||||
sheet = wb.copy_worksheet(template_sheet)
|
||||
|
||||
# Sheet-Name: Monatsname
|
||||
month_names = [
|
||||
"", "Januar", "Februar", "März", "April", "Mai", "Juni",
|
||||
"Juli", "August", "September", "Oktober", "November", "Dezember"
|
||||
]
|
||||
mi = record.month_index
|
||||
sheet_name = f"{mi:02d}_{month_names[mi]}" if 1 <= mi <= 12 else f"Monat_{idx + 1}"
|
||||
sheet.title = sheet_name[:31]
|
||||
|
||||
# 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 hasattr(record, source_field.lower().replace(" ", "_")):
|
||||
value = getattr(record, source_field.lower().replace(" ", "_"))
|
||||
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
|
||||
else:
|
||||
continue
|
||||
|
||||
formatted = _format_value(value, m.format)
|
||||
_set_cell_value(sheet, m.target_cell, formatted)
|
||||
|
||||
# Setze aktuelles Datum für das Erstelldatum
|
||||
today = date.today().strftime("%d.%m.%Y")
|
||||
_set_cell_value(sheet, "K94", today)
|
||||
|
||||
wb.save(output_path)
|
||||
wb.close()
|
||||
generated_files.append(output_path)
|
||||
|
||||
return generated_files
|
||||
Reference in new issue
Block a user