1422 lines
39 KiB
Markdown
1422 lines
39 KiB
Markdown
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
|
|
|
|
```php
|
|
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
|
|
|
|
```php
|
|
private BusinessUserRepository $repository; // Datenzugriff (DB-Queries)
|
|
private TreeHtmlRenderer $renderer; // HTML-Ausgabe
|
|
private LoggerInterface $logger; // Logging & Monitoring
|
|
```
|
|
|
|
---
|
|
|
|
## Initialisierung & Konstruktor
|
|
|
|
### Constructor
|
|
|
|
```php
|
|
__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:**
|
|
|
|
```php
|
|
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
|
|
|
|
```php
|
|
// 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:**
|
|
|
|
```php
|
|
// 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:**
|
|
|
|
```php
|
|
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:**
|
|
|
|
```php
|
|
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:**
|
|
|
|
```php
|
|
"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:**
|
|
|
|
```php
|
|
"User not found: 12345"
|
|
"High memory usage: 85% (340 MB / 400 MB)"
|
|
"Could not load sponsor for user 999"
|
|
```
|
|
|
|
**Error-Level:**
|
|
|
|
```php
|
|
"Error initializing admin structure: Invalid month: 13"
|
|
"Error calculating business user 456: Division by zero"
|
|
```
|
|
|
|
**Debug-Level:**
|
|
|
|
```php
|
|
"Processed user 789 at line 3 with 250.5 points"
|
|
"Loaded 15 parent users for user 321"
|
|
```
|
|
|
|
---
|
|
|
|
### Exception-Handling
|
|
|
|
**Strategie:**
|
|
|
|
```php
|
|
try {
|
|
$this->buildFreshStructure();
|
|
} catch (\Exception $e) {
|
|
$this->logger->error("Error: " . $e->getMessage());
|
|
throw $e; // Re-throw für Controller
|
|
}
|
|
```
|
|
|
|
**Partial Failures:**
|
|
|
|
```php
|
|
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:**
|
|
|
|
```php
|
|
$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:**
|
|
|
|
```php
|
|
// 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:**
|
|
|
|
```php
|
|
// 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:**
|
|
|
|
```php
|
|
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:**
|
|
|
|
```php
|
|
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:**
|
|
|
|
```php
|
|
$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:**
|
|
|
|
```php
|
|
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
|
|
|
|
```php
|
|
// 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
|
|
|
|
```php
|
|
// 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
|
|
|
|
```php
|
|
// 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:**
|
|
|
|
```ini
|
|
memory_limit = 512M
|
|
max_execution_time = 300 # 5 Minuten
|
|
```
|
|
|
|
---
|
|
|
|
### Logging-Konfiguration
|
|
|
|
**Log-Channels:**
|
|
|
|
```php
|
|
'channels' => [
|
|
'business_calculations' => [
|
|
'driver' => 'daily',
|
|
'path' => storage_path('logs/business-calc.log'),
|
|
'level' => 'info',
|
|
'days' => 14,
|
|
],
|
|
],
|
|
```
|
|
|
|
---
|
|
|
|
## Testing & Debugging
|
|
|
|
### Unit Tests
|
|
|
|
**Test-Cases:**
|
|
|
|
```php
|
|
// 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:**
|
|
|
|
```php
|
|
$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:**
|
|
|
|
```php
|
|
$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
|
|
|
|
```php
|
|
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
|
|
|
|
```php
|
|
// 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
|
|
|
|
```php
|
|
$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:**
|
|
|
|
```php
|
|
// Für vergangene Monate: Cache nutzen
|
|
$calc = new TreeCalcBotOptimized(10, 2025);
|
|
$calc->initStructureAdmin(check: true, forceLiveCalculation: false);
|
|
```
|
|
|
|
**DON'T:**
|
|
|
|
```php
|
|
// Laufender Monat: Immer Live-Berechnung
|
|
$calc = new TreeCalcBotOptimized(11, 2025);
|
|
$calc->initStructureAdmin(check: false, forceLiveCalculation: true);
|
|
```
|
|
|
|
---
|
|
|
|
### 2. Fehlerbehandlung
|
|
|
|
**DO:**
|
|
|
|
```php
|
|
try {
|
|
$calc->initStructureUser($userId);
|
|
} catch (\Exception $e) {
|
|
Log::error("Error loading structure for user {$userId}", [
|
|
'exception' => $e->getMessage()
|
|
]);
|
|
// Fallback oder User-Feedback
|
|
}
|
|
```
|
|
|
|
**DON'T:**
|
|
|
|
```php
|
|
// Fehler ignorieren oder unbehandelt lassen
|
|
$calc->initStructureUser($userId);
|
|
```
|
|
|
|
---
|
|
|
|
### 3. Dependency Injection
|
|
|
|
**DO:**
|
|
|
|
```php
|
|
// Testbar durch Dependency Injection
|
|
$mockRepo = Mockery::mock(BusinessUserRepository::class);
|
|
$calc = new TreeCalcBotOptimized(11, 2025, repository: $mockRepo);
|
|
```
|
|
|
|
**DON'T:**
|
|
|
|
```php
|
|
// Hardcoded Dependencies
|
|
class TreeCalcBot {
|
|
public function __construct() {
|
|
$this->repo = new BusinessUserRepository(); // Nicht testbar
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Wartung & Monitoring
|
|
|
|
### Logs überwachen
|
|
|
|
**Wichtige Log-Patterns:**
|
|
|
|
```bash
|
|
# 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:**
|
|
|
|
```php
|
|
// 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
|
|
|
|
```php
|
|
// ALT
|
|
TreeCalcBot::addUserID($userId);
|
|
|
|
// NEU
|
|
$calc->addProcessedUserId($userId);
|
|
```
|
|
|
|
2. **Constructor-Signatur**
|
|
|
|
```php
|
|
// 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 ✅
|