# Funktionsweise: Tiefenbonus (Growth Bonus) ## ⚠️ WICHTIG: Bug-Fix November 2025 ### Das Problem (vor November 2025) Die Payline-Prozentsätze (`pr_line_1` bis `pr_line_6`) in der Datenbank enthielten **bereits den Growth Bonus**. **Beispiel Gold Member (falsche Berechnung):** | Ebene | Wert in DB (`pr_line_X`) | Was ausgezahlt wurde | Was korrekt gewesen wäre | | ------- | ------------------------ | -------------------- | ------------------------------ | | Ebene 1 | 9% | 9% | 7% Payline + 2% Growth = 9% | | Ebene 2 | 9% | 9% | 7% Payline + 2% Growth = 9% | | Ebene 3 | 9% | 9% | 7% Payline + 2% Growth = 9% | | Ebene 4 | 6% | 6% | 4% Payline + 2% Growth = 6% | | Ebene 5 | 4% | 4% | 2% Payline + 2% Growth = 4% | | Ebene 6 | 4% | 4% | 2% Payline + 2% Growth = 4% | | Ebene 7 | - | 2% (Growth nochmal!) | 2% Growth (nur mit Differenz!) | **Problem:** Der Growth Bonus wurde **doppelt gezählt**: 1. Einmal IN den Payline-Prozentsätzen (pr_line_1 = 9% statt 7%) 2. Nochmal SEPARAT auf Ebenen ab 7+ (Legacy-Berechnung) ### Die Lösung (ab November 2025) 1. **Payline-Prozentsätze korrigiert:** `pr_line_X` enthält NUR den Payline-Anteil 2. **Growth Bonus separat:** Wird mit Differenz-Logik berechnet 3. **Einmal pro Bein:** Growth Bonus wird nur EINMAL pro Firstline-Zweig ausgezahlt **Beispiel Gold Member (korrekte Berechnung):** | Ebene | Payline (`pr_line_X`) | Growth Bonus (separat) | Gesamt | | -------- | --------------------- | ---------------------- | ------ | | Ebene 1 | 7% | +2% (Differenz-Logik) | 9% | | Ebene 2 | 7% | +2% | 9% | | Ebene 3 | 7% | +2% | 9% | | Ebene 4 | 4% | +2% | 6% | | Ebene 5 | 2% | +2% | 4% | | Ebene 6 | 2% | +2% | 4% | | Ebene 7+ | - | +2% (Differenz-Logik) | 2% | **Wichtig:** Der Growth Bonus wird NUR ausgezahlt, wenn kein gleichrangiger oder höherer Partner in der Downline ist (Differenz-Berechnung)! --- ## Differenz-Logik (ab November 2025) Der Tiefenbonus ist ein **Differenz-Bonus**, der **sofort ab der 1. Ebene** beginnt. Es gilt das Prinzip: **"Jeder Partner schützt sein eigenes Team-Volumen."** ### 1. Die Grundregel - **Start:** Der Bonus berechnet sich auf Points ab der **1. Ebene** (direkte Downline). - **Anspruch:** Ein Partner erhält seinen Status-Prozentsatz auf alle Points in seiner Linie, **bis** er auf einen Partner trifft, der selbst einen Status-Anspruch hat. - **Blockade:** Sobald ein Partner in der Downline einen Anspruch hat, zieht er diesen von der Upline ab (Differenz-Rechnung). - **⚠️ WICHTIG - Erreichtes Qualifikations-Level:** Die Blockade erfolgt NUR basierend auf dem **in dem Monat tatsächlich erreichten Level** (`qual_user_level`), NICHT auf dem aktuellen Karriere-Level des Partners! ### 1.1 Erreichte Qualifikation vs. Aktuelles Level Ein Partner kann ein bestimmtes Karriere-Level (z.B. Gold) haben, aber in einem Monat die Qualifikationsvoraussetzungen nicht erfüllen. In diesem Fall: | Situation | Aktuelles Level | Erreicht in Monat | Blockiert mit | | --------- | --------------- | ----------------- | ------------- | | Fall A | Gold (2%) | Gold qualifiziert | 2% ✅ | | Fall B | Gold (2%) | Team Leader (0%) | 0% ❌ | | Fall C | Team Leader | Silber (1.5%) | 1.5% ✅ | **Technische Umsetzung:** - Die Methode `getQualifiedGrowthBonus()` in `BusinessUserItemOptimized` gibt den Growth Bonus basierend auf dem **erreichten Qualifikations-Level** (`qual_user_level`) zurück. - Die alte Methode `getActiveGrowthBonus()` gibt den Growth Bonus basierend auf dem **aktuellen Karriere-Level** zurück (NUR für Legacy-Berechnungen!). - Der `GrowthBonusCalculator` verwendet ab November 2025 ausschließlich `getQualifiedGrowthBonus()`. --- ### 2. Die Differenz (Der Normalfall) Points entstehen irgendwo im Team von **Partner B** (egal ob in B's Ebene 1 oder B's Ebene 50). **Die Verteilung:** 1. **Sicht Partner B (Silber):** - Er hat Anspruch auf **1,5 %** auf sein gesamtes Team. - Da unter ihm (Partner C) niemand einen Status hat, der etwas wegnehmen könnte, erhält B die vollen **1,5 %**. - Damit sind 1,5 % des "Kuchens" verteilt. 2. **Sicht Partner A (Diamant):** - Du hast Anspruch auf **2,5 %**. - Du schaust auf die Linie von Partner B. - Partner B hat den Status Silber und beansprucht damit **1,5 %** für sich und sein ganzes Team. - **Deine Rechnung:** 2,5 % (Dein Anspruch) - 1,5 % (Anspruch B) = **1,0 %**. - **Ergebnis:** Du erhältst auf das gesamte Volumen unter Partner B exakt **1,0 %**. --- ### 2. Das "GAP" (Die direkte Ebene) Da der Bonus ab Ebene 1 beginnt, entsteht das GAP (die Auszahlung trotz gleichem Rang) immer am **Eigenumsatz des Partners**: - **Partner A** (Diamant, 2,5 %) ist Sponsor von **Partner B** (Diamant, 2,5 %). - **Punkte von B (Eigenbestellung/Kunden):** - Partner B erhält darauf _keinen_ Tiefenbonus (man kriegt keinen Tiefenbonus auf sich selbst). - Partner B zieht also **0 %** vom Topf ab. - **Partner A erhält die vollen 2,5 % auf die Punkte von B.** - **Punkte UNTER B (Team von B):** - Partner B greift hier zu (Start ab Ebene 1) und nimmt sich **2,5 %**. - Partner A rechnet: 2,5 % - 2,5 % = **0 %**. - **Partner A ist hier blockiert.** > Fazit: Bei gleichem Rang verdient man nur an den direkten Points des Partners (GAP), aber nicht mehr an dessen Team. --- ### 3. Das Szenario (A -> B -> F) Wir schauen uns deine Struktur mit 3 Diamanten in einer Linie an. Alle haben Anspruch auf **2,5 %**. - **Partner A** (Ebene 1) - **Partner B** (Ebene 2, direkt unter A) - ... dazwischen Berater ohne Status ... - **Partner F** (Ebene 6, unter B) - ... Punkte entstehen unter F ... ### Bereich 1: Punkte von Partner B - Das ist für **A** die Ebene 1. - B blockiert nicht (da Eigenumsatz). - **Ergebnis:** **A erhält 2,5 %**. ### Bereich 2: Punkte ZWISCHEN B und F (Ebene 3 bis 6) - Hier entstehen Punkte im Team von B. - **Sicht B:** Er ist qualifiziert (Start ab Ebene 1). Er nimmt sich **2,5 %**. - **Sicht A:** Er hat Anspruch auf 2,5 %. B hat aber schon 2,5 % genommen. Differenz = 0 %. - **Ergebnis:** **B erhält 2,5 %**. A geht leer aus. ### Bereich 3: Punkte von Partner F - Das ist für **B** eine Ebene in seiner Downline. - F blockiert hier noch nicht (Eigenumsatz). - **Ergebnis:** **B erhält 2,5 %** auf die Punkte von F. ### Bereich 4: Punkte UNTER F (ab Ebene 7) - Hier entstehen Punkte im Team von F. - **Sicht F:** Er ist qualifiziert (Start ab Ebene 1). Er nimmt sich **2,5 %**. - **Sicht B:** Anspruch 2,5 %. F hat schon 2,5 % genommen. Differenz = 0 %. - **Sicht A:** Anspruch 2,5 %. B (und F) haben alles genommen. Differenz = 0 %. - **Ergebnis:** **F erhält 2,5 %**. B und A gehen leer aus. --- ### 4. Zusammenfassung für die IT-Logik 1. **Trigger:** Ein Umsatz (Points) entsteht bei User X. 2. **Schleife:** Gehe die Upline hoch (Sponsor -> Sponsor...). 3. **Prüfung:** - Hat der Upline-Partner einen Status? (z.B. Diamant). - (Keine Prüfung auf Ebene mehr nötig, da Start immer ab Ebene 1). 4. **Rechnung:** - Auszahlung = Mein %-Satz - Bereits verteilter %-Satz. - Wenn Auszahlung > 0: Speichern. - Setze `Bereits verteilter %-Satz` auf den neuen Wert (also `Mein %-Satz`). --- ## Code-Implementierung Diese Implementierung nutzt eine **rekursive Aggregation von Volumen nach "Schutz-Level"**. Anstatt für jede Transaktion die Upline hochzulaufen ("Push"), holt sich der User die aggregierten Volumina seiner Downline gruppiert nach dem bereits beanspruchten Prozentsatz ("Pull"). ### A. Neue Methode `getVolumeByProtectionLevel()` Diese Methode liefert ein Array zurück, das das Volumen nach "bereits verteiltem Prozentsatz" gruppiert. Format: `['0.0' => 1000, '1.5' => 5000, ...]` ```php /** * Liefert das Volumen der Downline gruppiert nach dem "bereits verteilten Prozentsatz" (Protection Level). * Rekursive Funktion, die die "Differenz-Logik" vorbereitet. * * @return array Key = Protected Percent, Value = Volume Points */ public function getVolumeByProtectionLevel(): array { $volumes = []; // 1. Eigenes Volumen (Unprotected / GAP) // Man selbst schützt seinen eigenen Umsatz NICHT vor der Upline. // Daher Start mit Protection Level 0.0 (oder dem was von unten kommt, aber hier ist es ja Eigenumsatz) // WICHTIG: Wir nutzen das Feld, das auch TreeCalcBot für die Punkte nutzt // sales_volume_points_TP_sum scheint in der DB/Model Logik für das relevante Volumen zu stehen $ownVolume = (float) ($this->b_user->sales_volume_points_TP_sum ?? 0); if ($ownVolume > 0) { $key = '0.0'; if (!isset($volumes[$key])) $volumes[$key] = 0.0; $volumes[$key] += $ownVolume; } // 2. Mein Schutz-Level ermitteln // Das ist der Prozentsatz, den ICH auf mein Team beanspruche. // Alles Volumen, das durch MICH hindurch zur Upline fließt, hat mindestens diesen Schutz-Level. $myProtectionPercent = 0.0; if ($this->isQualLevel()) { $qual = $this->b_user->qual_user_level; if (!empty($qual['growth_bonus'])) { $myProtectionPercent = (float) $qual['growth_bonus']; } } // 3. Kinder verarbeiten if (!empty($this->businessUserItems)) { foreach ($this->businessUserItems as $childItem) { // Rekursion: Hol dir die Volumen-Töpfe aus der Downline // Hinweis: Hier muss sichergestellt sein, dass die Kinder geladen sind. // initBusinesslUserDetail lädt normalerweise die Struktur. // Falls Kinder nicht geladen sind, müssten sie hier theoretisch geladen werden. // Wir gehen davon aus, dass die Struktur bereits rekursiv via readParentsBusinessUsers geladen wurde. $childVolumes = $childItem->getVolumeByProtectionLevel(); // 4. Schutz-Level anwenden (Aggregation) foreach ($childVolumes as $protectedPercentStr => $vol) { $incomingProtection = (float) $protectedPercentStr; // Das Volumen ist bereits mit $incomingProtection geschützt. // Da es nun durch MICH fließt, erhöht sich der Schutz auf MEINEN Level (falls meiner höher ist). $effectiveProtection = max($incomingProtection, $myProtectionPercent); $newKey = (string) $effectiveProtection; if (!isset($volumes[$newKey])) $volumes[$newKey] = 0.0; $volumes[$newKey] += $vol; } } } return $volumes; } ``` ### B. Neue Methode `calculateGrowthBonusRecursive()` Diese Methode ersetzt die bisherige Berechnung und nutzt die oben definierte Aggregation. ```php /** * Berechnet den Growth Bonus (Tiefenbonus) basierend auf der Differenz-Logik. */ private function calculateGrowthBonusRecursive($qualUserLevel): float { if (empty($qualUserLevel->growth_bonus) || $qualUserLevel->growth_bonus <= 0) { return 0.0; } $myGrowthPercent = (float) $qualUserLevel->growth_bonus; $totalGrowthBonus = 0.0; // Wir iterieren über alle direkten Beine (Firstlines) foreach ($this->businessUserItems as $childItem) { // Volumen-Verteilung aus diesem Bein abrufen // Das Kind liefert uns: "Hier sind 1000 Punkte geschützt mit 0%, 5000 Punkte geschützt mit 1.5%" $volumeDistribution = $childItem->getVolumeByProtectionLevel(); foreach ($volumeDistribution as $protectedPercentStr => $volume) { $alreadyDistributedPercent = (float) $protectedPercentStr; // Differenz berechnen // Mein Anspruch MINUS was schon verteilt wurde $mySharePercent = max(0, $myGrowthPercent - $alreadyDistributedPercent); if ($mySharePercent > 0) { $commission = round($volume / 100 * $mySharePercent, 2); $totalGrowthBonus += $commission; // Optional Logging // \Log::debug("Growth Bonus: User {$this->b_user->user_id} earns {$mySharePercent}% on {$volume} pts (Protected: {$alreadyDistributedPercent}%) from leg {$childItem->b_user->user_id}"); } } } return $totalGrowthBonus; } ``` ### C. Integration in `calculateCommissions` ```php private function calculateCommissions($qualUserLevel): void { $commission_pp_total = 0; // 1. Normale Unilevel Provision (Payline) - NUR pr_line_X Werte for ($i = 1; $i <= $qualUserLevel->paylines; $i++) { if (isset($this->b_user->business_lines[$i])) { $object = $this->b_user->business_lines[$i]; $margin = (float) $this->b_user->qual_user_level['pr_line_' . $i]; $points = is_array($object) ? ((float)($object['points'] ?? 0)) : ((float)($object->points ?? 0)); $commission = round($points / 100 * $margin, 2); $commission_pp_total += $commission; // Rückschreiben if (is_array($object)) { $object['margin'] = $margin; $object['commission'] = $commission; $object['payline'] = true; } else { $object->margin = $margin; $object->commission = $commission; $object->payline = true; } $this->b_user->business_lines[$i] = $object; } } // 2. Growth Bonus - Unterscheidung Legacy vs. Neu $commission_growth_total = 0; if (!empty($qualUserLevel->growth_bonus)) { // Stichtag: 01.11.2025 $isLegacy = ($this->date->year < 2025) || ($this->date->year == 2025 && $this->date->month < 11); if ($isLegacy) { // ALT: Pauschal ab Ebene paylines+1 (FALSCH - doppelte Auszahlung!) $commission_growth_total = $this->calculateLegacyGrowthBonus($qualUserLevel); } else { // NEU: Differenz-Logik via GrowthBonusCalculator $commission_growth_total = $this->calculateGrowthBonusRecursive($qualUserLevel); } } $this->b_user->commission_pp_total = $commission_pp_total; $this->b_user->commission_growth_total = $commission_growth_total; } ``` --- ## Legacy-Berechnung (vor November 2025) - DEPRECATED **⚠️ Diese Logik war FALSCH und führte zu doppelter Auszahlung!** ```php /** * ALT: Pauschal Growth Bonus ab Ebene paylines+1 * PROBLEM: Growth Bonus war bereits in pr_line_X enthalten! */ private function calculateLegacyGrowthBonus($qualUserLevel): float { $commission_growth_total = 0; // Start ab Ebene paylines+1 (z.B. 7 bei Gold) $payline = (int) ($this->b_user->qual_user_level['paylines'] ?? 0) + 1; $maxlines = count($this->b_user->business_lines ?? []) + 1; $growth_bonus = (float) ($this->b_user->qual_user_level['growth_bonus'] ?? 0); // Auf JEDE Ebene ab payline wird der volle Growth Bonus gezahlt // OHNE Differenz-Prüfung = FALSCH! for ($i = $payline; $i <= $maxlines; $i++) { if (isset($this->b_user->business_lines[$i])) { $points = $this->b_user->business_lines[$i]['points'] ?? 0; $commission = round($points / 100 * $growth_bonus, 2); $commission_growth_total += $commission; } } return $commission_growth_total; } ``` **Warum war das falsch?** 1. `pr_line_1` bei Gold = 9% (enthielt bereits 2% Growth Bonus) 2. Growth Bonus wurde ab Ebene 7 NOCHMAL mit 2% berechnet 3. = **Doppelte Auszahlung** auf tieferen Ebenen --- ## Neue Berechnung (ab November 2025) - KORREKT Der `GrowthBonusCalculator` verwendet die Differenz-Logik: 1. **Aggregation:** Sammelt Volumen gruppiert nach "Schutz-Level" 2. **Differenz:** Berechnet nur die Differenz (mein Anspruch - bereits verteilt) 3. **Einmal pro Bein:** Growth Bonus wird nur einmal pro Firstline-Zweig ausgezahlt Siehe `GrowthBonusCalculator.php` für die Implementation.