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

39 KiB

Tiefenbonus (Infinity Differential Bonus) - Implementierungskonzept

Übersicht

Der Tiefenbonus (auch "Infinity Differential Bonus" genannt) ist ein unbegrenzter Tiefenbonus für qualifizierte Member, der durch gleichrangige oder höherrangige Downlines geblockt wird (Breakaway-Prinzip).


Geschäftslogik

Start-Ebenen nach Qualifikation

Die Ebene, ab der der Tiefenbonus greift, variiert nach Qualifikationsstufe:

Qualifikation Start-Ebene Beschreibung
Bronze 7 Bonus ab Ebene 7 bis unendlich
Silber 7 Bonus ab Ebene 7 bis unendlich
Gold 7 Bonus ab Ebene 7 bis unendlich
Diamant 7 Bonus ab Ebene 7 bis unendlich
Platin* 8 Bonus ab Ebene 8 bis unendlich
Platin** 8 Bonus ab Ebene 8 bis unendlich
Platin*** 9 Bonus ab Ebene 9 bis unendlich

Bonus-Prozentsätze nach Qualifikation

Qualifikation Bonus-Prozentsatz Bemerkung
Bronze 1.0% Einstiegslevel
Silber 1.5% +0.5% zu Bronze
Gold 2.0% +0.5% zu Silber
Diamant 2.5% +0.5% zu Gold
Platin* 3.0% +0.5% zu Diamant
Platin** 3.5% +0.5% zu Platin*
Platin*** 4.0% Höchste Qualifikation

Differenzbonus-Logik (Breakaway-Prinzip)

Grundprinzip:

  • Du erhältst den Bonus auf alle Umsätze in deinen qualifizierenden Ebenen
  • ABER: Wenn ein Downline-Member die gleiche oder höhere Qualifikation hat, wird er zum "Breakaway"
  • Ab diesem Breakaway-Punkt erhältst du nur noch die Differenz zwischen deinem und seinem Bonus-Prozentsatz

Formel:

Dein Bonus = MAX(0, Dein Prozentsatz - Downline Prozentsatz)

Beispiele:

Beispiel 1: Gold über Bronze

Du: Gold (2%)
└─> Ebene 7: Bronze Member (1%)
    └─> Ebene 8-∞: Deren Team

Dein Bonus auf Ebene 7+:
- Auf Umsätze des Bronze Members: 2% - 1% = 1% (Differenz)
- Bronze erhält seinen 1% (ab seiner Ebene 7)
- Du erhältst die Differenz von 1%

Beispiel 2: Gold über Gold (Breakaway)

Du: Gold (2%)
└─> Ebene 7: Gold Member (2%)
    └─> Ebene 8-∞: Deren Team

Dein Bonus auf Ebene 7+:
- Auf Umsätze des Gold Members: 2% - 2% = 0% (BREAKAWAY)
- Der Gold Member erhält seine vollen 2%
- Du erhältst NICHTS auf diesen Zweig

Beispiel 3: Diamant über Silber und Bronze

Du: Diamant (2.5%)
├─> Zweig A, Ebene 7: Silber (1.5%)
│   └─> Deren Team → Du erhältst: 2.5% - 1.5% = 1%
│
└─> Zweig B, Ebene 7: Bronze (1%)
    └─> Deren Team → Du erhältst: 2.5% - 1% = 1.5%

Beispiel 4: Mehrstufiger Breakaway

Du: Platin* (3%, Start Ebene 8)
└─> Ebene 8: Gold (2%, Start Ebene 7)
    ├─> Ebene 9: Bronze (1%, Start Ebene 7)
    │   └─> Ebene 10-∞: Team
    │       - Gold erhält: 2% - 1% = 1%
    │       - Du erhältst: 3% - 2% = 1% (Differenz zu Gold)
    │
    └─> Ebene 9: Platin* (3%, Start Ebene 8)
        └─> Ebene 10-∞: Team
            - Platin* erhält: 3%
            - Du erhältst: 3% - 3% = 0% (BREAKAWAY)

Algorithmus-Design

1. Datenstruktur

Qualifikations-Konfiguration

// Neue Klasse: GrowthBonusConfig
class GrowthBonusConfig
{
    private const QUALIFICATIONS = [
        'bronze' => [
            'level' => 1,
            'start_line' => 7,
            'bonus_percent' => 1.0,
        ],
        'silber' => [
            'level' => 2,
            'start_line' => 7,
            'bonus_percent' => 1.5,
        ],
        'gold' => [
            'level' => 3,
            'start_line' => 7,
            'bonus_percent' => 2.0,
        ],
        'diamant' => [
            'level' => 4,
            'start_line' => 7,
            'bonus_percent' => 2.5,
        ],
        'platin1' => [
            'level' => 5,
            'start_line' => 8,
            'bonus_percent' => 3.0,
        ],
        'platin2' => [
            'level' => 6,
            'start_line' => 8,
            'bonus_percent' => 3.5,
        ],
        'platin3' => [
            'level' => 7,
            'start_line' => 9,
            'bonus_percent' => 4.0,
        ],
    ];
}

Erweiterung BusinessUserItemOptimized

