- 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>
294 lines
34 KiB
Markdown
294 lines
34 KiB
Markdown
# 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()`.
|