# Entwicklungsplan — Statistik-Änderungen vom 2026-08-19 Basis: `dev/2026-07-22/Statistik-Aenderungen-19-08-2026.pdf` (Kundin, Folge-Feedback zur Umsetzung vom 2026-07-24). Ist-Stand-Analyse: 2026-08-19. > **Umsetzungsstand (2026-08-19): alle 8 Pakete gemäß den beantworteten F-Fragen umgesetzt** — Details je Paket im Abschnitt „Umgesetzt". Zusammenfassung: > - **N7** ✅ USt-Optionen getauscht (Werte unverändert), Text „§ 19 UStG" repariert (de/fr). > - **N5** ✅ Checkout: Dropdown raus, Freitext Pflichtfeld; Altdaten/Marker kompatibel; Admin-Fallback-Kette. Tests: `CheckoutOrderSourceValidationTest`. > - **N8** ✅ Neue Seite „Jahresgebühr" für **jeden Berater** (F8: eigenes Team): `AnnualFeeDueService` + Route `user_team_annual_fee` + Sidenav-Eintrag, Gruppen überfällig/≤1/≤2/≤3 Monate. Tests: `AnnualFeeDueServiceTest`. > - **N1** ✅ Monatsstatistik: neues „Team"-Cluster (Firstlines klickbar → neue Seite `user_team_firstlines`, Teampartner gesamt [nur aktive, F1], Teampartner Payline [aktive, konsistent], Abos Payline mit „+N neu") + Level-Block (Quali-Stufe des Monats, fehlende Payline-/persönliche Punkte, Karriere-Level als Zusatz, F2). Basis: `LiveCommissionService`-Snapshot v2 reicht jetzt `qual_level`/`next_level`/`payline_points_qual_kp`/`personal_points` durch (Cache-Key `live_business_v2_*`). > - **N2** ✅ Abo-Kacheln monatsbezogen: `TeamAboOverviewService::aboStockCount()` rechnet für vergangene Monate den Bestand zum Monatsende zurück (start/cancel/deleted + Status-Historie); laufender Monat bleibt live; VIP-Statistik bleibt tagesaktuell (F3). Näherung vor 07/2026 dokumentiert. > - **N3** ✅ „Recruiter des Monats" auf der VIP-Statistik (Neupartner-Definition wie KPI, F4a) mit rekursivem Ketten-Volumen als **Gesamtpunkte** (Starterpaket zählt damit); aufklappbare Detailzeilen; Neupartner-Drilldown + CSV zusätzlich mit Spalte „Punkte gesamt" (F4b). > - **N4** ✅ Prognose-Block „Level & Provision laut Prognose (Stand 25.)" mit „unverbindlich"-Kennzeichnung (F5): `PaylineForecastService::levelAndCommissionForecast()` — Ebenen-aufgelöste offene Abo-Punkte + heutige TP-Laufstände, Level-Erreichung inkl. persönlichem Volumen, Provisionsschätzung mit den Sätzen des laut Prognose erreichten Levels (Shop/WB fortgeschrieben). > - **N6** ✅ Profilfoto (`user_accounts.profile_image`, Upload mit Einwilligungs-Checkbox im Profil, DSGVO-Löschung integriert; Einwilligungstext als Sprachkey-Platzhalter — **finalen Text liefert die Kundin nach**, F7c) + Karte „Die besten Ergebnisse des Monats" **teamintern** (F7a) auf dem Home-Dashboard: Top 3 persönliches Volumen / Neupartner / neue Abos (Kundenabos + Eigenabo, F7b) mit Foto bzw. Initialen-Avatar, 6-h-Cache, Scope-Schalter (`TopResultsService::SCOPE`) für eine spätere firmenweite Variante. Tests: `TopResultsServiceTest`. ## Wie ich das Briefing verstehe Das Feedback betrifft diesmal überwiegend die **Monatsstatistik-Card auf dem Home-Dashboard** (für alle Berater), dazu drei Punkte außerhalb der Statistik (Checkout-Herkunftsfeld, Steuerdaten-Formular, Jahresgebühr-Nachfassliste). Es zerfällt in 8 Pakete: | # | Paket | Bereich | Einstufung | Aufwand | |---|---|---|---|---| | N1 | Monatsstatistik: 7 neue Kennzahlen (Firstlines klickbar, Teampartner, Payline-Zahlen, Level) | Home | Erweiterung | mittel | | N2 | Abo-Kacheln beim Monatswechsel monatsbezogen statt eingefroren | Home | Verhaltensänderung | mittel | | N3 | „Recruiter des Monats" — Neupartner-Ranking je Sponsor inkl. Volumen + Starterpaket | VIP-Statistik | neue Liste + Zählweise-Fix | mittel | | N4 | Prognose: „fehlen zum nächsten Level X Punkte" + voraussichtliche Provision | Home | Erweiterung, **komplexester Punkt** | groß | | N5 | Checkout-Herkunft: Dropdown raus, nur Freitext | Shop-Checkout | kleine Änderung | klein | | N6 | Teampartner-Foto + „Die besten Ergebnisse des Monats" (3× Top 3) | Home + Profil | neues Feature | mittel–groß | | N7 | Steuerdaten: Reihenfolge der USt-Optionen tauschen | Profil/Wizard | Mini-Änderung | sehr klein | | N8 | Jahresgebühr-Nachfassliste (fällig in 3/2/1 Monaten + überfällig) | VIP-Statistik | neue Liste | klein–mittel | Empfohlene Reihenfolge: **N7 → N5 → N8 → N1 → N3 → N2 → N6 → N4** (Quick-Wins zuerst; N4 zuletzt, weil er auf N1-Bausteinen aufsetzt und die meisten Klärungen braucht). --- ## N1 — Monatsstatistik: neue Kennzahlen **Briefing:** Übersichtsliste ergänzen um: Firstlines (Anzahl, klickbar → Liste mit Umsatz, Telefon, Mail), Teampartner insgesamt, Teampartner in der Payline, Abos insgesamt Payline, Neue Abos in der Payline, aktuelles Level, „zum nächsten Level fehlen … Punkte" (+ ggf. fehlendes persönliches Volumen und was die Provision dann wäre). **Ist-Stand (gut vorbereitet):** - Firstline-IDs werden im [HomeController.php:127-132](app/Http/Controllers/HomeController.php#L127-L132) bereits berechnet (für „Direkte Neupartner"), die Anzahl wird aber nicht angezeigt. - Teampartner gesamt: `BackofficeDashboardService::teamUserIds()` / `activeTeamUserIds()` vorhanden. - Payline-Mitglieder + Abo-Zähler: `PaylineForecastService::aboTurnover()` liefert seit Juli `members`, `team_abo_count`, `team_customer_abo_count` — wird vom Home-Dashboard **nur nicht aufgerufen** (nur die Team-Abo-Seite nutzt es). - „Neue Abos in der Payline": existiert nicht, aber trivial — `newAboCount()`-Logik auf die Payline-Mitglieder-IDs einschränken (Eigen-Abos über `user_id`, Kundenabos über `member_id`, `start_date` im Monat). - Level: `LiveCommissionService::snapshot()` berechnet die Quali-Daten (`qual_user_level`, `next_qual/next_can_user_level` mit `_calculated_*`-Feldern) bereits im Live-Lauf, **wirft sie aber weg** — nur `paylines` wird durchgereicht ([LiveCommissionService.php:117-128](app/Services/BusinessPlan/LiveCommissionService.php#L117-L128)). Eine „Punkte bis zum nächsten Level"-Differenz wird heute nirgends berechnet (auch der Marketingplan zeigt nur die statischen Anforderungen). **Umsetzungsvorschlag:** 1. Neues KPI-Cluster „Team" auf der Monatsstatistik-Card: Firstlines · Teampartner gesamt · Teampartner Payline · Abos Payline (Teamabos + Kundenabos) · „+N neue Abos Payline" — alle Werte aus vorhandenen Services, gecacht im bestehenden 6-h-Muster. 2. **Firstlines klickbar:** neue leichte Liste (Modal oder eigene Seite unter `user/team`) für **alle** Berater — Spalten: Name, Karriere-Level, Umsatz (Monats-Gesamtpunkte), Telefon, E-Mail, aktiv seit / Account-Status. Die VIP-Drilldown-Infrastruktur ist VIP-only; für Firstlines (eigene direkte Partner) ist eine eigene, nicht-VIP-Route unbedenklich. 3. **Level-Kachel:** „Aktuelles Level (Monat): BRONZE — zum nächsten Level SILBER fehlen X Payline-Punkte" + falls die persönliche Anforderung (`qual_kp`) noch nicht erfüllt ist: „dafür fehlen außerdem Y Punkte persönliches Volumen". Rechnung aus dem Snapshot: `nextLevel.qual_pp − payline_points_qual_kp` bzw. `nextLevel.qual_kp − sales_volume_points_KP_sum`. Dazu Anzeige der Provisionssätze des nächsten Levels („dann z. B. Ebene 1–3 je 7 %") — die exakte „was wäre die Provision"-Rechnung gehört zu N4. 4. Voraussetzung: `LiveCommissionService::buildSnapshot()` um die Level-/Punktefelder erweitern (kein zweiter Baum-Lauf, nur Durchreichen). **Offene Fragen:** F1 (aktive vs. alle Teampartner), F2 (welches „Level" gemeint ist — siehe unten). ## N2 — Abo-Kacheln beim Monatswechsel monatsbezogen **Briefing:** Beim Monatswechsel auf der Monatsstatistik passen sich die Kennzahlen an, die 4 Abo-Kacheln (Eigene Abos, Eigene Kundenabos, Teamabos, Team-Kundenabos) aber nicht — sie sollen die Zahlen des gewählten Monats zeigen. **Befund bestätigt:** `TeamAboOverviewService::teamKpis()`/`personalAboKpis()` zählen den **heutigen** `activeAbo`-Bestand; `month`/`year` fließen nur in die „+N neu"-Badges. Auch die Team-Mitgliederbasis ist der heutige Strukturbaum. Es gibt keinerlei Home-Snapshot. **Wichtiger Kontext:** Für die **VIP-Statistik** wurde 2026-07 das **Gegenteil** entschieden (F7: Abo-Bestände immer tagesaktuell, Snapshot wird live überlagert). Der neue Wunsch gilt dem Home-Dashboard — das ist kein Widerspruch, sollte aber bewusst entschieden werden (→ F3). **Umsetzungsvorschlag (Variante A, empfohlen): Rückrechnung über Abo-Daten** — Bestand zum Monatsende M: `start_date <= Ende(M)` UND nicht vor Ende(M) beendet (`cancel_date`/`deleted_at`/`user_abo_status_histories` nach Ende(M) oder Abo heute aktiv). Das ist ohne neue Tabellen möglich und für die Vergangenheit eine gute Näherung; seit Juli 2026 protokolliert `user_abo_status_histories` Statuswechsel, wodurch die Rückrechnung ab da exakt wird. Einschränkung dokumentieren: Für Monate vor Juli 2026 können Deaktivierungen ohne `cancel_date` fehlen (Zahlen dann leicht zu hoch). **Variante B (verworfen):** monatliche Bestands-Snapshots je User — für ALLE Berater (Home ist nicht VIP-only) unverhältnismäßig teuer. Umsetzung: `teamKpis()`/`personalAboKpis()` bekommen einen „Stichtag"-Modus (laufender Monat = live wie bisher; vergangener Monat = Rückrechnung zum Monatsende). Struktur (Team-Mitglieder) bleibt pragmatisch die heutige. ## N3 — „Recruiter des Monats" (VIP-Statistik) **Briefing:** Neben „Neue Kundenabos" eine Liste der neuen Teampartner je Sponsor, sortiert nach Anzahl: `Name | Anzahl gesponserte Neupartner | neues Volumen`. Klick auf die Anzahl → wer wurde gesponsert + dessen aktueller Umsatz; hat der Neue selbst schon Neupartner geworben, zählt deren Umsatz dazu. **Und: die Starterpaket-Punkte zählen zum Volumen — „werden jetzt noch nicht gezählt".** **Ist-Stand / Aufklärung Starterpaket:** - Ein Sponsor-Ranking existiert nicht; Blaupause ist `newCustomerAboRanking()`. - Die Starterpaket-Punkte des Neupartners **werden** systemisch verbucht (Status 1, voll umsatz- und payline-wirksam, 200/400 P. je nach Kit). Was die Kundin vermutlich sieht: Die Spalte „Kundenpunkte" im Neupartner-Drilldown nutzt `externalPoints()` (nur Shop-/Kundenumsatz, Status 2) — die **Eigenbestellung Starterpaket (Status 1) fällt dort raus**. → Fix: im Drilldown und im neuen Ranking „Volumen" als **Gesamtpunkte** (eigene + Kundenpunkte, `totalPoints()`-Logik) rechnen, dann ist das Starterpaket automatisch drin. - Zwei konkurrierende Neupartner-Definitionen im System: Statistik (`active_date` im Monat + aktiver Account) vs. Incentive (bezahlte Starterpaket-Bestellung, `created_at`). → F4. **Umsetzungsvorschlag:** 1. Neue Karte „Recruiter des Monats" auf der VIP-Statistik (Scope: eigenes Team, inkl. „ich" wie bei den anderen Listen): je Sponsor Anzahl Neupartner des Monats + Volumen = Summe der Monats-**Gesamtpunkte** aller seiner Neupartner, **plus rekursiv** die Gesamtpunkte der von diesen Neupartnern im selben Monat geworbenen weiteren Neupartner (Kettenlogik wie im Beispiel der Kundin). 2. Drilldown (Klick auf Anzahl): Liste der geworbenen Personen mit Monats-Gesamtpunkten, darunter eingerückt deren eigene Neupartner mit Punkten (eine Rekursionsebene sichtbar, Summe vollständig rekursiv). 3. Nebenbei: Neupartner-Drilldown-Spalte „Kundenpunkte" um „Punkte gesamt (inkl. Starterpaket)" ergänzen oder ersetzen (→ F4b). 4. Landet mit im Monats-Snapshot (monatsbezogene Liste, kein Live-Overlay nötig). ## N4 — Prognose: nächstes Level + voraussichtliche Provision **Briefing:** Bei der Prognose ein Feld „laut Prognose fehlen zum nächsten Level (z. B. Bronze Member) noch 2300 Punkte" und „voraussichtliche Provision laut Prognose: 1200 € (sofern das persönliche Volumen von Y erreicht wird)". **Ist-Stand:** Die Prognose-Kacheln rechnen `basePoints (Kundenumsatz + Payline-Punkte heute) + offene Abo-Punkte bis Termin` — als **eine Gesamtzahl**, ohne Linien-/Ebenen-Zuordnung. Die Provisionsrechnung braucht aber Punkte **pro Ebene** (Sätze `pr_line_1..8` je Level) und die Level-Qualifikation braucht die Trennung „persönliches Volumen (qual_kp)" vs. „Payline-Punkte (qual_pp)". **Umsetzungsvorschlag (zweistufig):** 1. **Stufe 1 — Level-Prognose (solide machbar):** Die offenen Abo-Ausführungen sind Payline-Mitgliedern zuordenbar (Ebene bekannt aus `lineBuckets`), eigene/eigene Kundenabos zählen aufs persönliche Volumen. Damit: prognostizierte `payline_points_qual_kp` und prognostiziertes persönliches Volumen zum 25. → daraus „laut Prognose fehlen zum nächsten Level X noch N Punkte" bzw. „nächstes Level wird laut Prognose erreicht, sofern das persönliche Volumen von Y P. erreicht wird". 2. **Stufe 2 — Provisions-Prognose (Näherung, klar kennzeichnen):** prognostizierte Punkte je Ebene × Provisionssätze des laut Prognose erreichten Levels (+ ggf. Wachstumsbonus ausklammern oder mit heutigem Wert fortschreiben). Das ist eine **Schätzung** — Rangverschiebungen im Team, 60%-Regeln etc. bleiben außen vor. Im UI als „voraussichtlich, unverbindlich" kennzeichnen (wie die bestehenden Live-Badges). 3. Technisch: Erweiterung von `PaylineForecastService` (Ebenen-aufgelöste `upcoming`-Punkte) + `LiveCommissionService` (Level-Daten durchreichen, s. N1.4). Kein neuer Baum-Lauf. **Risiko/Empfehlung:** größter Einzelaufwand des Pakets; erst nach N1 umsetzen und die Genauigkeitserwartung vorher mit der Kundin abstimmen (→ F5). ## N5 — Checkout-Herkunft: nur noch Freitext **Briefing:** Statt Dropdown („Sonstiges/Event-Messe sagt uns nichts") nur das Freitextfeld stehen lassen, damit Kunden den konkreten Namen/die konkrete Messe eintragen. **Ist-Stand:** Kunden-Checkout hat Dropdown `customer_order_source` (7 Optionen, Pflicht) **plus optionales Freitextfeld** `customer_order_source_comment` — beides an `shopping_orders` ([checkout.blade.php:556-578](resources/views/web/templates/checkout.blade.php#L556-L578), Optionen in [ShoppingOrder.php:192-200](app/Models/ShoppingOrder.php#L192-L200)). Es gibt **keine** Auswertung/Statistik dazu; Admin zeigt den Wert nur in der Bestell-Detailansicht. Achtung: `CreateAboReplacementOrder` nutzt das Comment-Feld als technischen Marker — Spalten dürfen nicht entfallen. **Umsetzungsvorschlag:** Dropdown aus dem Checkout entfernen, Freitextfeld wird das (einzige) Herkunftsfeld — als Pflichtfeld mit sprechendem Label („Wie bist du auf uns aufmerksam geworden? Bitte konkret: Name der Person, Messe, …"). DB-seitig: `customer_order_source` bleibt bestehen (Bestandsdaten + Marker), neue Bestellungen speichern nur noch den Freitext. Admin-Detailansicht zeigt künftig den Freitext. Aufwand klein. → F6 (Pflichtfeld ja/nein). ## N6 — Teampartner-Foto + „Die besten Ergebnisse des Monats" **Briefing:** (a) Feld, in dem jeder Teampartner ein Foto hochlädt; (b) auf der Startseite „Die besten Ergebnisse des Monats": Top 3 persönliches Volumen, Top 3 meiste Neupartner, Top 3 meiste neue Abos — je mit Foto + Name + Wert. Teamintern oder firmenintern ist **laut Kundin noch offen**. **Ist-Stand:** Es gibt **kein** Berater-Profilbild. Das einzige Foto ist das Shop-Bild (`user_shops.filename`, Slim-Cropper-Upload, nur mit eigenem Shop, Anzeige nur im öffentlichen Shop). Ranglisten-Datenquellen existieren teilweise (Neupartner je Sponsor → N3; neue Kundenabos je Berater → vorhandene Liste; persönliches Monatsvolumen je User → `user_sales_volumes`-Aggregat, firmenweit bisher nirgends). **Umsetzungsvorschlag:** 1. **Profilfoto** als eigenes Feature (unabhängig vom Shop): Spalte an `user_accounts` (oder kleine Tabelle), Upload im Profil (`user_form`/Wizard) mit dem vorhandenen Cropper-Muster aus `UserShopController`, Fallback Initialen-Avatar, DSGVO: Aufnahme in `GdprDeletionService` + Hinweis, dass das Foto team-/firmenintern angezeigt wird (Einwilligung beim Upload). 2. **Top-3-Karte** auf dem Home-Dashboard: drei Spalten (Volumen / Neupartner / neue Abos), je Top 3 mit Foto, Vor-/Nachname, Wert. Datenlogik: persönliches Volumen = Monats-Gesamtpunkte je Berater; Neupartner = N3-Zählung; neue Abos = neue Kundenabos + neues Eigenabo je Berater (→ F7b). Nächtliche Berechnung/Cache (firmenweit ein Ranking für alle) statt Live-Query pro Aufruf. 3. **Scope-Schalter teamintern/firmenintern** von Anfang an vorsehen (eine Konstante/Setting), Start laut Rückfrage-Ergebnis (→ F7a). ## N7 — Steuerdaten: Reihenfolge der USt-Optionen **Briefing:** „nicht umsatzsteuerpflichtig" soll an erster Stelle stehen. **Ist-Stand:** Hardcodiertes Array in [HTMLHelper.php:399-411](app/Services/HTMLHelper.php#L399-L411) (`1 => umsatzsteuerpflichtig, 2 => nicht umsatzsteuerpflichtig`), gespeichert als `user_accounts.taxable_sales`. Reihenfolge tauschen = Array-Reihenfolge ändern, **Werte bleiben unverändert** (1/2 werden downstream von Gutschriften/DATEV interpretiert, Wert 3 wird intern für Ausland gesetzt — nicht anfassen). **Mitzunehmende Nebenbefunde:** Der Text `taxable_sales_2` ist abgeschnitten und nutzt `$` statt `§` („Kleinunternehmer im Sinne von $ 19") → korrigieren auf „… im Sinne von § 19 UStG)". Aufwand: Minuten. Keine offenen Fragen. ## N8 — Jahresgebühr-Nachfassliste **Briefing:** Liste aller Teampartner (komplettes Team), bei denen die Jahresgebühr in 3/2/1 Monaten fällig oder überfällig ist — Spalten Vorname, Nachname, Mail, Telefon, Ebene, Level — „damit wir nachfassen können". **Ist-Stand:** `users.payment_account` = „bezahlt bis"; Verlängerung +1 Jahr bei Zahlung; Erinnerungs-Mails macht der Cron `payments:check-accounts` (21/14/2 Tage vorher), 2 Wochen nach Ablauf deaktiviert `user:cleanup`, nach 2 Monaten wird konvertiert/gelöscht. **Einen „läuft bald ab"-Report gibt es weder für Admin noch Berater** — nur binäre aktiv/inaktiv-Filter in der Team-Liste. **Umsetzungsvorschlag:** Neue Karte/Unterseite auf der VIP-Statistik „Jahresgebühr ausstehend": komplettes Team, gruppiert in **überfällig / fällig ≤ 1 Monat / ≤ 2 Monate / ≤ 3 Monate** (aus `payment_account`), Spalten Vorname, Nachname, Mail, Telefon (aus `user_accounts`), Ebene (Linie aus `lineBuckets`), Karriere-Level, Fälligkeitsdatum; CSV-Export wie bei den anderen Drilldowns. Wichtig: Anzeige „überfällig" nur bis zur Cleanup-Grenze (nach 2 Monaten verschwinden die User systemisch) — mit Hinweis darauf. → F8 (nur VIP oder jeder Berater fürs eigene Team?). --- ## Offene Fragen an die Kundin (vor bzw. während der Umsetzung) | # | Frage | Empfehlung | |---|---|---| | F1 | N1: „Teampartner insgesamt" — nur aktive Berater (wie die „Berater"-Spalte der Statistik) oder alle inkl. inaktiver? | aktive | nur Aktive | F2 | N1/N4: „Aktuelles Level" — die monatliche Qualifikationsstufe (bestimmt Payline/Provision, dazu passt „es fehlen X Punkte") oder das Karriere-Level (steht bereits in der Mitgliedschafts-Card)? | Quali-Stufe des Monats, mit Karriere-Level als Zusatzinfo | JA | F3 | N2: Home zeigt künftig Monats-Bestand (Rückrechnung; vor 07/2026 Näherung) — VIP-Statistik bleibt wie 07/2026 entschieden tagesaktuell? | ja, so lassen; sonst in einem Zug mit umstellen | JA | F4 | N3: Neupartner-Definition fürs Ranking — wie die Statistik-KPI (`aktiv geworden im Monat`) oder wie das Incentive (bezahltes Starterpaket im Monat)? (b) Neupartner-Drilldown: Spalte „Kundenpunkte" ersetzen durch „Punkte gesamt" oder beide zeigen? | (a) wie Statistik-KPI, damit Zahlen zusammenpassen; (b) beide | a Wie KPI | F5 | N4: Provisions-Prognose ist eine gekennzeichnete Schätzung (Rangverschiebungen/Boni nicht exakt) — reicht das? | ja, mit „unverbindlich"-Kennzeichnung | Ja mit Kennzeichnung | F6 | N5: Freitext-Herkunft als Pflichtfeld? | ja, Pflicht mit Beispiel-Platzhalter | Ja | F7 | N6: (a) Top 3 teamintern oder firmenintern? (b) „meiste neue Abos" = neue Kundenabos (+ Eigenabo) je Berater? (c) Foto-Einwilligungstext | (a) firmenintern hinter Schalter, (b) neue Kundenabos + Eigenabo, (c) Einwilligung beim Upload | a teamintern b neue Kundenabos + Eigenabo c Einwilligung beim Upload - lagern wird aber nach | F8 | N8: Jahresgebühr-Liste nur VIP-Statistik oder für jeden Berater (eigenes Team)? | zunächst VIP | eigenes team ## Kontroll-Review (2026-08-19, /code-review high mit Fix-Anwendung) Nach der Umsetzung lief eine mehrstufige Review (8 Finder-Perspektiven) über den gesamten Diff. **Angewendete Fixes:** **Korrektheit:** - **Level-Kachel/Prognose zeigten für Normalfälle „Höchstes Level erreicht"**: `qual_user_level_next` ist im Steady-State (Quali == Karriere-Level) bewusst null — `LiveCommissionService::resolveNextLevel()` fällt jetzt auf `next_qual_user_level`/`next_can_user_level` (direkt nächster Karriere-Level, inkl. `_calculated_*`) zurück. Verifiziert an Steady-State-User (Berater → Aktiv Berater). - **„fehlen X Punkte" bei mehr Paylines im nächsten Level überhöht**: Basis ist jetzt `_calculated_payline_points_qual_kp` des NÄCHSTEN Levels (rechnet mit dessen Paylines/KP-Überschuss). - **Historischer Abo-Bestand (N2) konnte deaktivierte Abos „wiederbeleben"**, wenn ein späteres Ereignis (z. B. Löschung) nach dem Stichtag lag: die Rückrechnung prüft jetzt zusätzlich, ob das letzte Statusereignis bis zum Stichtag deaktivierend war (inkl. Reaktivierungs-Logik). Test erweitert. - **Top-3-Karte schrumpfte**, wenn ein Top-Kandidat keinen Account-Datensatz hat: Nachrücker füllen jetzt auf. - **Payline-Abo-Kachel widersprach beim Monatswechsel den N2-Kacheln** (live vs. Monatsbestand): zählt jetzt ebenfalls monatsbezogen über `aboStockCount()`. - Checkout-Pflichtfeld-Meldung (N5) war hardcodiert deutsch → Sprachkey `order.source_comment_required` (de/en/es/fr). **Performance:** - `lineBuckets()`-Baum-Traversierung lief pro Dashboard-Request 3–5×: jetzt Singleton + Prozess-Memo (`flushLineBucketsMemo()` für Batch/Struktur-Änderungen; `storeSnapshot()` flusht selbst). - `levelAndCommissionForecast()` war als einzige neue Kennzahl ungecacht → 6-h-Cache (`level_forecast_*`). - Snapshot-Cache-Key auf `live_business_v3_*` (Payload-Format geändert). **Wiederverwendung/Konventionen:** - Neue User-Scopes `firstlinesOf()` und `newPartnerIn()` — EINE Definition für Firstlines (Kachel, Seite, Top 3) und Neupartner (KPI, Recruiter-Ranking, Top 3) statt 3–4 Kopien. - `SalesVolumePoints::totalPointsByUser()` als gemeinsamer Batch-Helper (Firstlines-Seite, Recruiter-Volumen, Top-3-Volumen). - Slim-Cropper-Speicherlogik nach `Slim::storeCroppedImage()` extrahiert (Profilfoto + beide Shop-Uploads nutzen sie). - `activeAbo`-Scope statt handgeschriebener Prädikate (Payline-Zähler, Top 3, N2-Rückrechnung); `formatDate()`/`getFullName(false)` statt Inline-Kopien; Firstlines-Filter auf `Form::open` (Projektregel); Return-Types ergänzt. - Neue Tests: `TeamFirstlinesPageTest`, `ProfileImageTest` (die einzigen bis dahin ungetesteten neuen Endpunkte). **Nachtrag (gleicher Tag, vom Kunden freigegeben) — die drei gemeldeten Punkte sind umgesetzt:** 1. ✅ **VP-Nachtreparatur gehärtet:** Der Live-Pfad (`Payment::addSponsorBuyingPoints` → `SalesPointsVolume::addSalesPointsVolume`) schreibt jetzt die `shopping_order_id` mit; der Repair dedupliziert primär darüber (immun gegen Umbenennungen), Alt-Zeilen ohne Referenz werden **zählend** je info-Text verbraucht (zwei gleiche Bestellungen ≠ eine). Scan-Fenster umfasst den **Vormonat** (Monatswechsel-Fälle heilen automatisch, Buchung weiterhin nur in den Zielmonat). Dedup ist **sponsor-unabhängig** mit zwei Fällen (Kundenentscheid 2026-08-19): Liegt die Buchung beim **Firmenaccount** (`config('mivita.company_user_id')`, User 6 — Registrierung über die Hauptwebseite ist ein User-Fehler, Punkte stehen immer dem Berater zu), wird **automatisch umgebucht** — im Zielmonat in place (eine Zeile, Syslog-Spur), aus einem abgeschlossenen Monat per neuer Buchung beim Berater + Gegenbuchung („VP-Storno") beim Firmenaccount im Zielmonat (der alte Monat bleibt unangetastet). Liegt sie bei einem anderen **Berater** (echte Umhängung), wird weiterhin nur gewarnt (`skipped_other_sponsor` + Command-Tabelle + Log). Am Echtfall Anna Schall (Order 58797) auf dem Testsystem ausgeführt: Juli-Zeile bei #6 unverändert, Christina Schuder (#1241) +200 P. im August mit Order-Referenz, Firmenaccount −200 P. im August; Folgelauf idempotent. Käufer/Sponsoren und Bestandszeilen werden gebatcht geladen (N+1 weg). 5 neue Tests, 12/12 grün. 2. ✅ **Incentive-Reparatur gehärtet:** `removeStaleSponsorTrackings()` überspringt `m_sponsor = NULL` (nur echter Sponsorwechsel löscht, mit Log-Spur); `repairForeignKeys()` läuft jetzt VOR der Stale-Bereinigung (Waisen-Logs ohne FK werden erst angebunden und dann konsistent mitbereinigt — vorher Doppelzählung möglich); N+1-Queries in Stale-Check und `rebuildFromSourceTables()` durch gebatchte Lookups ersetzt; Qualifikationsfenster zentral über neue Helper `Incentive::qualificationWindow()`/`qualificationWindowContains()` + Scope (vorher 4 unabhängige Kopien — die Fehlerklasse des Dino-Portaro-Randtag-Bugs). Kleine dokumentierte Abweichung: `registered_at` beim Rebuild = `MIN(created_at)` der Starterkit-Orders (statt „erste nach id"). 6 neue Tests (u. a. NULL-Sponsor bleibt erhalten, Randtag 23:15 zählt, End-to-End ohne Doppelzählung nach Umhängung). 3. ✅ **`.env` aus dem Git-Tracking entfernt** (`git rm --cached`; `.gitignore` enthielt sie bereits, `.env.example` existiert). Die Datei bleibt lokal/auf dem Server liegen — Pflege wie bisher per SFTP (vom Kunden bestätigt). Empfehlung bleibt: die bereits historisierten Keys (PAYONE, Stripe-Test, DHL) bei Gelegenheit rotieren. ## Technische Querschnittsthemen - **`LiveCommissionService::buildSnapshot()` erweitern** (Level-/Punktefelder durchreichen) ist gemeinsame Voraussetzung für N1 und N4 — einmal bauen, zweimal nutzen. Cache-Key-Version erhöhen, damit alte 6-h-Caches nicht mit schmalem Payload kollidieren. - **Performance Home-Dashboard:** alle neuen Kennzahlen in die bestehenden 6-h-Caches integrieren (`home_payline_*`-Muster); keine zusätzlichen Baum-Läufe. - **Tests:** je Paket Pest-Feature-Tests analog der Juli-Suite (`BackofficeStatisticsListsTest`-Muster, Helfer mit eindeutigem Prefix, keine `route()`-Nutzung). - **Unkommittetes WIP beachten:** `SponsorBuyingPointsRepairService` + `RepairSponsorBuyingPoints` (Nacht-Repair der Sponsor-VP-Zeilen) liegen ungetrackt im Arbeitsverzeichnis und berühren dieselbe Status-5-Logik wie N3 — vor Beginn von N3 committen/abstimmen.