// Neue Properties für BusinessUserItemOptimized
private ?string $qualification_level;      // 'bronze', 'silber', 'gold', etc.
private float $growth_bonus_percent = 0.0; // Bonus-Prozentsatz
private int $growth_bonus_start_line = 0;  // Ab welcher Ebene
private array $growth_bonus_details = [];  // Detaillierte Berechnung

2. Berechnungsalgorithmus

Phase 1: Qualifikation ermitteln

Methode: determineQualificationLevel(BusinessUserItemOptimized $user): string

Input:

  • $user->qual_kp (Kundenpunkte)
  • $user->qual_pp (Payline-Punkte)
  • $user->sales_volume_points_sum (Gesamtumsatz)

Output:

  • Qualifikationslevel als String ('bronze', 'silber', etc.)

Logik:

Prüfe von oben nach unten (Platin*** → Bronze):
1. Erfüllt User die Kriterien für Platin***?
   JA → return 'platin3'
   NEIN → weiter

2. Erfüllt User die Kriterien für Platin**?
   JA → return 'platin2'
   NEIN → weiter

3. ... (alle Stufen durchgehen)

4. Erfüllt User mindestens Bronze?
   JA → return 'bronze'
   NEIN → return null (kein Growth Bonus)

Qualifikationskriterien (Beispielwerte, müssen angepasst werden):

private const QUALIFICATION_CRITERIA = [
    'bronze' => [
        'min_kp' => 1000,
        'min_pp' => 2000,
    ],
    'silber' => [
        'min_kp' => 2500,
        'min_pp' => 5000,
    ],
    'gold' => [
        'min_kp' => 5000,
        'min_pp' => 15000,
    ],
    'diamant' => [
        'min_kp' => 10000,
        'min_pp' => 50000,
    ],
    'platin1' => [
        'min_kp' => 20000,
        'min_pp' => 100000,
    ],
    'platin2' => [
        'min_kp' => 40000,
        'min_pp' => 250000,
    ],
    'platin3' => [
        'min_kp' => 80000,
        'min_pp' => 500000,
    ],
];

Phase 2: Tiefenbonus berechnen (Hauptalgorithmus)

Methode: calculateGrowthBonus(BusinessUserItemOptimized $user): array

Voraussetzung:

  • User muss mindestens Bronze-Qualifikation haben
  • business_lines müssen bereits berechnet sein (aus calculateUserPointsOptimized)

Algorithmus:

1. Qualifikation des aktuellen Users ermitteln
   └─> Wenn keine Qualifikation: RETURN (kein Bonus)

2. Start-Ebene und Bonus-Prozentsatz aus Config laden
   Beispiel: Gold → Start Ebene 7, Bonus 2%

3. Für jede Ebene >= Start-Ebene:

   3.1 Durchlaufe alle Sub-User auf dieser Ebene

   3.2 Für jeden Sub-User:

       a) Ermittle dessen Qualifikation

       b) Berechne Differenz-Prozentsatz:
          differential = Dein Prozentsatz - Sub-User Prozentsatz

          Wenn differential <= 0:
              → BREAKAWAY (Blocker)
              → Markiere diesen Zweig
              → Keine weiteren Berechnungen in diesem Zweig

          Sonst:
              → Berechne Bonus auf Umsatz:
                bonus_amount = Sub-User Umsatz * differential

              → Speichere Details:
                - Ebene
                - Sub-User ID
                - Umsatz
                - Differential %
                - Bonus-Betrag

       c) Wenn KEIN Breakaway:
          → Rekursiv in tiefere Ebenen dieses Zweigs
          → ABER: Übergebe als "Blocker-Prozentsatz"
                  den höheren Wert zwischen:
                  - Deinem Prozentsatz
                  - Sub-User Prozentsatz

4. Summiere alle Bonus-Beträge

5. Return Detaillierte Ergebnisse:
   - total_growth_bonus (Gesamtbonus)
   - details_by_line (Array pro Ebene)
   - breakaway_users (Liste der Blocker)

Pseudo-Code (Detailliert)

