mivita/dev/2026-07-22/feedback-analyse-2026-07-24.md
Kevin ac3bd6e8ea Statistiken/Abo: Kundenfeedback 2026-07-24 komplett umgesetzt + Steuern: DATEV-Fixes 08+09/2025 (Sammel-Commit Branch-WIP)
Feedback-Umsetzung (dev/2026-07-22/feedback-analyse-2026-07-24.md, alle 10 Punkte):
- VIP-Statistik: eigener Berater ('ich', Linie 0) in Neue-Kundenabos- und beiden Top-10-Listen; Top-10-Umsatz sortiert nach abgerechneten Ist-Punkten + Abo-Anzahl-Spalte; Fußnote nicht-payline-wirksame Punkte (SalesVolumePoints::nonPaylinePoints)
- Führungskräfte-Liste: beide Sichten 'direkt' (an nächster FK abgeschnitten, additiv) und 'gesamt' (komplette Downline), Gesamt-Spalte nach Kundenzählweise
- Team-Abos: je Ebene 'abgerechnet (Monat)' und 'bis zum 25. erwartet'; Payline-Karte mit Teamabo-/Kundenabo-Zählern
- Incentive-Live-Ranking: Qualifizierte zuerst nummeriert (fixt auch isWinner rank<=max_winners)
- Porto: Netto-/Brutto-Kennzeichnung (Berater netto, Endkunden inkl. MwSt., portal_view-Flag; auch CRM-/Shop-Warenkorb + Checkout), Yard::shipping_tax-Fix
- Struktur-Ansicht: Abo-Badges 'A' (eigenes Abo) und '(n)' (Kundenabos) je Teampartner, gebatcht
- Abo-Löschung: Softdelete setzt active=0 (UserAbo deleting-Hook) + Bereinigungs-Migration für gelöschte-aber-aktive Abos
- Doku: technische Analyse + Kundinnen-Feedback-Dokument unter dev/2026-07-22/
- Tests: IncentiveRankingTest, TeamStructureAboBadgesTest, YardShippingTaxTest (neu); BackofficeStatisticsListsTest, TeamAboOverviewTest, PaylineForecastServiceTest, AboAdminDeleteTest, AboOneTimeViewTest erweitert

Außerdem enthalten (Branch-WIP vor dem Feedback): DATEV-Abgleich-Fixes 08+09/2025, USt-ID-Live-Validierung sowie restliche unkommittete Stände (u. a. Abo-Pause-Sprachkeys).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 13:56:02 +00:00

30 KiB
Raw Permalink Blame History

Analyse: Feedback „Statistiken kontrolliert" (2026-07-24)

Basis: dev/2026-07-22/Statistiken kontrolliert.pdf (Kundin, getestet mit User 454 / info@claudialattner.com). Alle Zahlen wurden am 2026-07-24 gegen die aktuelle Test-DB verifiziert (Datenstand von heute).

Umsetzungsstand (2026-07-24): ✅ ALLE Punkte sind gemäß den „++"-Antworten des Kunden umgesetzt — Details je Punkt im jeweiligen Abschnitt „Umgesetzt". Tests: BackofficeStatisticsListsTest (12), TeamAboOverviewTest (11), PaylineForecastServiceTest (6), AboAdminDeleteTest (3), Incentive/IncentiveRankingTest (4, neu), TeamStructureAboBadgesTest (5, neu), YardShippingTaxTest (neu) + erweiterte AboOneTimeViewTest — alle grün. Einziger roter Test im Umfeld: IncentiveTeaserPageTest (Galerie-Bild nikki-beach.jpg liegt lokal im Ordner) — vorbestehend, ohne Bezug zu diesen Änderungen.

