mivita/app/Services/BusinessPlan/TreeCalcBotOptimized.md
2026-01-23 17:35:23 +01:00

39 KiB

Hier ist die technische Zusammenfassung des Marketingplans (MIVITA)

Das System ist eine Mischung aus Unilevel-Plan (feste % auf Ebenen) und Differenz-Bonus (Tiefenbonus ab einer gewissen Stufe) mit qualifikationsbedingten Struktur-Anforderungen.

1. Grundbegriffe & Variablen

  • Punkte (Points): Interne Währung (1 Punkt ≈ 1 Euro).
  • PV (Persönliches Volumen):
    • Eigene Bestellungen
    • + Bestellungen von Kunden (Shop)
    • + Starterkits von direkten Firstlines (je 100 Punkte)
    • + Abos (Eigene + Kunden + Firstlines).
  • Payline (Gruppenvolumen): Die Summe der Punkte aus dem Team in einer bestimmten Tiefe (je nach Level 3 bis 8 Ebenen).
    • Wichtig: Wenn PV > Erforderliches PV, fließen die überschüssigen PV-Punkte in die Payline-Berechnung mit ein.
  • Schecksicherung: Ein gesonderter PV-Wert, der erreicht sein muss, um Provisionen zu erhalten (meist identisch mit Qualifikations-PV, Ausnahme: Bronze Member = 690 Punkte).

2. Die Level-Logik (State Machine)

Ein User hat einen current_rank. Um diesen zu erreichen, müssen im aktuellen Monat folgende Bedingungen (WHERE) erfüllt sein. Die Prüfung erfolgt von oben (höchstes Level) nach unten.

Level ID Level Name Min. PV Min. Payline Points Payline Tiefe (für Quali) Struktur-Voraussetzung (Linien)
1 Junior Berater 150 0 - -
2 Aktiv Junior Berater 250 500 3 Ebenen -
3 Berater 350 1.000 4 Ebenen -
4 Aktiv Berater 450 2.500 6 Ebenen* -
5 Vertriebspartner 600 5.000 6 Ebenen -
6 Vertriebsleiter 600 9.000 6 Ebenen Sonderregel: 2-Monats-Bestätigung (siehe unten)
7 Bronze Member 600 (690 zur Auszahlung) 18.000 6 Ebenen -
8 Silber Member 600 30.000 6 Ebenen -
9 Gold Member 600 50.000 6 Ebenen -
10 Diamant Member 600 100.000 6 Ebenen -
11 Platin Member* 600 250.000 7 Ebenen Min. 1 Linie mit "Gold Member"
12 Platin Member** 600 500.000 7 Ebenen Min. 1x Gold-Linie AND 1x Diamant-Linie
13 Platin Member*** 600 1.000.000 8 Ebenen Min. 1x Gold, 1x Diamant, 1x Platin*

Hinweis zu Aktiv Berater: Im Text steht Header "6 Ebenen", im Fließtext "5 Ebenen". Da ab Aktiv Berater die Payline Sprünge macht, würde ich sicherheitshalber 6 Ebenen scannen oder das im Business klären. Im Zweifel gilt die höhere Zahl für die Qualifikation.


3. Provisions-Berechnung (Commission Engine)

Wenn der Status feststeht, wird die Provision berechnet. Es gibt drei Töpfe:

A. Sofortrabatt / Eigenumsatz-Rückvergütung

Basierend auf dem Status bekommt der Berater % auf seinen eigenen Umsatz zurückerstattet (bzw. als Rabatt beim Einkauf).

  • Junior: 20%
  • Aktiv Junior: 25%
  • Berater: 30%
  • Aktiv Berater: 31%
  • Vertriebspartner: 32%
  • Vertriebsleiter bis Platin: 33%

B. Unilevel Provision (Passive Teamprovision)

Feste Prozentsätze auf den Umsatz der Downline, begrenzt auf eine bestimmte Tiefe.

Level Ebene 1 Ebene 2 Ebene 3 Ebene 4 Ebene 5 Ebene 6 Ebene 7 Ebene 8
Junior 6% 3% 1% - - - - -
Aktiv Junior 6% 4% 2% - - - - -
Berater 6% 5% 3% 2% - - - -
Aktiv Berater 6% 5% 4% 2% 1% - - -
Vertriebspartner 6% 6% 5% 3% 2% 1% - -
Vertriebsleiter 6% 6% 6% 4% 2% 1% - -
Bronze 7% 7% 7% 5% 3% 3% - -
Silber 8,5% 8,5% 8,5% 5,5% 3,5% 3,5% - -
Gold 9% 9% 9% 6% 4% 4% - -
Diamant 9,5% 9,5% 9,5% 6,5% 4,5% 4,5% - -
Platin* 10% 10% 10% 7% 6% 5% 4% -
Platin** 10,5% 10,5% 10,5% 7,5% 6,5% 5,5% 4,5% -
Platin*** 11% 11% 11% 8% 8% 7% 6% 5%

C. Tiefenbonus (Infinity Differential Bonus)