function calculateGrowthBonus(
    BusinessUserItemOptimized $user,
    ?float $blockerPercent = null
): array {

    // 1. Qualifikation prüfen
    $userQual = $this->determineQualificationLevel($user);
    if (!$userQual) {
        return ['total' => 0, 'details' => []];
    }

    // 2. Config laden
    $config = GrowthBonusConfig::get($userQual);
    $userPercent = $blockerPercent ?? $config['bonus_percent'];
    $startLine = $config['start_line'];

    $totalBonus = 0;
    $details = [];
    $breakawayUsers = [];

    // 3. Durchlaufe alle Ebenen ab Start-Ebene
    foreach ($user->business_lines as $lineNumber => $lineData) {

        if ($lineNumber < $startLine) {
            continue; // Noch nicht qualifizierend
        }

        // 4. Hole alle Sub-User auf dieser Ebene
        $subUsers = $this->getSubUsersAtLine($user, $lineNumber);

        foreach ($subUsers as $subUser) {

            // 5. Ermittle Sub-User Qualifikation
            $subUserQual = $this->determineQualificationLevel($subUser);
            $subUserPercent = $subUserQual
                ? GrowthBonusConfig::get($subUserQual)['bonus_percent']
                : 0.0;

            // 6. Berechne Differenz
            $differential = $userPercent - $subUserPercent;

            if ($differential <= 0) {
                // BREAKAWAY - Blocker gefunden
                $breakawayUsers[] = [
                    'user_id' => $subUser->user_id,
                    'line' => $lineNumber,
                    'qualification' => $subUserQual,
                    'percent' => $subUserPercent,
                ];

                // STOP: Keine weiteren Berechnungen in diesem Zweig
                continue;
            }

            // 7. Berechne Bonus auf Sub-User Umsatz
            $subUserVolume = $subUser->sales_volume_points_TP_sum ?? 0;
            $bonusAmount = $subUserVolume * ($differential / 100);

            $totalBonus += $bonusAmount;

            $details[$lineNumber][] = [
                'user_id' => $subUser->user_id,
                'user_name' => $subUser->b_user->full_name ?? 'Unknown',
                'qualification' => $subUserQual,
                'volume' => $subUserVolume,
                'your_percent' => $userPercent,
                'their_percent' => $subUserPercent,
                'differential' => $differential,
                'bonus_amount' => $bonusAmount,
            ];

            // 8. Rekursiv in tiefere Ebenen (wenn kein Breakaway)
            $deeperBonus = $this->calculateGrowthBonusRecursive(
                $subUser,
                $lineNumber + 1,
                max($userPercent, $subUserPercent) // Höherer Wert als Blocker
            );

            $totalBonus += $deeperBonus['total'];
            $details = array_merge_recursive($details, $deeperBonus['details']);
            $breakawayUsers = array_merge($breakawayUsers, $deeperBonus['breakaways']);
        }
    }

    return [
        'total' => $totalBonus,
        'details' => $details,
        'breakaways' => $breakawayUsers,
        'user_qualification' => $userQual,
        'user_percent' => $userPercent,
        'start_line' => $startLine,
    ];
}

3. Hilfsmethoden

getSubUsersAtLine()

Zweck: Holt alle direkten Sub-User auf einer bestimmten Ebene

private function getSubUsersAtLine(
    BusinessUserItemOptimized $user,
    int $lineNumber
): array {
    $subUsers = [];

    // Rekursive Suche durch businessUserItems
    $this->collectUsersAtLine(
        $user->businessUserItems,
        1, // Aktuelle Ebene relativ zum User
        $lineNumber,
        $subUsers
    );

    return $subUsers;
}

private function collectUsersAtLine(
    array $items,
    int $currentLine,
    int $targetLine,
    array &$result
): void {
    if ($currentLine > $targetLine) {
        return; // Zu tief
    }

    foreach ($items as $item) {
        if ($currentLine === $targetLine) {
            $result[] = $item;
        } else {
            // Rekursiv tiefer
            $this->collectUsersAtLine(
                $item->businessUserItems ?? [],
                $currentLine + 1,
                $targetLine,
                $result
            );
        }
    }
}

Code-Anpassungen

Neue Dateien erstellen

1. GrowthBonusConfig.php

Pfad: /app/Services/BusinessPlan/GrowthBonusConfig.php

Zweck:

  • Zentrale Konfiguration für Qualifikationsstufen
  • Bonus-Prozentsätze
  • Start-Ebenen

Struktur:

<?php

namespace App\Services\BusinessPlan;

class GrowthBonusConfig
{
    private const QUALIFICATIONS = [...];
    private const CRITERIA = [...];

    public static function getConfig(string $level): ?array;
    public static function getAllLevels(): array;
    public static function getPercent(string $level): float;
    public static function getStartLine(string $level): int;
    public static function getCriteria(string $level): array;
}

2. GrowthBonusCalculator.php

Pfad: /app/Services/BusinessPlan/GrowthBonusCalculator.php

Zweck:

  • Hauptlogik für Tiefenbonus-Berechnung
  • Separation of Concerns (aus TreeCalcBotOptimized ausgelagert)

Struktur:

<?php

namespace App\Services\BusinessPlan;

use Psr\Log\LoggerInterface;

class GrowthBonusCalculator
{
    private GrowthBonusConfig $config;
    private LoggerInterface $logger;

    public function __construct(
        ?GrowthBonusConfig $config = null,
        ?LoggerInterface $logger = null
    );

    // Hauptmethode
    public function calculateGrowthBonus(
        BusinessUserItemOptimized $user
    ): array;

    // Qualifikation ermitteln
    public function determineQualificationLevel(
        BusinessUserItemOptimized $user
    ): ?string;

    // Hilfsmethoden
    private function calculateGrowthBonusRecursive(...);
    private function getSubUsersAtLine(...);
    private function collectUsersAtLine(...);
}

Bestehende Dateien anpassen

1. BusinessUserItemOptimized.php

Neue Properties hinzufügen:

// Nach Zeile ~40 (bei anderen Properties)
private ?string $qualification_level = null;
private float $growth_bonus_percent = 0.0;
private int $growth_bonus_start_line = 0;
private float $growth_bonus_total = 0.0;
private array $growth_bonus_details = [];
private array $growth_bonus_breakaways = [];

Neue Getter-Methoden:

public function getGrowthBonusTotal(): float
{
    return $this->growth_bonus_total;
}

public function getGrowthBonusDetails(): array
{
    return $this->growth_bonus_details;
}

public function getQualificationLevel(): ?string
{
    return $this->qualification_level;
}

