- September-Plan: B2, E1 (Gleichstand, Zahlungsdatum), A07 und die Abo-Warnung nachgetragen; Audit-Stand A07 erledigt. - Bisher nur im Arbeitsbaum: Entwicklungsplan und Kundenbrief vom 07.09. samt PDF, Tickets vom 08.09., Log-Analyse 26.08., Ledger-Plan, August-Anerkennungen. E-Mail-Adressen von Kunden und Beratern durch Platzhalter bzw. Namen ersetzt. Nicht versioniert (personenbezogene Daten): Kundenliste CSV/XLSX und die Produktions-Logs in dev/2026-08-26-logs/*.log. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
34 KiB
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-monthssollte 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_recurringwird 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 mituser_sales_volumes.pointsüberein; die Positionspunkte sind Stückpunkte zum Bestellzeitpunkt. Es gibt produktiv 726 Positionen mitis_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.phpund werden von beiden Seiten eingebunden, statt für K8 kopiert zu werden.- Termin-Logik entdoppelt:
forecastDays()undpersonalCustomerAboForecast()teilen sichcumulateByDeliveryDay()— 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(): Kündigung erst ab
getCountPaidOrders() >= abo-min-duration. Setting steht produktiv auf 6 — also ab der 6. bezahlten Lieferung, danach jederzeit. - AboHelper::canPauseAbo(): Pause verlangt zusätzlich, dass die letzte Pause mindestens
abo-pause-interval-monthszurückliegt (rollierend ablast_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 zeigt eine Spalte „Abgerechnet (Punkte)" aus customerAboActualPoints(). Die Summe kommt aus user_sales_volumes gefiltert auf shopping_orders.is_abo = 1 (SalesVolumePoints::customerAboPoints()) — 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, PaymentHelper und dem Abo-Cron UserMakeOrder. 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_addongibt es erst seit dem 05.06.2026. Für Bestellungen davor ist der Wertfalse— 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.pointsderselben Bestellung entspricht, muss an echten Daten gegengerechnet werden (Rundung, Kompensationspositionencomp, 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 zeigt 4 Team-Spalten (team_abo_count, team_customer_abo_count, ..._total), leaderAboOverview() 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 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 rendert abo.payline_abo_counts mit team_abo_count und team_customer_abo_count aus PaylineForecastService::aboTurnover(). 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() 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:
- „Hier würde ich auch die Telefonnummer des Neupartners hinzufügen."
- „Was bedeutet die Spalte Kundenpunkte vs Punkte gesamt? Das ist für mich nicht schlüssig."
Ist-Stand: newPartnerRows() 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:
- Telefon-Spalte ergänzen (mit
phone-Fallback aufmobil, wie auf der Firstlines-Seite), inklusive CSV-Export. - 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() 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 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() 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 ist genau als dieser Schalter angelegt ('team' / 'company'), und scopedUserIds() ist die einzige Stelle, die sich ändern muss. Die Ranglisten-Logik bleibt unangetastet.
Was trotzdem zu tun ist — der Schalter allein reicht nicht:
- 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. - „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.
- 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() |
TreeCalcBotOptimized → readParentsBusinessUsers() |
| 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() 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:
- 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.
- Gemeinsames Prädikat aus
PaylineForecastServicein denBackofficeDashboardServiceheben,lineBuckets()bekommt die Provisionsbaum-Variante als zweite Methode. - 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 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), 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):
cleanUpInActiveUser()umsetShoppingUserToNewMember()+setUserToClient()erweitern; Reaktivierungspfad entsprechend anpassen oder bewusst entfernen.- Neue Mail an den Sponsor mit beiden Listen — Datenquelle sind exakt die im selben Transaktionsblock umgezogenen IDs, nicht ein nachträglicher Query.
- Vorwarn-Mail an den Berater 2 Wochen vor Ablauf. Achtung:
CheckPaymentsAccountverschickt bereits Erinnerungen — die neue Mail muss sich dort einreihen statt eine zweite Mailstrecke aufzumachen. - 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).
Interessanter Fund: Es existieren bereits fertige Texte für genau dieses Feature — payment.php:15-17 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 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_useran 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 —
LocalizationSyncTesterzwingt 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().