Gesamtbild: Von den 10 Punkten sind 2 echte Fixes (Rang-Nummerierung Incentive, Porto-Anzeige), 3 kleine Erweiterungen („ich" in Listen, Abo-Zähler Payline, Struktur-Symbole), 2 Erklärungs-/Beschriftungsthemen (Zahlen stimmen, sind aber unterschiedliche Metriken) und 1 Konzeptänderung (Führungskräfte: komplette Downline statt Abschneiden — widerspricht der früheren Entscheidung F9). Alle vom Kunden gemeldeten „falschen" Zahlen ließen sich vollständig aufklären — kein Rechenfehler in den neuen Statistiken.

# Punkt Einstufung Aufwand
1 „Neue Kundenabos": ich selbst fehlt Erweiterung (bewusster Ist-Stand) klein
2a Top 10 Anzahl: ich selbst fehlt Erweiterung klein (mit 1)
2b Top 10 Anzahl ≠ Montenegro-Zahlen kein Bug — andere Metrik, erklärbar Doku/UI-Text
3 Top 10 Umsatz: Soll/Ist unklar, Abo-Anzahl fehlt Erweiterung + Beschriftung klein
4 Führungskräfte-Zahlen „stimmen nicht" Konzeptänderung (F9 wird revidiert) klein–mittel + Rückfrage
5 Statistik 97.453 ≠ Struktur 92.211 kein Bug — Neupartner-Punkte sind nicht payline-wirksam Doku/UI-Text
6 Was bedeuten 42.628 Abo-Punkte? Erklärung + sinnvolle Erweiterung (Ist je Ebene) klein–mittel
7 Payline: Anzahl Abos anzeigen Erweiterung klein
8 Montenegro: Nummerierung falsch Fix (Ranking-Sortierung) klein, aber mit Nebenwirkungen prüfen
9 Porto DE Abo 19,90 vs. Einzel 16,72 Anzeige-Fix (brutto vs. netto) klein
10 Struktur: Abo-Symbole je Teampartner neue Anforderung mittel

1. „Neue Kundenabos": eigene Person muss erscheinen

Feedback: „Hier muss ICH auch in die Liste aufgenommen werden, sollte ich neue Kundenabos abgeschlossen haben."

Befund/Ursache: Alle Team-Auswertungen der VIP-Statistik basieren auf lineBuckets() (BackofficeDashboardService.php:215-240). Dort steht der Root-User von Anfang an in visitedUserIds und wird per whereNotIn('id', …) in jeder Linie ausgeschlossen. Damit fehlt die eingeloggte Person in „Neue Kundenabos" (newCustomerAboRanking(), Z. 499-533), in beiden Top-10-Listen und in der Führungskräfte-Liste. Einzige bisherige Ausnahme ist shop_1000 — dort wurde der Root-User auf Kundenwunsch (B4) explizit ergänzt (Z. 96-98, Muster „(ich)" existiert also schon).

Hinweis: User 454 hat aktuell 0 neue Kundenabos im Juli — sie würde in dieser Liste derzeit ohnehin nicht auftauchen. Die Anforderung ist trotzdem korrekt.