public function setGrowthBonusData(array $data): void
{
    $this->growth_bonus_total = $data['total'] ?? 0.0;
    $this->growth_bonus_details = $data['details'] ?? [];
    $this->growth_bonus_breakaways = $data['breakaways'] ?? [];
    $this->qualification_level = $data['user_qualification'] ?? null;
    $this->growth_bonus_percent = $data['user_percent'] ?? 0.0;
    $this->growth_bonus_start_line = $data['start_line'] ?? 0;
}

In __get() Methode erweitern:

public function __get(string $name)
{
    switch ($name) {
        // ... bestehende Cases ...

        case 'growth_bonus_total':
            return $this->growth_bonus_total;
        case 'growth_bonus_details':
            return $this->growth_bonus_details;
        case 'qualification_level':
            return $this->qualification_level;
        case 'growth_bonus_percent':
            return $this->growth_bonus_percent;
        case 'growth_bonus_start_line':
            return $this->growth_bonus_start_line;
        case 'growth_bonus_breakaways':
            return $this->growth_bonus_breakaways;

        // ... rest ...
    }
}

2. TreeCalcBotOptimized.php

Neue Property hinzufügen:

// Nach Zeile ~35 (bei anderen Dependencies)
private GrowthBonusCalculator $growthBonusCalculator;

Constructor erweitern:

public function __construct(
    int $month,
    int $year,
    string $initFrom = 'member',
    bool $forceLiveCalculation = false,
    ?BusinessUserRepository $repository = null,
    ?TreeHtmlRenderer $renderer = null,
    ?LoggerInterface $logger = null,
    ?GrowthBonusCalculator $growthBonusCalculator = null  // NEU
) {
    // ... bestehender Code ...

    // NEU
    $this->growthBonusCalculator = $growthBonusCalculator
        ?? new GrowthBonusCalculator(null, $this->logger);
}

Neue Methode nach calculateAllBusinessUsers():

/**
 * Berechnet Growth Bonus für alle Business-Users
 * WICHTIG: Muss NACH calculateAllBusinessUsers() aufgerufen werden
 */
private function calculateGrowthBonusForAll(): void
{
    $startTime = microtime(true);
    $this->logger->info("Starting Growth Bonus calculation for " . count($this->businessUsers) . " business users");

    $totalBonusAmount = 0;
    $usersWithBonus = 0;

    foreach ($this->businessUsers as $businessUser) {
        try {
            // Berechne Growth Bonus
            $bonusData = $this->growthBonusCalculator->calculateGrowthBonus($businessUser);

            // Speichere in BusinessUser
            $businessUser->setGrowthBonusData($bonusData);

            if ($bonusData['total'] > 0) {
                $totalBonusAmount += $bonusData['total'];
                $usersWithBonus++;

                $this->logger->info(
                    "Growth Bonus for user {$businessUser->user_id}: " .
                    number_format($bonusData['total'], 2) .
                    " ({$bonusData['user_qualification']})"
                );
            }
        } catch (\Exception $e) {
            $this->logger->error(
                "Error calculating growth bonus for user {$businessUser->user_id}: " .
                $e->getMessage()
            );
            continue;
        }
    }

    $endTime = microtime(true);
    $executionTime = round(($endTime - $startTime) * 1000, 2);

    $this->logger->info(
        "Completed Growth Bonus calculations in {$executionTime}ms. " .
        "Users with bonus: {$usersWithBonus}. " .
        "Total bonus amount: " . number_format($totalBonusAmount, 2)
    );
}

In buildFreshStructure() erweitern:

private function buildFreshStructure(): void
{
    $this->loadRootUsers();
    $this->loadParentsUsers();
    $this->loadParentlessUsers();

    // Berechne Punkte und Qualifikationen
    $this->calculateAllBusinessUsers();
    $this->calculateAllParentlessUsers();

    // NEU: Growth Bonus berechnen
    $this->calculateGrowthBonusForAll();
}

In initBusinesslUserDetail() erweitern:

public function initBusinesslUserDetail(User $user, bool $forceLiveCalculation = false): void
{
    try {
        // ... bestehender Code bis calcQualPP() ...

        $this->businessUser->calcQualPP();

        // NEU: Growth Bonus berechnen
        $bonusData = $this->growthBonusCalculator->calculateGrowthBonus($this->businessUser);
        $this->businessUser->setGrowthBonusData($bonusData);

        $this->logger->info(
            "Growth Bonus for user {$user->id}: " .
            number_format($bonusData['total'], 2)
        );
    } catch (\Exception $e) {
        // ... bestehender Error Handler ...
    }
}

Neue öffentliche Getter-Methode:

/**
 * Gibt detaillierte Growth Bonus Informationen zurück
 */
public function getGrowthBonusDetails(): array
{
    if (!$this->businessUser) {
        return [];
    }

    return [
        'total' => $this->businessUser->growth_bonus_total,
        'qualification' => $this->businessUser->qualification_level,
        'bonus_percent' => $this->businessUser->growth_bonus_percent,
        'start_line' => $this->businessUser->growth_bonus_start_line,
        'details_by_line' => $this->businessUser->growth_bonus_details,
        'breakaways' => $this->businessUser->growth_bonus_breakaways,
    ];
}

3. UserBusinessStructure Model

