"""Export der Händler-Abrechnung als Excel – Aufbau wie „so soll es zum schluss ausschauen.xlsx“. Spalten: A Zeitraum | B Provisionsart | C Unterhändler-/VO-Nr | D Datum | E Rufnummer | F IMEI/SN | G Gerät | H Vorname | I Name | J Tarif | K Betrag (Auszahlung) | L Netz | M Dienst | N Aktionscode Sortierung: Zeitraum -> Provisionsart -> Datum (sonst Reihenfolge der Abrechnung). """ from __future__ import annotations import re from datetime import date from pathlib import Path from provtool.importer import ANBIETER from provtool.money import to_euro SPALTEN = [ ("Zeitraum", 20), ("Provisionsart", 38), ("Unterhändler", 11), ("Datum", 11), ("Rufnummer", 13), ("IMEI/SN", 16), ("Gerät", 16), ("Vorname", 14), ("Name", 30), ("Tarif", 34), ("Betrag", 12), ("Netz", 7), ("Dienst", 22), ("Aktionscode", 12), ] EURO_FORMAT = '_-* #,##0.00\\ "€"_-;\\-* #,##0.00\\ "€"_-;_-* "-"??\\ "€"_-;_-@_-' def sortiert(positionen: list[dict]) -> list[dict]: return sorted(positionen, key=lambda p: (p.get("zeitraum") or "", p.get("provisionsart") or "", p.get("datum") or "", p.get("reihenfolge") or 0)) def _zahl_oder_text(s: str): """Rufnummern/IMEI als Zahl (Format „0“) wie in der Vorlage – führende Nullen bleiben als Text erhalten.""" s = (s or "").strip() if s.isdigit() and not s.startswith("0") and len(s) <= 15: return int(s) return s or None def dateiname(text: str) -> str: return re.sub(r'[<>:"/\\|?*\x00-\x1f]', "_", text).strip(" .") or "Export" def schreibe_xlsx(ziel: str | Path, positionen: list[dict], *, kopfzeile: bool = False, summe: bool = True, blattname: str = "Tabelle1") -> Path: import openpyxl from openpyxl.styles import Font from openpyxl.utils import get_column_letter ziel = Path(ziel) wb = openpyxl.Workbook() ws = wb.active ws.title = blattname[:31] or "Tabelle1" font = Font(name="Aptos Narrow", size=10) bold = Font(name="Aptos Narrow", size=10, bold=True) zeile = 1 if kopfzeile: for c, (titel, _) in enumerate(SPALTEN, start=1): ws.cell(zeile, c, titel).font = bold ws.freeze_panes = "A2" zeile += 1 erste = zeile for p in sortiert(positionen): werte = [ p.get("zeitraum") or None, p.get("provisionsart") or None, _zahl_oder_text(p.get("kennung")), date.fromisoformat(p["datum"]) if p.get("datum") else None, _zahl_oder_text(p.get("rufnummer")), _zahl_oder_text(p.get("imei")), p.get("geraet") or None, p.get("vorname") or None, p.get("name") or None, p.get("tarif") or None, to_euro(p["betrag"]), p.get("netz") or None, p.get("dienst") or None, p.get("aktionscode") or None, ] for c, v in enumerate(werte, start=1): cell = ws.cell(zeile, c, v) cell.font = font ws.cell(zeile, 4).number_format = "DD.MM.YYYY" ws.cell(zeile, 5).number_format = "0" ws.cell(zeile, 6).number_format = "0" ws.cell(zeile, 11).number_format = EURO_FORMAT zeile += 1 letzte = zeile - 1 if summe and letzte >= erste: s = ws.cell(zeile + 1, 11, f"=SUM(K{erste}:K{letzte})") s.number_format = EURO_FORMAT s.font = bold ws.cell(zeile + 1, 10, "Summe").font = bold for c, (_, breite) in enumerate(SPALTEN, start=1): ws.column_dimensions[get_column_letter(c)].width = breite ws.page_setup.orientation = "landscape" ws.page_setup.fitToWidth = 1 ws.page_setup.fitToHeight = 0 ws.sheet_properties.pageSetUpPr.fitToPage = True ziel.parent.mkdir(parents=True, exist_ok=True) wb.save(ziel) return ziel def exportiere_haendler(ordner: str | Path, haendler_name: str, positionen: list[dict], titel: str, *, pro_anbieter: bool = True, kopfzeile: bool = False, summe: bool = True) -> list[Path]: """Schreibt eine (oder je Anbieter eine) Datei für einen Händler. Gibt die erzeugten Pfade zurück.""" ordner = Path(ordner) if not positionen: return [] gruppen: dict[str, list[dict]] = {} if pro_anbieter: for p in positionen: gruppen.setdefault(p["anbieter"], []).append(p) else: gruppen[""] = positionen out = [] for anbieter, pos in sorted(gruppen.items()): teile = [haendler_name] + ([ANBIETER.get(anbieter, anbieter)] if anbieter else []) + [titel] name = dateiname(" - ".join(t for t in teile if t)) + ".xlsx" out.append(schreibe_xlsx(ordner / name, pos, kopfzeile=kopfzeile, summe=summe)) return out