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

26 KiB
Raw Permalink Blame History

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 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). 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, Optionen in ShoppingOrder.php:192-200). 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 (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.