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.
- Wichtig: Wenn
- 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:
-
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_dateoder einen History-Log, um zu prüfen, ob es das erste Mal ist.
-
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!
- Für Platin reicht Umsatz nicht. Es muss geprüft werden:
-
Jahresbonus (Platin*):**
- 1% vom gesamten Firmenumsatz wird gesammelt.
- Ausgeschüttet im Januar des Folgejahres an alle Platin***.
- Aufgeteilt nach Anzahl der Qualifizierten ("Share"-System).
-
Ü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.
- Formel für Payline:
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
- Repository Pattern:
BusinessUserRepositorytrennt Datenzugriff von Businesslogik - Renderer Pattern:
TreeHtmlRenderertrennt Darstellung von Berechnung - Dependency Injection: Alle Dependencies können injiziert werden (Testbarkeit)
- 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
UserBusinessStructureTabelle
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:
- Validierung:
validateInput()prüft Monat (1-12) und Jahr (2020-aktuell+1) - Datumsinitialisierung: Erstellt
stdClassmit Start-/Enddatum des Monats - 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:
-
Gespeicherte Struktur (
loadStoredStructure):- Lädt aus
UserBusinessStructureTabelle - Validiert Vollständigkeit der Daten
- Berechnet fehlende Werte nach
- Lädt aus
-
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:
processedUserIdsArray - Memory-Effizienz: Stack statt Rekursion (kein Stack Overflow)
2. Qualifikations-Berechnung (calcQualPP)
Methode: BusinessUserItemOptimized::calcQualPP() (delegiert)
Zweck: Ermittelt die Qualifikationsstufe des Users
Berechnungsgrundlagen:
-
qual_kp (Kunden-Punkte):
- Direkte Käufe/Verkäufe des Users
- Basis für persönliche Qualifikation
-
qual_pp (Payline-Punkte):
- Summe der Punkte aus dem Team
- Basis für Team-Qualifikation
-
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 Qualifikationsstufenext_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
processedUserIdszur 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):
- Prüft
user_business_structuresTabelle - Lädt JSON-Daten
- Validiert Vollständigkeit
- Rendert HTML (< 1 Sekunde)
Ablauf (wenn nicht gespeichert):
- Lädt alle Root-User aus DB
- Lädt rekursiv alle Sub-User
- Berechnet Punkte für alle User
- Berechnet Qualifikationen
- 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:
- Lädt User 12345 mit Relations
- Lädt Upline (Sponsoren-Kette nach oben)
- Lädt Downline (Team-Struktur nach unten)
- Berechnet nur relevante Daten für diesen User
- 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:
-
Static Method
addUserID()→ Deprecated// ALT TreeCalcBot::addUserID($userId); // NEU $calc->addProcessedUserId($userId); -
Constructor-Signatur
// ALT new TreeCalcBot($month, $year); // NEU (mit optionalen Dependencies) new TreeCalcBotOptimized($month, $year, $initFrom, $forceLiveCalculation); -
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:
-
Queue-basierte Verarbeitung
- Große Strukturen in Background-Jobs
- Progress-Tracking für User
- Retry-Mechanismen
-
Inkrementelles Update
- Nur geänderte User neu berechnen
- Delta-Updates statt Full-Recalculation
-
Distributed Caching
- Redis für schnelleren Zugriff
- Cache-Invalidierung bei Änderungen
-
API-Endpoints
- RESTful API für externe Zugriffe
- JSON-Export für Drittanwendungen
-
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 ✅