Migration erstellen für neue Felder:

Schema::table('user_business_structures', function (Blueprint $table) {
    $table->json('growth_bonus_data')->nullable()->after('structure');
});

Beim Speichern der Struktur Growth Bonus mit speichern:

// In der Methode die die Struktur speichert
$structure->growth_bonus_data = json_encode([
    'total_bonus_paid' => $totalBonusAmount,
    'users_with_bonus' => $usersWithBonus,
    'calculation_date' => now(),
    // Optional: Details pro User
    'user_bonuses' => $this->collectGrowthBonusData(),
]);

Datenbank-Schema

Erweiterte Tabelle: user_business_structures

ALTER TABLE user_business_structures
ADD COLUMN growth_bonus_data JSON NULL
AFTER structure;

JSON-Struktur:

{
  "total_bonus_paid": 15750.5,
  "users_with_bonus": 47,
  "calculation_date": "2025-11-26 10:30:00",
  "user_bonuses": [
    {
      "user_id": 123,
      "qualification": "gold",
      "bonus_total": 1250.75,
      "start_line": 7,
      "lines_calculated": [7, 8, 9, 10, 11],
      "breakaways": [456, 789]
    },
    {
      "user_id": 456,
      "qualification": "silber",
      "bonus_total": 450.25,
      "start_line": 7,
      "lines_calculated": [7, 8, 9],
      "breakaways": []
    }
  ]
}

Testing-Strategie

Unit Tests

1. GrowthBonusConfig Test

class GrowthBonusConfigTest extends TestCase
{
    /** @test */
    public function it_returns_correct_config_for_bronze()
    {
        $config = GrowthBonusConfig::getConfig('bronze');

        $this->assertEquals(7, $config['start_line']);
        $this->assertEquals(1.0, $config['bonus_percent']);
    }

    /** @test */
    public function it_returns_correct_config_for_platin3()
    {
        $config = GrowthBonusConfig::getConfig('platin3');

        $this->assertEquals(9, $config['start_line']);
        $this->assertEquals(4.0, $config['bonus_percent']);
    }
}

2. GrowthBonusCalculator Test

class GrowthBonusCalculatorTest extends TestCase
{
    /** @test */
    public function it_calculates_differential_bonus_correctly()
    {
        // Arrange: Gold User mit Bronze in Ebene 7
        $goldUser = $this->createUserWithQualification('gold', [
            'qual_kp' => 5000,
            'qual_pp' => 15000,
        ]);

        $bronzeSubUser = $this->createSubUser($goldUser, 7, 'bronze', [
            'sales_volume' => 10000,
        ]);

        $calculator = new GrowthBonusCalculator();

        // Act
        $result = $calculator->calculateGrowthBonus($goldUser);

        // Assert
        // Gold 2% - Bronze 1% = 1% Differenz
        // 10000 * 0.01 = 100
        $this->assertEquals(100, $result['total']);
        $this->assertEquals('gold', $result['user_qualification']);
        $this->assertEquals(2.0, $result['user_percent']);
    }

    /** @test */
    public function it_stops_at_breakaway()
    {
        // Arrange: Gold User mit Gold Sub-User (gleiche Qualifikation)
        $goldUser = $this->createUserWithQualification('gold');
        $goldSubUser = $this->createSubUser($goldUser, 7, 'gold', [
            'sales_volume' => 20000,
        ]);

        $calculator = new GrowthBonusCalculator();

        // Act
        $result = $calculator->calculateGrowthBonus($goldUser);

        // Assert: Keine Bonus (Breakaway)
        $this->assertEquals(0, $result['total']);
        $this->assertCount(1, $result['breakaways']);
        $this->assertEquals($goldSubUser->user_id, $result['breakaways'][0]['user_id']);
    }

    /** @test */
    public function it_calculates_multi_level_differential()
    {
        // Arrange: Diamant mit mehreren Sub-Usern
        $diamantUser = $this->createUserWithQualification('diamant');

        // Ebene 7: Bronze (10000 Umsatz)
        $bronzeUser = $this->createSubUser($diamantUser, 7, 'bronze', [
            'sales_volume' => 10000,
        ]);

        // Ebene 7: Silber (15000 Umsatz)
        $silberUser = $this->createSubUser($diamantUser, 7, 'silber', [
            'sales_volume' => 15000,
        ]);

        // Ebene 8: Unter Bronze (5000 Umsatz)
        $subBronze = $this->createSubUser($bronzeUser, 1, null, [
            'sales_volume' => 5000,
        ]);

        $calculator = new GrowthBonusCalculator();

        // Act
        $result = $calculator->calculateGrowthBonus($diamantUser);

        // Assert
        // Diamant = 2.5%
        // Bronze: (2.5% - 1.0%) * 10000 = 150
        // Silber: (2.5% - 1.5%) * 15000 = 150
        // Sub unter Bronze: (2.5% - 1.0%) * 5000 = 75 (Bronze blockt teilweise)
        // Total: 375
        $this->assertEquals(375, $result['total']);
    }
}

Integration Tests

