validateInput($month, $year); $this->initializeDate($month, $year); $this->initFrom = $initFrom; // Dependency Injection mit Fallback $this->repository = $repository ?? new BusinessUserRepository($month, $year); $this->renderer = $renderer ?? new TreeHtmlRenderer($initFrom); $this->logger = $logger ?? app(LogContract::class); } /** * Initialisiert die Business-Struktur für Admin-Ansicht */ public function initStructureAdmin(bool $check = true): void { try { $storedStructure = null; if ($check) { $storedStructure = $this->repository->getStoredStructure(); } if ($storedStructure) { $this->logger->info("Loading stored business structure for {$this->date->month}/{$this->date->year}"); $this->loadStoredStructure($storedStructure); } else { $this->logger->info("Building fresh business structure for {$this->date->month}/{$this->date->year}"); $this->buildFreshStructure(); } } catch (\Exception $e) { $this->logger->error("Error initializing admin structure: " . $e->getMessage()); throw $e; } } /** * Initialisiert die Struktur für einen spezifischen User */ public function initStructureUser(int $userId): void { try { $this->logger->info("Initializing structure for user: {$userId}"); $user = $this->repository->getUserWithRelations($userId); if (!$user) { $this->logger->warning("User not found: {$userId}"); return; } $businessUserItem = new BusinessUserItemOptimized($this->date); $businessUserItem->makeUserFromModel($user); // ✅ Nutzt bereits geladene Relations $this->addUserIdToProcessed($userId); $this->businessUsers[] = $businessUserItem; $storedStructure = $this->repository->getStoredStructure(); if ($storedStructure) { $this->loadStoredParentsUsers($storedStructure); if (isset($this->businessUsers[0]) && $this->businessUsers[0]->sponsor) { $this->loadStoredSponsorUser($this->businessUsers[0]->sponsor->user_id); } } else { $this->loadParentsUsers(); $this->loadSponsorUser($userId); } } catch (\Exception $e) { $this->logger->error("Error initializing user structure for {$userId}: " . $e->getMessage()); throw $e; } } /** * Initialisiert detaillierte Business-User-Informationen */ public function initBusinesslUserDetail(User $user): void { try { $this->logger->info("Initializing business user details for: {$user->id}"); $this->businessUser = new BusinessUserItemOptimized($this->date); $this->businessUser->makeUserFromModel($user); // ✅ Nutzt bereits User-Objekt $this->businessUser->checkSponsor($user); if (!$this->businessUser->isSave()) { // Aufbau der Struktur für den User in die unendliche Tiefe $this->businessUser->readParentsBusinessUsers(); // Calculate Points in Lines (optimiert für Memory-Effizienz) if (count($this->businessUser->businessUserItems) > 0) { $this->calculateUserPointsOptimized($this->businessUser->businessUserItems, 1); } // Qualifikation nach qual_kp (KundenPoints) und qual_pp (PaylinePoints) $this->businessUser->calcQualPP(); } } catch (\Exception $e) { $this->logger->error("Error initializing business user details for {$user->id}: " . $e->getMessage()); throw $e; } } /** * Gibt Growth Bonus zurück (ab Linie 6) */ public function getGrowthBonus(): array { if (!$this->businessUser || !$this->businessUser->business_lines) { return []; } if (count($this->businessUser->business_lines) > 6) { $bLines = $this->businessUser->business_lines->toArray(); return array_slice($bLines, 6); } return []; } /** * Gibt Wert für spezifische Linie zurück */ public function getKeybyLine(int $line, string $key) { if (!$this->businessUser || !$this->businessUser->business_lines) { return 0; } $bLines = $this->businessUser->business_lines; if (!isset($bLines[$line])) { return 0; } $lineData = $bLines[$line]; if ($lineData instanceof stdClass) { return $lineData->{$key} ?? 0; } if (is_array($lineData)) { return $lineData[$key] ?? 0; } return 0; } /** * HTML-Rendering Methoden (Delegation an Renderer) */ public function makeHtmlTree(): string { return $this->renderer->renderTree($this->businessUsers); } public function makeParentlessHtml(): string { return $this->renderer->renderParentless($this->parentless); } public function makeSponsorHtml(): string { return $this->renderer->renderSponsor($this->sponsor); } /** * Getter-Methoden (Rückwärtskompatibilität) */ public function getItems(): array { return $this->businessUsers; } public function isParentless(): bool { return !empty($this->parentless); } /** * Static Methoden (Rückwärtskompatibilität) */ public static function isFromStored(int $month, int $year): ?UserBusinessStructure { $structure = UserBusinessStructure::where('year', $year) ->where('month', $month) ->first(); return ($structure && $structure->completed) ? $structure : null; } public static function addUserID(int $id): void { // Deprecated: Wird durch Instanz-Methode ersetzt // Bleibt für Rückwärtskompatibilität erhalten } // ===== Private Methoden ===== /** * Validiert Eingabeparameter */ private function validateInput(int $month, int $year): void { if ($month < 1 || $month > 12) { throw new \InvalidArgumentException("Invalid month: {$month}"); } $currentYear = (int) date('Y'); if ($year < 2020 || $year > $currentYear + 1) { throw new \InvalidArgumentException("Invalid year: {$year}"); } } /** * Initialisiert Datums-Objekt */ private function initializeDate(int $month, int $year): void { $this->date = new stdClass(); $date = Carbon::parse($year . '-' . $month . '-1'); $this->date->month = $month; $this->date->year = $year; $this->date->start_date = $date->format('Y-m-d H:i:s'); $this->date->end_date = $date->endOfMonth()->format('Y-m-d H:i:s'); } /** * Lädt gespeicherte Struktur */ private function loadStoredStructure(UserBusinessStructure $structure): void { $this->loadStoredRootUsers($structure); $this->loadStoredParentsUsers($structure); $this->loadStoredParentlessUsers($structure); } /** * Baut frische Struktur auf */ private function buildFreshStructure(): void { $this->loadRootUsers(); $this->loadParentsUsers(); $this->loadParentlessUsers(); } /** * Lädt Root-Users (optimiert mit Memory-Monitoring) */ private function loadRootUsers(): void { $startMemory = memory_get_usage(); $users = $this->repository->getRootUsers(); foreach ($users as $user) { // Memory-Check vor jeder User-Verarbeitung $this->checkMemoryUsage('loadRootUsers', $user->id); $businessUserItem = new BusinessUserItemOptimized($this->date); $businessUserItem->makeUserFromModel($user); // ✅ Nutzt bereits geladene Relations $this->addUserIdToProcessed($user->id); $this->businessUsers[] = $businessUserItem; } $endMemory = memory_get_usage(); $memoryUsed = $this->formatBytes($endMemory - $startMemory); $this->logger->info("Loaded " . count($users) . " root users with optimized relations. Memory used: {$memoryUsed}"); } /** * Lädt Parent-Users für alle Business-Users */ private function loadParentsUsers(): void { foreach ($this->businessUsers as $businessUser) { $businessUser->readParentsBusinessUsers(); } } /** * Lädt parentlose Users (Memory-optimiert) */ private function loadParentlessUsers(): void { $count = 0; $excludeIds = array_keys($this->processedUserIds); foreach ($this->repository->getParentlessUsers($excludeIds) as $user) { $businessUserItem = new BusinessUserItemOptimized($this->date); $businessUserItem->makeUserFromModel($user); // ✅ Nutzt bereits geladene Relations $this->parentless[] = $businessUserItem; $count++; } $this->logger->info("Loaded {$count} parentless users with optimized relations"); } /** * Lädt Sponsor für User */ private function loadSponsorUser(int $userId): void { try { $sponsorUser = $this->repository->getSponsorForUser($userId); if ($sponsorUser) { $this->sponsor = new BusinessUserItem($this->date); $this->sponsor->makeUser($sponsorUser->id); $this->logger->info("Loaded sponsor {$sponsorUser->id} for user {$userId}"); } } catch (\Exception $e) { $this->logger->warning("Could not load sponsor for user {$userId}: " . $e->getMessage()); } } /** * Gespeicherte Root-Users laden */ private function loadStoredRootUsers(UserBusinessStructure $structure): void { if (!$structure->structure) { return; } foreach ($structure->structure as $obj) { $businessUserItem = new BusinessUserItem($this->date); $businessUserItem->makeUser($obj->user_id); $this->addUserIdToProcessed($obj->user_id); $this->businessUsers[] = $businessUserItem; } } /** * Gespeicherte Parent-Users laden */ private function loadStoredParentsUsers(UserBusinessStructure $structure): void { foreach ($this->businessUsers as $businessUser) { $businessUser->readStoredParentsBusinessUsers($structure->structure); } } /** * Gespeicherte parentlose Users laden */ private function loadStoredParentlessUsers(UserBusinessStructure $structure): void { if (!$structure->parentless) { return; } foreach ($structure->parentless as $obj) { if (!isset($this->processedUserIds[$obj->user_id])) { $businessUserItem = new BusinessUserItem($this->date); $businessUserItem->makeUser($obj->user_id); $this->parentless[] = $businessUserItem; } } } /** * Gespeicherten Sponsor laden */ private function loadStoredSponsorUser(int $userId): void { $this->sponsor = new BusinessUserItem($this->date); $this->sponsor->makeUser($userId); } /** * Optimierte Punkte-Berechnung (Stack-basiert mit korrekter Depth-First Reihenfolge) * * KRITISCH: Stack muss gleiche Reihenfolge wie Original-Rekursion produzieren * Original: Depth-First Traversierung (erst tief, dann Punkte addieren) * Stack: Muss umgekehrt arbeiten - erst alle Kinder sammeln, dann von tief zu flach verarbeiten */ private function calculateUserPointsOptimized(array $businessUserItems, int $startLine): void { $processingStack = []; $collectionStack = []; // Sammelt Items in korrekter Reihenfolge // Phase 1: Sammle alle Items in Depth-First Reihenfolge foreach ($businessUserItems as $item) { $collectionStack[] = ['item' => $item, 'line' => $startLine, 'depth' => 0]; } // Expandiere alle Kinder (Depth-First) $processedItems = []; while (!empty($collectionStack)) { $current = array_shift($collectionStack); // FIFO für Breadth-First Sammlung $item = $current['item']; $line = $current['line']; $depth = $current['depth']; // Markiere für Verarbeitung (mit Tiefe für spätere Sortierung) $processingStack[] = [ 'item' => $item, 'line' => $line, 'depth' => $depth, 'id' => $item->user_id ?? uniqid() ]; // Füge Kinder hinzu (werden später verarbeitet = Depth-First) if (isset($item->businessUserItems) && count($item->businessUserItems) > 0) { // Kinder in umgekehrter Reihenfolge hinzufügen für korrekte Stack-Verarbeitung $children = array_reverse($item->businessUserItems); foreach ($children as $childItem) { array_unshift($collectionStack, [ 'item' => $childItem, 'line' => $line + 1, 'depth' => $depth + 1 ]); } } } // Phase 2: Sortiere nach Tiefe (tiefste zuerst, wie bei Rekursion) usort($processingStack, function($a, $b) { return $b['depth'] <=> $a['depth']; // Tiefste zuerst }); // Phase 3: Verarbeite in korrekter Reihenfolge (von tief zu flach) foreach ($processingStack as $current) { $item = $current['item']; $line = $current['line']; try { // Business Line initialisieren falls nötig if (!isset($this->businessUser->business_lines[$line])) { $obj = new stdClass(); $obj->points = 0; $this->businessUser->addBusinessLineToUser($line, $obj); } // Punkte hinzufügen (mit Validierung) $points = (float) ($item->sales_volume_points_TP_sum ?? 0); if ($points > 0) { $this->businessUser->addBusinessLinePoints($line, $points); $this->businessUser->addTotalTP($points); } $this->logger->debug("Processed user {$current['id']} at line {$line} with {$points} points"); } catch (\Exception $e) { $this->logger->error("Error processing user points for {$current['id']}: " . $e->getMessage()); } } $this->logger->info("Processed " . count($processingStack) . " business user items in depth-first order"); } /** * User-ID zu verarbeiteten IDs hinzufügen */ private function addUserIdToProcessed(int $id): void { $this->processedUserIds[$id] = true; } /** * Prüft ob User bereits verarbeitet wurde */ private function isUserProcessed(int $id): bool { return isset($this->processedUserIds[$id]); } /** * Memory-Monitoring Methoden */ private function checkMemoryUsage(string $operation, $identifier = null): void { $currentMemory = memory_get_usage(); $memoryLimit = $this->parseMemoryLimit(ini_get('memory_limit')); $memoryPercent = ($currentMemory / $memoryLimit) * 100; if ($memoryPercent > 80) { $currentFormatted = $this->formatBytes($currentMemory); $limitFormatted = $this->formatBytes($memoryLimit); $this->logger->warning("High memory usage detected in {$operation}", [ 'identifier' => $identifier, 'current_memory' => $currentFormatted, 'memory_limit' => $limitFormatted, 'usage_percent' => round($memoryPercent, 2) ]); // Garbage Collection bei hohem Memory-Verbrauch if ($memoryPercent > 90) { $this->logger->warning("Critical memory usage - forcing garbage collection"); gc_collect_cycles(); } } } private function parseMemoryLimit(string $limit): int { $limit = trim($limit); $last = strtolower($limit[strlen($limit)-1]); $number = (int) $limit; switch($last) { case 'g': $number *= 1024; case 'm': $number *= 1024; case 'k': $number *= 1024; } return $number; } private function formatBytes(int $bytes, int $precision = 2): string { $units = array('B', 'KB', 'MB', 'GB', 'TB'); for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) { $bytes /= 1024; } return round($bytes, $precision) . ' ' . $units[$i]; } /** * Public Properties für Rückwärtskompatibilität */ public function __get(string $name) { switch ($name) { case 'date': return $this->date; case 'business_user': return $this->businessUser; case 'business_users': return $this->businessUsers; case 'parentless': return $this->parentless; default: throw new \InvalidArgumentException("Property {$name} does not exist"); } } public function __set(string $name, $value) { switch ($name) { case 'business_users': $this->businessUsers = $value; break; case 'parentless': $this->parentless = $value; break; default: throw new \InvalidArgumentException("Property {$name} cannot be set"); } } }