diff --git a/src/writer.py b/src/writer.py index 4dfe0b8..ecc40bc 100644 --- a/src/writer.py +++ b/src/writer.py @@ -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() diff --git a/template/Tätigkeitsnachweise Stellenförderung.xlsx b/template/Tätigkeitsnachweise Stellenförderung.xlsx index 4abb9a6..c60d9e3 100644 Binary files a/template/Tätigkeitsnachweise Stellenförderung.xlsx and b/template/Tätigkeitsnachweise Stellenförderung.xlsx differ