date = $date; $this->treeCalcBot = $treeCalcBot; $this->businessUserItems = []; // Initialize array return $this; } /** * Erstellt BusinessUser aus User-ID (Original-Methode für Rückwärtskompatibilität) * * @param int $user_id Die User-ID * @param bool $forceLiveCalculation Erzwingt Live-Berechnung und überspringt gespeicherte Daten */ public function makeUser($user_id, bool $forceLiveCalculation = false): void { try { // Prüfe nur nach gespeicherten Daten, wenn keine Live-Berechnung erzwungen wird if (!$forceLiveCalculation) { $this->b_user = UserBusiness::where('user_id', $user_id) ->where('month', $this->date->month) ->where('year', $this->date->year) ->first(); if ($this->b_user !== null) { \Log::debug("BusinessUserItem: Using stored data for user {$user_id} ({$this->date->month}/{$this->date->year})"); // WICHTIG: Auch bei gespeicherten Daten User-Model laden für Grunddaten $user = User::with(['account', 'user_level'])->find($user_id); if ($user) { $this->enrichStoredDataWithUserModel($user); // Prüfe ob Level-Qualifikationsdaten nachberechnet werden müssen if ($this->needsQualificationRecalculation) { \Log::debug("BusinessUserItem: Triggering qualification recalculation for user {$user_id}"); $this->calcQualPP(); // Berechne fehlende Level-Qualifikationsdaten } } return; // Bereits gespeicherte Daten verwenden } } else { \Log::debug("BusinessUserItem: Force live calculation for user {$user_id} ({$this->date->month}/{$this->date->year})"); } // Lade User mit Relations (weniger effizient als makeUserFromModel) $user = User::with(['account', 'user_level'])->find($user_id); if (!$user) { \Log::warning("BusinessUserItem: User not found: {$user_id}"); return; } $this->initializeFromUserModel($user); // WICHTIG: Bei Live-Berechnung auch Level-Qualifikationsdaten berechnen // (nicht bei forceLiveCalculation=false, da dort gespeicherte Daten bevorzugt werden) if ($forceLiveCalculation) { $this->calcQualPP(); } } catch (\Exception $e) { \Log::error("BusinessUserItem: Error creating user {$user_id}: " . $e->getMessage()); throw $e; } } /** * NEUE OPTIMIERTE METHODE: Erstellt BusinessUser aus bereits geladenem User-Objekt * Konsistent zur ursprünglichen makeUser Logik - prüft explizit nach bereits berechneten Daten * * @param User $user Das User-Model * @param bool $forceLiveCalculation Erzwingt Live-Berechnung und überspringt gespeicherte Daten */ public function makeUserFromModel(User $user, bool $forceLiveCalculation = false): void { try { if (!$user || !$user->id) { throw new \InvalidArgumentException('Invalid user model provided'); } // Prüfe nur nach gespeicherten Daten, wenn keine Live-Berechnung erzwungen wird if (!$forceLiveCalculation) { $this->b_user = UserBusiness::where('user_id', $user->id) ->where('month', $this->date->month) ->where('year', $this->date->year) ->first(); if ($this->b_user !== null) { \Log::debug("BusinessUserItem: Using stored data for user {$user->id} ({$this->date->month}/{$this->date->year})"); // WICHTIG: Auch bei gespeicherten Daten User-Grunddaten anreichern $this->enrichStoredDataWithUserModel($user); // Prüfe ob Level-Qualifikationsdaten nachberechnet werden müssen if ($this->needsQualificationRecalculation) { \Log::debug("BusinessUserItem: Triggering qualification recalculation for user {$user->id}"); $this->calcQualPP(); // Berechne fehlende Level-Qualifikationsdaten } return; // Bereits berechnete Daten verwenden } } else { \Log::debug("BusinessUserItem: Force live calculation for user {$user->id} ({$this->date->month}/{$this->date->year})"); } // Erstelle neuen User und führe Live-Berechnung durch $this->initializeFromUserModel($user); // WICHTIG: Bei Live-Berechnung auch Level-Qualifikationsdaten berechnen // (nicht bei forceLiveCalculation=false, da dort gespeicherte Daten bevorzugt werden) if ($forceLiveCalculation) { $this->calcQualPP(); } } catch (\Exception $e) { \Log::error("BusinessUserItem: Error creating user from model {$user->id}: " . $e->getMessage()); throw $e; } } /** * Initialisiert BusinessUser aus User-Model (gemeinsame Logik) */ private function initializeFromUserModel(User $user): void { // Nutze geladene Relations wenn verfügbar $user_level_active = null; if ($user->relationLoaded('user_level')) { $user_level_active = $user->user_level; } else { $user_level_active = $user->user_level; // Fallback auf Original-Relation } $this->user_level_active_pos = $user_level_active ? $user_level_active->pos : 0; // Neues UserBusiness Objekt erstellen $this->b_user = new UserBusiness(); // Account-Daten (mit intelligentem Laden und Error-Handling) $account = $this->getAccountForUser($user); $fill = [ 'user_id' => $user->id, 'month' => $this->date->month, 'year' => $this->date->year, 'm_level_id' => $user->m_level, 'user_level_name' => $user_level_active ? $user_level_active->name : '', 'active_account' => $this->calculateActiveAccount($user), 'payment_account_date' => $user->payment_account ? $user->getPaymentAccountDateFormat(false) : null, 'active_date' => $user->active_date, // Account-Daten mit korrekten Fallback-Werten 'm_account' => $account ? ($account->m_account ?? null) : null, 'email' => $user->email, 'first_name' => $account ? ($account->first_name ?? '') : '', 'last_name' => $account ? ($account->last_name ?? '') : '', 'user_birthday' => $account ? $account->birthday : null, 'user_phone' => $account ? ($account->getPhoneNumber() ?? '') : '', // Sales Volume (mit Caching falls möglich) 'sales_volume_KP_points' => $this->getUserSalesVolumeOptimized($user, 'sales_volume_KP_points'), 'sales_volume_TP_points' => $this->getUserSalesVolumeOptimized($user, 'sales_volume_TP_points'), 'sales_volume_points_shop' => $this->getUserSalesVolumeOptimized($user, 'sales_volume_points_shop'), 'sales_volume_points_KP_sum' => $this->getUserSalesVolumeOptimized($user, 'sales_volume_points_KP_sum'), 'sales_volume_points_TP_sum' => $this->getUserSalesVolumeOptimized($user, 'sales_volume_points_TP_sum'), 'sales_volume_total' => $this->getUserSalesVolumeOptimized($user, 'sales_volume_total'), 'sales_volume_total_shop' => $this->getUserSalesVolumeOptimized($user, 'sales_volume_total_shop'), 'sales_volume_total_sum' => $this->getUserSalesVolumeOptimized($user, 'sales_volume_total_sum'), // Level-Daten mit Boundary-Checks 'margin' => $user_level_active ? max(0, $user_level_active->margin) : 0, 'margin_shop' => $user_level_active ? max(0, $user_level_active->margin_shop) : 0, 'qual_kp' => $user_level_active ? max(0, $user_level_active->qual_kp) : 0, 'qual_pp' => $user_level_active ? max(0, $user_level_active->qual_pp) : 0, // Initialisierung 'payline_points' => 0, 'commission_pp_total' => 0, 'commission_shop_sales' => 0, 'commission_growth_total' => 0, 'version' => 2, ]; $this->b_user->fill($fill); $this->b_user->business_lines = []; $this->b_user->user_items = []; // Shop-Provision berechnen (mit verbessertem Logging) $shopVolume = (float) $this->b_user->sales_volume_total_shop; $shopMargin = (float) $this->b_user->margin_shop; $calculatedCommission = round($shopVolume / 100 * $shopMargin, 2); $this->b_user->commission_shop_sales = $calculatedCommission; \Log::debug("BusinessUserItem: Created optimized user {$user->id} for {$this->date->month}/{$this->date->year} - Shop commission: {$calculatedCommission} (Volume: {$shopVolume}, Margin: {$shopMargin}%)"); } /** * Ergänzt gespeicherte UserBusiness-Daten mit aktuellen User-Grunddaten * Erweitert um Level-Qualifikationsdaten-Validierung für Struktur-Ansicht */ private function enrichStoredDataWithUserModel(User $user): void { try { $account = $this->getAccountForUser($user); // Ergänze fehlende User-Grunddaten in gespeicherten UserBusiness-Daten $this->b_user->user_id = $user->id; $this->b_user->email = $user->email; $this->b_user->first_name = $account ? ($account->first_name ?? '') : ''; $this->b_user->last_name = $account ? ($account->last_name ?? '') : ''; $this->b_user->user_birthday = $account ? $account->birthday : null; $this->b_user->user_phone = $account ? ($account->getPhoneNumber() ?? '') : ''; $this->b_user->m_account = $account ? ($account->m_account ?? null) : null; // Berechne aktiven Account-Status $this->b_user->active_account = $this->calculateActiveAccount($user); $this->b_user->payment_account_date = $user->payment_account; // User-Level Informationen $user_level_active = $user->user_level; if ($user_level_active) { $this->b_user->user_level_name = $user_level_active->name; $this->user_level_active_pos = $user_level_active->pos; } // WICHTIG: Validiere Level-Qualifikationsdaten für Struktur-Ansicht $this->validateLevelQualificationData(); // Prüfe ob Sales Volume Felder aktualisiert werden müssen $this->updateSalesVolumeFields($user); \Log::debug("BusinessUserItem: Enriched stored data for user {$user->id} with current user model data"); } catch (\Exception $e) { \Log::error("BusinessUserItem: Error enriching stored data for user {$user->id}: " . $e->getMessage()); } } /** * Aktualisiert Sales Volume und Commission Felder bei gespeicherten Daten */ private function updateSalesVolumeFields(User $user): void { try { // Prüfe ob Sales Volume Felder leer sind $fieldsToUpdate = [ 'sales_volume_KP_points', 'sales_volume_TP_points', 'sales_volume_points_shop', 'sales_volume_points_KP_sum', 'sales_volume_points_TP_sum', 'sales_volume_total', 'sales_volume_total_shop', 'sales_volume_total_sum' ]; $needsUpdate = false; foreach ($fieldsToUpdate as $field) { if (!isset($this->b_user->{$field}) || $this->b_user->{$field} === null || $this->b_user->{$field} === 0) { $newValue = $this->getUserSalesVolumeOptimized($user, $field); $this->b_user->{$field} = $newValue; if ($newValue > 0) { $needsUpdate = true; \Log::debug("BusinessUserItem: Updated {$field} for user {$user->id}: {$newValue}"); } } } // Aktualisiere Shop Commission falls nötig if (!isset($this->b_user->commission_shop_sales) || $this->b_user->commission_shop_sales === 0) { $shopVolume = (float) $this->b_user->sales_volume_total_shop; $shopMargin = (float) $this->b_user->margin_shop; if ($shopVolume > 0 && $shopMargin > 0) { $calculatedCommission = round($shopVolume / 100 * $shopMargin, 2); $this->b_user->commission_shop_sales = $calculatedCommission; $needsUpdate = true; \Log::debug("BusinessUserItem: Updated commission_shop_sales for user {$user->id}: {$calculatedCommission}"); } } if ($needsUpdate) { \Log::info("BusinessUserItem: Updated sales volume fields for user {$user->id}"); } } catch (\Exception $e) { \Log::error("BusinessUserItem: Error updating sales volume fields for user {$user->id}: " . $e->getMessage()); } } /** * Validiert und aktualisiert Level-Qualifikationsdaten wenn nötig * Stellt sicher, dass next_qual_user_level und next_can_user_level für Struktur-Ansicht verfügbar sind */ private function validateLevelQualificationData(): void { try { // Prüfe ob Level-Qualifikationsdaten vorhanden sind $hasNextQual = !empty($this->b_user->next_qual_user_level); $hasNextCan = !empty($this->b_user->next_can_user_level); $hasQualUserLevel = !empty($this->b_user->qual_user_level); // Wenn Level-Qualifikationsdaten fehlen, führe Neuberechnung durch if (!$hasNextQual && !$hasNextCan && !$hasQualUserLevel) { \Log::debug("BusinessUserItem: Level qualification data missing for user {$this->b_user->user_id}, triggering recalculation"); // Setze Flag für notwendige Neuberechnung $this->needsQualificationRecalculation = true; } } catch (\Exception $e) { \Log::warning("BusinessUserItem: Error validating level qualification data for user {$this->b_user->user_id}: " . $e->getMessage()); } } /** * Berechnet ob Account aktiv ist (mit Error-Handling) */ private function calculateActiveAccount(User $user): bool { try { if (!$user->payment_account) { return false; } // Verwende aktuelles Datum, nicht das Berechnungs-Startdatum return Carbon::parse($user->payment_account)->gt(Carbon::now()); } catch (\Exception $e) { \Log::warning("BusinessUserItem: Error calculating active account for user {$user->id}: " . $e->getMessage()); return false; } } /** * Optimierte Sales Volume Abfrage mit detailliertem Logging */ private function getUserSalesVolumeOptimized(User $user, string $field) { try { // Direkter Aufruf mit detailliertem Logging $value = $user->getUserSalesVolumeBy($this->date->month, $this->date->year, $field); // Log nur bei ersten Aufruf für diesen User (Performance) static $loggedUsers = []; if (!isset($loggedUsers[$user->id])) { $loggedUsers[$user->id] = true; // Prüfe ob UserSalesVolume Daten existieren $userSalesVolume = $user->getUserSalesVolume($this->date->month, $this->date->year, 'first'); if (!$userSalesVolume) { \Log::info("BusinessUserItem: No UserSalesVolume found for user {$user->id} in {$this->date->month}/{$this->date->year}"); // Prüfe neueste verfügbare Daten $latestVolume = \App\Models\UserSalesVolume::where('user_id', $user->id) ->orderBy('year', 'desc') ->orderBy('month', 'desc') ->first(); if ($latestVolume) { \Log::info("BusinessUserItem: Latest UserSalesVolume for user {$user->id}: {$latestVolume->month}/{$latestVolume->year}"); } else { \Log::warning("BusinessUserItem: No UserSalesVolume records found for user {$user->id} at all"); } } else { \Log::debug("BusinessUserItem: UserSalesVolume found for user {$user->id} in {$this->date->month}/{$this->date->year}"); } } return $value; } catch (\Exception $e) { \Log::error("BusinessUserItem: Error getting sales volume {$field} for user {$user->id}: " . $e->getMessage()); return 0; // Sicherer Fallback } } // ===== ORIGINALE METHODEN (unverändert für Kompatibilität) ===== public function getSalesVolumeTotalMargin() { return $this->b_user->getSalesVolumeTotalMargin(); } public function addUserID() { if ($this->treeCalcBot) { $this->treeCalcBot->addProcessedUserId($this->b_user->user_id); } else { // Fallback für Rückwärtskompatibilität - sollte in Logs sichtbar sein \Log::warning("BusinessUserItemOptimized: TreeCalcBotOptimized Referenz fehlt für User ID: " . $this->b_user->user_id); } } public function getBUser() { return $this->b_user; } public function addBusinessLineToUser($line, $obj) { $this->b_user->business_lines[$line] = $obj; } public function addBusinessLinePoints($line, $points) { if (!isset($this->b_user->business_lines[$line])) { \Log::warning("BusinessUserItem: Trying to add points to non-existent line {$line}"); return; } $obj = $this->b_user->business_lines[$line]; // Handle both array and object types (JSON deserialization inconsistency) if (is_array($obj)) { $obj['points'] = ($obj['points'] ?? 0) + (float) $points; } else { // Ensure it's an object if (!is_object($obj)) { $obj = (object) $obj; } $obj->points = ($obj->points ?? 0) + (float) $points; } $this->b_user->business_lines[$line] = $obj; } public function addTotalTP($points) { $this->b_user->total_pp += (float) $points; // Type-Safety } public function isQualKP(): bool { return ($this->b_user->sales_volume_points_KP_sum >= $this->b_user->qual_kp); } public function isQualLevel(): bool { return !empty($this->b_user->qual_user_level); } public function isQualEqualLevel(): bool { if (!$this->b_user->qual_user_level) { return false; } return ($this->b_user->m_level_id == $this->b_user->qual_user_level['id']); } public function getQualPaylines(): int { if (!$this->b_user->qual_user_level) { return 0; } return (int) $this->b_user->qual_user_level['paylines']; } public function getRestQualKP(): float { $ret = $this->b_user->sales_volume_points_KP_sum - $this->b_user->qual_kp; return max(0, $ret); // Boundary-Check } public function getCommissionTotal(): float { return round( $this->b_user->commission_shop_sales + $this->b_user->commission_pp_total + $this->b_user->commission_growth_total, 2 ); } // ===== PROVISIONSBERECHNUNG (Original-Logik) ===== public function calcQualPP($force = false): void { try { $qualUserLevel = $this->calcuQualLevel(); if ($qualUserLevel !== null) { //das erreichte level setzen $this->b_user->qual_user_level = $qualUserLevel->toArray(); //next_qual_user_level nächster qualifizierten level $this->setNextUserLevel($force); //qual_user_level_next nächste Provisions-Stufe, $this->setQualNextLevel($force); //provisionen berechnen $this->calculateCommissions($qualUserLevel); } else { $this->setFirstQualLevel(); } } catch (\Exception $e) { \Log::error("BusinessUserItem: Error calculating qualifications for user {$this->b_user->user_id}: " . $e->getMessage()); } } /** * Berechnet Provisionen mit Error-Handling * Erweitert um Array/Object-Kompatibilität für business_lines */ private function calculateCommissions($qualUserLevel): void { $commission_pp_total = 0; $commission_growth_total = 0; // Payline-Provisionen for ($i = 1; $i <= $qualUserLevel->paylines; $i++) { if (isset($this->b_user->business_lines[$i])) { $object = $this->b_user->business_lines[$i]; $margin = (float) $this->b_user->qual_user_level['pr_line_' . $i]; // Handle both array and object types (JSON deserialization inconsistency) if (is_array($object)) { $points = (float) ($object['points'] ?? 0); $object['margin'] = $margin; $object['commission'] = round($points / 100 * $margin, 2); $object['payline'] = true; $commission_pp_total += $object['commission']; } else { $points = (float) ($object->points ?? 0); $object->margin = $margin; $object->commission = round($points / 100 * $margin, 2); $object->payline = true; $commission_pp_total += $object->commission; } $this->b_user->business_lines[$i] = $object; } } // Growth Bonus if (!empty($qualUserLevel->growth_bonus)) { $payline = (int) $this->b_user->qual_user_level['paylines'] + 1; $maxlines = count($this->b_user->business_lines) + 1; $growth_bonus = (float) $this->b_user->qual_user_level['growth_bonus']; for ($i = $payline; $i <= $maxlines; $i++) { if (isset($this->b_user->business_lines[$i])) { $object = $this->b_user->business_lines[$i]; // Handle both array and object types (JSON deserialization inconsistency) if (is_array($object)) { $points = (float) ($object['points'] ?? 0); $object['margin'] = $growth_bonus; $object['commission'] = round($points / 100 * $growth_bonus, 2); $object['growth_bonus'] = true; $commission_growth_total += $object['commission']; } else { $points = (float) ($object->points ?? 0); $object->margin = $growth_bonus; $object->commission = round($points / 100 * $growth_bonus, 2); $object->growth_bonus = true; $commission_growth_total += $object->commission; } $this->b_user->business_lines[$i] = $object; } } } $this->b_user->commission_pp_total = $commission_pp_total; $this->b_user->commission_growth_total = $commission_growth_total; } // ===== WEITERE ORIGINAL-METHODEN (gekürzt, vollständige Implementation in Original) ===== //aktuelles level berechnen, max das eigene level, wenn weniger Points dann darunter public function calcuQualLevel() { $qualUserLevels = UserLevel::where('qual_kp', '<=', $this->b_user->sales_volume_points_KP_sum) ->where('pos', '<=', $this->user_level_active_pos) ->orderBy('qual_pp', 'desc') ->get(); foreach ($qualUserLevels as $qualUserLevel) { $payline_points = $this->getPointsforPayline($qualUserLevel->paylines); $payline_points_qual_kp = $payline_points + $this->getRestQualKP(); if ($payline_points_qual_kp >= $qualUserLevel->qual_pp) { $this->b_user->payline_points = $payline_points; $this->b_user->payline_points_qual_kp = $payline_points_qual_kp; return $qualUserLevel; } } return null; } private function getPointsforPayline($paylines): float { $payline_points = 0; for ($i = 1; $i <= $paylines; $i++) { if (isset($this->b_user->business_lines[$i])) { $line = $this->b_user->business_lines[$i]; // Handle both array and object types (JSON deserialization inconsistency) if (is_array($line)) { $payline_points += (float) ($line['points'] ?? 0); } else { $payline_points += (float) ($line->points ?? 0); } } } return $payline_points; } /** * Setzt das nächste Provision-Level * Wenn das aktuelle Level nicht erreicht ist, dann wird bei aktuelle Provisions-Stufe die erreichte level angezeigt und berechnet * Zur Info wird das nächste level angezeigt, der folgt, sonst leer */ private function setQualNextLevel($force = false): void { //ist der level nicht das aktuelle level, dann sucht es den nächsten level //isQualEqualLevel wenn das erreichte level das akutelle user level ist. if (!$this->isQualEqualLevel() && $this->b_user->qual_user_level['next_id'] != null) { $qualUserLevelNext = UserLevel::where('id', '=', $this->b_user->qual_user_level['next_id']) ->orderBy('qual_pp', 'asc') ->first(); if ($qualUserLevelNext) { $this->b_user->qual_user_level_next = $qualUserLevelNext->toArray(); } else { $this->b_user->qual_user_level_next = null; } } else { $this->b_user->qual_user_level_next = null; } } private function setNextUserLevel($force = false): void { //sucht den nächsten level, der mehr points hat als das aktuelle level $nextQualUserLevel = UserLevel::where('qual_pp', '<=', $this->b_user->payline_points_qual_kp) ->where('pos', '>', $this->user_level_active_pos) ->orderBy('qual_pp', 'desc') ->first(); //wenn der nächste level qualifiziert ist und die KP-Qualifikation erfüllt ist, dann setzt es den nächsten level if ($nextQualUserLevel && $this->isQualKP()) { $this->b_user->next_qual_user_level = $nextQualUserLevel->toArray(); $this->b_user->next_can_user_level = null; } else { //wenn der nächste level nicht qualifiziert ist, dann sucht es den nächsten level, nach pos $nextCanUserLevel = UserLevel::where('pos', '>', $this->user_level_active_pos) ->orderBy('qual_pp', 'asc') ->first(); if ($nextCanUserLevel) { $this->b_user->next_can_user_level = $nextCanUserLevel->toArray(); } $this->b_user->next_qual_user_level = null; } } private function setFirstQualLevel(): void { $qualUserLevelNext = UserLevel::where('pos', '=', 1) ->orderBy('qual_pp', 'asc') ->first(); if ($qualUserLevelNext) { $this->b_user->qual_user_level_next = $qualUserLevelNext->toArray(); } } // Magic Methods für Property-Zugriff (Rückwärtskompatibilität) public function __get($name) { if (isset($this->b_user->$name)) { return $this->b_user->$name; } // Legacy-Properties $legacyMap = [ 'sales_volume_points_KP_sum' => 'sales_volume_points_KP_sum', 'sales_volume_points_TP_sum' => 'sales_volume_points_TP_sum', 'business_lines' => 'business_lines', 'user_id' => 'user_id' ]; if (isset($legacyMap[$name]) && isset($this->b_user->{$legacyMap[$name]})) { return $this->b_user->{$legacyMap[$name]}; } return null; } /** * Prüft und setzt Sponsor-Informationen (Original-Implementation) */ public function checkSponsor($user): void { try { // Check if already stored if ($this->isSave()) { return; } $sponsor = new stdClass(); $sponsor->is_sponsor = false; $sponsor->user_id = false; $sponsor->first_name = ''; $sponsor->last_name = ''; $sponsor->email = ''; $sponsor->m_account = ''; $sponsor->full_name = 'Keinen Sponsor zugewiesen'; if ($user->m_sponsor) { if ($user->user_sponsor) { $sponsor->is_sponsor = true; $sponsor->user_id = $user->user_sponsor->id; if ($user->user_sponsor->account) { $sponsor->full_name = substr( 'Sponsor: ' . $user->user_sponsor->account->first_name . ' ' . $user->user_sponsor->account->last_name . ' | ' . $user->user_sponsor->email . ' | ' . $user->user_sponsor->account->m_account, 0, 250 ); $sponsor->first_name = $user->user_sponsor->account->first_name; $sponsor->last_name = $user->user_sponsor->account->last_name; $sponsor->m_account = $user->user_sponsor->account->m_account; } else { $sponsor->full_name = 'Sponsor: ' . $user->user_sponsor->email; } $sponsor->email = $user->user_sponsor->email; } else { $sponsor->full_name = 'Sponsor wurde gelöscht.'; } } $this->b_user->sponsor = $sponsor; } catch (\Exception $e) { Log::error("BusinessUserItem: Error checking sponsor for user {$user->id}: " . $e->getMessage()); } } /** * Lädt Parent Business Users rekursiv (Original-Implementation mit Optimierungen) * BUGFIX: Schutz vor unendlicher Rekursion durch zirkuläre Referenzen */ public function readParentsBusinessUsers($forceLiveCalculation = false, $depth = 0): void { // Schutz vor zu tiefer Rekursion (maximale Tiefe: 20 Levels) $maxDepth = 20; if ($depth > $maxDepth) { Log::warning("BusinessUserItem: Maximale Rekursionstiefe ({$maxDepth}) erreicht für User {$this->b_user->user_id}"); return; } try { // Optimiert: Lade mit Relations $users = User::with(['account']) ->select('users.*') ->where('users.deleted_at', '=', null) ->where('users.id', '!=', 1) ->where('users.admin', '<', 4) ->where('users.m_level', '!=', null) ->where('users.m_sponsor', '=', $this->b_user->user_id) ->where('users.payment_account', '!=', null) ->where('users.active_date', '<=', $this->date->end_date) ->get(); if ($users->isNotEmpty()) { foreach ($users as $user) { // KRITISCHER BUGFIX: Prüfe ob User bereits verarbeitet wurde if ($this->treeCalcBot && $this->treeCalcBot->isUserProcessed($user->id)) { Log::debug("BusinessUserItem: Überspringe bereits verarbeiteten User {$user->id} (zirkuläre Referenz verhindert)"); continue; } $businessUserItem = new BusinessUserItemOptimized($this->date, $this->treeCalcBot); $businessUserItem->makeUserFromModel($user, $forceLiveCalculation); $businessUserItem->addUserID(); $this->businessUserItems[] = $businessUserItem; } } // Rekursiver Aufruf für alle Child-Items mit Tiefenprüfung foreach ($this->businessUserItems as $businessUserItem) { $businessUserItem->readParentsBusinessUsers($forceLiveCalculation, $depth + 1); } } catch (\Exception $e) { Log::error("BusinessUserItem: Error reading parent users for {$this->b_user->user_id} at depth {$depth}: " . $e->getMessage()); } } /** * Lädt Parent Business Users aus gespeicherter Struktur (Original-Implementation) * BUGFIX: Schutz vor unendlicher Rekursion durch zirkuläre Referenzen */ public function readStoredParentsBusinessUsers($structure, $depth = 0): void { // Schutz vor zu tiefer Rekursion (maximale Tiefe: 50 Levels) $maxDepth = 50; if ($depth > $maxDepth) { Log::warning("BusinessUserItem: Maximale Rekursionstiefe ({$maxDepth}) erreicht für gespeicherte User {$this->b_user->user_id}"); return; } try { $parents = $this->findParentsBusinessOnStored($this->b_user->user_id, $structure); if ($parents) { foreach ($parents as $obj) { // KRITISCHER BUGFIX: Prüfe ob User bereits verarbeitet wurde if ($this->treeCalcBot && $this->treeCalcBot->isUserProcessed($obj->user_id)) { Log::debug("BusinessUserItem: Überspringe bereits verarbeiteten gespeicherten User {$obj->user_id} (zirkuläre Referenz verhindert)"); continue; } $businessUserItem = new BusinessUserItemOptimized($this->date, $this->treeCalcBot); $businessUserItem->makeUser($obj->user_id); $businessUserItem->addUserID(); $this->businessUserItems[] = $businessUserItem; } foreach ($this->businessUserItems as $businessUserItem) { $businessUserItem->readStoredParentsBusinessUsers($parents, $depth + 1); } } } catch (\Exception $e) { Log::error("BusinessUserItem: Error reading stored parent users at depth {$depth}: " . $e->getMessage()); } } /** * Findet Parent Business Items in gespeicherter Struktur (Original-Implementation) */ private function findParentsBusinessOnStored($user_id, $structures) { if (!$structures) { return null; } foreach ($structures as $obj) { if ($user_id === $obj->user_id) { return $obj->parents ?? null; } if (!empty($obj->parents)) { $result = $this->findParentsBusinessOnStored($user_id, $obj->parents); if ($result) { return $result; } } } return null; } /** * Prüft ob User bereits gespeichert ist * Konsistent zur ursprünglichen BusinessUserItem Implementation */ public function isSave(): bool { return $this->b_user && $this->b_user->isSave(); } /** * Gibt die Anzahl der qualifizierten Paylines zurück */ public function getQualLevelPaylines() { if ($this->b_user && isset($this->b_user->qual_user_level) && $this->b_user->qual_user_level) { return $this->b_user->qual_user_level['paylines'] ?? 0; } return 0; } /** * Prüft ob eine Line für Growth-Bonus qualifiziert ist */ public function isQualLevelGrowth($line) { if ($this->b_user && isset($this->b_user->business_lines[$line])) { $object = $this->b_user->business_lines[$line]; if (isset($object->growth_bonus)) { return $object->growth_bonus > 0; } } return false; } /** * Intelligentes Laden des UserAccount für einen User * Prüft zuerst geladene Relations, lädt nach wenn nötig */ private function getAccountForUser(User $user): ?UserAccount { try { // Prüfe ob Account-Relation bereits geladen ist if ($user->relationLoaded('account')) { $account = $user->account; if ($account instanceof UserAccount) { \Log::debug("BusinessUserItem: Using pre-loaded account for user {$user->id}"); return $account; } } // Wenn User keine account_id hat, gibt es definitiv kein Account if (!$user->account_id) { \Log::info("BusinessUserItem: User {$user->id} has no account_id - no account available"); return null; } // Account nachladen falls nötig \Log::info("BusinessUserItem: Loading account for user {$user->id} (account_id: {$user->account_id})"); $account = UserAccount::find($user->account_id); if (!$account) { \Log::warning("BusinessUserItem: Account {$user->account_id} not found for user {$user->id}"); return null; } \Log::debug("BusinessUserItem: Successfully loaded account {$account->id} for user {$user->id}"); return $account; } catch (\Exception $e) { \Log::error("BusinessUserItem: Error loading account for user {$user->id}: " . $e->getMessage()); return null; } } }