class TreeCalcBotGrowthBonusIntegrationTest extends TestCase
{
    /** @test */
    public function it_calculates_growth_bonus_in_full_structure()
    {
        // Arrange: Erstelle komplette Test-Struktur
        $this->seedTestStructure();

        // Act
        $calc = new TreeCalcBotOptimized(11, 2025);
        $calc->initStructureAdmin();

        // Assert
        $users = $calc->getItems();
        $totalGrowthBonus = 0;

        foreach ($users as $user) {
            $totalGrowthBonus += $user->growth_bonus_total;
        }

        $this->assertGreaterThan(0, $totalGrowthBonus);
        $this->assertDatabaseHas('user_business_structures', [
            'month' => 11,
            'year' => 2025,
            'completed' => true,
        ]);
    }
}

Performance-Überlegungen

1. Komplexität

Worst Case:

  • O(n * d) wobei n = Anzahl User, d = Durchschnittliche Tiefe
  • Bei 10.000 Usern und Tiefe 15: ~150.000 Operationen

Optimierungen:

  • Early Breakaway Detection (stoppt Zweig bei Blocker)
  • Memoization von Qualifikationen (nicht mehrfach berechnen)
  • Batch-Processing für große Strukturen

2. Caching-Strategie

Level 1: Qualifikations-Cache

// In GrowthBonusCalculator
private array $qualificationCache = [];

private function determineQualificationLevel(
    BusinessUserItemOptimized $user
): ?string {
    $userId = $user->user_id;

    if (isset($this->qualificationCache[$userId])) {
        return $this->qualificationCache[$userId];
    }

    $qual = $this->calculateQualification($user);
    $this->qualificationCache[$userId] = $qual;

    return $qual;
}

Level 2: Sub-User Cache

// Cache für getSubUsersAtLine()
private array $subUserCache = [];

private function getSubUsersAtLine(
    BusinessUserItemOptimized $user,
    int $lineNumber
): array {
    $cacheKey = $user->user_id . '_' . $lineNumber;

    if (isset($this->subUserCache[$cacheKey])) {
        return $this->subUserCache[$cacheKey];
    }

    $subUsers = $this->collectSubUsers($user, $lineNumber);
    $this->subUserCache[$cacheKey] = $subUsers;

    return $subUsers;
}

3. Memory-Management

Monitoring:

private function calculateGrowthBonusForAll(): void
{
    $startMemory = memory_get_usage();

    foreach ($this->businessUsers as $businessUser) {
        // Berechnung...

        // Memory-Check alle 100 User
        if ($this->processedCount % 100 === 0) {
            $this->checkMemoryUsage('growthBonusCalculation');
        }
    }

    $endMemory = memory_get_usage();
    $memoryUsed = $this->formatBytes($endMemory - $startMemory);

    $this->logger->info("Growth Bonus calculation used: {$memoryUsed}");
}

Edge Cases & Fehlerbehandlung

Edge Case 1: User ohne Sub-User

Problem: User hat Qualifikation, aber keine Team-Mitglieder

Lösung:

if (empty($user->businessUserItems)) {
    return [
        'total' => 0,
        'details' => [],
        'note' => 'No sub-users to calculate bonus from',
    ];
}

Edge Case 2: Zirkuläre Referenzen

Problem: User A sponsort B, B sponsort C, C sponsort A (Daten-Fehler)

Lösung:

private array $processingStack = [];

private function calculateGrowthBonusRecursive(
    BusinessUserItemOptimized $user,
    ...
): array {
    if (in_array($user->user_id, $this->processingStack)) {
        $this->logger->error("Circular reference detected for user: {$user->user_id}");
        return ['total' => 0, 'details' => []];
    }

    $this->processingStack[] = $user->user_id;

    // Berechnung...

    array_pop($this->processingStack);
}

Edge Case 3: Fehlende Qualifikationsdaten

Problem: User hat keine qual_kp/qual_pp Werte

Lösung:

private function determineQualificationLevel(
    BusinessUserItemOptimized $user
): ?string {
    $qualKp = $user->qual_kp ?? 0;
    $qualPp = $user->qual_pp ?? 0;

    if ($qualKp === 0 && $qualPp === 0) {
        $this->logger->warning(
            "User {$user->user_id} has no qualification data"
        );
        return null;
    }

    // Berechnung...
}

Edge Case 4: Start-Ebene nicht erreicht

Problem: User hat Qualifikation, aber Struktur ist nicht tief genug

Lösung:

$maxLine = max(array_keys($user->business_lines));

if ($maxLine < $config['start_line']) {
    return [
        'total' => 0,
        'details' => [],
        'note' => "Structure not deep enough (max: {$maxLine}, required: {$config['start_line']})",
    ];
}

UI/Frontend Integration

Controller-Methode (Beispiel)

// In BusinessPointsController
public function showGrowthBonus(Request $request)
{
    $userId = auth()->id();
    $month = $request->input('month', date('m'));
    $year = $request->input('year', date('Y'));

    $calc = new TreeCalcBotOptimized($month, $year, 'member');
    $calc->initStructureUser($userId);

    $growthBonusDetails = $calc->getGrowthBonusDetails();

    return view('business.growth-bonus', [
        'bonusTotal' => $growthBonusDetails['total'],
        'qualification' => $growthBonusDetails['qualification'],
        'bonusPercent' => $growthBonusDetails['bonus_percent'],
        'startLine' => $growthBonusDetails['start_line'],
        'detailsByLine' => $growthBonusDetails['details_by_line'],
        'breakaways' => $growthBonusDetails['breakaways'],
    ]);
}

View (Blade Template Beispiel)

<div class="growth-bonus-container">
    <h2>Tiefenbonus (Growth Bonus)</h2>

    <div class="bonus-summary">
        <div class="qualification-badge {{ $qualification }}">
            {{ ucfirst($qualification) }}
        </div>

        <div class="bonus-amount">
            <span class="label">Gesamtbonus:</span>
            <span class="amount">{{ number_format($bonusTotal, 2) }} €</span>
        </div>

        <div class="bonus-info">
            <p>Dein Bonus-Prozentsatz: <strong>{{ $bonusPercent }}%</strong></p>
            <p>Bonus gilt ab Ebene: <strong>{{ $startLine }}</strong></p>
        </div>
    </div>

    <div class="bonus-details">
        <h3>Details nach Ebenen</h3>

        @foreach($detailsByLine as $line => $users)
            <div class="line-section">
                <h4>Ebene {{ $line }}</h4>

                <table class="bonus-table">
                    <thead>
                        <tr>
                            <th>Partner</th>
                            <th>Qualifikation</th>
                            <th>Umsatz</th>
                            <th>Dein %</th>
                            <th>Deren %</th>
                            <th>Differenz</th>
                            <th>Bonus</th>
                        </tr>
                    </thead>
                    <tbody>
                        @foreach($users as $user)
                            <tr>
                                <td>{{ $user['user_name'] }}</td>
                                <td>
                                    <span class="badge {{ $user['qualification'] }}">
                                        {{ ucfirst($user['qualification'] ?? 'Keine') }}
                                    </span>
                                </td>
                                <td>{{ number_format($user['volume'], 2) }}</td>
                                <td>{{ $user['your_percent'] }}%</td>
                                <td>{{ $user['their_percent'] }}%</td>
                                <td>{{ $user['differential'] }}%</td>
                                <td class="bonus-amount">
                                    {{ number_format($user['bonus_amount'], 2) }} €
                                </td>
                            </tr>
                        @endforeach
                    </tbody>
                </table>
            </div>
        @endforeach
    </div>

    @if(count($breakaways) > 0)
        <div class="breakaways-section">
            <h3>Breakaways (Blocker)</h3>
            <p>Folgende Partner haben die gleiche oder höhere Qualifikation und blocken daher den Tiefenbonus in ihrem Zweig:</p>

            <ul class="breakaway-list">
                @foreach($breakaways as $breakaway)
                    <li>
                        <strong>{{ $breakaway['user_name'] }}</strong>
                        (Ebene {{ $breakaway['line'] }}) -
                        <span class="badge {{ $breakaway['qualification'] }}">
                            {{ ucfirst($breakaway['qualification']) }}
                        </span>
                        ({{ $breakaway['percent'] }}%)
                    </li>
                @endforeach
            </ul>
        </div>
    @endif
</div>

Migration & Rollout-Plan

Phase 1: Vorbereitung (Woche 1)

  1. Code Review dieses Konzepts

    • Stakeholder-Abstimmung
    • Anpassung von Prozentsätzen/Kriterien
    • Freigabe für Entwicklung
  2. Testdaten erstellen

    • Realistische Test-Strukturen
    • Edge Cases abdecken
    • Performance-Test-Daten (10.000+ User)

Phase 2: Entwicklung (Woche 2-3)

  1. Tag 1-2: GrowthBonusConfig implementieren
  2. Tag 3-5: GrowthBonusCalculator implementieren
  3. Tag 6-7: Integration in TreeCalcBotOptimized
  4. Tag 8-9: BusinessUserItemOptimized erweitern
  5. Tag 10-11: Unit Tests schreiben
  6. Tag 12-14: Integration Tests & Bug Fixes

Phase 3: Testing (Woche 4)

  1. Tag 1-3: QA Testing

    • Manuelle Tests mit Test-Strukturen
    • Edge Cases testen
    • Performance-Tests
  2. Tag 4-5: Bug Fixes & Optimierungen


Phase 4: Staging (Woche 5)

  1. Migration auf Staging-Umgebung

    • Database Migration ausführen
    • Code deployen
    • Monitoring einrichten
  2. Testberechnungen mit echten Daten

    • Vergangenen Monat berechnen
    • Ergebnisse mit erwarteten Werten vergleichen
    • Performance-Monitoring

Phase 5: Production Rollout (Woche 6)

  1. Soft Launch

    • Feature zunächst nur für Admins sichtbar
    • Berechnung im Hintergrund laufen lassen
    • Werte validieren
  2. Full Launch

    • Feature für alle User aktivieren
    • Kommunikation an User
    • Support-Team briefen

Monitoring & Logging

Key Metrics

// Growth Bonus Metriken
[
    'calculation_time_ms' => 4521,
    'users_processed' => 1247,
    'users_with_bonus' => 487,
    'total_bonus_amount' => 125430.50,
    'breakaways_found' => 123,
    'max_line_calculated' => 15,
    'memory_used_mb' => 85.3,
]

Alert Thresholds

Performance Alerts:

  • Berechnungszeit > 60 Sekunden
  • Memory Usage > 90%
  • Fehlerrate > 5%

Business Alerts:

  • Unerwartete Bonus-Summe (z.B. > 2x des Vormonats)
  • Zu viele Breakaways (> 50% der User)
  • Qualifikationen fehlen für > 10% der User

Dokumentation für User

Help-Artikel: "Was ist der Tiefenbonus?"

Überschrift: Wie funktioniert der Tiefenbonus?

Text:

Der Tiefenbonus (auch Growth Bonus oder Infinity Bonus genannt) belohnt qualifizierte Partner für den Aufbau tiefer Strukturen.

Wann bekomme ich Tiefenbonus?

Ab der Bronze-Qualifikation erhältst du einen prozentualen Bonus auf Umsätze ab einer bestimmten Ebene:

  • Bronze bis Diamant: Ab Ebene 7
  • Platin*: Ab Ebene 8
  • Platin***: Ab Ebene 9

Wie hoch ist der Bonus?

  • Bronze: 1%
  • Silber: 1,5%
  • Gold: 2%
  • Diamant: 2,5%
  • Platin*: 3%
  • Platin**: 3,5%
  • Platin***: 4%

Was ist ein Breakaway?

Wenn ein Partner in deiner Downline die gleiche oder eine höhere Qualifikation erreicht als du, wird er zum "Breakaway". Ab diesem Punkt erhältst du nur noch die Differenz zwischen deinem und seinem Bonus-Prozentsatz.

Beispiel:

Du bist Gold (2%) und hast einen Bronze-Partner (1%) in Ebene 7. → Du erhältst 1% Differenz auf dessen Umsätze (2% - 1% = 1%)

Du bist Gold (2%) und hast einen Gold-Partner (2%) in Ebene 7. → Du erhältst NICHTS (2% - 2% = 0%) - Breakaway!


Risiken & Mitigation

Risiko 1: Performance-Probleme

Risiko: Berechnung dauert zu lange bei großen Strukturen

Mitigation:

  • Caching von Qualifikationen
  • Early Breakaway Detection
  • Optional: Async-Processing via Queue

Risiko 2: Fehlerhafte Berechnungen

Risiko: Falsche Bonus-Beträge durch Bug in Differenz-Logik

Mitigation:

  • Umfangreiche Unit Tests
  • Integration Tests mit echten Daten
  • Manual Testing mit bekannten Strukturen
  • Soft Launch nur für Admins

Risiko 3: Dateninkonsistenz

Risiko: Qualifikationen nicht aktuell oder fehlerhaft

Mitigation:

  • Validierung vor Berechnung
  • Fallback-Werte bei fehlenden Daten
  • Logging von Inkonsistenzen
  • Retry-Mechanismus

Offene Fragen (vor Implementierung klären)

  1. Qualifikationskriterien:

    • Sind die Beispiel-Werte für min_kp und min_pp korrekt?
    • Gibt es weitere Kriterien (z.B. Mindestanzahl Direkt-Partner)?
  2. Umsatz-Basis:

    • Wird Bonus auf sales_volume_points_TP_sum berechnet?
    • Oder auf einen anderen Wert?
  3. Zeitraum:

    • Gilt der Bonus rückwirkend für vergangene Monate?
    • Ab wann startet die Berechnung?
  4. Auszahlung:

    • Wird der Bonus automatisch ausgezahlt?
    • Oder nur angezeigt/berechnet?
  5. Breakaway-Regel:

    • Gilt Breakaway auch bei temporärer Qualifikation?
    • Oder nur bei "gefestigter" Qualifikation?
  6. Maximaler Bonus:

    • Gibt es ein Cap (z.B. max. 10.000 € pro Monat)?
    • Oder wirklich unbegrenzt?
  7. Frontend:

    • Soll Growth Bonus im Dashboard prominent angezeigt werden?
    • Separate Seite oder Integration in bestehendes Dashboard?

Nächste Schritte

  1. Konzept-Review durch Stakeholder

    • Geschäftslogik bestätigen
    • Offene Fragen klären
    • Go/No-Go Entscheidung
  2. Freigabe für Entwicklung

    • Ressourcen allokieren
    • Sprint planen
    • Tickets erstellen
  3. Entwicklung starten

    • GrowthBonusConfig
    • GrowthBonusCalculator
    • Integration in bestehenden Code
  4. Testing & QA

    • Unit Tests
    • Integration Tests
    • Performance Tests
  5. Deployment

    • Staging
    • Production Soft Launch
    • Full Launch

Zusammenfassung

Der Tiefenbonus ist ein komplexes Feature, das:

Belohnt:

  • Aufbau tiefer Strukturen
  • Qualifikations-Erreichen
  • Langfristiges Engagement

Herausforderungen:

  • Performance bei großen Strukturen
  • Korrekte Differenz-Berechnung
  • Breakaway-Logik
  • UI/UX für komplexe Daten

Technische Umsetzung:

  • Neue Klassen (Config, Calculator)
  • Erweiterung bestehender Klassen
  • Umfangreiche Tests
  • Monitoring & Logging

Business Value:

  • Motiviert Partner zu Struktur-Aufbau
  • Belohnt Führungskräfte
  • Differenzierung zu Wettbewerb

Status: Konzept vollständig - Bereit für Review

Autor: AI Assistant
Datum: 26. November 2025
Version: 1.0