orderBy('pos')->get()->keyBy('id'); // Query UserBusiness Einträge mit Level-Aufstiegen $query = UserBusiness::whereNotNull('next_qual_user_level') ->whereRaw("JSON_LENGTH(next_qual_user_level) > 0") ->orderBy('year', 'desc') ->orderBy('month', 'desc') ->orderBy('user_id'); // Filter anwenden if ($month) { $query->where('month', $month); } if ($year) { $query->where('year', $year); } if ($userId) { $query->where('user_id', $userId); } $userBusinesses = $query->get(); return $this->processLevelPromotions($userBusinesses, $userLevels, $onlyNotUpdated); } public function processLevelPromotions($userBusinesses, $userLevels, $onlyNotUpdated = false): Collection { $promotions = []; // Lade User-Daten für alle Level-Vergleiche $userIds = $userBusinesses->pluck('user_id')->unique(); $users = User::whereIn('id', $userIds)->get(['id', 'm_level']); $currentUserLevels = $users->keyBy('id'); foreach ($userBusinesses as $userBusiness) { $nextQualUserLevel = $userBusiness->next_qual_user_level; if (is_array($nextQualUserLevel) && !empty($nextQualUserLevel)) { // next_qual_user_level kann sowohl ein einzelnes Level-Objekt als auch ein Array von Level-Objekten sein $levelArray = isset($nextQualUserLevel['id']) ? [$nextQualUserLevel] : $nextQualUserLevel; foreach ($levelArray as $newLevelData) { // Überprüfe ob es ein vollständiges Level-Objekt ist if (is_array($newLevelData) && isset($newLevelData['id'])) { $currentLevel = $userLevels->get($userBusiness->m_level_id); $newLevelId = $newLevelData['id']; // Lade aktuellen User Level $currentUser = $currentUserLevels->get($userBusiness->user_id); $currentUserLevelName = 'Unbekannt'; $currentUserLevel = null; if ($currentUser && $currentUser->m_level) { $currentUserLevel = $userLevels->get($currentUser->m_level); $currentUserLevelName = $currentUserLevel ? $currentUserLevel->name : 'Level ID: ' . $currentUser->m_level; } // Lade neues Level für POS-Vergleich (wird für Filter und level_updated benötigt) $newLevel = $userLevels->get($newLevelId); $newLevelPos = $newLevelData['pos'] ?? ($newLevel ? $newLevel->pos : 0); // Filter: Nur User die noch nicht auf das neue Level umgestellt wurden if ($onlyNotUpdated) { // Skip wenn: // 1. User existiert nicht oder hat kein Level // 2. User ist bereits auf dem neuen Level (gleiche ID) // 3. User hat bereits ein höheres oder gleichwertiges Level (POS >= neue Level POS) if ( !$currentUser || $currentUser->m_level == $newLevelId || ($currentUserLevel && $currentUserLevel->pos >= $newLevelPos) ) { continue; // Skip - User ist bereits auf das neue Level umgestellt oder hat bereits ein höheres Level } } $promotions[] = [ 'user_id' => $userBusiness->user_id, 'email' => $userBusiness->email, 'first_name' => $userBusiness->first_name, 'last_name' => $userBusiness->last_name, 'month' => $userBusiness->month, 'year' => $userBusiness->year, 'date' => sprintf('%04d-%02d', $userBusiness->year, $userBusiness->month), 'from_level_id' => $userBusiness->m_level_id, 'from_level_name' => $currentLevel ? $currentLevel->name : 'Unbekannt', 'to_level_id' => $newLevelId, 'to_level_name' => $newLevelData['name'] ?? 'Unbekannt', 'to_level_margin' => $newLevelData['margin'] ?? 0, 'to_level_margin_shop' => $newLevelData['margin_shop'] ?? 0, 'to_level_qual_kp' => $newLevelData['qual_kp'] ?? 0, 'to_level_qual_pp' => $newLevelData['qual_pp'] ?? 0, 'to_level_growth_bonus' => $newLevelData['growth_bonus'] ?? 0, 'to_level_pos' => $newLevelData['pos'] ?? 0, 'current_user_level_id' => $currentUser ? $currentUser->m_level : null, 'current_user_level_name' => $currentUserLevelName, 'level_updated' => $onlyNotUpdated ? 'Nein' : ($currentUser && ($currentUser->m_level == $newLevelId || ($currentUserLevel && $currentUserLevel->pos >= $newLevelPos)) ? 'Ja' : 'Nein'), 'total_pp' => $userBusiness->total_pp ?? 0, 'total_qual_pp' => $userBusiness->total_qual_pp ?? 0, 'payline_points_qual_kp' => $userBusiness->payline_points_qual_kp ?? 0, 'sales_volume_points_sum' => $userBusiness->sales_volume_points_KP_sum ?? 0, 'active_account' => $userBusiness->active_account ? 'Ja' : 'Nein', ]; } } } } // Sortiere nach Datum (neueste zuerst) und dann nach User ID usort($promotions, function ($a, $b) { if ($a['year'] !== $b['year']) { return $b['year'] - $a['year']; } if ($a['month'] !== $b['month']) { return $b['month'] - $a['month']; } return $a['user_id'] - $b['user_id']; }); return collect($promotions); } public function getStatistics(Collection $promotions): array { $stats = [ 'total_count' => $promotions->count(), 'level_stats' => [], 'period_stats' => [] ]; // Statistik nach Level foreach ($promotions as $promotion) { $levelName = $promotion['to_level_name']; if (!isset($stats['level_stats'][$levelName])) { $stats['level_stats'][$levelName] = 0; } $stats['level_stats'][$levelName]++; } // Statistik nach Monat/Jahr foreach ($promotions as $promotion) { $period = $promotion['date']; if (!isset($stats['period_stats'][$period])) { $stats['period_stats'][$period] = 0; } $stats['period_stats'][$period]++; } return $stats; } public function exportToCsv(Collection $promotions, ?string $filename = null): string { if (!$filename) { $filename = 'level_promotions_' . date('Y-m-d_H-i-s') . '.csv'; } $filepath = storage_path('app/reports/' . $filename); // Erstelle Verzeichnis falls nicht vorhanden if (!file_exists(dirname($filepath))) { mkdir(dirname($filepath), 0755, true); } $file = fopen($filepath, 'w'); // UTF-8 BOM für korrekte Darstellung in Excel fwrite($file, "\xEF\xBB\xBF"); // CSV Headers $headers = [ 'Datum', 'User ID', 'Vorname', 'Nachname', 'E-Mail', 'Von Level ID', 'Von Level Name', 'Zu Level ID', 'Zu Level Name', 'Zu Level KP Anforderung', 'Zu Level PP Anforderung', 'User PP Total', 'User PP Qualifiziert', 'Aktueller User Level ID', 'Aktueller User Level Name', 'Level bereits geupdatet', 'Aktiver Account', 'Monat', 'Jahr' ]; fputcsv($file, $headers, ';'); // Daten schreiben foreach ($promotions as $promotion) { $row = [ $promotion['date'], $promotion['user_id'], $promotion['first_name'], $promotion['last_name'], $promotion['email'], $promotion['from_level_id'], $promotion['from_level_name'], $promotion['to_level_id'], $promotion['to_level_name'], $promotion['to_level_qual_kp'], $promotion['to_level_qual_pp'], $promotion['sales_volume_points_sum'], $promotion['payline_points_qual_kp'], $promotion['current_user_level_id'] ?? 'N/A', $promotion['current_user_level_name'], $promotion['level_updated'], $promotion['active_account'], $promotion['month'], $promotion['year'] ]; fputcsv($file, $row, ';'); } fclose($file); return $filepath; } /** * Holt Level-Aufstiege für ein Team basierend auf TreeCalcBotOptimized * Nur für einen spezifischen Monat/Jahr und nur für Team-Mitglieder */ public function getTeamLevelPromotions(TreeCalcBotOptimized $treeCalcBot, int $month, int $year, array $filters = []): Collection { $onlyNotUpdated = $filters['only_not_updated'] ?? false; // Lade UserLevels für Referenz $userLevels = UserLevel::where('active', 1)->orderBy('pos')->get()->keyBy('id'); // Extrahiere alle User-IDs aus dem Team $teamUserIds = $this->extractTeamUserIds($treeCalcBot); if (empty($teamUserIds)) { return collect([]); } // Lade UserBusiness Einträge für Team-Mitglieder mit Level-Aufstiegen $userBusinesses = UserBusiness::whereIn('user_id', $teamUserIds) ->where('month', $month) ->where('year', $year) ->whereNotNull('next_qual_user_level') ->whereRaw("JSON_LENGTH(next_qual_user_level) > 0") ->orderBy('user_id') ->get(); return $this->processLevelPromotions($userBusinesses, $userLevels, $onlyNotUpdated); } /** * Extrahiert alle User-IDs aus TreeCalcBotOptimized-Struktur * Ähnlich wie getTeamUsersFromStructure im TeamController, aber nur IDs */ private function extractTeamUserIds(TreeCalcBotOptimized $treeCalcBot): array { $userIds = []; $processedIds = []; // Sammle User-IDs aus Root-Items $businessUsers = $treeCalcBot->getItems(); foreach ($businessUsers as $businessUser) { $userId = $businessUser->user_id ?? null; if ($userId && !isset($processedIds[$userId])) { $processedIds[$userId] = true; $userIds[] = $userId; $this->collectUserIdsRecursive($businessUser->businessUserItems ?? [], $userIds, $processedIds); } } // Sammle parentless User-IDs if ($treeCalcBot->isParentless()) { $parentless = $treeCalcBot->__get('parentless'); if (is_array($parentless)) { foreach ($parentless as $businessUser) { if ($businessUser) { $userId = $businessUser->user_id ?? null; if ($userId && !isset($processedIds[$userId])) { $processedIds[$userId] = true; $userIds[] = $userId; $this->collectUserIdsRecursive($businessUser->businessUserItems ?? [], $userIds, $processedIds); } } } } } return $userIds; } /** * Sammelt rekursiv User-IDs aus businessUserItems */ private function collectUserIdsRecursive(array $businessUserItems, array &$userIds, array &$processedIds, int $depth = 0): void { $maxDepth = 20; if ($depth > $maxDepth) { return; } foreach ($businessUserItems as $businessUserItem) { if ($businessUserItem) { $userId = $businessUserItem->user_id ?? null; if ($userId && !isset($processedIds[$userId])) { $processedIds[$userId] = true; $userIds[] = $userId; // Rekursiv für verschachtelte Items if (isset($businessUserItem->businessUserItems) && is_array($businessUserItem->businessUserItems)) { $this->collectUserIdsRecursive($businessUserItem->businessUserItems, $userIds, $processedIds, $depth + 1); } } } } } }