mivita/dev/2026-07-22/entwicklungsplan-2026-08-19.md
Kevin 867dc21b57 Statistiken: Kundenfeedback 2026-08-19 komplett (N1-N8) + Review-Fixes + VP-/Incentive-Repair gehärtet
Statistik-Änderungen 2026-08-19 (dev/2026-07-22/entwicklungsplan-2026-08-19.md, alle 8 Pakete gemäß beantworteten F-Fragen):
- N1 Monatsstatistik: Team-Cluster (Firstlines klickbar -> neue Seite user_team_firstlines, Teampartner gesamt/Payline [nur aktive], Abos Payline mit +N) + Level-Kachel (Quali-Stufe, fehlende Payline-/persönliche Punkte, Karriere-Level); LiveCommissionService-Snapshot v3 reicht Level-/Quali-Daten durch
- N2 Abo-Kacheln auf Home monatsbezogen (aboStockCount-Rückrechnung über cancel/deleted/Status-Historie; VIP-Statistik bleibt tagesaktuell)
- N3 VIP-Statistik 'Recruiter des Monats' (Neupartner-Definition wie KPI, rekursives Ketten-Volumen als Gesamtpunkte inkl. Starterpaket) + Drilldown/CSV-Spalte 'Punkte gesamt'
- N4 Prognose-Block 'Level & Provision laut Prognose (Stand 25.)' mit unverbindlich-Kennzeichnung (ebenengenaue offene Abo-Punkte + TP-Laufstände)
- N5 Checkout-Herkunft: Dropdown raus, Pflicht-Freitext (lokalisierte Fehlermeldung)
- N6 Berater-Profilfoto (user_accounts.profile_image, Einwilligung als Sprachkey-Platzhalter, DSGVO-Löschung) + teaminterne Top-3-Karte (TopResultsService, Scope-Schalter)
- N7 USt-Optionen: 'nicht umsatzsteuerpflichtig' zuerst + '§ 19 UStG'-Textfix
- N8 Jahresgebühr-Nachfassliste je Berater (AnnualFeeDueService, überfällig/1-3 Monate, Sidenav)

Kontroll-Review (8 Perspektiven) angewendet: next-Level-Fallback im Steady-State (resolveNextLevel — sonst 'Höchstes Level erreicht' für Normalfälle), _calculated-Punktebasis bei mehr Paylines, keine Wiederbelebung deaktivierter Abos in der Monatsrückrechnung, Payline-Abo-Kachel monatskonsistent, Top-3-Nachrücker; Performance: BackofficeDashboardService als Singleton mit lineBuckets-Memo (+flushLineBucketsMemo), level_forecast 6h-Cache; Reuse: User-Scopes firstlinesOf/newPartnerIn, SalesVolumePoints::totalPointsByUser, Slim::storeCroppedImage (3 Upload-Kopien vereinigt), activeAbo-Scope statt Inline-Prädikate, Form::open-Konvention, Return-Types

Repair-Härtung (Kundenentscheide): VP-Nachtrepair dedupliziert über shopping_order_id (Live-Pfad schreibt sie jetzt mit), zählende Alt-Zeilen-Erkennung, Vormonats-Scan für Monatswechsel, Firmenaccount-Buchungen (config mivita.company_user_id) werden automatisch auf den Berater umgebucht (in place bzw. per Gegenbuchung bei geschlossenem Monat; Echtfall Anna Schall Order 58797 -> Christina Schuder), Berater-zu-Berater nur Warnung; Incentive-Repair: m_sponsor=NULL löscht nicht mehr, FK-Repair vor Stale-Bereinigung, gebatchte Lookups, zentrales Incentive::qualificationWindow(); .env aus dem Git-Tracking entfernt (Pflege per SFTP)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 10:44:44 +00:00

190 lines
26 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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