mivita/dev/buinessPlan/_bak/Growth-Bonus.md
2026-01-23 17:35:23 +01:00

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:

  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, ...]

    /**
     * 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?

  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.