Ab Bronze Member gibt es einen Bonus, der theoretisch "unendlich" tief geht, aber durch gleichrangige Downlines geblockt wird (Differenzbonus).

  • Start-Ebene:
    • Bronze bis Diamant: Bonus gilt ab Ebene 7.
    • Platin*: Bonus gilt ab Ebene 8.
    • Platin***: Bonus gilt ab Ebene 9.
  • Bonus-Höhe (Maximal):
    • Bronze: 1%
    • Silber: 1,5%
    • Gold: 2%
    • Diamant: 2,5%
    • Platin*: 3%
    • Platin**: 3,5%
    • Platin***: 4%
  • Logik (Differenz):
    • Du bist Gold (Anspruch 2%).
    • In einer Linie ist unter dir ein Bronze (Anspruch 1%).
    • Auf dessen Umsätze (ab seiner Ebene 7) erhältst du nur noch die Differenz: 2% - 1% = 1%.
    • Wenn unter dir jemand auch Gold ist: 2% - 2% = 0% (Breakaway).

4. Spezielle Developer-Regeln (Edge Cases)

Hier sind die Fallstricke für deinen Algorithmus:

  1. Vertriebsleiter "2-Monats-Regel" (Seite 7):

    • Wenn man das Level das erste Mal erreicht: Man bekommt den Titel, wird aber provisionstechnisch noch wie das vorherige Level (Vertriebspartner) abgerechnet.
    • Erst bei Wiederholung (2. Mal erreicht): Abrechnung nach Vertriebsleiter-Sätzen.
    • Implikation DB: Du brauchst im User-Model Felder wie vertriebsleiter_first_reached_date oder einen History-Log, um zu prüfen, ob es das erste Mal ist.
  2. Struktur-Check für Platin (Seite 17+):

    • Für Platin reicht Umsatz nicht. Es muss geprüft werden: Has user in direct line (any depth within line?) with Rank >= Gold.
    • Text sagt: "Linie mit einem Gold Member". Das bedeutet meistens: Irgendwo in diesem Bein (Ast) muss einer den Status haben, nicht zwingend die Firstline. Das ist rechenintensiv!
  3. Jahresbonus (Platin*):**

    • 1% vom gesamten Firmenumsatz wird gesammelt.
    • Ausgeschüttet im Januar des Folgejahres an alle Platin***.
    • Aufgeteilt nach Anzahl der Qualifizierten ("Share"-System).
  4. Überschuss-Punkte:

    • Formel für Payline: SUM(Downline Points) + MAX(0, Own_PV - Required_PV).
    • Beispiel Aktiv Junior: Braucht 250 PV. Hat er 300 PV, zählen 50 Punkte zusätzlich in seine Payline-Summe.

TreeCalcBotOptimized - Technische Dokumentation

Übersicht

Die TreeCalcBotOptimized Klasse ist eine optimierte Implementierung zur Berechnung und Verwaltung von Multi-Level-Marketing (MLM) Business-Strukturen. Sie berechnet Punkte, Qualifikationen und Provisionen für ein hierarchisches Vertriebsnetzwerk.

Hauptzweck

Der Algorithmus:

  • Lädt und verarbeitet MLM-Strukturbäume (Sponsor-Ketten)
  • Berechnet Verkaufsvolumen-Punkte über mehrere Ebenen (Lines)
  • Ermittelt Qualifikationsstufen basierend auf Performance
  • Unterstützt Caching über gespeicherte Strukturen für Performance
  • Bietet Live-Berechnungen für aktuelle Daten

Architektur & Design Patterns

Design Patterns

  1. Repository Pattern: BusinessUserRepository trennt Datenzugriff von Businesslogik
  2. Renderer Pattern: TreeHtmlRenderer trennt Darstellung von Berechnung
  3. Dependency Injection: Alle Dependencies können injiziert werden (Testbarkeit)
  4. Iterator Pattern: Stack-basierte Traversierung für Memory-Effizienz

Optimierungen

  • N+1 Problem gelöst: Eager Loading von Relations im Repository
  • Memory-Management: Stack-basierte Algorithmen statt Rekursion
  • Lazy Loading: Strukturen werden nur bei Bedarf berechnet
  • Caching: Gespeicherte Strukturen in UserBusinessStructure Tabelle

Datenstruktur

Haupt-Properties

private stdClass $date;              // Berechnungszeitraum (Monat/Jahr)
private string $initFrom;             // Kontext: 'member' oder 'admin'
private array $businessUsers;         // Root-Level Business-User (Top Sponsoren)
private array $parentless;            // User ohne Sponsor (Waisen)
private ?BusinessUserItemOptimized $businessUser;  // Einzelner Detail-User
private ?BusinessUserItemOptimized $sponsor;       // Sponsor des Users
private array $processedUserIds;     // Verhindert Duplikate/Endlosschleifen
private bool $forceLiveCalculation;  // Erzwingt Neuberechnung (ignoriert Cache)

Abhängigkeiten

private BusinessUserRepository $repository;  // Datenzugriff (DB-Queries)
private TreeHtmlRenderer $renderer;          // HTML-Ausgabe
private LoggerInterface $logger;             // Logging & Monitoring

