# 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<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.

```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.
