mivita/dev/2026-08-12-intensiv-tickets
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
..
juli-anerkennungen.md Statistiken: Kundenfeedback 2026-08-19 komplett (N1-N8) + Review-Fixes + VP-/Incentive-Repair gehärtet 2026-08-21 10:44:44 +00:00
README.md Statistiken: Kundenfeedback 2026-08-19 komplett (N1-N8) + Review-Fixes + VP-/Incentive-Repair gehärtet 2026-08-21 10:44:44 +00:00

2026-08-12 – Incentive („Intensiv"): Ticket-Analyse, Fixes & Team-Struktur-Sortierung

Analyse von 4 Kundentickets zum Montenegro Incentive 2026 (ID 2, Qualifikation 01.04.–31.08.2026) plus Prüfung der Tracking-Lücke durch die verspätete Zeitraum-Verlängerung.

Kontext: Verspätete Verlängerung (Anfang August)

Das Incentive endete ursprünglich am 31.07.2026 und wurde erst am 03.08.2026 16:57 auf den 31.08.2026 verlängert. Ereignisse vom 01.–03.08. wurden live nicht getrackt.

Ergebnis der Prüfung: Die Lücke hat sich selbst geheilt. Der Nacht-Cron incentive:calculate (täglich 05:00, siehe app/Console/Kernel.php) nutzt den IncentivePointsLogRepairService, der fehlende Trackings anhand des aktuellen Qualifikationszeitraums idempotent nachzieht. Am 04.08. um 05:00–05:01 wurden alle 10 fehlenden Abos + 1 Neupartner aus dem Lücken-Fenster nachgetragen (verifizierbar an incentive_new_abos.created_at). Ein separates Ausgleichs-Command war nicht nötig.

Die 4 Tickets

1. Steffi Karrer – „Dino Portaro fehlt im Incentive" → Bug, gefixt

Dino Portaro (User 1045) registrierte sein Konto bereits im Okt. 2024 (als Kunde) und bezahlte das Starterpaket erst am 31.07.2026 11:05 (Order 59302). Er fehlte aus zwei unabhängigen Gründen:

  1. Datums-Randfall im Live-Tracker: incentives.qualification_end ist eine DATE-Spalte. Der Vergleich qualification_end >= '2026-07-31 11:05:52' schlägt fehl, weil MySQL das Datum als Mitternacht interpretiert. Bestellungen am letzten Tag des Zeitraums gingen verloren. → Fix: whereDate() in IncentiveTracker.
  2. Falsche Neupartner-Definition im Repair/Rebuild: syncMissingTrackingPartners und rebuildFromSourceTables filterten auf users.created_at im Zeitraum. Der Live-Tracker zählt aber nach Bestelldatum des Starterpakets. Bestandskunden, die im Zeitraum zum Berater upgraden, waren damit nie reparierbar. → Fix: Kandidaten über das Bestelldatum des bezahlten Starterpakets im Zeitraum; registered_at/Log-Monat = Bestelldatum.

Ergebnis nach incentive:calculate 2: Steffi 10.022 → 10.822 Punkte, 3 Partner.

2. Monika Kunz – „Mai-Punkte für Bockhacker (24) und Dannenhauer (54) fehlen" → kein Bug

Die Mai-Abbuchungen beider Kundenabos (Abo 197 Bockhacker, Abo 198 Dannenhauer) schlugen fehl (shopping_orders.txaction = 'prev'). Die Nachzahlung gelang am 17.06.; die 24 P bzw. 54 P wurden korrekt für Juni gutgeschrieben und zählen im Incentive. Regel: Punkte entstehen im Monat der erfolgreichen Zahlung, nicht im Fälligkeitsmonat.

Nebenbefund: Dannenhauers Abo 198 wurde am 06.07. gelöscht (Status 3, kein Ersatz) – die Kundin hat aktuell kein aktives Abo. Bockhacker bekam am 10.07. ein Ersatz-Abo (496).

3. Christina Schuder – „Anna Schall fehlt" → Bug, gefixt

Anna Schall (User 1426, Konto seit Dez. 2025, Starterpaket 22.07.2026) war getrackt, aber beim Firmenkonto (User 6, mivita care gmbh): Beim verspäteten Zahlungs-Callback am 24.07. stand ihr m_sponsor noch auf 6; sie wurde erst danach zu Christina (1241) umgehängt. Der Repair konnte das nicht korrigieren (gleiche users.created_at-Falle wie bei Ticket 1, plus fehlende Bereinigung veralteter Trackings).

→ Fix: Neue Methode IncentivePointsLogRepairService::removeStaleSponsorTrackings() – entfernt Partner-Trackings (+ zugehörige Log-Zeilen), wenn der Partner laut m_sponsor inzwischen woanders hängt; der reguläre Sync legt sie im selben Lauf beim aktuellen Sponsor neu an. Im incentive:calculate eingebunden (Statistik-Zeile „Neupartner-Trackings entfernt (Sponsorwechsel)").

Ergebnis: Christina 7.165 → 7.965 Punkte, 2 Partner; Firmenkonto von 800 auf 0 Punkte (alle 4 dort hängenden Fremd-Trackings zum jeweils richtigen Sponsor verschoben).

4. Jasmin Grund – „Madeleine Schrade fehlt" → hatte sich bereits selbst geheilt

Madeleine (User 1600) registrierte + bezahlte am 09.08.; der Nacht-Cron zog sie am 11.08. 05:00 nach (600 P, August). Das Ticket entstand in den ~1,5 Tagen dazwischen. Ursache für das fehlende Live-Tracking: IncentiveTracker::trackNewPartner feuert nur über Payment::paymentStatusPaidAction; Zahlungswege daran vorbei fängt erst der Nacht-Cron ab (by design).

1b. Steffi Karrer – „Dino steht mitten in der Team-Struktur" → Sortierung umgestellt

/user/team/structure listete die Downline in users.id-Reihenfolge (= Konto-Registrierung), weil die Lade-Queries kein orderBy hatten. Dino (Konto Okt. 2024, Partner erst Juli 2026) stand deshalb zwischen Margarete Fislage und Renate Hofrichter statt am Ende.

→ Umgestellt auf Sortierung nach active_date (Partner-Aktivierung), Tiebreaker users.id:

  • Zentral beim Rendern: TreeHtmlRenderer::sortByActivation() (wirkt sofort, auch für bereits gespeicherte Monats-Snapshots, die die alte Reihenfolge konservieren).
  • Zusätzlich orderBy in beiden Downline-Queries (BusinessUserItemOptimized::readParentsBusinessUsers, BusinessUserItem::readParentsBusinessUsers), damit künftige Snapshots und der Standard-Bot-Fallback konsistent sind.

Stolperfalle dabei: BusinessUserItem(Optimized) hat __get ohne __isset – $item->active_date ?? null liefert dort immer null. Deshalb direkter Property-Zugriff in sortByActivation().

Nachtrag 2026-08-19: VP-Punkte-Nacht-Repair (Ticket Jasmin Grund, Teil 2)

Folgeticket: Madeleine Schrade war im Incentive-Ranking sichtbar (Nacht-Cron 11.08.), aber nicht im Punkteranking für August (Team → Punkte). Ursache: Die Vertriebspartner-Punkte des Sponsors (200 P, user_sales_volumes status=5 „Registrierung") schreibt nur Payment::addSponsorBuyingPoints() im Zahlungs-Callback – ohne Repair-Mechanismus. Scan über den Incentive-Zeitraum: 7 fehlende VP-Zeilen (5× Juni/PAYONE, Anna Schall Juli/PAYONE, Madeleine Schrade August/Stripe).

Entscheidung (Kevin): Keine rückwirkenden Buchungen in abgeschlossene Provisionsmonate (Juni/Juli bleiben wie sie sind). Stattdessen dauerhaftes Nacht-Repair, das den laufenden Monat heilt – damit wurde auch der August-Fall (Madeleine → Jasmin) nachgebucht.

Umsetzung:

  • app/Services/SponsorBuyingPointsRepairService.php – idempotenter Repair: bezahlte Bestellungen des Ziel-Monats mit sponsor_buying_points-Produkt, Sponsor vorhanden, aber keine VP-Zeile (Dedup: shopping_order_id der Repair-Zeilen bzw. exaktes Live-Info-Format VP: <Name> | <Produkt> im Monat). Repair-Zeilen tragen shopping_order_id + Message-Suffix „(Nacht-Repair)". Danach reCalculateSalesPointsVolume je Sponsor.
  • app/Console/Commands/RepairSponsorBuyingPoints.php – sales:repair-sponsor-vp mit --dry-run; fremde Monate nur mit --force (Schutz vor Retro-Buchungen).
  • Schedule: täglich 02:30 (vor business:store-optimized um 03:00), Kernel.php.
  • Tests: tests/Feature/SalesVolume/SponsorBuyingPointsRepairTest.php (7 Tests; neues Verzeichnis in tests/Pest.php registriert).

Verifiziert auf der Live-Kopie (19.08.): 13 August-Bestellungen geprüft, 12 bereits gebucht, genau 1 nachgebucht (Order 59900, Jasmin Grund +200 P), Juni/Juli unberührt.

Geänderte Dateien

Datei Änderung
app/Services/Incentive/IncentiveTracker.php whereDate() für Zeitraum-Vergleiche (DATE-Spalte vs. Datetime)
app/Services/Incentive/IncentivePointsLogRepairService.php Neupartner per Starterpaket-Bestelldatum; neu: removeStaleSponsorTrackings()
app/Models/IncentiveParticipant.php Force-Rebuild nutzt dieselbe Bestelldatum-Definition
app/Console/Commands/IncentiveCalculate.php Sponsorwechsel-Bereinigung eingebunden + Statistik
app/Services/BusinessPlan/TreeHtmlRenderer.php sortByActivation(); Sortierung in renderTree/renderUserItem
app/Services/BusinessPlan/BusinessUserItemOptimized.php orderBy(active_date, id) in Downline-Query
app/Services/BusinessPlan/BusinessUserItem.php dito (Standard-Bot-Fallback)

Tests

  • tests/Feature/Incentive/IncentivePartnerRepairSponsorChangeTest.php (neu, 5 Tests): Bestandskunden-Upgrade zählt, Zeitraum-Grenzen, letzter-Tag-Randfall, Sponsorwechsel-Verschiebung, korrekte Trackings bleiben unangetastet.
  • tests/Unit/BusinessPlan/TreeHtmlRendererTest.php (+2): Sortierlogik inkl. __get-ohne-__isset-Falle.
  • tests/Feature/BusinessPlan/TreeCalcBotIntegrationTest.php (+1): Downline-Query-Reihenfolge.

Vorbestehende Fehlschläge (nicht durch diese Änderungen): IncentiveTeaserPageTest (fehlendes Galeriebild) sowie die Legacy-Tests in tests/Unit/BusinessPlan/ und tests/Feature/BusinessPlan/ (fehlende Factories, route()/DB in Unit-Kontext).

Deployment-Hinweise

  1. Nach dem Deploy korrigiert der nächste 05:00-Lauf von incentive:calculate die Incentive-Daten automatisch (Dino → Steffi, Anna Schall → Christina). Sofort: php artisan incentive:calculate 2.
  2. Die neue Struktur-Sortierung wirkt in der Ansicht sofort (Renderer-Sortierung), unabhängig vom Monats-Snapshot.
  3. Achtung: Durch die neue Neupartner-Definition (Bestelldatum statt Registrierung) können beim ersten Lauf weitere, bisher fehlende Bestandskunden-Upgrades nachgetragen werden – das ist gewollt und entspricht dem Live-Tracking. Auf dem Teststand (Live-Dump 12.08.) waren es genau die zwei Ticket-Fälle.