Empfehlung: Root-User-ID in die Kandidatenmenge von newCustomerAboRanking() und topCustomerAboRanking() aufnehmen (Linie 0 / Markierung „(ich)", analog shop_1000). Kleiner, klar umrissener Eingriff + Test.

++ so umsetzen wie in der Empfehlung

Umgesetzt (2026-07-24): usersWithLine() nimmt optional den Root-User als Linie 0 auf; newCustomerAboRanking() und topCustomerAboRanking() (beide Listen) zählen ihn jetzt mit. Neue Zeilen tragen is_self; die View zeigt ein „ich"-Badge und „Eigene Kundenabos" statt „Linie 0". Test: „nimmt den Statistik-Nutzer selbst in die Kundenabo-Ranglisten auf".

2. Top 10 Kundenabos (Anzahl)

2a. Eigene Person fehlt

Gleiche Ursache und Empfehlung wie Punkt 1. Zum Vergleich: User 454 hat aktuell 7 aktive Kundenabos — wäre also derzeit nicht in den Top 10 (Platz 10 = 10 Abos), muss aber grundsätzlich mitspielen.

++ so umsetzen wie in der Empfehlung

Umgesetzt (2026-07-24): mit Punkt 1 erledigt (beide Top-10-Listen inkl. „ich").

2b. „Zahlen stimmen nicht" (Rosmarie 14 statt 13, Tanja 16 statt 13, …)

Befund: Beide Zahlen sind korrekt — es sind zwei verschiedene Metriken. Die Service-Methode liefert heute exakt die Screenshot-Werte (Tanja 13 usw.), und die Montenegro-Werte stimmen ebenfalls exakt mit der DB überein.

Die Montenegro-Spalte „ABOS" zählt etwas anderes als die Top-10-Liste:

Top 10 Kundenabos (Anzahl) Montenegro „ABOS x/6"
Was aktueller Bestand aktiver Kundenabos im Qualifikationszeitraum (1.4.–31.7.) aktivierte Abos
Eigenabo des Beraters zählt nicht (is_for='ot') zählt mit (+1, inkl. Alt-Eigenabo)
Inzwischen gelöschte/inaktive Abos zählen nicht bleiben gezählt (Tracking-Tabelle incentive_new_abos, einmal erreicht = bleibt)
Vor dem Zeitraum angelegte Abos zählen (solange aktiv) zählen, wenn Aktivierung/erste Zahlung in den Zeitraum fällt

Konkret nachgerechnet (DB-Stand heute), Beispiel Tanja Longerich (Montenegro 17 vs. Top-10 13):

  • 13 = aktive, nicht gelöschte Kundenabos heute (Top-10-Wert ✓)
  • 17 = 13 + 1 Eigenabo + 2 im Juli gelöschte Kundenabos (soft-delete am 06.07./21.07., im Mai angelegt) + 1 inzwischen storniertes Abo (Status 6, Januar-Abo, erste Ausführung im April)

Analog: Rosa Maria Assenbaum 15 = 13 + 1 Eigenabo + 2 gelöschte (16.07./21.07.) − 1 vor dem Zeitraum aktiviertes; Monika Kunz 16 = 13 + 1 + 2; Melanie Hoffmann-Gebhard 15 = 13 + 1 + 1. Die Erwartung der Kundin („Tanja 16") ist die Montenegro-Kundenabo-Zahl (17 − 1 Eigenabo) — kein Bestand.

Code: Top-10 BackofficeDashboardService.php:764-776 (active=1, status NOT IN (4,5,6), SoftDelete-Scope) vs. Incentive IncentiveParticipant.php:300-354 + IncentiveTracker.php:85-135.

Empfehlungen:

  1. UI-Text schärfen: Untertitel der Top-10-Karte z. B. „aktueller Bestand aktiver Kundenabos — weicht bewusst von der Incentive-Zählung ab (dort zählen alle im Aktionszeitraum gestarteten Abos dauerhaft)". Dazu eine kurze Erklärung an die Kundin (Text kann aus der Tabelle oben übernommen werden).
  2. Daten-Auffälligkeit klären (separat!): Es gibt im Team von 454 7 Kundenabos, die im Juli per Soft-Delete gelöscht wurden, aber weiter active=1/Status 2-3 tragen (Löschung vermutlich über den Admin-Delete AboController.php:170-178, der Status/Flags nicht anfasst). Diese Abos werden durch den SoftDelete-Scope nirgends mehr ausgeführt oder gezählt — der inkonsistente Zustand (aktiv + gelöscht) sollte aber geprüft werden: War die Löschung beabsichtigt? Sollte destroy() zusätzlich active=0/Status setzen?

++ Umsetzen wie in der Empfehlung zusätzlich ja es ist richtig die Abos mussten gelöscht werden, da diese neu angelegt worden sind. Hier gab's Zahlungsprobleme grundsätzlich auf active null setzen und auch gleich die Funktion reparieren.

Umgesetzt (2026-07-24):

  • UI-Text: Untertitel der Karte lautet jetzt „aktueller Bestand aktiver Kundenabos · weicht bewusst von der Incentive-Zählung ab" mit Erklär-Tooltip.
  • Funktion repariert: UserAbo hat einen deleting-Hook — jeder Softdelete setzt jetzt automatisch active = false (gilt für Admin-destroy(), Portal-deletePending() und alle weiteren Model-Deletes). Test in AboAdminDeleteTest („setzt beim Softdelete auch active auf false").
  • Datenbereinigung: Migration 2026_07_24_120000_deactivate_soft_deleted_user_abos deaktiviert alle bestehenden gelöschten-aber-aktiven Abos (auf dem Testsystem ausgeführt: 7 Datensätze bereinigt; läuft in Produktion beim Deploy).

3. Top 10 Kundenabos (Umsatz): Soll/Ist unklar, Abo-Anzahl fehlt

Feedback: Warum ist Soll mal höher, mal niedriger als Ist? Was heißt „pro Zyklus"? Gewünscht: bereits angerechnete Kundenabopunkte des Monats + Anzahl der Abos dahinter (z. B. „Tanja: 1123 Punkte mit 16 Kundenabos").

Befund/Ursache:

  • Soll = Punktebestand der heute aktiven Kundenabos (Produktpunkte × Menge, ohne gelöschte/stornierte) — „was die Abos pro Lieferzyklus bringen" (BackofficeDashboardService.php:721-736).
  • Ist = im gewählten Monat tatsächlich abgerechnete Kundenabo-Punkte aus user_sales_volumes (Z. 744-756).
  • Ist > Soll entsteht, wenn Abos nach ihrer Abrechnung gelöscht/storniert wurden (Beispiel Tanja: 2 im Juli gelöschte Abos wurden Anfang Juli noch abgerechnet → stecken im Ist 1.123, fehlen im Soll 992) oder Mengen/Artikel reduziert wurden. Ist < Soll, wenn Abos im Monat neu dazukamen und ihr Liefertermin noch aussteht. Beides ist korrekt, nur unglücklich erklärt.

Empfehlung (kleiner Umbau):

  1. Ist als Hauptwert (Sortierung nach Ist statt Soll — die Kundin denkt vom abgerechneten Monatsumsatz her), Soll als Zweitwert.
  2. Spalte „Abos" ergänzen (Anzahl aus customerAboCounts(), ist bereits berechnet).
  3. Beschriftung: „Soll" → z. B. „Punkte pro Lieferung (heutiger Bestand)", „Ist" → „im {Monat} abgerechnet".
  4. Eigene Person aufnehmen (Punkt 1/2a).

++ umsetzen wie in der Empfehlung.

Umgesetzt (2026-07-24): Sortierung nach Ist (points_actual), neue Spalte „Abos"; Beschriftungen: „Abgerechnet (Punkte)" (Hauptwert, fett) und „Bestand/Lieferung" (Zweitwert), Untertitel „sortiert nach abgerechneten Kundenabo-Punkten {Monat}" mit Erklär-Tooltip; „ich" inklusive. Test: „sortiert die Top-10-Umsatzliste nach abgerechneten Ist-Punkten und liefert die Abo-Anzahl mit".

4. Führungskräfte-Statistik: „Zahlen stimmen leider nicht"

Feedback: Teamabos/Team-Kundenabos sollen die komplette Downline umfassen. Beispiele: Rosmarie tatsächlich 81 Teamabos + 200 Teamkundenabos (Liste: 35 + 54), Nina 21 + 52 (Liste: 4 + 12).

Befund: Kein Rechenfehler — die Liste tut exakt das, was in F9 entschieden wurde: Der Teilbaum jeder Führungskraft wird an der nächsten Führungskraft abgeschnitten (BackofficeDashboardService.php:627-659, continue bei Leader-Kind), damit nichts doppelt zählt und die Summen additiv sind (Kopfzeile „464" = exakte Gesamtsumme).

Nachgerechnet ohne Abschneiden (DB heute): Rosa 82 Teamabos + 201 Teamkundenabos (Kundin: 81/200 — Stand ihres Tests), Nina exakt 21 + 52 wie von der Kundin angegeben. Die Kundin hat also die komplette Downline gezählt — ihre Erwartung widerspricht der eigenen Entscheidung F9 („an der nächsten Führungskraft abschneiden", siehe entwicklungskonzept.md, Entscheidungstabelle).

Konsequenz der Umstellung, die die Kundin kennen sollte: Zählt jede Führungskraft ihre komplette Downline, werden Abos mehrfach gezählt (Ninas 73 Abos stecken dann auch komplett in Rosas 283). Die Kopf-Summe „464" und die Spaltensummen sind dann keine echten Gesamtwerte mehr.

Empfehlung:

  1. Umstellung auf komplette Downline ist technisch klein (das continue in teamIdsPerLeader() entfernen bzw. Schalter einbauen); Performance unkritisch (eine gemeinsame Sponsor-Map, BFS in memory).
  2. Vorher kurz an die Kundin: „Dann zählen Abos bei mehreren Führungskräften gleichzeitig — die Gesamtsumme oben rechnen wir dann weiterhin ohne Doppelzählung. Okay?" (Kopfzeile auf Basis der Gesamtmenge, Zeilen mit kompletter Downline.)
  3. Optional beide Sichten: Spalten „Team (direkt)" und „Team (gesamt)" — dann bleibt die additive Sicht erhalten.

++ wir sitzen Option drei, um dass wir beides zeigen und kurz erklären. Das ist relativ komplex und vermutlich wohl nicht verständlich. Von daher würde ich erst mal beides zeigen und dann klären, was dein sinnvoller ist..

Umgesetzt (2026-07-24, Option 3 — beide Sichten): leaderAboOverview() liefert je Führungskraft jetzt zusätzlich team_abo_count_total/team_customer_abo_count_total (komplette Downline, ohne Abschneiden) neben den bisherigen „direkt"-Werten. Die Tabelle zeigt beide Spaltenpaare — „Teamabos/Team-Kundenabos (direkt)" und „(gesamt)" — mit Tooltips; die Gesamt-Spalte rechnet mit der kompletten Downline (entspricht der Zählung der Kundin: eigenes Abo + Kundenabos + Teamabos gesamt + Team-Kundenabos gesamt), sortiert danach. Die Kopf-Summe bleibt bewusst überschneidungsfrei („direkt"), eine Fußnote unter der Tabelle erklärt den Unterschied. Test: „liefert je Fuehrungskraft zusaetzlich die Teamzahlen der kompletten Downline". → Nach Livegang mit der Kundin klären, ob beide Sichten bleiben oder eine reicht.

5. Statistik 97.453 ≠ Struktur 92.211 („theoretisch gleich")

Befund: Beide Zahlen sind korrekt — die Differenz sind Punkte, die nicht payline-wirksam sind.

Nachgerechnet für das Team von 454, Juli 2026 (heute): Statistik-Summe 97.769, TP-Summe 92.569, Differenz 5.200 = 19 Neupartner-Einschreibungen (Status 5, je 200/400 Punkte = 5.000) + 1 Gutschrift (200 Punkte), alle mit status_points=2. Die Screenshot-Werte (97.453/92.211, Differenz 5.242) sind derselbe Effekt zum Testzeitpunkt.

Empfehlung: Kein Fix nötig. In der Statistik-Übersicht eine Fußnote/Tooltip ergänzen: „enthält auch Punkte, die nicht in die Payline fließen (z. B. Neupartner-Einschreibungspunkte)" — optional als eigene Aufschlüsselungszeile (Datenbasis vorhanden: status_points=2-Summe je Linie).

++ Umsetzung wie in der Empfehlung.

Umgesetzt (2026-07-24): Neues SQL-Fragment SalesVolumePoints::nonPaylinePoints() (status_points = 2) + Kennzahl non_payline_points im Overview/Snapshot. Unter der Linien-Tabelle erscheint eine Fußnote: „In den Gesamtpunkten enthalten: X Punkte, die nicht in die Payline fließen (z. B. Neupartner-Einschreibungspunkte) …" (nur wenn > 0; alte Snapshots ohne den Wert zeigen nichts). Test: „summiert Punkte ohne Payline-Wirkung (status_points 2) als eigene Kennzahl".

6. Team-Abo-Seite: „Was bedeuten diese 42.628 Abo-Punkte?"

Befund: Die Spalte „Punkte" in „Abos nach Ebenen" ist der Soll-Bestand pro Lieferzyklus: Summe getTotalPoints() (Produktpunkte × Menge) aller aktiven Abos (Eigen- + Kundenabos) je Ebene — über alle 10 Ebenen des Teams (TeamAboOverviewService.php:282-326). Es ist also nicht der abgerechnete Umsatz, sondern „was alle Team-Abos pro Zyklus bringen würden".

Zur Einordnung der Karte darüber: „Abo-Umsatz in der Payline" (Soll 38.375 / Ist 37.912) ist dieselbe Soll-Logik, aber nur für die Payline (6 Ebenen), plus Ist = im Monat abgerechnete Abo-Punkte (PaylineForecastService.php:75-86). 42.628 > 38.375, weil das Team 10 Ebenen hat, die Payline nur 6.

Der Wunsch „Zahlen, die zum Stichtag 25. im aktuellen Monat abgerechnet werden" ist auf dem Home-Dashboard durch die Prognose-Kacheln 5./10./20./25. (B10) bereits abgedeckt.

Empfehlung:

  1. Spaltenüberschrift präzisieren: „Punkte pro Lieferzyklus (Bestand)".
  2. Ist-Spalte je Ebene ergänzen („im Juli abgerechnet") — SQL-Fragment SalesVolumePoints::aboPoints() existiert bereits, nur je Ebenen-User-Menge gruppieren.
  3. Optional dritte Spalte „davon bis zum 25. erwartet" (Logik aus forecastDays() wiederverwendbar).

++ Umsetzung wie in einer Empfehlung alle drei Punkte entsprechend umsetzen.

Umgesetzt (2026-07-24): Die Ebenen-Tabelle hat jetzt drei Punkte-Spalten: „Punkte pro Lieferung (Bestand)" (bisherige Spalte, präzisiert), „abgerechnet ({Monat})" (abo_points_billed, gleiche Zählweise wie der Payline-Ist-Wert) und „bis zum 25. erwartet" (abo_points_expected = abgerechnet + offene Ausführungen mit next_date bis zum 25.; nur im laufenden Monat, sonst = abgerechnet) — inkl. Summenzeile und Tooltip. Sprachkeys in de/en/es/fr. Test: „liefert je Ebene abgerechnete Abo-Punkte und die Prognose bis zum letzten Liefertag".

7. Payline: Anzahl der Abos ausweisen

Feedback: „Wieviel ABOs gibt es in der Payline? z. B. 205 Teampartnerabos + 302 Teamkundenabos (6 Ebenen)."

Befund: Die Karte zeigt bisher nur „Payline = 6 Ebenen · 504 Berater". Die Zähler sind trivial ermittelbar (gleiche Filter wie aboTargetPoints(), PaylineForecastService.php:164-183). Heute für User 454: 6 Ebenen, 502 Berater, 129 Teampartner-Abos + 340 Team-Kundenabos (die Beispielzahlen der Kundin waren Schätzungen).

Empfehlung: aboTurnover() um team_abo_count/team_customer_abo_count erweitern und in der Kartenunterzeile anzeigen: „6 Ebenen · 502 Berater · 129 Teamabos + 340 Kundenabos". Aufwand klein, Test in PaylineForecastServiceTest ergänzen.

++ Umsetzung wie in einer Empfehlung

Umgesetzt (2026-07-24): aboTurnover() liefert zusätzlich team_abo_count/team_customer_abo_count (gleiche Filter wie das Soll); die Karten-Unterzeile zeigt jetzt z. B. „Payline = 6 Ebenen · 502 Berater — 129 Teamabos + 340 Kundenabos in der Payline" (Sprachkey abo.payline_abo_counts, de/en/es/fr). Test in PaylineForecastServiceTest erweitert.

8. Live-Rangliste Montenegro: Nummerierung

Feedback (berechtigt): Gewinner müssen 1, 2, 3, … nummeriert sein; wer mehr Punkte hat, aber die Mindestqualifikation nicht erfüllt, wird darunter gereiht (Beispiel Tanja: meiste Punkte, nicht qualifiziert → Platz 5, nicht Platz 1).

Befund/Ursache: Der gespeicherte Rang ist ein reines Punkte-Ranking über alle Teilnehmer (IncentiveTracker.php:320-341, Qualifikation spielt keine Rolle). Die Anzeige sortiert aber zuerst nach is_qualified DESC (IncentiveParticipant.php:126-143). Ergebnis: Qualifizierte stehen oben, tragen aber ihre globalen Punkte-Ränge (4, 7, 9), darunter stehen Rang 1, 2, 5 — genau die gemeldete Verwirrung.

Zusätzliche Brisanz: isWinner prüft rank <= max_winners (IncentiveParticipant.php:519-526). Mit dem heutigen Punkte-Rang könnte ein qualifizierter Berater mit globalem Punkte-Rang 21+ fälschlich kein Gewinner sein, obwohl weniger als 20 qualifiziert sind. Die Sortier-Korrektur repariert also auch die Gewinner-Logik.

Empfehlung: updateRanking() auf ORDER BY is_qualified DESC, total_points DESC umstellen (Tie-Break unverändert) — dann ist der gespeicherte Rang identisch mit der Anzeigereihenfolge: Qualifizierte zuerst (1..n), Nicht-Qualifizierte dahinter; „springt nach oben", sobald die Qualifikation erreicht ist. Vorher prüfen: Wo wird rank noch verwendet (Mails/Benachrichtigungen bei Rangänderung, withRankingActivity())? Rangsprünge durch die Umstellung könnten einmalig Benachrichtigungen auslösen — ggf. Benachrichtigung für den Umstellungs-Lauf unterdrücken. Test ergänzen (qualifiziert schlägt Punkte).

++ Umsetzung wie in einer Empfehlung

Umgesetzt (2026-07-24): IncentiveTracker::updateRanking() sortiert jetzt is_qualified DESC → total_points DESC → Terms-Tie-Break; der gespeicherte Rang entspricht damit exakt der Anzeigereihenfolge (Qualifizierte 1..n, danach die Nicht-Qualifizierten; wer sich qualifiziert, springt hoch — genau das Kundin-Beispiel „Tanja → Platz 5"). Nebenwirkungen geprüft: is_qualified wird vor jedem Ranking-Lauf frisch berechnet, es existiert kein Rang-Benachrichtigungsmechanismus (keine Mail-Flut möglich), und isWinner (rank <= max_winners) wird durch den Fix erst korrekt (vorher konnte ein Qualifizierter mit Punkte-Rang 21+ fälschlich leer ausgehen). Neuer Test Incentive/IncentiveRankingTest (4 Fälle); ein vorbestehend roter Unit-Test (Reflection auf falsche Klasse) wurde mitrepariert.

9. Porto Deutschland: Abo 19,90 vs. Einzelbestellung 16,72

Befund: Es gibt keinen echten Preisunterschied — es ist derselbe Versandsatz, einmal brutto und einmal netto angezeigt. 16,72 × 1,19 = 19,90.

Die tatsächlich berechneten Gesamtbeträge sind konsistent (netto + MwSt); es ist eine Anzeige-Inkonsistenz, kein Abrechnungsfehler.

Empfehlung: Porto-Ausweis vereinheitlichen — im Abo-Checkout in der Netto-Aufstellung den Netto-Versand zeigen (bzw. „19,90 € inkl. MwSt" kennzeichnen), damit Abo und Einzelbestellung vergleichbar sind. Nebenbefund: Yard.php:323 berechnet shipping_tax falsch (liefert den Nettowert statt des Steueranteils) — mit fixen.

++ Umsetzung wie in einer Empfehlung, Mit einer Kennzeichnung, denn wichtig ist. Bei MU Preise müssen wir immer den Bruttowert anzeigen. Bei den Beratern im System können wir den Nettowert anzeigen. Das ist leider nun mal so, dass die Berater als selbstständige im netto handeln und alles, was nach außen geht, an Kunden brutto zu behandeln ist. Das müsste einmal geprüft werden, dass das einheitlich ist und dann bitte netto oder inklusive Mehrwertsteuer kennzeichnen.

Umgesetzt (2026-07-24) nach der Regel „Berater netto (gekennzeichnet), Endkunden brutto (gekennzeichnet)":

  • Wichtiger Befund: _order_combined_summary und _order_abo_show werden auch im Kundenportal eingebunden. Deshalb neues View-Flag portal_view (Default = Berater), gesetzt von den Portal-Aufrufern (Portal/AboController, portal/abo/my_abo).
  • Berater-Ansichten (Abo-Detail Berater/Admin, Einzelbestellungs-Übersicht): Versand netto + Kennzeichnung „(netto)" (Key order.net, existierte). Die Netto-Summenzeilen passen jetzt rechnerisch zur Porto-Zeile.
  • Kundenportal (Abo anlegen/ansehen): Versand brutto + „(inkl. MwSt.)" (neuer Key order.incl_VAT, de/en/es/fr); bei Reverse-Charge/steuerfrei automatisch netto + „(netto)".
  • Nebenbefund gefixt: Yard::shipping_tax berechnete den Nettowert statt des Steueranteils (jetzt brutto − netto); alle anderen shipping_tax-Verwendungen (HomepartyCart, ShopApiOrderCart, Correction) waren bereits korrekt und blieben unverändert. Tests: YardShippingTaxTest (neu) + 2 Rendering-Tests in AboOneTimeViewTest.
  • Nachtrag (gleicher Tag): Drei weitere Versandkosten-Anzeigen ergänzt, die in der ersten Runde fehlten (dort waren die Kundin-Screenshots entstanden): CRM-Warenkorb der Berater user/order/yard_view_form.blade.php → „(netto)"; Shop-Warenkorb web/templates/card.blade.php und Shop-Checkout web/templates/checkout.blade.php → „(inkl. MwSt.)", bei Reverse-Charge/steuerfrei netto + „(netto)" (dort zeigte die Porto-Zeile zuvor sogar brutto über einer Netto-Summenzeile). Nicht angefasst: Homeparty-/Sammelbestellungs- und PDF-/Mail-Ansichten (eigene Belegformate).
  • Für die Kundin: Es gab nie einen Preisunterschied — DE-Teampartner-Porto ist 19,90 € brutto = 16,72 € netto; jetzt ist überall gekennzeichnet, welcher Wert gezeigt wird.

10. Struktur-Ansicht: Symbole „A" (eigenes Abo) und „(n)" (Kundenabos)

Feedback (neue Anforderung): In der Struktur je Teampartner zwei Kennzeichen: grünes A = hat eigenes Abo, rotes (8) = Anzahl eigener Kundenabos.

Einschätzung: Machbar, Datenbasis vorhanden (aktives me-Abo je user_id, Kundenabo-Zählung je member_id — gleiche Queries wie in der Statistik). Umsetzungsort: Struktur-/Tree-Rendering (TeamController.php, TreeHtmlRenderer). Wichtig: Zählungen gebatcht für alle sichtbaren Knoten laden (zwei Group-by-Queries), nicht pro Knoten. Aufwand mittel (inkl. Badge-Styling + Test). Vorschlag: gleiche Zählweise wie Top-10 (aktiver Bestand, ohne gelöschte) — dann sind Struktur und Statistik konsistent.

++ So umsetzen

Umgesetzt (2026-07-24): Badges im TreeHtmlRenderer (Struktur-Ansicht, TeamController::structure()): grünes „A"-Badge bei eigenem aktivem Abo, rotes „(n)"-Badge mit der Anzahl aktiver eigener Kundenabos (nur bei n > 0), direkt neben dem Karriere-Level. Zählweise identisch zur Statistik (activeAbo, ohne gelöschte); Daten werden für den ganzen Baum mit zwei gebatchten Queries geladen (keine Query pro Knoten). Tooltips über Sprachkeys team.badge_own_abo/team.badge_customer_abos (de/en/es/fr). Neuer Test TeamStructureAboBadgesTest (5 Fälle).


Rückfragen an die Kundin — Stand nach Umsetzung

  1. Führungskräfte direkt vs. gesamt → entschieden: beide Sichten (Option 3, umgesetzt). Nach Livegang klären, ob beide Spaltenpaare bleiben.
  2. Top-10-Umsatz-Sortierung → entschieden: nach Ist (umgesetzt).
  3. Gelöschte aktive Abos → beantwortet: Löschungen waren beabsichtigt (Abos wurden wegen Zahlungsproblemen neu angelegt). Fix: Softdelete setzt jetzt active=0 (Model-Hook) + Datenbereinigung per Migration.
  4. Neu/offen (F12 aus dem ursprünglichen Konzept, weiterhin unbeantwortet): Zählen eigene Abos/Kundenabos in der Payline-Prognose mit? Aktuell ja (PaylineForecastService::INCLUDE_OWN_ABOS = true).

Anhang: Verifikationsdaten (DB-Stand 2026-07-24, Team von User 454)

Beraterin Top-10 (aktiver Bestand) davon-Rechnung Montenegro
Tanja Longerich (909) 13 17 = 13 + 1 Eigenabo + 2 gelöschte + 1 storniertes
Rosa M. Assenbaum (456) 13 15 = 13 + 1 Eigenabo + 2 gelöschte − 1 vor Zeitraum aktiviert
Monika Kunz (1218) 13 16 = 13 + 1 Eigenabo + 2 gelöschte
Melanie Hoffmann-G. (1143) 13 15 = 13 + 1 Eigenabo + 1 gelöschtes
Claudia Lattner (454) 7 (fehlt in Liste, Punkt 1) 8 = 7 + 1 Eigenabo
  • Statistik-Linien-Summe heute: 97.769; TP-Summe (Struktur): 92.569; Differenz 5.200 = 19 Neupartner-Einschreibungen (5.000 P.) + 1 Gutschrift (200 P.), alle status_points=2.
  • Führungskräfte komplette Downline heute: Rosa 82 Teamabos / 201 Teamkundenabos (Kundin: 81/200), Nina 21 / 52 (exakt wie Kundin).
  • Payline 454 heute: 6 Ebenen, 502 Berater, 129 Teamabos, 340 Teamkundenabos.
  • Montenegro-Incentive: incentives id 2, Qualifikation 01.04.–31.07.2026, min. 4 Partner / 6 Abos, max. 20 Gewinner.

Anhang: Geänderte Dateien (Umsetzung 2026-07-24)

Services/Models: BackofficeDashboardService (ich-Aufnahme, Ist-Sortierung, direkt+gesamt-Teamzahlen, nonPaylinePointsSum), SalesVolumePoints::nonPaylinePoints(), TeamAboOverviewService (abo_points_billed/abo_points_expected), PaylineForecastService (Abo-Zähler), UserAbo (deleting-Hook), IncentiveTracker::updateRanking(), Yard (shipping_tax), TreeHtmlRenderer (Abo-Badges), Portal/AboController (portal_view).

Views: user/backoffice/statistics/index.blade.php, user/team/abos.blade.php, admin/abo/_order_combined_summary.blade.php, admin/abo/_order_abo_show.blade.php, portal/abo/_create_check.blade.php, portal/abo/my_abo.blade.php, user/shop/sales/api_order_list_total.blade.php.

Sprachdateien: abo.php (payline_abo_counts, abo_points_per_cycle/billed/expected + hint), order.php (incl_VAT), team.php (badge_own_abo, badge_customer_abos) — jeweils de/en/es/fr.

Migration: 2026_07_24_120000_deactivate_soft_deleted_user_abos (Datenbereinigung gelöschte-aber-aktive Abos).

Tests: neu Incentive/IncentiveRankingTest, TeamStructureAboBadgesTest, YardShippingTaxTest; erweitert BackofficeStatisticsListsTest, TeamAboOverviewTest, PaylineForecastServiceTest, AboAdminDeleteTest, AboOneTimeViewTest, Unit/Incentive/IncentiveTrackerTest.