Initialisierung & Konstruktor

Constructor

__construct(
    int $month,                              // Monat (1-12)
    int $year,                               // Jahr (2020 - aktuelles Jahr + 1)
    string $initFrom = 'member',             // Kontext
    bool $forceLiveCalculation = false,      // Cache-Bypass
    ?BusinessUserRepository $repository = null,
    ?TreeHtmlRenderer $renderer = null,
    ?LoggerInterface $logger = null
)

Ablauf:

  1. Validierung: validateInput() prüft Monat (1-12) und Jahr (2020-aktuell+1)
  2. Datumsinitialisierung: Erstellt stdClass mit Start-/Enddatum des Monats
  3. Dependency Injection: Injiziert oder erstellt Dependencies

Haupt-Workflows

1. Admin-Struktur Initialisierung

Methode: initStructureAdmin(bool $check = true, bool $forceLiveCalculation = false)

Zweck: Lädt die komplette MLM-Struktur für Admin-Übersichten

Ablauf:

┌─────────────────────────────────────┐
│ initStructureAdmin()                │
└───────────┬─────────────────────────┘
            │
            ├─── forceLiveCalculation = true?
            │    └─> buildFreshStructure() ──────┐
            │                                     │
            └─── Stored Structure vorhanden?     │
                 ├─> JA: loadStoredStructure()   │
                 └─> NEIN: buildFreshStructure() │
                                                  │
                 ┌────────────────────────────────┘
                 │
                 v
         buildFreshStructure():
         ├─> loadRootUsers()
         ├─> loadParentsUsers()
         ├─> loadParentlessUsers()
         ├─> calculateAllBusinessUsers()
         └─> calculateAllParentlessUsers()

Details:

  1. Gespeicherte Struktur (loadStoredStructure):

    • Lädt aus UserBusinessStructure Tabelle
    • Validiert Vollständigkeit der Daten
    • Berechnet fehlende Werte nach
  2. Frische Struktur (buildFreshStructure):

    • Lädt Root-User (Top-Sponsoren ohne eigenen Sponsor)
    • Lädt rekursiv alle Sub-Strukturen
    • Lädt parentlose User (Waisen)
    • Berechnet alle Punkte und Qualifikationen

2. User-Struktur Initialisierung

Methode: initStructureUser(int $userId, bool $forceLiveCalculation = false)

Zweck: Lädt die Struktur für einen spezifischen User (Member-Ansicht)

Ablauf:

┌─────────────────────────────────────┐
│ initStructureUser(userId)           │
└───────────┬─────────────────────────┘
            │
            ├─> User laden (mit Relations)
            ├─> BusinessUserItem erstellen
            ├─> User zu processedUserIds hinzufügen
            │
            ├─── forceLiveCalculation?
            │    ├─> JA: Live-Berechnung
            │    │   ├─> loadParentsUsers()
            │    │   ├─> loadSponsorUser()
            │    │   └─> calcQualPP() für alle
            │    │
            │    └─> NEIN: Stored Structure?
            │        ├─> JA: loadStoredParentsUsers()
            │        └─> NEIN: wie Live-Berechnung
            │
            └─> Fertig

Besonderheiten:

  • Lädt nur relevante Upline (Sponsoren-Kette)
  • Lädt Downline (Team-Struktur unter dem User)
  • Berechnet Qualifikationen nur bei Bedarf

3. Business-User Details

Methode: initBusinesslUserDetail(User $user, bool $forceLiveCalculation = false)

Zweck: Detaillierte Berechnung für einen einzelnen User

Ablauf:

┌─────────────────────────────────────┐
│ initBusinesslUserDetail(user)       │
└───────────┬─────────────────────────┘
            │
            ├─> BusinessUserItem erstellen
            ├─> makeUserFromModel(user, forceLiveCalculation)
            ├─> checkSponsor(user)
            │
            ├─── Daten gespeichert UND !forceLiveCalculation?
            │    └─> NEIN:
            │        ├─> readParentsBusinessUsers() (rekursive Struktur)
            │        ├─> calculateUserPointsOptimized() (Punkte in Linien)
            │        └─> calcQualPP() (Qualifikation)
            │
            └─> Fertig

Berechnungsalgorithmen

1. Punkte-Berechnung (calculateUserPointsOptimized)

Zweck: Berechnet Verkaufsvolumen-Punkte über alle Ebenen (Lines)

Problem: Original-Code verwendete Rekursion → Stack Overflow bei großen Strukturen

Lösung: Stack-basierter Depth-First Algorithmus

Algorithmus:

Input: businessUserItems (Array von Sub-Usern)
       startLine (Start-Ebene, z.B. 1)
       businessUserToUpdate (Parent-User zum Updaten)

Phase 1: SAMMELN (Depth-First Order)
┌────────────────────────────────────────┐
│ collectionStack = [alle Root-Items]    │
└────────────┬───────────────────────────┘
             │
             v
    ┌─────────────────────────┐
    │ While collectionStack:  │
    │ ├─> Item nehmen (FIFO)  │
    │ ├─> Zu processingStack  │
    │ └─> Kinder hinzufügen   │
    │     (in umgekehrter     │
    │      Reihenfolge)       │
    └─────────────────────────┘

