# Entwicklungsplan — Kundenfeedback vom 2026-09-07 Basis: `dev/2026-07-22/mivita_Statistiken_07_09_2026.pdf` (Kundin, gesammelte Rückmeldung vom 30.08./07.09.). Ist-Stand-Analyse: 2026-09-07, gegen den Stand nach dem Livegang der 08/2026-Pakete. Das Feedback zerfällt in **15 Pakete plus 2 reine Rückfragen**. Vier Punkte sind reine Beschriftungs-/Spaltenänderungen, vier neue oder umgebaute Ansichten, zwei grundsätzliche Entscheidungen und vier Prozess-Automatisierungen außerhalb der Statistik. Zwei Punkte sind bereits umgesetzt und brauchen nur eine Antwort. > ## Umsetzungsstand 2026-09-07: K1–K9 umgesetzt > > Die Quick-Wins und die drei Ansichts-Pakete sind gebaut und getestet. **K10–K15 sind bewusst offen** — sie hängen an den Rückfragen F3–F8. > > | # | Status | Kurz | > |---|---|---| > | A1 | ✅ beantwortet | Läuft bereits: 6 Lieferungen / 6 Monate Pause / 3 Tage Sperrfrist. Setting `abo-pause-interval-months` sollte noch explizit auf 6 gesetzt werden (heute Code-Default). | > | A2 | ✅ bestätigt | Prognose-Punkte stimmen. | > | K1 | ✅ | Drei Spalten in „Top 10 Kundenabos (Umsatz)": Abgerechnet · davon reine Abo-Punkte · einmalig dazu. Neuer `customerAboAddonPoints()`; `points_recurring` wird als **Rest** aus dem Gesamtwert gebildet, damit beide Spalten immer die angezeigte Summe ergeben. | > | K2 | ✅ | „Teamabos (direkt)" und „Team-Kundenabos (direkt)" aus der Führungskräfte-Tabelle entfernt. Kopf-Summe bleibt (F1, Variante a) und ist jetzt per Tooltip und Fußnote als überschneidungsfreie Zählung erklärt. | > | K3 | ✅ | Unterzeile „Basis: Paylinepunkte + persönliches Volumen" unter der Payline-Prognose. | > | K4 | ✅ | Summe der Payline-Abos ergänzt: „149 Teamabos + 391 Kundenabos = 540 Abos gesamt". | > | K5 | ✅ | Abo-Bestellungen in der Punkteübersicht türkis mit Zusatz „(Abo)", plus Legende über der Tabelle. | > | K6 | ✅ | Telefonspalte (mit Mobil-Fallback) im Neupartner-Drilldown inkl. CSV; Spalten umbenannt in „Punkte seiner Kunden" und „Gesamt (inkl. Eigenbestellung)" mit erklärenden Tooltips. | > | K7 | ✅ | Summenzeile bei „Recruiter des Monats" — **dedupliziert**, nicht als Spaltensumme (sonst zählt das Kettenvolumen doppelt). Eigener Payload-Schlüssel `recruiter_totals`, ältere Snapshots bleiben lesbar. | > | K8 | ✅ | Eigene Kundenabos als sortierbare Liste mit Suche (Kunde · Liefertag · Punkte · nächste Ausführung · Startdatum · Ausführungen · Status). Abos mit offener Erstzahlung bleiben als Karte darüber, damit deren Aktionsbuttons erhalten bleiben. | > | K9 | ✅ | Liefertag-Vorschau 5./10./20./25. nur aus den eigenen Kundenabos, über der neuen Liste. | > > **Nennenswerte Umsetzungsentscheidungen** > > - **K1 an Produktivdaten geprüft:** Für 200 von 200 Abo-Bestellungen aus 08/2026 stimmt `SUM(shopping_order_items.points * qty)` exakt mit `user_sales_volumes.points` überein; die Positionspunkte sind Stückpunkte zum Bestellzeitpunkt. Es gibt produktiv 726 Positionen mit `is_abo_addon = 1`. Die Aufteilung ist damit belastbar — die Einschränkung „erst ab 06/2026 erfasst" steht als Tooltip an der Spalte. > - **Sortierlogik entdoppelt:** Die 80 Zeilen Sortier-/Such-JavaScript aus der Statistik-Detailseite liegen jetzt in `resources/views/components/_sortable_table.blade.php` und werden von beiden Seiten eingebunden, statt für K8 kopiert zu werden. > - **Termin-Logik entdoppelt:** `forecastDays()` und `personalCustomerAboForecast()` teilen sich `cumulateByDeliveryDay()` — es gibt weiterhin nur eine Stelle, die die vier Liefertermine kumuliert. > - **Yajra vs. clientseitig:** K8 nutzt bewusst das vorhandene clientseitige Sortier-Muster statt Yajra. Grund: „Anzahl Ausführungen" (`getCountPaidOrders()`) und „Abo-Punkte" (`getTotalPoints()`) sind in PHP berechnet und in SQL nicht sortierbar — genau zwei der drei Sortierungen, die die Kundin nennt. Die Datenmenge (eigene Kundenabos eines Beraters) rechtfertigt kein Serverpaging. > > **Tests:** 10 neue Tests (Aufteilung der Abo-Punkte inkl. Altdatenfall, deduplizierte Recruiter-Summe, Telefon mit Mobil-Fallback, persönliche Liefertag-Prognose, Rendering der Kundenabo-Liste mit und ohne Prognose). Gesamtsuite: 987 grün gegen unveränderte Baseline von 92 vorbestehenden Fehlern (Payone-Race, DATEV, `payment.php`-Sprachkeys aus dem Stripe-Commit). > > **Beim Deploy:** `php artisan cache:clear`. K1, K2, K6 und K7 ändern den Snapshot-Payload — abgeschlossene Monate zeigen die neuen Spalten erst nach einer Neuberechnung der Snapshots; bis dahin greifen die Fallbacks („–" bzw. keine Summenzeile). | # | Paket | Bereich | Einstufung | Aufwand | |---|---|---|---|---| | **A1** | Abo pausieren/stornieren nach der 6. Lieferung | Abo | **läuft bereits** — nur Antwort | — | | **A2** | Payline-Prognose-Punkte stimmen | Home | **bestätigt** — nur Antwort | — | | K1 | Kundenabos (Umsatz): Punkte in 3 Spalten aufteilen | VIP-Statistik | Erweiterung | klein–mittel | | K2 | Führungskräfte: „direkt"-Spalten entfernen | VIP-Statistik | Rückbau | klein | | K3 | Payline-Prognose: Beschriftung der Basis | Home | Mini-Änderung | sehr klein | | K4 | Team-Abos Payline: Summe zusätzlich ausweisen | Team-Abos | Mini-Änderung | sehr klein | | K5 | Punkteübersicht: Abo- vs. Shop-Bestellung markieren | Team-Punkte | kleine Änderung | klein | | K6 | Neupartner-Statistik: Telefon + Spalten verständlich machen | VIP-Statistik | Erweiterung | klein | | K7 | Recruiter des Monats: Summenzeile | VIP-Statistik | Erweiterung | klein | | K8 | Eigene Kundenabos als sortierbare Liste | Portal/Abo | Umbau der Ansicht | mittel | | K9 | Persönliche Liefertag-Prognose 5./10./20./25. | Portal/Abo | neue Kennzahl | mittel | | K10 | „Die besten Ergebnisse" firmenweit statt teamintern | Home | **Grundsatzentscheidung** | mittel | | K11 | Statistik und Struktur zur Deckung bringen | VIP-Statistik | **Grundsatzentscheidung** | groß | | K12 | Deaktivierte Berater nach 2 Wochen zu Kunden des Sponsors | Cron/Prozess | Verhaltensänderung | groß | | K13 | Jahresgebühr automatisch verlängern und abbuchen | Payment/Prozess | neues Feature | groß | | K14 | Kundenabo → Partnerabo per Button im Account | Portal/Abo | neues Feature | groß | | K15 | Neupartner-Registrierung: rabattierte Zusatzprodukte | Registrierung | neues Feature | groß | **Empfohlene Reihenfolge:** A1/A2 beantworten → **K3 · K4 · K2 · K7 · K6 · K5** (Quick-Wins, ein Release) → **K1 · K8 · K9** → **K10** (nach Anwaltsfreigabe) → **K11** (eigene Runde, viel Abstimmung) → **K14 · K15 · K12 · K13** (Prozess-Pakete, je einzeln planen). --- ## A1 — Abo pausieren/stornieren: läuft bereits automatisiert **Ihre Frage:** *„Wie funktioniert aktuell das Pausieren, stornieren bei den Abos? Ausgemacht war: Nach der 6. Lieferung kann man das Abo 1x in 6 Monaten pausieren / monatlich stornieren (das sollte alles automatisiert laufen, läuft es aber meines Wissens noch nicht)."* **Befund: Es läuft — und zwar genau nach diesen Regeln.** Kein Entwicklungsbedarf. - [AboHelper::canCancelAbo()](../../app/Services/AboHelper.php#L125-L134): Kündigung erst ab `getCountPaidOrders() >= abo-min-duration`. **Setting steht produktiv auf 6** — also ab der 6. bezahlten Lieferung, danach jederzeit. - [AboHelper::canPauseAbo()](../../app/Services/AboHelper.php#L163-L179): Pause verlangt zusätzlich, dass die letzte Pause mindestens `abo-pause-interval-months` zurückliegt (rollierend ab `last_pause_date`). **Effektiv 6 Monate.** - Zusätzlich greift eine Sperrfrist von **3 Tagen** vor der Ausführung (`abo-lock-days-pause-cancel`), damit eine Pause nicht in eine schon angestoßene Lieferung fällt. **Eine Kleinigkeit zum Aufräumen (5 Minuten):** Das Setting `abo-pause-interval-months` ist in der Datenbank **nicht gepflegt**; der Wert 6 kommt aus der Code-Konstante `DEFAULT_PAUSE_INTERVAL_MONTHS`. Funktional identisch, aber im Admin nicht sichtbar — sollte explizit auf 6 gesetzt werden, damit die Regel nachlesbar ist. ## A2 — Payline-Prognose: Zahlen bestätigt **Ihre Rückmeldung:** *„Die Prognose-Punkte stimmen."* — Deckt sich mit unserer Gegenprüfung. Übernehmen wir so, keine Änderung. Die gewünschte Beschriftung ist K3. --- ## K1 — Kundenabos (Umsatz): abgerechnete Punkte in 3 Spalten **Briefing:** Statt einer Spalte „Abgerechnet (Punkte)" drei Spalten: **Abgerechnet** · **davon reine Abo-Punkte** · **einmalige Punkte zum Abo dazu**. Beispiel: *Tanja Longerich 1231 abgerechnet – davon 1000 reine Abo-Punkte – 231 einmalige Bestellungen dazu.* Nur bereits abgerechnete Abos. **Ist-Stand:** [statistics/index.blade.php:627](../../resources/views/user/backoffice/statistics/index.blade.php#L627) zeigt eine Spalte „Abgerechnet (Punkte)" aus [`customerAboActualPoints()`](../../app/Services/Backoffice/BackofficeDashboardService.php#L799-L811). Die Summe kommt aus `user_sales_volumes` gefiltert auf `shopping_orders.is_abo = 1` ([SalesVolumePoints::customerAboPoints()](../../app/Services/Backoffice/SalesVolumePoints.php)) — **auf Bestellebene**, also Abo-Positionen und Einmal-Zusätze in einem Topf. **Gute Nachricht:** Der Marker existiert bereits. `shopping_order_items.is_abo_addon` (Migration `2026_06_05_120100`) unterscheidet Abo-Positionen von Einmal-Zusätzen und wird an allen drei Schreibpfaden gesetzt — [CheckoutRepository](../../app/Repositories/CheckoutRepository.php#L154), [PaymentHelper](../../app/Services/PaymentHelper.php#L133) und dem Abo-Cron [UserMakeOrder](../../app/Cron/UserMakeOrder.php#L315). `shopping_order_items.points` gibt es ebenfalls. **Umsetzung:** Zusätzliches Aggregat über `shopping_order_items`, join auf die Abo-Bestellungen des Monats, gruppiert nach `is_abo_addon`. Zwei neue Werte je Berater (`abo_points_recurring`, `abo_points_addon`); die bestehende Spalte bleibt als Summe stehen und muss weiterhin aus `user_sales_volumes` kommen, damit sie mit den übrigen Punkte-Kennzahlen konsistent bleibt. > **Wichtige Einschränkung, die in die Kundenantwort gehört:** `is_abo_addon` gibt es erst **seit dem 05.06.2026**. Für Bestellungen davor ist der Wert `false` — die Aufteilung zeigt dort alles als „reine Abo-Punkte". Für Mai und früher ist die Spalte also nicht aussagekräftig. Entweder so kennzeichnen oder die Aufteilung erst ab 06/2026 anzeigen (Empfehlung: kennzeichnen, nicht ausblenden). > > **Zweite Prüfung vor der Umsetzung:** Ob die Summe der Positionspunkte je Bestellung exakt den `user_sales_volumes.points` derselben Bestellung entspricht, muss an echten Daten gegengerechnet werden (Rundung, Kompensationspositionen `comp`, Gutschriften). Weicht sie ab, zeigen wir die Aufteilung als *Anteile* der bestehenden Summe statt als eigenständige Zahlen. ## K2 — Führungskräfte: „direkt"-Spalten entfernen **Briefing:** *„Die Spalte (Teamabos direkt) und (Team-Kundenabos direkt) braucht es nicht!"* Damit ist die **offene Frage 3 aus dem August-Brief beantwortet** — sie wollte damals beide Sichten sehen, jetzt reicht „gesamt". **Ist-Stand:** [statistics/index.blade.php:386-390](../../resources/views/user/backoffice/statistics/index.blade.php#L386-L390) zeigt 4 Team-Spalten (`team_abo_count`, `team_customer_abo_count`, `..._total`), [`leaderAboOverview()`](../../app/Services/Backoffice/BackofficeDashboardService.php#L591-L640) berechnet beide Sichten über zwei Durchläufe von `teamIdsPerLeader()`. **Achtung — ein Detail, das leicht übersehen wird:** Die **Kopf-Summe** über der Tabelle (`total_team` / `total_team_customer`) ist bewusst aus den **direkt**-Werten gebildet, weil nur die überschneidungsfrei sind. Fallen die Spalten weg, muss entschieden werden, was die Kopfzahl künftig zeigt: - **(a)** Kopf-Summe bleibt intern auf der direkt-Rechnung (korrekt, aber die Zahl lässt sich dann nicht mehr aus der Tabelle nachrechnen), oder - **(b)** Kopf-Summe entfällt ebenfalls. Empfehlung: **(a)** mit einer Fußnote „Summe überschneidungsfrei gerechnet". → **Rückfrage F1.** **Umsetzung:** Spalten und Fußnotentext aus der View entfernen, `teamIdsByLeader`-Zweig in `leaderAboOverview()` nur noch für die Kopf-Summe rechnen (spart einen Durchlauf). Der Drilldown auf die entfernten Badges muss mit raus. ## K3 — Payline-Prognose: Basis beschriften **Briefing:** *„Aber ich würde dazuschreiben: Paylinepunkte + persönliches Volumen, dann kennt man sich aus."* **Ist-Stand:** Die Basis der vier Kacheln ist in [HomeController](../../app/Http/Controllers/HomeController.php#L117-L121) genau das: `customerPoints + paylinePoints`, wobei `customerPoints = getPointsKPSum()` (persönliches Volumen inkl. Shop). Die Beschriftung fehlt nur. **Umsetzung:** Sprachkey `home.payline_forecast_title` um den Zusatz ergänzen bzw. eine Unterzeile „Basis: Paylinepunkte + persönliches Volumen" — 4 Sprachen. **Reine Textänderung.** ## K4 — Team-Abos Payline: Summe ausweisen **Briefing:** *„Wenn da steht 149 Teamabos – 391 Kundenabos in der Payline, wäre es schön, wenn die Summe der Abos in der Payline auch noch da steht."* **Ist-Stand:** [abos.blade.php:70](../../resources/views/user/team/abos.blade.php#L70) rendert `abo.payline_abo_counts` mit `team_abo_count` und `team_customer_abo_count` aus [`PaylineForecastService::aboTurnover()`](../../app/Services/Abo/PaylineForecastService.php). Beide Werte liegen vor, die Summe wird nur nicht gebildet. **Umsetzung:** Sprachkey um `:total` erweitern, in der View summieren. **Reine Textänderung**, 4 Sprachen. ## K5 — Punkteübersicht: Abo- gegen Shop-Bestellung absetzen **Briefing:** *„Hier wäre es noch hilfreich zu sehen, welcher dieser Bestellungen eine ABO-Bestellung ist (statt Shop-Bestellung – Abo-Bestellung und in einer anderen Farbe)."* **Ist-Stand:** [TeamController::datatablePoints()](../../app/Http/Controllers/User/TeamController.php#L1084-L1127) rendert die Spalte „Bestellung" als Badge — heute unterscheidet die Farbe nur *eigene* (`btn-primary`) von *Kunden*-Bestellungen (`btn-secondary`). `shopping_orders.is_abo` ist über die geladene Relation direkt verfügbar. **Umsetzung:** Badge-Farbe und Beschriftung um die Abo-Unterscheidung erweitern (z. B. Abo = `btn-info` mit Zusatz „Abo"), Legende über der Tabelle. Klein — aber die Yajra-Sortierung auf der Spalte muss weiterhin funktionieren. ## K6 — Neupartner-Statistik: Telefon + Spalten verständlich machen **Briefing:** Zwei Punkte: 1. *„Hier würde ich auch die Telefonnummer des Neupartners hinzufügen."* 2. *„Was bedeutet die Spalte Kundenpunkte vs Punkte gesamt? Das ist für mich nicht schlüssig."* **Ist-Stand:** [`newPartnerRows()`](../../app/Services/Backoffice/BackofficeDrilldownService.php#L133-L164) liefert Name, E-Mail, Karriere-Level, Sponsor, `customer_points`, `total_points`, aktiv seit, Konto bis. **Telefon fehlt** — steht aber in `user_accounts.phone` / `.mobil` und wird über `with('account')` bereits mitgeladen. Zu 2.: Die Spalten sind korrekt, aber schlecht benannt. **Kundenpunkte** = Punkte aus Bestellungen *seiner Kunden* (Shop + Abo). **Punkte gesamt** = Kundenpunkte **plus seine Eigenbestellungen**, und da steckt vor allem das **Starterpaket** drin. Genau diese Spalte hatten wir im August auf ihren Hinweis hin ergänzt, damit das Starterpaket sichtbar wird — der Zusammenhang ist aus der Überschrift nicht erkennbar. **Umsetzung:** 1. Telefon-Spalte ergänzen (mit `phone`-Fallback auf `mobil`, wie auf der Firstlines-Seite), inklusive CSV-Export. 2. Spalten umbenennen: **„Kundenpunkte"** → **„Punkte seiner Kunden"**, **„Punkte gesamt"** → **„Gesamt (inkl. Eigenbestellung/Starterpaket)"**, plus eine Erklärzeile unter der Tabelle. Textänderung, kein Rechenweg-Eingriff. ## K7 — Recruiter des Monats: Summenzeile **Briefing:** *„Hier kann man am Ende noch eine Summe bilden – das Volumen aller Neupartner zusammen, die in diesem Monat eingestiegen sind."* **Ist-Stand:** [`recruiterRanking()`](../../app/Services/Backoffice/BackofficeDashboardService.php#L834-L936) liefert Zeilen mit `new_partner_count` und `volume_points`. Der Kopf zeigt bereits die Gesamtzahl der Neupartner; eine Volumen-Summe fehlt. **Wichtig — nicht einfach die Spalte aufaddieren:** `volume_points` enthält das **Kettenvolumen** (Punkte der Neupartner, die diese Neupartner selbst geworben haben). Wer im selben Monat von A geworben wurde und selbst B wirbt, taucht in A's Kettenvolumen **und** in seiner eigenen Zeile auf — die Spaltensumme zählt B doppelt. **Umsetzung:** Die Summe muss **einmal je Neupartner** über die deduplizierte Neupartner-Menge gerechnet werden (`SalesVolumePoints::totalPointsByUser()` über alle `newPartnerIds`), nicht als Spaltensumme. Als Fußzeile „Volumen aller N Neupartner des Monats: X Punkte" mit Tooltip, warum das weniger sein kann als die Spalte darüber. ## K8 — Eigene Kundenabos als sortierbare Liste **Briefing:** *„Eigene Kundenabos sehr unübersichtlich – in Listenform. Kann man das ähnlich anzeigen lassen wie die Teamabos und Teamkundenabos in Listenform? Evtl. mit der Möglichkeit es auch zu sortieren – nach Startdatum, nach Anzahl der Ausführungen, nach Liefertag."* Gewünschte Spalten laut ihrer Skizze: `Name des Kunden | Abo-Liefertag | Abo-Punkte | Nächste Ausführung | Start-Datum | Abo-Ausführungen` **Ist-Stand:** [user/abo/index.blade.php](../../resources/views/user/abo/index.blade.php) rendert **eine Karte pro Abo** (267 Zeilen Karten-Markup) — bei vielen Kundenabos entsteht genau die beschriebene Unübersichtlichkeit. Die Daten liegen alle vor: `shopping_user` (Kundenname), `abo_interval` (Liefertag), `getTotalPoints()`, `next_date`, `start_date`, `getCountPaidOrders()`. **Umsetzung:** Tabellenansicht für `view = 'ot'` als Standard, Karten als Detail hinter dem Klick (Detailseite existiert: `user_abos_detail`). Sortierung projektkonform über **Yajra Datatables** (CLAUDE.md: Listenansichten ausschließlich Yajra) — dafür braucht es eine Datatable-Route analog zu `user_team_datatable_points`. Der bestehende Monatsfilter und die Chart-Karte bleiben darüber stehen. > **Achtung:** Die Karten enthalten heute die Sonderfälle „Zahlungslink offen", „Erstzahlung ausstehend/fehlgeschlagen" mit Aktionsbuttons. Die dürfen nicht verloren gehen — entweder als Statusspalte mit Badge in der Liste oder als Kartenblock **über** der Tabelle (Empfehlung: Letzteres, da es Ausnahmefälle sind). ## K9 — Persönliche Liefertag-Prognose 5./10./20./25. **Briefing:** *„Hier wäre es auch noch interessant zu sehen, wieviel Volumenpunkte habe ich am 5. / 10. / 20. / 25. Nur mit meinen Kundenabos."* **Ist-Stand:** [`PaylineForecastService::forecastDays()`](../../app/Services/Abo/PaylineForecastService.php) rechnet genau diese vier Termine — aber für die **Payline** (Team) und mit dem persönlichen Umsatz als Basis. Eine Variante „nur meine eigenen Kundenabos" gibt es nicht. **Umsetzung:** Zweite, schlanke Methode auf demselben Baustein: gleiche Termin-Logik (`UserAbo::$aboDeliveryDays`, kumuliert, `next_date` im laufenden Monat), aber Mitgliedermenge = nur der Berater selbst, `is_for = 'ot'`. Anzeige auf der neuen Kundenabo-Liste aus K8 als vier kleine Kacheln — das passt inhaltlich zusammen und sollte **mit K8 in einem Zug** gebaut werden. > **Klärung:** Sie schreibt „nur mit meinen Kundenabos" — also **ohne** das eigene Abo. Das ist die umgekehrte Entscheidung zur Payline-Prognose, wo eigene Abos bewusst mitzählen (Frage 2 aus dem August-Brief, von ihr noch nicht beantwortet). Wir bauen es wie beschrieben (nur Kundenabos), sollten den Unterschied aber benennen. → **Rückfrage F2.** ## K10 — „Die besten Ergebnisse" firmenweit statt teamintern **Briefing:** *„Das ist sehr toll umgesetzt, doch es wäre sinnvoller dies nicht für das eigene Team zu machen, sondern für die ganze Firma – das müsste ja jetzt auch mit dem Anwalt geklärt sein?"* **Ist-Stand — dafür vorgesehen:** [`TopResultsService::SCOPE`](../../app/Services/Team/TopResultsService.php#L30) ist genau als dieser Schalter angelegt (`'team'` / `'company'`), und [`scopedUserIds()`](../../app/Services/Team/TopResultsService.php#L78-L84) ist die einzige Stelle, die sich ändern muss. Die Ranglisten-Logik bleibt unangetastet. **Was trotzdem zu tun ist — der Schalter allein reicht nicht:** 1. **Performance/Cache.** Teamintern wird je Betrachter gecacht (`top_results_{user}_{month}_{year}`). Firmenweit gibt es **ein** Ranking für alle — der Cache-Key muss den User verlieren, sonst rechnen wir dasselbe Ergebnis 900-mal. Gleichzeitig läuft die Rangliste dann über alle Berater statt über eine Downline; das ist als eine gebatchte Query unkritisch, aber die Kandidaten-Hydrierung (`TOP_LIMIT + 22`) muss geprüft werden. 2. **„ich"-Markierung** bleibt, muss aber nach dem Cache gesetzt werden (wie heute schon die Profilfotos), sonst sieht jeder das „ich" des Ersten, der die Seite geladen hat. **Das ist der Fehler, den man hier am leichtesten baut.** 3. **Datenschutz.** Firmenweit sieht jeder Berater Namen, Fotos und Umsatzhöhe fremder Berater ohne Vertriebsbeziehung. Das ist eine andere Qualität als teamintern und braucht die Freigabe, die sie selbst anspricht — inklusive des Einwilligungstexts fürs Profilfoto, der **weiterhin ein Platzhalter** ist (offener Punkt aus dem August-Brief). → **Rückfrage F3: schriftliche Bestätigung der Anwaltsfreigabe + finaler Einwilligungstext.** Ohne beides würde ich das nicht scharf schalten. ## K11 — Statistik und Struktur zur Deckung bringen **Briefing:** *„Kann man Statistik und Struktur so anzeigen, damit man die gleiche Summe bekommt – ich würde die Zahlen der Struktur nehmen, denn diese sind für die Provisionen relevant!"* **Befund: Sie hat recht, und wir kennen die Ursache genau.** Die beiden Seiten laufen über zwei verschiedene Baum-Definitionen: | | Statistik (`user/backoffice/statistics`) | Struktur (`user/team/structure`) | |---|---|---| | Baum | [`BackofficeDashboardService::lineBuckets()`](../../app/Services/Backoffice/BackofficeDashboardService.php#L247-L277) | `TreeCalcBotOptimized` → [`readParentsBusinessUsers()`](../../app/Services/BusinessPlan/BusinessUserItemOptimized.php#L1080-L1092) | | Nimmt auf | **jeden** nicht gelöschten Downline-User | nur `m_level` **und** `payment_account` gesetzt, `admin < 4`, nicht `id = 1`, `active_date <= Monatsende` | | Bei ausgefiltertem Knoten | Unterbaum wird **weiter** traversiert | Unterbaum wird **komplett gekappt** | | Max. Tiefe | 100 | 20 | Gemessen an Claudias Account (08/2026): **578 Personen in der Downline, davon 134 nicht im Provisionsbaum.** Die Punkte-Differenz war an diesem Stichtag zufällig 0, weil die ausgefilterten Konten Blätter ohne Umsatz waren — strukturell ist die Abweichung aber jederzeit möglich. **Vorarbeit ist schon geleistet:** Für die Level-/Provisions-Prognose wurde diese Angleichung am 01.09. bereits gebaut — [`PaylineForecastService::lineByUserId()`](../../app/Services/Abo/PaylineForecastService.php) filtert und kappt jetzt nach denselben Regeln, inklusive Regressionstest. **Dasselbe Prädikat lässt sich wiederverwenden**; es gehört dann als gemeinsame Definition in den `BackofficeDashboardService`. **Warum das trotzdem groß ist:** `lineBuckets()` ist die Grundlage von **allem** in der VIP-Statistik und auf dem Home-Dashboard — Berater-Zahlen, Neupartner, Abo-Bestände, Punkte, Payline-Mitglieder, Top-3, Führungskräfte, Recruiter. Eine Umstellung ändert praktisch jede Zahl auf beiden Seiten, und zwar auch rückwirkend in der Anzeige. **Umsetzungsvorschlag in drei Schritten:** 1. **Erst messen, dann umstellen.** Ein Auswertungs-Command, der für alle VIPs beide Baum-Definitionen gegenüberstellt (Anzahl Berater, Neupartner, Abos, Punkte je Ebene). Damit weiß man vor der Umstellung, wo und wie stark sich Zahlen bewegen — und kann es der Kundin vorher sagen, statt sie es entdecken zu lassen. 2. **Gemeinsames Prädikat** aus `PaylineForecastService` in den `BackofficeDashboardService` heben, `lineBuckets()` bekommt die Provisionsbaum-Variante als zweite Methode. 3. **Umstellen mit Stichtag**, gespeicherte Snapshots neu berechnen, und in der Kundenmitteilung offenlegen, welche Zahlen sich warum geändert haben — analog zur Neuberechnung im August. > **Ein Punkt, den sie kennen sollte:** Die Statistik zählt heute bewusst auch Berater mit **abgelaufener** Jahresgebühr als „Berater" (Nachfass-Perspektive: sie sollen auffindbar bleiben). Der Provisionsbaum tut das nicht. Nach der Umstellung verschwinden diese Personen aus der Statistik — genau die, für die wir im August die Jahresgebühr-Liste gebaut haben. → **Rückfrage F4:** Provisionssicht überall, oder Provisionssicht als Standard mit umschaltbarer „inkl. abgelaufene"-Ansicht? Ich empfehle Letzteres. ## K12 — Deaktivierte Berater nach 2 Wochen zu Kunden des Sponsors **Briefing (sie kennzeichnet ihn selbst als „Vorschlag, den wir noch besprechen müssen"):** Bei Deaktivierung **innerhalb von 2 Wochen, nicht 2 Monaten**: Berater wird als Kunde geführt, seine Kunden **und er selbst** gehen als Kunden an den Sponsor, seine Teampartner werden neue Firstlines des Sponsors. Sponsor bekommt eine Mail mit beiden Listen (Vorname, Nachname, Telefon, E-Mail). 2 Wochen vorher eine Mail an den Berater mit der Konsequenz. **Ist-Stand — die Logik existiert komplett, nur zum falschen Zeitpunkt.** [`UserCleanUp`](../../app/Console/Commands/UserCleanUp.php) läuft täglich um 03:30 in zwei Stufen: | Stufe | Zeitpunkt | Was passiert | |---|---|---| | `cleanUpInActiveUser()` | payment_account **< −2 Wochen** | Teampartner → nächster aktiver Sponsor, User deaktiviert (`pre_sponsor` gemerkt). **Kunden bleiben beim deaktivierten User.** | | `deleteInactiveUsers()` | payment_account **< −2 Monate** | zusätzlich `setShoppingUserToNewMember()` + `setUserToClient()`, dann Soft-Delete | Ihr Wunsch = die beiden `UserUtil`-Aufrufe aus der 2-Monats-Stufe in die 2-Wochen-Stufe vorziehen. **Die Bausteine sind fertig** ([UserUtil](../../app/Services/UserUtil.php)), es fehlen nur die Mails. **Das eigentliche Problem ist nicht technisch, sondern fachlich — Reversibilität:** Die 2-Wochen-Stufe ist heute bewusst **umkehrbar**. `UserUtil::reactiveUserResetChilds()` holt Teampartner über `pre_sponsor` zurück, wenn jemand verspätet zahlt. Zieht man den Kundentransfer und die Kundenumwandlung vor, ist das nach 2 Wochen **endgültig** — wer am 15. Tag zahlt, bekommt seine Kunden nicht zurück. Sie schreibt selbst: *„Wenn ein Berater rausgefallen ist, muss er sich wieder mit Starterpaket registrieren… seine Teampartner und Kunden sind dann aber schon seinem Sponsor übergegangen."* — das klingt nach bewusster Akzeptanz. Bei 2 Wochen Frist und einer Jahresgebühr halte ich das trotzdem für hart und rechtlich nicht ohne. → **Rückfrage F5.** **Umsetzung (wenn bestätigt):** 1. `cleanUpInActiveUser()` um `setShoppingUserToNewMember()` + `setUserToClient()` erweitern; Reaktivierungspfad entsprechend anpassen oder bewusst entfernen. 2. **Neue Mail an den Sponsor** mit beiden Listen — Datenquelle sind exakt die im selben Transaktionsblock umgezogenen IDs, nicht ein nachträglicher Query. 3. **Vorwarn-Mail an den Berater 2 Wochen vor Ablauf.** Achtung: [`CheckPaymentsAccount`](../../app/Console/Commands/CheckPaymentsAccount.php) verschickt bereits Erinnerungen — die neue Mail muss sich dort einreihen statt eine zweite Mailstrecke aufzumachen. 4. **Alles idempotent und transaktional**, mit Trockenlauf-Modus. Ein fehlerhafter Lauf verschiebt Kundenbeziehungen unwiederbringlich. ## K13 — Jahresgebühr automatisch verlängern und abbuchen **Briefing:** *„Jahresgebühr: diese wird automatisch verlängert und abgebucht. Falls man sie nicht verlängern möchte, muss man eingreifen und nicht, wenn man verlängern möchte!"* **Ist-Stand:** Heute ausdrücklich **manuell** — der Sprachkey sagt es dem Berater wörtlich: *„Sobald Deine Zahlung bei uns eingegangen ist, verlängert sie sich um ein weiteres Jahr. **Eine automatische Verlängerung gibt es nicht.**"* ([membership.php:36](../../resources/lang/de/membership.php#L36)). **Interessanter Fund:** Es existieren bereits fertige Texte für genau dieses Feature — [payment.php:15-17](../../resources/lang/de/payment.php#L15-L17) beschreibt SEPA-Mandat, Speicherung und Abbuchung **14 Tage vor Ablauf** inklusive jederzeitigem Widerruf. Diese Keys werden **nirgends** verwendet, und es gibt weder ein Feld noch einen Cron dafür. Das Feature war offenbar konzipiert und nicht gebaut. **Umfang (bewusst grob, das braucht eine eigene Planungsrunde):** Opt-out-Kennzeichen am Account, gespeichertes Zahlungsmittel/SEPA-Mandat (PAYONE bzw. Stripe — beide Wege existieren im System), Cron „14 Tage vor Ablauf abbuchen", Fehler-/Retry-Behandlung bei Rücklastschrift, Rechnungserzeugung, Kündigungsweg im Account, sowie AGB/Widerruf. **Rechtlich ist die Umkehrung von Opt-in auf Opt-out der kritische Teil** — für Bestandsberater lässt sich ein Lastschriftmandat nicht rückwirkend annehmen. → **Rückfrage F6:** gilt das nur für Neuregistrierungen, oder sollen Bestandsberater aktiv zustimmen? ## K14 — Kundenabo → Partnerabo per Button im Account **Briefing:** Wenn ein Kunde mit aktivem Abo Partner wird, musste bisher eine Mail an mivita gehen, damit das Abo gestoppt und in ein Partnerabo umgewandelt wird. Gewünscht: Button im Account — *„jetzt Kundenabo in Partnerabo ändern, damit du auch dein ABO 30 % günstiger bekommst"*, dann ein Vorschlag aus dem Kundenabo plus die Möglichkeit, Kompensationsprodukte hinzuzufügen, mit einem Feld für die Begründung. **Ist-Stand:** Kein Umwandlungspfad im Code — der Vorgang läuft heute tatsächlich manuell. Vorhanden und wiederverwendbar sind: die Abo-Anlage über `AboOrderCart`, die Kompensationslogik (`comp`-Flag auf `user_abo_items`, wird in allen Punkte-Abfragen bereits berücksichtigt) und das Einmalprodukt-Muster [`AboOneTimeService`](../../app/Services/AboOneTimeService.php) als Vorlage für „Vorschlag + Positionen ergänzen". **Fachliche Knackpunkte, die vor der Umsetzung geklärt sein müssen:** - Läuft das alte Kundenabo aus oder wird es storniert — und was passiert mit den bereits gezählten **Ausführungen** (die 6-Lieferungen-Regel aus A1 beginnt sonst von vorn)? - Der Kunde hing als `shopping_user` an einem Berater. Wird er selbst Partner, wandert der Umsatz von dessen Kundenpunkten in seine Eigenpunkte — das verschiebt Provisionen und Statistik. - Kompensationsprodukte mit Begründungsfeld heißt: es braucht eine Freigabe (mivita bzw. Admin), sonst kann sich jeder Partner beliebig Kompensation eintragen. → **Rückfrage F7.** Ohne diese drei Antworten würde ich nicht anfangen. ## K15 — Neupartner-Registrierung: rabattierte Zusatzprodukte **Briefing:** *„Die Möglichkeit gleich bei der Registrierung noch weitere Produkte rabattiert dazuzubestellen (bei Starterpaket -30 %; ohne Starterpaket -20 %), wenn jemand z. B. das Basic Health Paket auswählt und noch eine Aer Sun dazubestellen möchte."* **Ist-Stand:** In der Registrierungsstrecke gibt es keinen Weg, dem Startpaket weitere Produkte hinzuzufügen. Die Rabattstaffel 30 %/20 % ist die reguläre Berater-Kondition; die Preislogik existiert also, nur nicht an dieser Stelle. **Zu klären:** Punktewirkung der Zusatzprodukte (zählen sie wie das Starterpaket zu den Eigenpunkten des Neupartners und damit ins „neue Volumen" des Recruiters?), Versand (eine Sendung oder zwei), Storno/Widerruf bei gescheiterter Registrierung, und ob der Rabatt schon vor der Freischaltung des Accounts greifen darf. → **Rückfrage F8.** --- ## Rückfragen an die Kundin | # | Frage | Blockiert | |---|---|---| | **F1** | Führungskräfte: Wenn die „direkt"-Spalten entfallen — soll die Kopf-Summe bleiben (überschneidungsfrei gerechnet, aber nicht mehr aus der Tabelle nachrechenbar) oder ebenfalls weg? | K2 | | **F2** | Persönliche Liefertag-Prognose „nur mit meinen Kundenabos" — **ohne** das eigene Abo, richtig? (In der Payline-Prognose zählen die eigenen Abos bewusst mit.) | K9 | | **F3** | Firmenweite Ranglisten: Liegt die Anwaltsfreigabe schriftlich vor? Und bitte den **finalen Einwilligungstext fürs Profilfoto** — der steht seit August auf Platzhalter. | K10 | | **F4** | Statistik = Struktur: Sollen Berater mit **abgelaufener** Jahresgebühr dann überall verschwinden, oder Provisionssicht als Standard mit umschaltbarer „inkl. abgelaufene"-Ansicht? (Empfehlung: umschaltbar — sonst fehlen genau die Leute, für die die Jahresgebühr-Liste gebaut wurde.) | K11 | | **F5** | Deaktivierung nach 2 Wochen: Soll der Kundentransfer **endgültig** sein? Heute ist die 2-Wochen-Stufe umkehrbar — wer verspätet zahlt, bekommt sein Team zurück. | K12 | | **F6** | Automatische Jahresgebühr: nur für **Neuregistrierungen**, oder sollen Bestandsberater aktiv ein Lastschriftmandat erteilen? | K13 | | **F7** | Kundenabo → Partnerabo: (a) zählen die bisherigen Ausführungen für die 6-Lieferungen-Regel weiter? (b) Wer gibt Kompensationsprodukte frei? | K14 | | **F8** | Registrierungs-Zusatzprodukte: Zählen deren Punkte wie das Starterpaket zu den Eigenpunkten des Neupartners (und damit ins „neue Volumen" des Recruiters)? | K15 | ## Technische Querschnittsthemen - **Sprachkeys:** Jede Textänderung in **de/en/es/fr** — `LocalizationSyncTest` erzwingt Parität. Betrifft K2–K7. - **Tests:** Für jedes Paket ein Test, der die Regel festhält, nicht nur das Rendering. Bei K1 und K7 sind es die Rechenwege (Aufteilung bzw. Doppelzählung), bei K11 die Baum-Definition. - **Snapshots:** K1, K2, K6, K7 und K11 ändern den `BackofficeStatisticsSnapshot`-Payload. Abgeschlossene Monate zeigen sonst die alte Struktur — wie im August müssen die Snapshots nachgerechnet und die Kundin über abweichende Altzahlen informiert werden. Die Views brauchen weiterhin den „älterer Snapshot ohne diese Liste"-Fallback. - **Caches:** Nach jedem Livegang `php artisan cache:clear` — die 6-h-Caches (`payline_context_*`, `live_business_v3_*`, `top_results_*`, `level_forecast_*`) haben keine Invalidierung. Bei K10 ändert sich zusätzlich der Cache-Key-Zuschnitt. - **Offen aus dem letzten Code-Review** (unabhängig von diesem Feedback, sollte mitlaufen): Cache-Invalidierungsstrategie statt fester 6 h; Widerspruch Home-Kacheln ↔ VIP-Statistik bei Abo-Beständen vergangener Monate (Kundenentscheid F7 aus dem Juli — passt thematisch zu K11 und sollte in derselben Runde mitentschieden werden); Performance von `monthlyGrowth()`.