"""Besondere Provisionsarten: Airtime und Jahresbonus Neuaktivierung (Staffel). Typ einer Position ------------------ - ``normal`` – Auszahlung = Prozent der Provision (Händler-Standard bzw. Regel je Provisionsart) - ``airtime`` – Auszahlung = Airtime-Prozent des Händlers (0 % = keine Airtime). Im Export als eine Summenzeile in der Abrechnung, die Einzelposten in einer eigenen Airtime-Liste. - ``jahresbonus`` – Jahresbonus Neuaktivierung: Betrag je Karte nach Staffel, abhängig davon, wie viele Karten der Händler in der Abrechnung hat (z. B. 1–9 Karten: 5 € je Karte ... ab 40: voll). Der Typ wird aus Provisionsart/Dienst/Tarif erkannt und kann je Provisionsart fest eingestellt werden. """ from __future__ import annotations import json NORMAL, AIRTIME, JAHRESBONUS = "normal", "airtime", "jahresbonus" TYPEN = {NORMAL: "Normale Provision", AIRTIME: "Airtime", JAHRESBONUS: "Jahresbonus Neuaktivierung (Staffel)"} # Staffel: [(ab Anzahl Karten, Cent je Karte oder None = voller Betrag laut Abrechnung), ...] Staffel = list[tuple[int, "int | None"]] STANDARD_STAFFEL: Staffel = [(1, 500), (10, 1000), (15, 1500), (20, 2000), (25, 2500), (30, 3000), (35, 3500), (40, None)] _AIRTIME_WOERTER = ("airtime", "air time", "air-time", "umsatzbeteiligung") def _text(*teile) -> str: return " ".join(str(t or "") for t in teile).lower() def erkennen(provisionsart: str | None, dienst: str | None = None, tarif: str | None = None) -> str: """Typ aus den Texten der Position erkennen (ohne feste Einstellung).""" art = _text(provisionsart) if "jahresbonus" in art or ("bonus" in art and "neuaktiv" in art): return JAHRESBONUS if any(wort in _text(provisionsart, dienst, tarif) for wort in _AIRTIME_WOERTER): return AIRTIME return NORMAL def typ_von(p: dict, fest: dict[str, str]) -> str: """Typ einer Position: feste Einstellung der Provisionsart, sonst automatisch erkannt.""" art = p.get("provisionsart") or "" if art in fest: return fest[art] return erkennen(art, p.get("dienst"), p.get("tarif")) # --------------------------------------------------------------------------- Staffel def staffel_pruefen(staffel) -> Staffel: """Normalisiert und prüft eine Staffel (aufsteigend, ab >= 1, Beträge >= 0).""" out: Staffel = [] for ab, cent in staffel: ab = int(ab) if ab < 1: raise ValueError("Die Anzahl Karten muss mindestens 1 sein.") if cent is not None: cent = int(cent) if cent < 0: raise ValueError("Der Betrag je Karte darf nicht negativ sein.") out.append((ab, cent)) out.sort(key=lambda s: s[0]) if len({ab for ab, _ in out}) != len(out): raise ValueError("Jede Stufe braucht eine andere Anzahl Karten.") if not out: raise ValueError("Die Staffel braucht mindestens eine Stufe.") return out def staffel_laden(text: str | None) -> Staffel | None: if not text: return None try: return staffel_pruefen(json.loads(text)) except (ValueError, TypeError): return None def staffel_text(staffel: Staffel) -> str: return json.dumps([[ab, cent] for ab, cent in staffel]) def stufe(staffel: Staffel, karten: int) -> tuple[bool, int | None]: """(gefunden, Cent je Karte oder None = voll) für die Anzahl Karten.""" treffer = None for ab, cent in staffel: if karten >= ab: treffer = (True, cent) return treffer or (False, 0) def bonus_betrag(staffel: Staffel, karten: int, betrag_orig: int) -> int: """Auszahlung einer Jahresbonus-Position. Nie mehr als die Provision laut Abrechnung; Stornos (negative Beträge) werden mit derselben Stufe abgezogen.""" gefunden, cent = stufe(staffel, karten) if not gefunden: return 0 if cent is None: return betrag_orig if betrag_orig >= 0: return min(cent, betrag_orig) return -min(cent, -betrag_orig) def staffel_beschreibung(staffel: Staffel, kurz: bool = False) -> str: from provtool.money import fmt teile = [] for i, (ab, cent) in enumerate(staffel): bis = staffel[i + 1][0] - 1 if i + 1 < len(staffel) else None bereich = f"ab {ab}" if bis is None else (f"{ab}" if bis == ab else f"{ab}-{bis}") wert = "voll" if cent is None else fmt(cent) teile.append(f"{bereich}: {wert}") return (", " if kurz else "\n").join(teile)