17 KiB
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:
- Einmal IN den Payline-Prozentsätzen (pr_line_1 = 9% statt 7%)
- Nochmal SEPARAT auf Ebenen ab 7+ (Legacy-Berechnung)
Die Lösung (ab November 2025)
- Payline-Prozentsätze korrigiert:
pr_line_Xenthält NUR den Payline-Anteil - Growth Bonus separat: Wird mit Differenz-Logik berechnet
- 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()inBusinessUserItemOptimizedgibt 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
GrowthBonusCalculatorverwendet ab November 2025 ausschließlichgetQualifiedGrowthBonus().
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:
-
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.
-
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
- Trigger: Ein Umsatz (Points) entsteht bei User X.
- Schleife: Gehe die Upline hoch (Sponsor -> Sponsor...).
- 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).
- Rechnung:
- Auszahlung = Mein %-Satz - Bereits verteilter %-Satz.
- Wenn Auszahlung > 0: Speichern.
- Setze
Bereits verteilter %-Satzauf den neuen Wert (alsoMein %-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, ...]
/**
* Liefert das Volumen der Downline gruppiert nach dem "bereits verteilten Prozentsatz" (Protection Level).
* Rekursive Funktion, die die "Differenz-Logik" vorbereitet.
*
* @return array<string, float> 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.
/**
* 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
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!
/**
* 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?
pr_line_1bei Gold = 9% (enthielt bereits 2% Growth Bonus)- Growth Bonus wurde ab Ebene 7 NOCHMAL mit 2% berechnet
- = Doppelte Auszahlung auf tieferen Ebenen
Neue Berechnung (ab November 2025) - KORREKT
Der GrowthBonusCalculator verwendet die Differenz-Logik:
- Aggregation: Sammelt Volumen gruppiert nach "Schutz-Level"
- Differenz: Berechnet nur die Differenz (mein Anspruch - bereits verteilt)
- Einmal pro Bein: Growth Bonus wird nur einmal pro Firstline-Zweig ausgezahlt
Siehe GrowthBonusCalculator.php für die Implementation.