Phase 2: SORTIEREN
┌────────────────────────────────────────┐
│ Sortiere processingStack nach Tiefe:  │
│ Tiefste Items zuerst                   │
└────────────┬───────────────────────────┘

Phase 3: VERARBEITEN
┌────────────────────────────────────────┐
│ For each Item in processingStack:     │
│ ├─> Business Line initialisieren      │
│ ├─> Punkte aus sales_volume_points_TP │
│ ├─> addBusinessLinePoints(line, pts)  │
│ └─> addTotalTP(pts)                   │
└────────────────────────────────────────┘

Beispiel:

Struktur:
    A (Root)
    ├── B (Line 1)
    │   ├── D (Line 2)
    │   └── E (Line 2)
    └── C (Line 1)
        └── F (Line 2)

Verarbeitungsreihenfolge (Depth-First):
1. D (Line 2, Depth 2) → 100 Punkte
2. E (Line 2, Depth 2) → 150 Punkte
3. B (Line 1, Depth 1) → 200 Punkte
4. F (Line 2, Depth 2) → 120 Punkte
5. C (Line 1, Depth 1) → 180 Punkte

Ergebnis für User A:
business_lines[1] = 380 Punkte (B + C)
business_lines[2] = 370 Punkte (D + E + F)
total_points_TP = 750 Punkte

Kritische Aspekte:

  • Depth-First Order: Wichtig für korrekte Punkteaggregation
  • Duplikate verhindern: processedUserIds Array
  • Memory-Effizienz: Stack statt Rekursion (kein Stack Overflow)

2. Qualifikations-Berechnung (calcQualPP)

Methode: BusinessUserItemOptimized::calcQualPP() (delegiert)

Zweck: Ermittelt die Qualifikationsstufe des Users

Berechnungsgrundlagen:

  1. qual_kp (Kunden-Punkte):

    • Direkte Käufe/Verkäufe des Users
    • Basis für persönliche Qualifikation
  2. qual_pp (Payline-Punkte):

    • Summe der Punkte aus dem Team
    • Basis für Team-Qualifikation
  3. business_lines:

    • Array mit Punkten pro Ebene (Line)
    • Line 1 = Direkte Partner
    • Line 2-5 = Weitere Ebenen
    • Line 6+ = Growth Bonus Ebenen

Qualifikationskriterien (typisch im MLM):

  • Bronze: 1.000 KP + 2.000 PP
  • Silber: 2.500 KP + 5.000 PP
  • Gold: 5.000 KP + 15.000 PP
  • Platin: 10.000 KP + 50.000 PP
  • (Werte beispielhaft, tatsächliche Logik in BusinessUserItemOptimized)

Zusätzliche Berechnungen:

  • next_qual_user_level: Nächste erreichbare Qualifikationsstufe
  • next_can_user_level: Potenzielle Qualifikationsstufe
  • Verwendet für UI (grüne Pfeile in Struktur-Ansicht)

3. Growth Bonus Berechnung

Methode: getGrowthBonus()

Zweck: Berechnet Bonuszahlungen ab Ebene 6

Logik:

if (count(business_lines) > 6) {
    return array_slice(business_lines, 6);  // Ebenen 7, 8, 9, ...
}

Verwendung:

  • Infinity-Bonus für Top-Leader
  • Zahlt auf alle Ebenen ab 6+
  • Nur bei entsprechender Qualifikation

Datenfluss & Performance

Root-User Laden (loadRootUsers)

Optimierung: Eager Loading im Repository

// Repository lädt mit Relations:
$users = User::whereNull('sponsor_id')
    ->with(['business_user', 'orders', 'subscriptions'])
    ->get();

// Vermeidet N+1 Queries
foreach ($users as $user) {
    // Alle Relations bereits geladen
    $businessUserItem->makeUserFromModel($user);
}

Memory-Monitoring:

  • Prüft Speicherverbrauch vor jedem User
  • Warnung bei > 80% Memory-Limit
  • Erzwingt Garbage Collection bei > 90%

Parent-User Laden (loadParentsUsers)

Rekursiver Aufbau:

Root-User A
├─> readParentsBusinessUsers()
    ├─> Lädt alle direkten Kinder (Line 1)
    └─> Für jedes Kind: readParentsBusinessUsers()
        └─> Lädt Enkel (Line 2)
            └─> Rekursiv in die Tiefe

Performance-Aspekte:

  • Kann bei großen Strukturen lange dauern
  • Verwendet processedUserIds zur Duplikats-Vermeidung
  • Unterbricht bei Zirkelbezügen (Sponsor-Loops)

Parentless-User (loadParentlessUsers)

Zweck: Findet "Waisen" (User ohne Sponsor)

Abfrage:

// Alle User die nicht in der Haupt-Struktur sind
$parentlessUsers = User::whereNotIn('id', $processedUserIds)
    ->whereNull('sponsor_id')
    ->orWhereHas('sponsor', function($q) {
        $q->where('deleted_at', '!=', null);
    })
    ->get();

