Incentive: Ticket-Fixes 2026-08-12 + Team-Struktur nach active_date sortiert
Incentive (Montenegro 2026): - Live-Tracker: whereDate() für Zeitraum-Vergleiche – Bestellungen am letzten Tag des Qualifikationszeitraums gingen verloren (DATE-Spalte vs. Datetime) - Repair/Rebuild: Neupartner-Definition auf Starterpaket-Bestelldatum im Zeitraum umgestellt (statt users.created_at) – Bestandskunden-Upgrades zählen jetzt wie im Live-Tracking (Fall Dino Portaro → Steffi Karrer) - Neu: removeStaleSponsorTrackings() – Partner-Trackings wandern nach Sponsorwechsel zum aktuellen Sponsor (Fall Anna Schall lag beim Firmenkonto) - incentive:calculate: Sponsorwechsel-Bereinigung eingebunden + Statistik Team-Struktur (/user/team/structure): - Downline sortiert nach active_date (Partner-Aktivierung) statt users.id: zentral in TreeHtmlRenderer::sortByActivation() (wirkt sofort, auch für gespeicherte Monats-Snapshots) + orderBy in beiden Downline-Queries Doku: dev/2026-08-12-intensiv-tickets/README.md (4 Kundentickets analysiert, Lücke durch verspätete Zeitraum-Verlängerung hatte sich per Nacht-Cron geheilt) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
ac3bd6e8ea
commit
841d87e161
11 changed files with 689 additions and 115 deletions
130
dev/2026-08-12-intensiv-tickets/README.md
Normal file
130
dev/2026-08-12-intensiv-tickets/README.md
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
# 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()`.
|
||||
|
||||
## 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue