- Neue Seite Anerkennungen (/user/recognitions, Menue unter News Archiv) fuer alle Berater mit aktivem Konto: oben die Anerkennungen des Vormonats in den fuenf Kategorien der Monatsmail, Filter zurueck bis Januar 2026; darunter Live-Ranglisten (Top 10 je Kategorie) des laufenden Monats, 15 Min. Cache. - Kategorien: Top-Recruiter (ab 2 Neupartnern), neue Kunden-Abos, Kundenabo-Punkte, Kunden-Abos gesamt, Gesamtpunkte (1.000-Punkte-Shop, mit Platz 4). Regeln wie TopResultsService/VIP-Statistik, nur firmenweit. - Firmenkonto (6) und Grosskundin (3) nie aufgefuehrt: config mivita.recognition_excluded_user_ids. - Abgeschlossene Monate sind bis zum 3. des Folgemonats vorlaeufig und werden dann eingefroren: neue Tabelle recognition_snapshots, Command recognitions:freeze (taeglich 06:15, idempotent, holt offene Monate nach). - September-Anerkennungen firmenweit geprueft und korrigiert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| august-anerkennungen.md | ||
| juli-anerkennungen.md | ||
| README.md | ||
| september-anerkennungen.md | ||
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:
- Datums-Randfall im Live-Tracker:
incentives.qualification_endist eine DATE-Spalte. Der Vergleichqualification_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()inIncentiveTracker. - Falsche Neupartner-Definition im Repair/Rebuild:
syncMissingTrackingPartnersundrebuildFromSourceTablesfilterten aufusers.created_atim 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
orderByin 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 mitsponsor_buying_points-Produkt, Sponsor vorhanden, aber keine VP-Zeile (Dedup:shopping_order_idder Repair-Zeilen bzw. exaktes Live-Info-FormatVP: <Name> | <Produkt>im Monat). Repair-Zeilen tragenshopping_order_id+ Message-Suffix „(Nacht-Repair)". DanachreCalculateSalesPointsVolumeje Sponsor.app/Console/Commands/RepairSponsorBuyingPoints.php–sales:repair-sponsor-vpmit--dry-run; fremde Monate nur mit--force(Schutz vor Retro-Buchungen).- Schedule: täglich 02:30 (vor
business:store-optimizedum 03:00), Kernel.php. - Tests:
tests/Feature/SalesVolume/SponsorBuyingPointsRepairTest.php(7 Tests; neues Verzeichnis intests/Pest.phpregistriert).
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.
Nachtrag 2026-08-21: Ticket Kunz Teil 2 – Mai-Punkte & Bockhacker-Lieferzähler
Re-Verifikation mit DHL-Daten ergab ein anderes Bild als die erste Analyse:
- Die Mai-Lieferungen wurden versandt und zugestellt (DHL 12.05.), aber nie bezahlt
(
txaction='prev'). Am 17.06. hat ein Admin zwei Bestellungen per „extern erledigt" (Status 8) nachgebucht: Bockhackers Mai-Order 53619 (24 P) und Dannenhauers Juni-Order 55443 (54 P) – Buchungsmonat jeweils Juni (Systemregel: aktueller Monat). - Bockhacker: Es fehlt nichts – die 24 P der Mai-Lieferung existieren (als Juni gebucht). Kein Nachtrag, sonst Doppelbuchung.
- Dannenhauer: Ihre Mai-Order 53274 (geliefert!) wurde beim Nachtragen am 17.06.
vergessen → 54 P fehlten wirklich. Nachgebucht am 21.08. per neuem Command
abo:extern-done 53274(Buchung August, keine Retro-Buchung; +54 P auch im Incentive). Hinweis: Abo 198 ist seit 06.07. gelöscht, Kundin hat kein aktives Abo mehr; die Orders 54890/55441/55442/57268 wurden laut DHL nie versandt und bleiben offen. - Bockhacker Lieferzähler: Abo 197 (09.04.) wurde am 06.07. gelöscht und durch
Abo 496 ersetzt → Zähler startete bei 0. Per neuem Command
abo:move-orders 197 496wurde die Lieferhistorie umgehängt; Abo 496 zählt jetzt 4 ausgeführte Lieferungen (09.04., 12.05. [extern erledigt], 10.07., 10.08.). Laut DHL war die Juli-Lieferung die 3. (nicht die 4. wie im Ticket) – es gab keine Juni-Sendung an Bockhacker; die Juni-Buchung vom 17.06. gehörte zur Mai-Lieferung.
Code-Änderungen dafür:
AboExternalCompletionService: funktioniert jetzt auch für Bestellungen gelöschter/ersetzter Abos (Punkte + Status 8 + Versandstatus; das gelöschte Abo wird nicht reaktiviert, kein neuer Zyklus).IncentiveTracker::trackSalesVolume: Abo-Zuordnung inkl. gelöschter Abos (withTrashed) – nachgebuchte Umsätze bereits getrackter, inzwischen gelöschter Abos zählen weiter im Incentive.- Neue Wartungs-Commands:
abo:extern-done {shopping_order_id}undabo:move-orders {from_abo} {to_abo} [--dry-run](Guards: gleicher Kunde/Berater). - Fix eines latent datumsabhängigen Tests in
AboExternalCompletionTest(Erwartung nutztesetTestNow, Produktion bucht mitdate()).
Live-Ausführung nach Deploy:
php artisan abo:extern-done 53274 # Dannenhauer Mai-Lieferung: +54 P an Kunz
php artisan abo:move-orders 197 496 # Bockhacker Lieferhistorie → Abo 496
php artisan incentive:calculate 2 # optional sofort (sonst Nacht-Cron 05:00)
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
- Nach dem Deploy korrigiert der nächste 05:00-Lauf von
incentive:calculatedie Incentive-Daten automatisch (Dino → Steffi, Anna Schall → Christina). Sofort:php artisan incentive:calculate 2. - Die neue Struktur-Sortierung wirkt in der Ansicht sofort (Renderer-Sortierung), unabhängig vom Monats-Snapshot.
- 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.