Use Cases:

  • Fehlerhafte Datenimporte
  • Gelöschte Sponsoren
  • System-Test-Accounts

Caching & Persistierung

Gespeicherte Strukturen (UserBusinessStructure)

Tabellen-Schema:

user_business_structures {
    id
    month
    year
    structure (JSON)        // Root-User mit komplettem Tree
    parentless (JSON)       // Parentlose User
    completed (bool)        // Struktur vollständig berechnet
    created_at
    updated_at
}

Vorteile:

  • Admin-Struktur muss nur 1x pro Monat berechnet werden
  • Massive Performance-Verbesserung (Sekunden statt Minuten)
  • Konsistente Daten für Reports

Nachteile:

  • Daten können veraltet sein
  • Bei Änderungen: forceLiveCalculation = true nötig

Validierung gespeicherter Daten

Methode: validateAndRecalculateIfNeeded()

Prüfung:

function isBusinessUserIncomplete($businessUser): bool {
    // Prüfe grundlegende Felder
    $salesVolumeSum = $businessUser->sales_volume_points_sum;
    $qualKp = $businessUser->qual_kp;

    // Prüfe Level-Qualifikationsdaten
    $nextQualUserLevel = $businessUser->next_qual_user_level;
    $nextCanUserLevel = $businessUser->next_can_user_level;

    // Unvollständig wenn Daten fehlen
    return ($salesVolumeSum === null || $salesVolumeSum === 0) &&
           ($qualKp === null || $qualKp === 0) ||
           (empty($nextQualUserLevel) && empty($nextCanUserLevel));
}

Nachberechnung:

  • Nur für unvollständige User
  • Erhält Integrität bei partiellen Caches
  • Logged für Monitoring

Fehlerbehandlung & Logging

Logging-Strategien

Info-Level:

"Loading stored business structure for 11/2025"
"Building fresh business structure for 11/2025"
"Loaded 127 root users. Memory used: 15.3 MB"

Warning-Level:

"User not found: 12345"
"High memory usage: 85% (340 MB / 400 MB)"
"Could not load sponsor for user 999"

Error-Level:

"Error initializing admin structure: Invalid month: 13"
"Error calculating business user 456: Division by zero"

Debug-Level:

"Processed user 789 at line 3 with 250.5 points"
"Loaded 15 parent users for user 321"

Exception-Handling

Strategie:

try {
    $this->buildFreshStructure();
} catch (\Exception $e) {
    $this->logger->error("Error: " . $e->getMessage());
    throw $e;  // Re-throw für Controller
}

Partial Failures:

foreach ($businessUsers as $user) {
    try {
        $user->calcQualPP();
    } catch (\Exception $e) {
        $this->logger->error("Error for user {$user->id}");
        continue;  // Fahre fort mit nächstem User
    }
}

Memory-Management

Memory-Monitoring

Methode: checkMemoryUsage(string $operation, $identifier = null)

Schwellwerte:

  • 80% Memory: Warning-Log
  • 90% Memory: Erzwingt Garbage Collection

Implementierung:

$currentMemory = memory_get_usage();
$memoryLimit = parseMemoryLimit(ini_get('memory_limit'));  // z.B. "512M"
$memoryPercent = ($currentMemory / $memoryLimit) * 100;

if ($memoryPercent > 80) {
    $this->logger->warning("High memory usage in {$operation}");
}

if ($memoryPercent > 90) {
    gc_collect_cycles();  // Force Garbage Collection
}

Stack vs. Rekursion

Problem mit Rekursion:

// ALT: Rekursiver Ansatz (Stack Overflow bei großen Strukturen)
function calculateRecursive($items) {
    foreach ($items as $item) {
        // Berechnung
        if ($item->children) {
            calculateRecursive($item->children);  // Rekursion
        }
    }
}

Lösung mit Stack:

// NEU: Iterativer Ansatz (Memory-effizient)
function calculateIterative($items) {
    $stack = $items;
    while (!empty($stack)) {
        $item = array_shift($stack);
        // Berechnung
        if ($item->children) {
            $stack = array_merge($stack, $item->children);
        }
    }
}

Vorteile:

  • Kein Stack Overflow
  • Kontrolle über Memory
  • Bessere Performance bei großen Strukturen

HTML-Rendering

Delegation an TreeHtmlRenderer

Methoden:

public function makeHtmlTree(): string
     $renderer->renderTree($businessUsers)

public function makeParentlessHtml(): string
     $renderer->renderParentless($parentless)

public function makeSponsorHtml(): string
     $renderer->renderSponsor($sponsor)

Separation of Concerns:

  • Berechnung: TreeCalcBotOptimized
  • Darstellung: TreeHtmlRenderer
  • Ermöglicht alternative Renderer (JSON, PDF, etc.)

API & Public Methods

Initialisierungsmethoden

Methode Zweck Use Case
initStructureAdmin() Komplette Struktur Admin-Dashboard
initStructureUser($userId) User-spezifische Struktur Member-Bereich
initBusinesslUserDetail($user) Einzelner User Details User-Profil

Getter-Methoden

Methode Rückgabe Beschreibung
getItems() array Alle Root-BusinessUsers
getTotalUserCount() int Gesamtanzahl User in Struktur
getGrowthBonus() array Ebenen 6+ für Infinity-Bonus
getKeybyLine($line, $key) mixed Spezifischer Wert einer Ebene
isParentless() bool Gibt es parentlose User?

Static Methods

Methode Zweck
isFromStored($month, $year) Prüft ob gespeicherte Struktur existiert

Utility Methods

Methode Zweck
addProcessedUserId($id) Markiert User als verarbeitet
isUserProcessed($id) Prüft ob User bereits verarbeitet

Abhängigkeiten & Klassen

BusinessUserRepository

Verantwortlichkeiten:

  • Datenbankabfragen für User
  • Eager Loading von Relations
  • Abfragen von gespeicherten Strukturen

Wichtige Methoden:

getRootUsers(): Collection               // Top-Sponsoren
getUserWithRelations($id): ?User         // User mit Relations
getParentlessUsers($excludeIds): Generator  // Waisen
getSponsorForUser($userId): ?User        // Sponsor eines Users
getStoredStructure(): ?UserBusinessStructure // Cached Structure

BusinessUserItemOptimized

Verantwortlichkeiten:

  • Repräsentiert einen Business-User
  • Hält Berechnungsdaten (Punkte, Qualifikation)
  • Verwaltet Sub-User (businessUserItems)

Wichtige Properties:

$user_id                      // User ID
$b_user                       // Business User Model
$sponsor                      // Sponsor (BusinessUserItemOptimized)
$businessUserItems            // Array von Sub-Usern
$sales_volume_points_TP_sum   // Total Points (Verkaufsvolumen)
$sales_volume_points_sum      // Sales Volume Summe
$qual_kp                      // Qualifikation Kundenpunkte
$qual_pp                      // Qualifikation Payline-Punkte
$business_lines               // Array [line => points]
$next_qual_user_level         // Nächste Qualifikation
$next_can_user_level          // Potenzielle Qualifikation

Wichtige Methoden:

makeUserFromModel(User $user, bool $forceLiveCalculation)  // Initialisierung
readParentsBusinessUsers(bool $forceLiveCalculation)       // Lädt Sub-User
calcQualPP()                                               // Berechnet Qualifikation
addBusinessLinePoints($line, $points)                      // Fügt Punkte hinzu
addTotalTP($points)                                        // Fügt Total-Punkte hinzu

TreeHtmlRenderer

Verantwortlichkeiten:

  • Generiert HTML für Tree-Darstellung
  • Formatiert User-Daten für UI
  • Styling & Icons

Workflow-Beispiele

Beispiel 1: Admin lädt Struktur für November 2025

// Initialisierung
$treeCalc = new TreeCalcBotOptimized(11, 2025, 'admin');

// Check für gespeicherte Struktur
$treeCalc->initStructureAdmin(check: true, forceLiveCalculation: false);

// Ausgabe
$html = $treeCalc->makeHtmlTree();
$parentlessHtml = $treeCalc->makeParentlessHtml();
$totalUsers = $treeCalc->getTotalUserCount();

echo "Struktur mit {$totalUsers} Usern geladen.";

Ablauf (wenn gespeichert):

  1. Prüft user_business_structures Tabelle
  2. Lädt JSON-Daten
  3. Validiert Vollständigkeit
  4. Rendert HTML (< 1 Sekunde)

Ablauf (wenn nicht gespeichert):

  1. Lädt alle Root-User aus DB
  2. Lädt rekursiv alle Sub-User
  3. Berechnet Punkte für alle User
  4. Berechnet Qualifikationen
  5. Rendert HTML (10-60 Sekunden, je nach Größe)

Beispiel 2: Member lädt eigene Struktur

// Initialisierung
$treeCalc = new TreeCalcBotOptimized(11, 2025, 'member');

// User-spezifische Struktur
$treeCalc->initStructureUser(userId: 12345, forceLiveCalculation: false);

// Sponsor
$sponsorHtml = $treeCalc->makeSponsorHtml();

// Team-Struktur
$teamHtml = $treeCalc->makeHtmlTree();

Ablauf:

  1. Lädt User 12345 mit Relations
  2. Lädt Upline (Sponsoren-Kette nach oben)
  3. Lädt Downline (Team-Struktur nach unten)
  4. Berechnet nur relevante Daten für diesen User
  5. Rendert HTML (2-10 Sekunden)

Beispiel 3: Live-Berechnung erzwingen

// Für aktuelle Daten (z.B. laufender Monat)
$treeCalc = new TreeCalcBotOptimized(11, 2025, 'admin');

$treeCalc->initStructureAdmin(
    check: false,                      // Ignoriere gespeicherte Daten
    forceLiveCalculation: true         // Erzwinge Neuberechnung
);

// Alle Daten frisch berechnet
$html = $treeCalc->makeHtmlTree();

Use Cases:

  • Aktueller laufender Monat
  • Nach Datenimport/-änderung
  • Debugging/Testing

Performance-Charakteristiken

Laufzeit-Komplexität

Admin-Struktur (Complete Tree):

  • Best Case: O(1) - Gespeicherte Struktur laden
  • Worst Case: O(n) - n = Anzahl aller User
  • Typisch: 10-60 Sekunden für 1000-10000 User

User-Struktur (Subtree):

  • Best Case: O(1) - Gespeicherte Struktur
  • Worst Case: O(d * b) - d = Tiefe, b = Durchschnittliche Breite
  • Typisch: 2-10 Sekunden für Struktur mit 100-1000 Sub-Usern

Speicher-Komplexität

Stack-basierter Algorithmus:

  • O(n) - n = Anzahl User in Verarbeitung
  • Max. Speicher: ~100 MB für 10.000 User
  • Bei Rekursion: O(d) - d = Max. Tiefe (Stack Overflow-Risiko)

Datenbankabfragen

Optimiert (mit Eager Loading):

Queries für 1000 User:
- loadRootUsers: 1 Query (alle Root-User)
- loadParentsUsers: ~10-50 Queries (chunked)
- Total: ~50-100 Queries

Unoptimiert (ohne Eager Loading):

Queries für 1000 User:
- N+1 Problem: ~3000-5000 Queries
- Laufzeit: 10x langsamer

Konfiguration & Limits

Memory Limit

Empfohlen:

  • Kleine Strukturen (< 1000 User): 256 MB
  • Mittlere Strukturen (1000-5000 User): 512 MB
  • Große Strukturen (> 5000 User): 1024 MB

php.ini:

memory_limit = 512M
max_execution_time = 300  # 5 Minuten

Logging-Konfiguration

Log-Channels:

'channels' => [
    'business_calculations' => [
        'driver' => 'daily',
        'path' => storage_path('logs/business-calc.log'),
        'level' => 'info',
        'days' => 14,
    ],
],

Testing & Debugging

Unit Tests

Test-Cases:

// Test: Punkte-Berechnung korrekt
testCalculateUserPointsOptimized() {
    $user = createUserWithTeam(3 levels, 10 users);
    $calc = new TreeCalcBotOptimized(11, 2025);
    $calc->initStructureUser($user->id);

    assertEquals(expected_points, $user->business_lines[1]);
}

// Test: Keine Duplikate
testNoDuplicateProcessing() {
    $calc = new TreeCalcBotOptimized(11, 2025);
    $calc->addProcessedUserId(123);

    assertTrue($calc->isUserProcessed(123));
    assertFalse($calc->isUserProcessed(456));
}

Performance-Testing

Benchmark:

$startTime = microtime(true);
$startMemory = memory_get_usage();

$calc = new TreeCalcBotOptimized(11, 2025, 'admin');
$calc->initStructureAdmin();

$endTime = microtime(true);
$endMemory = memory_get_usage();

echo "Zeit: " . ($endTime - $startTime) . "s\n";
echo "Memory: " . (($endMemory - $startMemory) / 1024 / 1024) . " MB\n";

Debug-Modus

Aktivierung:

$logger = new DebugLogger('debug');  // Debug-Level Logging

$calc = new TreeCalcBotOptimized(
    month: 11,
    year: 2025,
    initFrom: 'admin',
    forceLiveCalculation: true,
    logger: $logger
);

Debug-Output:

[DEBUG] Processed user 789 at line 3 with 250.5 points
[DEBUG] Loaded 15 parent users for user 321
[INFO] Completed calculations for all business users in 1234.56ms

Bekannte Limitierungen & Edge Cases

1. Zirkelbezüge

Problem: User A sponsort User B, User B sponsort User A

Lösung: processedUserIds Array verhindert Endlosschleifen

if ($this->isUserProcessed($userId)) {
    return;  // Skip bereits verarbeitete User
}

2. Gelöschte User

Problem: Sponsor gelöscht, aber Referenz existiert noch

Lösung: Soft-Deletes & Parentless-User Handling

// Parentless-Query berücksichtigt gelöschte Sponsoren
User::whereNull('sponsor_id')
    ->orWhereHas('sponsor', function($q) {
        $q->whereNotNull('deleted_at');
    })

3. Große Strukturen

Problem: > 10.000 User → Memory-Probleme

Lösung:

  • Chunked Loading
  • Memory-Monitoring
  • Garbage Collection
  • Ggf. Queue-basierte Verarbeitung

4. Zeitzonenprobleme

Problem: User in verschiedenen Zeitzonen

Lösung: Alle Berechnungen in UTC

$this->date->start_date = Carbon::parse($year . '-' . $month . '-1')
    ->setTimezone('UTC')
    ->format('Y-m-d H:i:s');

Best Practices & Empfehlungen

1. Caching nutzen

DO:

// Für vergangene Monate: Cache nutzen
$calc = new TreeCalcBotOptimized(10, 2025);
$calc->initStructureAdmin(check: true, forceLiveCalculation: false);

DON'T:

// Laufender Monat: Immer Live-Berechnung
$calc = new TreeCalcBotOptimized(11, 2025);
$calc->initStructureAdmin(check: false, forceLiveCalculation: true);

2. Fehlerbehandlung

DO:

try {
    $calc->initStructureUser($userId);
} catch (\Exception $e) {
    Log::error("Error loading structure for user {$userId}", [
        'exception' => $e->getMessage()
    ]);
    // Fallback oder User-Feedback
}

DON'T:

// Fehler ignorieren oder unbehandelt lassen
$calc->initStructureUser($userId);

3. Dependency Injection

DO:

// Testbar durch Dependency Injection
$mockRepo = Mockery::mock(BusinessUserRepository::class);
$calc = new TreeCalcBotOptimized(11, 2025, repository: $mockRepo);

DON'T:

// Hardcoded Dependencies
class TreeCalcBot {
    public function __construct() {
        $this->repo = new BusinessUserRepository();  // Nicht testbar
    }
}

Wartung & Monitoring

Logs überwachen

Wichtige Log-Patterns:

# High Memory Usage
grep "High memory usage" storage/logs/business-calc.log

# Error Patterns
grep "Error calculating" storage/logs/business-calc.log

# Performance Issues (> 60 Sekunden)
grep "Completed calculations" storage/logs/business-calc.log | awk '{print $NF}'

Performance-Metriken

KPIs:

  • Durchschnittliche Berechnungszeit pro User
  • Memory Peak Usage
  • Anzahl DB-Queries
  • Cache Hit Rate

Monitoring:

// In Controller oder Command
$metrics = [
    'users_processed' => $calc->getTotalUserCount(),
    'execution_time' => $executionTime,
    'memory_peak' => memory_get_peak_usage(),
    'cache_used' => $storedStructure ? 'yes' : 'no'
];

Log::info('Structure calculation completed', $metrics);

Migration von alter Version

TreeCalcBot → TreeCalcBotOptimized

Breaking Changes:

  1. Static Method addUserID() → Deprecated

    // ALT
    TreeCalcBot::addUserID($userId);
    
    // NEU
    $calc->addProcessedUserId($userId);
    
  2. Constructor-Signatur

    // ALT
    new TreeCalcBot($month, $year);
    
    // NEU (mit optionalen Dependencies)
    new TreeCalcBotOptimized($month, $year, $initFrom, $forceLiveCalculation);
    
  3. HTML-Rendering

    • Intern an TreeHtmlRenderer delegiert
    • Public API bleibt gleich

Zusammenfassung

Hauptmerkmale

Performance-optimiert

  • Stack-basiert statt Rekursion
  • Eager Loading (N+1 gelöst)
  • Memory-Management
  • Caching

Wartbar

  • Separation of Concerns
  • Dependency Injection
  • Umfangreiches Logging
  • Fehlerbehandlung

Skalierbar

  • Handhabt > 10.000 User
  • Chunked Processing
  • Garbage Collection
  • Memory-Monitoring

Testbar

  • Dependency Injection
  • Repository Pattern
  • Unit-testbar

Typische Use Cases

Use Case Methode Cache Laufzeit
Admin-Monatsabschluss initStructureAdmin() Ja < 1s
Admin-Live-Dashboard initStructureAdmin(force: true) Nein 10-60s
Member-Dashboard initStructureUser() Ja < 2s
User-Profil-Details initBusinesslUserDetail() Ja 2-5s

Erweiterungsmöglichkeiten

Mögliche Verbesserungen:

  1. Queue-basierte Verarbeitung

    • Große Strukturen in Background-Jobs
    • Progress-Tracking für User
    • Retry-Mechanismen
  2. Inkrementelles Update

    • Nur geänderte User neu berechnen
    • Delta-Updates statt Full-Recalculation
  3. Distributed Caching

    • Redis für schnelleren Zugriff
    • Cache-Invalidierung bei Änderungen
  4. API-Endpoints

    • RESTful API für externe Zugriffe
    • JSON-Export für Drittanwendungen
  5. Real-time Updates

    • WebSocket-Integration
    • Live-Aktualisierung ohne Page Reload

Glossar

Business User: User mit MLM-Vertriebsaktivitäten Sponsor: Derjenige der einen User geworben hat (Upline) Line/Ebene: Hierarchieebene im MLM (1 = direkt, 2 = Enkel, etc.) Parentless: User ohne Sponsor (Waisen) TP (Total Points): Gesamtpunkte Verkaufsvolumen KP (Kunden-Punkte): Persönliche Verkäufe PP (Payline-Punkte): Team-Verkäufe für Provisionsberechtigung Growth Bonus: Provisions-Ebenen ab 6+ Forced Live Calculation: Erzwingt Neuberechnung, ignoriert Cache Eager Loading: Lädt Relations in einer Query (vs. Lazy Loading)


Kontakt & Support

Bei Fragen oder Problemen:

  • Code Review: Siehe BusinessUserRepository, BusinessUserItemOptimized
  • Logging: storage/logs/business-calc.log
  • Performance Issues: Memory-Limit erhöhen, Caching prüfen
  • Bugs: Exception-Stack-Trace analysieren

Letzte Aktualisierung: November 2025 Version: TreeCalcBotOptimized v2.0 Status: Production Ready