'', 2 => '', 3 => '']; // Wird in getFilterActive() übersetzt private $filter_next_level = [0 => '', 1 => '', 2 => '', 3 => '']; // Wird in getFilterNextLevel() übersetzt private $month; private $year; private $forceLiveCalculation; public function __construct() { $this->middleware('active.account'); } /** * Zeigt die Team-Übersicht mit optimierter TreeCalcBotOptimized-Datenverarbeitung * Lädt Team-Daten für DataTable-Anzeige */ public function show() { $startTime = microtime(true); $startMemory = memory_get_usage(); try { $this->setFilterVars(); $user = User::find(\Auth::user()->id); $this->month = session('team_user_filter_month'); $this->year = session('team_user_filter_year'); // Prüfe ob Live-Berechnung erzwungen werden soll $forceLiveCalculation = Request::get('force_live_calculation', false) || Request::get('live', false); $forceLiveCalculation = false; \Log::info("TeamController: Building optimized team overview for user {$user->id} ({$this->month}/{$this->year})". ($forceLiveCalculation === true ? ' with forced live calculation' : 'not live calculation')); // Verwende TreeCalcBotOptimized für bessere Performance // $TreeCalcBot = new TreeCalcBotOptimized($this->month, $this->year, 'member', $forceLiveCalculation); // $TreeCalcBot->initStructureUser($user->id); $endTime = microtime(true); $endMemory = memory_get_usage(); $executionTime = round(($endTime - $startTime) * 1000, 2); $memoryUsed = $this->formatBytes($endMemory - $startMemory); $calculationType = $forceLiveCalculation ? ' (LIVE)' : ' (CACHE)'; \Log::info("TeamController: Optimized team overview built in {$executionTime}ms, Memory: {$memoryUsed}{$calculationType}"); $data = [ 'filter_months' => HTMLHelper::getTransMonths(), 'filter_years' => HTMLHelper::getYearRange(2022), 'filter_active' => $this->getFilterActive(), 'filter_levels' => $this->getFilterLevels(), 'filter_next_level' => $this->getFilterNextLevel(), // 'TreeCalcBot' => $TreeCalcBot, 'performance' => [ 'execution_time' => $executionTime, 'memory_used' => $memoryUsed, 'user_id' => $user->id, 'user_count' => 0, // $TreeCalcBot->getTotalUserCount(), 'version' => 'Optimized', 'calculation_type' => $forceLiveCalculation ? 'Live' : 'Cache', ], 'optimized' => true, 'forceLiveCalculation' => $forceLiveCalculation, ]; return view('user.team.show', $data); } catch (\Exception $e) { \Log::error("TeamController: Error in optimized show for user {$user->id}: ".$e->getMessage()); // Fallback mit minimalen Daten $endTime = microtime(true); $executionTime = round(($endTime - $startTime) * 1000, 2); $data = [ 'filter_months' => HTMLHelper::getTransMonths(), 'filter_years' => HTMLHelper::getYearRange(2022), 'filter_active' => $this->getFilterActive(), 'filter_levels' => $this->getFilterLevels(), 'filter_next_level' => $this->getFilterNextLevel(), 'error' => __('team.error_loading_optimized_overview').$e->getMessage(), 'performance' => [ 'execution_time' => $executionTime, 'memory_used' => 'N/A', 'version' => 'Fallback', 'calculation_type' => 'Error', ], 'optimized' => false, ]; return view('user.team.show', $data); } } public function structure() { $startTime = microtime(true); $startMemory = memory_get_usage(); $user = User::find(\Auth::user()->id); if (config('app.debug')) { // $user = User::find(454); } $this->setFilterVars(); // Prüfe ob optimierte Version explizit angefordert wird $useOptimized = Request::get('use_optimized', true); // Prüfe ob Live-Berechnung erzwungen werden soll $forceLiveCalculation = Request::get('force_live_calculation', false) || Request::get('live', false); try { if ($useOptimized) { // Verwende User-spezifische optimierte Version $TreeCalcBot = new TreeCalcBotOptimized( session('team_user_filter_month'), session('team_user_filter_year'), 'member', $forceLiveCalculation ); $TreeCalcBot->initStructureUser($user->id, $forceLiveCalculation); $optimizedUsed = true; } else { // Standard TreeCalcBot mit Performance-Monitoring $TreeCalcBot = new TreeCalcBot( session('team_user_filter_month'), session('team_user_filter_year'), 'member' ); // Standard TreeCalcBot unterstützt forceLiveCalculation nicht $TreeCalcBot->initStructureUser($user->id); $optimizedUsed = false; } $endTime = microtime(true); $endMemory = memory_get_usage(); $executionTime = round(($endTime - $startTime) * 1000, 2); $memoryUsed = $this->formatBytes($endMemory - $startMemory); $versionInfo = ($optimizedUsed ? 'OPTIMIZED' : 'STANDARD'). ($forceLiveCalculation ? ' + LIVE' : ' + CACHE'); \Log::info("TeamController: Structure built for user {$user->id} in {$executionTime}ms, Memory: {$memoryUsed} ({$versionInfo})"); $data = [ 'filter_months' => HTMLHelper::getTransMonths(), 'filter_years' => HTMLHelper::getYearRange(2022), 'TreeCalcBot' => $TreeCalcBot, 'performance' => [ 'execution_time' => $executionTime, 'memory_used' => $memoryUsed, 'user_count' => $optimizedUsed && method_exists($TreeCalcBot, 'getTotalUserCount') ? $TreeCalcBot->getTotalUserCount() : '-', 'version' => $optimizedUsed ? 'Optimized' : 'Standard', 'calculation_type' => $forceLiveCalculation ? 'Live' : 'Cache', ], 'optimized' => $optimizedUsed, 'forceLiveCalculation' => $forceLiveCalculation, ]; return view('user.team.structure', $data); } catch (\Exception $e) { \Log::error("TeamController: Error in structure for user {$user->id}: ".$e->getMessage()); // Fallback zur Standard-Implementierung $TreeCalcBot = new TreeCalcBot(session('team_user_filter_month'), session('team_user_filter_year'), 'member'); $TreeCalcBot->initStructureUser($user->id); $endTime = microtime(true); $executionTime = round(($endTime - $startTime) * 1000, 2); $data = [ 'filter_months' => HTMLHelper::getTransMonths(), 'filter_years' => HTMLHelper::getYearRange(2022), 'TreeCalcBot' => $TreeCalcBot, 'error' => 'Fehler aufgetreten, Standard-Version wird verwendet: '.$e->getMessage(), 'performance' => [ 'execution_time' => $executionTime, 'memory_used' => 'N/A', 'user_count' => '-', 'version' => 'Fallback', 'calculation_type' => $forceLiveCalculation ? __('team.live_not_supported_fallback') : __('team.cache'), ], 'optimized' => false, 'forceLiveCalculation' => $forceLiveCalculation, ]; return view('user.team.structure', $data); } } public function structureOld() { abort(403, 'This page is removed'); $user = User::find(\Auth::user()->id); if (config('app.debug')) { $user = User::find(454); } $this->setFilterVars(); $TreeCalcBot = new TreeCalcBot(session('team_user_filter_month'), session('team_user_filter_year'), 'member'); $TreeCalcBot->initStructureUser($user->id); // for testing // $TreeCalcBot->initUser(56); $data = [ 'filter_months' => HTMLHelper::getTransMonths(), 'filter_years' => HTMLHelper::getYearRange(2022), 'TreeCalcBot' => $TreeCalcBot, ]; return view('user.team.structure', $data); } /** * Optimierte DataTable für Team-Übersicht mit TreeCalcBotOptimized-Daten * Nutzt bereits berechnete Business-Daten für bessere Performance */ public function datatableOptimized() { try { $startTime = microtime(true); $this->setFilterVars(); $user = User::find(\Auth::user()->id); $this->month = Request::get('team_user_filter_month') ?: session('team_user_filter_month'); $this->year = Request::get('team_user_filter_year') ?: session('team_user_filter_year'); // Prüfe ob Live-Berechnung erzwungen werden soll $forceLiveCalculation = Request::get('force_live_calculation', false) || Request::get('live', false); $forceLiveCalculation = false; \Log::info("TeamController: Building optimized datatable for user {$user->id} ({$this->month}/{$this->year})". ($forceLiveCalculation == true ? ' with forced live calculation' : '')); // Lade TreeCalcBotOptimized-Daten $TreeCalcBot = new TreeCalcBotOptimized($this->month, $this->year, 'member', $forceLiveCalculation); $TreeCalcBot->initStructureUser($user->id, $forceLiveCalculation); // Extrahiere alle User aus der Struktur $teamUsersRaw = $this->getTeamUsersFromStructure($TreeCalcBot); // KRITISCH: Bereinige die Objekte für DataTables (entferne zirkuläre Referenzen) $teamUsers = collect($this->cleanBusinessUserItemsForDataTable($teamUsersRaw)); \Log::info('TeamController: TeamUsers cleaned for DataTable: '.$teamUsers->count()); $endTime = microtime(true); $executionTime = round(($endTime - $startTime) * 1000, 2); $this->forceLiveCalculation = $forceLiveCalculation; \Log::info("TeamController: Optimized datatable data prepared in {$executionTime}ms for ".$teamUsers->count().' users'); return \DataTables::of($teamUsers) ->addColumn('id', function ($teamUser) { return ''; }) ->addColumn('m_account', function ($teamUser) { return $teamUser->m_account; }) ->addColumn('email', function ($teamUser) { $button = ''. ' '. ''; return $button.e($teamUser->email); }) ->addColumn('first_name', function ($teamUser) { return e($teamUser->first_name); }) ->addColumn('last_name', function ($teamUser) { return e($teamUser->last_name); }) ->addColumn('user_level', function ($teamUser) { return $teamUser->user_level_name ? TranslationHelper::transUserLevelName($teamUser->user_level_name) : ''; }) ->addColumn('is_qual_kp', function ($teamUser) { $user = User::find($teamUser->user_id); return TreeHelperOptimized::generateQualKPBadgeForUser($user, $this->month, $this->year); }) ->addColumn('sales_volume_KP_points', function ($teamUser) { return formatNumber($teamUser->sales_volume_points_KP_sum, 0); }) ->addColumn('sales_volume_total', function ($teamUser) { return formatNumber($teamUser->payline_points_qual_kp, 0); }) ->addColumn('next_level_qualified', function ($teamUser) { $userBusiness = UserBusiness::where('user_id', $teamUser->user_id) ->where('month', $this->month) ->where('year', $this->year) ->first(); if ($userBusiness) { return NextLevelBadgeHelper::generateBadgeFromUserBusiness($userBusiness); } return NextLevelBadgeHelper::renderNoDataBadge(); }) ->addColumn('active_account', function ($teamUser) { return get_active_badge($teamUser->active_account); }) ->addColumn('payment_account_date', function ($teamUser) { return $teamUser->active_date ? formatDate($teamUser->active_date) : '-'; }) ->rawColumns(['id', 'email', 'next_level_qualified', 'active_account', 'is_qual_kp', 'sales_volume_KP_points', 'sales_volume_total']) ->make(true); } catch (\Exception $e) { \Log::error('TeamController: Error in optimized datatable: '.$e->getMessage()); // Fallback zur Standard-DataTable return $this->datatable(); } } /** * Standard DataTable für Team-Übersicht (Fallback-Version) */ public function datatable() { try { $user = User::find(\Auth::user()->id); $query = $this->initTeamSearch($user); return \DataTables::eloquent($query) ->addColumn('id', function (User $teamUser) { return ''; }) ->addColumn('m_account', function (User $teamUser) { return $teamUser->account ? e($teamUser->account->m_account) : ''; }) ->addColumn('user_level', function (User $teamUser) { return $teamUser->user_level ? e($teamUser->user_level->getLang('name')) : ''; }) ->addColumn('is_qual_kp', function (User $teamUser) { if (! $teamUser->user_level) { return '-'; } $qualKP = (int) $teamUser->user_level->qual_kp; $month = Request::get('team_user_filter_month') ?: session('team_user_filter_month'); $year = Request::get('team_user_filter_year') ?: session('team_user_filter_year'); $pointsSum = (int) $teamUser->getUserSalesVolumeBy($month, $year, 'sales_volume_points_KP_sum'); $isQual = $pointsSum >= $qualKP; $badgeClass = $isQual ? 'badge-outline-success' : 'badge-outline-warning-dark'; return ' KU '.$qualKP.''; }) ->addColumn('sales_volume_KP_points', function (User $teamUser) { $month = Request::get('team_user_filter_month') ?: session('team_user_filter_month'); $year = Request::get('team_user_filter_year') ?: session('team_user_filter_year'); $total = (int) $teamUser->getUserSalesVolumeBy($month, $year, 'sales_volume_points_KP_sum'); $individual = (int) $teamUser->getUserSalesVolumeBy($month, $year, 'sales_volume_KP_points'); $shop = (int) $teamUser->getUserSalesVolumeBy($month, $year, 'sales_volume_points_shop'); return '
'.$total.'
'. 'E: '.$individual.' | S: '.$shop.''; }) ->addColumn('sales_volume_total', function (User $teamUser) { $month = Request::get('team_user_filter_month') ?: session('team_user_filter_month'); $year = Request::get('team_user_filter_year') ?: session('team_user_filter_year'); $total = (float) $teamUser->getUserSalesVolumeBy($month, $year, 'sales_volume_total_sum'); $individual = (float) $teamUser->getUserSalesVolumeBy($month, $year, 'sales_volume_total'); $shop = (float) $teamUser->getUserSalesVolumeBy($month, $year, 'sales_volume_total_shop'); return '
'.formatNumber($total).' €
'. 'E: '.formatNumber($individual).' | S: '.formatNumber($shop).' €'; }) ->addColumn('email', function (User $teamUser) { return e($teamUser->email); }) ->addColumn('first_name', function (User $teamUser) { return $teamUser->account ? e($teamUser->account->first_name) : ''; }) ->addColumn('last_name', function (User $teamUser) { return $teamUser->account ? e($teamUser->account->last_name) : ''; }) ->addColumn('sponsor', function (User $teamUser) { if (! $teamUser->user_sponsor) { return '-'; } $sponsor = $teamUser->user_sponsor; $html = ''; if ($sponsor->account) { $html .= e($sponsor->account->first_name.' '.$sponsor->account->last_name); $html .= '
'.e($sponsor->email); $html .= ' | '.e($sponsor->account->m_account); $html .= ''; } return $html; }) ->addColumn('active_account', function (User $teamUser) { return get_active_badge($teamUser->isActiveAccount()); }) ->addColumn('payment_account_date', function (User $teamUser) { return $teamUser->payment_account ? $teamUser->getPaymentAccountDateFormat(false) : '-'; }) ->addColumn('next_level_qualified', function (User $teamUser) { // Verwende bereits berechnete UserBusiness-Daten für bessere Performance $month = Request::get('team_user_filter_month') ?: session('team_user_filter_month'); $year = Request::get('team_user_filter_year') ?: session('team_user_filter_year'); $userBusiness = UserBusiness::where('user_id', $teamUser->id) ->where('month', $month) ->where('year', $year) ->first(); if ($userBusiness) { return NextLevelBadgeHelper::generateBadgeFromUserBusiness($userBusiness); } return NextLevelBadgeHelper::renderNoDataBadge(); }) ->filterColumn('m_account', function ($query, $keyword) { if ($keyword != '') { $query->whereHas('account', function ($q) use ($keyword) { $q->where('m_account', 'LIKE', '%'.$keyword.'%'); }); } }) ->filterColumn('first_name', function ($query, $keyword) { if ($keyword != '') { $query->whereHas('account', function ($q) use ($keyword) { $q->where('first_name', 'LIKE', '%'.$keyword.'%'); }); } }) ->filterColumn('last_name', function ($query, $keyword) { if ($keyword != '') { $query->whereHas('account', function ($q) use ($keyword) { $q->where('last_name', 'LIKE', '%'.$keyword.'%'); }); } }) ->filterColumn('email', function ($query, $keyword) { if ($keyword != '') { $query->where('email', 'LIKE', '%'.$keyword.'%'); } }) ->orderColumn('id', 'users.id $1') ->orderColumn('m_account', 'user_accounts.m_account $1') ->orderColumn('first_name', 'user_accounts.first_name $1') ->orderColumn('last_name', 'user_accounts.last_name $1') ->orderColumn('email', 'users.email $1') ->orderColumn('active_account', 'users.payment_account $1') ->rawColumns(['id', 'is_qual_kp', 'sales_volume_KP_points', 'sales_volume_total', 'sponsor', 'active_account', 'next_level_qualified']) ->make(true); } catch (\Exception $e) { \Log::error('TeamController: Error in userDatatable: '.$e->getMessage()); return response()->json([ 'error' => 'Team-Datatable konnte nicht geladen werden: '.$e->getMessage(), ], 500); } } /** * Zeigt Level-Aufstieg Reports für das eigene Team * Nur für einen ausgewählten Monat/Jahr basierend auf TreeCalcBotOptimized */ public function levelReports(Request $request) { $startTime = microtime(true); try { $user = User::find(\Auth::user()->id); $this->setFilterVars(); // Monat und Jahr aus Request oder Session $month = Request::get('month') ?: session('team_user_filter_month_prev', intval(date('m') - 1)); $year = Request::get('year') ?: session('team_user_filter_year', date('Y')); $onlyNotUpdated = Request::boolean('not_updated', false); // Prüfe ob Live-Berechnung erzwungen werden soll $forceLiveCalculation = false; // Request::get('force_live_calculation', false) || Request::get('live', false); \Log::info("TeamController: Building level reports for user {$user->id} ({$month}/{$year})"); // Lade Team-Struktur mit TreeCalcBotOptimized $treeCalcBot = new TreeCalcBotOptimized($month, $year, 'member', $forceLiveCalculation); $treeCalcBot->initStructureUser($user->id, $forceLiveCalculation); // Lade Level-Reports für Team $levelReportService = new LevelReportService; $filters = ['only_not_updated' => $onlyNotUpdated]; $promotions = $levelReportService->getTeamLevelPromotions($treeCalcBot, $month, $year, $filters); $statistics = $levelReportService->getStatistics($promotions); $endTime = microtime(true); $executionTime = round(($endTime - $startTime) * 1000, 2); \Log::info("TeamController: Level reports loaded for user {$user->id} in {$executionTime}ms - ".$promotions->count().' promotions found'); $availableYears = range(date('Y'), date('Y') - 5); $availableMonths = [ 1 => __('cal.months.January'), 2 => __('cal.months.February'), 3 => __('cal.months.March'), 4 => __('cal.months.April'), 5 => __('cal.months.May'), 6 => __('cal.months.June'), 7 => __('cal.months.July'), 8 => __('cal.months.August'), 9 => __('cal.months.September'), 10 => __('cal.months.October'), 11 => __('cal.months.November'), 12 => __('cal.months.December'), ]; $data = [ 'promotions' => $promotions, 'statistics' => $statistics, 'filters' => [ 'month' => $month, 'year' => $year, 'only_not_updated' => $onlyNotUpdated, ], 'availableYears' => $availableYears, 'availableMonths' => $availableMonths, 'performance' => [ 'execution_time' => $executionTime, 'user_id' => $user->id, ], ]; return view('user.team.level-reports', $data); } catch (\Exception $e) { \Log::error('TeamController: Error loading level reports: '.$e->getMessage()); return view('user.team.level-reports', [ 'error' => 'Fehler beim Laden der Level-Reports: '.$e->getMessage(), 'promotions' => collect([]), 'statistics' => ['total_count' => 0, 'level_stats' => [], 'period_stats' => []], 'filters' => ['month' => date('m'), 'year' => date('Y'), 'only_not_updated' => false], 'availableYears' => range(date('Y'), date('Y') - 5), 'availableMonths' => [ 1 => __('cal.months.January'), 2 => __('cal.months.February'), 3 => __('cal.months.March'), 4 => __('cal.months.April'), 5 => __('cal.months.May'), 6 => __('cal.months.June'), 7 => __('cal.months.July'), 8 => __('cal.months.August'), 9 => __('cal.months.September'), 10 => __('cal.months.October'), 11 => __('cal.months.November'), 12 => __('cal.months.December'), ], ]); } } /** * CSV Export für Team Level-Reports */ public function levelReportsExport(Request $request) { try { $user = User::find(\Auth::user()->id); $this->setFilterVars(); $month = Request::get('month') ?: session('team_user_filter_month_prev', intval(date('m') - 1)); $year = Request::get('year') ?: session('team_user_filter_year', date('Y')); $onlyNotUpdated = Request::boolean('not_updated', false); $forceLiveCalculation = Request::get('force_live_calculation', false) || Request::get('live', false); // Lade Team-Struktur $treeCalcBot = new TreeCalcBotOptimized($month, $year, 'member', $forceLiveCalculation); $treeCalcBot->initStructureUser($user->id, $forceLiveCalculation); // Lade Level-Reports $levelReportService = new LevelReportService; $filters = ['only_not_updated' => $onlyNotUpdated]; $promotions = $levelReportService->getTeamLevelPromotions($treeCalcBot, $month, $year, $filters); if ($promotions->isEmpty()) { return redirect()->back()->with('error', 'Keine Daten für Export gefunden.'); } // CSV erstellen $filename = 'team_level_promotions_'.date('Y-m-d_H-i-s').'.csv'; $filepath = $levelReportService->exportToCsv($promotions, $filename); return response()->download($filepath, $filename)->deleteFileAfterSend(true); } catch (\Exception $e) { \Log::error('TeamController: Error exporting level reports: '.$e->getMessage()); return redirect()->back()->with('error', 'Fehler beim Export: '.$e->getMessage()); } } /** * Zeigt den Marketingplan für User an * Übersichtliche Darstellung aller Karriere-Level mit wichtigen Informationen */ public function marketingplan() { $startTime = microtime(true); try { $user = User::find(\Auth::user()->id); $currentLevel = $user->user_level; // Lade alle aktiven User Level, sortiert nach Position $userLevels = \App\Models\UserLevel::where('active', true) ->orderBy('pos', 'asc') ->get(); $endTime = microtime(true); $executionTime = round(($endTime - $startTime) * 1000, 2); \Log::info("TeamController: Marketingplan loaded for user {$user->id} in {$executionTime}ms"); $data = [ 'userLevels' => $userLevels, 'currentUser' => $user, 'currentLevel' => $currentLevel, 'performance' => [ 'execution_time' => $executionTime, ], ]; return view('user.team.marketingplan', $data); } catch (\Exception $e) { \Log::error('TeamController: Error loading marketingplan: '.$e->getMessage()); return view('user.team.marketingplan', [ 'error' => __('marketingplan.loading_error').' '.$e->getMessage(), 'userLevels' => collect(), 'currentUser' => null, 'currentLevel' => null, ]); } } /** * Link für neue Mitglieder */ public function addMember() { $user = User::find(\Auth::user()->id); if ($user->isActiveShop() && $user->shop) { $shop_register_link = $user->shop->getSubdomain(false).'/reg'; } else { $member_id = 'm'.($user->id + config('mivita.add_number_id')); $shop_register_link = config('app.protocol').config('app.domain').config('app.tld_care').'/reg/'.$member_id; } $data = [ 'shop_register_link' => $shop_register_link, ]; return view('user.team.members', $data); } /** * Zeigt die Abos der Team-Mitglieder an */ public function showAbos() { $user = User::find(\Auth::user()->id); $this->setFilterVars(); // Hole Team-Mitglieder-IDs effizient via Sponsor-Hierarchie $teamUserIds = AboHelper::getTeamUserIds($user->id); $selectedYear = (int) Request::get('year', now()->year); $baseQuery = \App\Models\UserAbo::whereIn('user_id', $teamUserIds) ->where('is_for', 'me') ->where('status', '>', 1); // Hole Abos der Team-Mitglieder $abos = (clone $baseQuery) ->with(['user', 'user.account', 'user_abo_items', 'user_abo_items.product']) ->orderBy('next_date', 'asc') ->get(); $data = [ 'filter_months' => HTMLHelper::getTransMonths(), 'filter_years' => HTMLHelper::getYearRange(2022), 'abos' => $abos, 'chartData' => AboHelper::getMonthlyAboCounts($baseQuery, $selectedYear, 'team_abos', $user->id), 'chartYear' => $selectedYear, 'chartYears' => HTMLHelper::getYearRange(2026), 'chartMonths' => HTMLHelper::getTransMonths(), ]; return view('user.team.abos', $data); } /** * Zeigt eine Übersicht der Kunden-Abos aller Team-Mitglieder (anonymisiert) */ public function showTeamCustomerAbos(): \Illuminate\View\View { $user = User::find(\Auth::user()->id); $teamUserIds = AboHelper::getTeamUserIds($user->id); $selectedYear = (int) Request::get('year', now()->year); $baseQuery = \App\Models\UserAbo::whereIn('member_id', $teamUserIds) ->where('is_for', 'ot') ->where('status', '>', 1); $abos = (clone $baseQuery) ->with(['member', 'member.account', 'user_abo_items', 'user_abo_items.product']) ->orderBy('member_id') ->orderBy('next_date', 'asc') ->get(); $groupedByMember = $abos->groupBy('member_id'); return view('user.team.customer_abos', [ 'groupedByMember' => $groupedByMember, 'chartData' => AboHelper::getMonthlyAboCounts($baseQuery, $selectedYear, 'team_cust_abos', $user->id), 'chartYear' => $selectedYear, 'chartYears' => HTMLHelper::getYearRange(2026), 'chartMonths' => HTMLHelper::getTransMonths(), ]); } /** * Zeigt die Detail-Ansicht eines Team-Abos an */ public function detailAbo($id) { $user = User::find(\Auth::user()->id); $user_abo = \App\Models\UserAbo::findOrFail($id); // Prüfe ob das Abo zu einem Team-Mitglied gehört (effizient via Sponsor-Kette) if (! AboHelper::isUserInTeam($user->id, $user_abo->user_id)) { abort(403, 'Unauthorized action. This subscription does not belong to your team.'); } // Lade Abo-Details (ähnlich wie AboController) \App\Services\AboOrderCart::initYard($user_abo); $customer_detail = \App\Services\AboOrderCart::getCustomerDetail(); \App\Services\AboOrderCart::makeOrderYard($user_abo); $data = [ 'user_abo' => $user_abo, 'isAdmin' => false, 'customer_detail' => $customer_detail, 'view' => 'team', 'comp_products' => [], ]; return view('user.team.abo_detail', $data); } /** * Initialisiert die Team-Suche für den eingeloggten User */ private function initTeamSearch($currentUser) { $this->setFilterVars(); // Finde alle Team-Mitglieder des aktuellen Users (direkte und indirekte) $query = User::with(['account', 'user_level', 'user_sponsor.account']) ->select('users.*', 'user_accounts.m_account', 'user_accounts.first_name', 'user_accounts.last_name') ->leftJoin('user_accounts', 'users.id', '=', 'user_accounts.id') ->where('users.deleted_at', '=', null) ->where('users.id', '!=', 1) ->where('users.admin', '<', 4) ->where('users.m_level', '!=', null) ->where('users.payment_account', '!=', null); // Filtere Team-Mitglieder basierend auf Sponsor-Hierarchie // TODO: Hier müsste die Logik implementiert werden, um nur Team-Mitglieder des aktuellen Users zu finden // Für jetzt zeigen wir alle aktiven User (kann später spezifiziert werden) $activeFilter = Request::get('team_user_filter_active') ?: session('team_user_filter_active', 1); if ($activeFilter == 1) { $query->where('users.payment_account', '>=', now()); } elseif ($activeFilter == 2) { $query->where('users.payment_account', '<', now()); } // activeFilter == 3 bedeutet alle (keine weitere Einschränkung) return $query; } public function points() { $this->setFilterVars(); $user = User::find(\Auth::user()->id); $userSalesVolume = $user->getUserSalesVolume(intval(session('team_user_points_filter_month')), intval(session('team_user_points_filter_year')), 'first'); $data = [ 'userSalesVolume' => $userSalesVolume, 'filter_months' => HTMLHelper::getTransMonths(), 'filter_years' => HTMLHelper::getYearRange(2022), ]; return view('user.team.points', $data); } public function export() { $user = User::find(\Auth::user()->id); if (! $user->isVIP()) { abort(404); } $ExportBot = new ExportBot('member'); $ExportBot->initStructureUser($user, 'list'); // tree or list $data = [ 'ExportBot' => $ExportBot, ]; return view('user.team.export', $data); } public function userTeamExport() { if (Request::get('action') === 'export') { $user = User::find(\Auth::user()->id); $ExportBot = new ExportBot('member'); $ExportBot->initStructureUser($user, 'list'); // tree or list $columns = []; $filename = __('team.filename_export').date('Y-m-d-H-i-s'); $headers = [ __('tables.line'), __('tables.level'), __('tables.email'), __('tables.firstname'), __('tables.lastname'), __('tables.address'), __('tables.addition'), __('tables.postcode'), __('tables.city'), __('tables.country'), __('tables.phone'), __('tables.mobil'), __('tables.birthday'), __('tables.partner_since'), __('tables.account'), __('tables.account_to'), __('tables.sponsor'), ]; if (isset($ExportBot->user_list->childs)) { foreach ($ExportBot->user_list->childs as $child) { $columns[] = [ __('tables.line') => $child->line, __('tables.level') => $child->level_name, __('tables.email') => $child->email, __('tables.firstname') => $child->first_name, __('tables.lastname') => $child->last_name, __('tables.address') => $child->address, __('tables.addition') => $child->address_2, __('tables.postcode') => $child->zipcode, __('tables.city') => $child->city, __('tables.country') => $child->country_id, __('tables.phone') => $child->phone, __('tables.mobil') => $child->mobil, __('tables.birthday') => $child->birthday, __('tables.partner_since') => $child->partner_since, __('tables.account') => ($child->active_account == 1 ? __('yes') : __('no')), __('tables.account_to') => $child->payment_account_date, __('tables.sponsor') => $child->sponsor_name, ]; } } return Excel::download(new UserTeamExport($columns, $headers), $filename.'.xls'); } } private function setFilterVars() { if (! session('team_user_filter_month')) { session(['team_user_filter_month' => intval(date('m'))]); } if (! session('team_user_filter_month_prev')) { session(['team_user_filter_month_prev' => intval(date('m') - 1)]); } if (! session('team_user_filter_year')) { session(['team_user_filter_year' => intval(date('Y'))]); } if (! session('team_user_points_filter_month')) { session(['team_user_points_filter_month' => intval(date('m'))]); } if (! session('team_user_points_filter_year')) { session(['team_user_points_filter_year' => intval(date('Y'))]); } if (! session('team_user_filter_active')) { session(['team_user_filter_active' => 1]); } if (! session('team_user_filter_level')) { session(['team_user_filter_level' => 0]); } if (! session('team_user_filter_next_level')) { session(['team_user_filter_next_level' => 0]); } if (Request::get('team_user_filter_month')) { session(['team_user_filter_month' => Request::get('team_user_filter_month')]); } if (Request::get('team_user_filter_month_prev')) { session(['team_user_filter_month_prev' => Request::get('team_user_filter_month_prev')]); } if (Request::get('team_user_filter_year')) { session(['team_user_filter_year' => Request::get('team_user_filter_year')]); } if (Request::get('team_user_points_filter_month')) { session(['team_user_points_filter_month' => Request::get('team_user_points_filter_month')]); } if (Request::get('team_user_points_filter_year')) { session(['team_user_points_filter_year' => Request::get('team_user_points_filter_year')]); } if (Request::get('team_user_filter_active')) { session(['team_user_filter_active' => Request::get('team_user_filter_active')]); } if (Request::get('team_user_filter_level')) { session(['team_user_filter_level' => Request::get('team_user_filter_level')]); } else { session(['team_user_filter_level' => 0]); } if (Request::get('team_user_filter_next_level')) { session(['team_user_filter_next_level' => Request::get('team_user_filter_next_level')]); } else { session(['team_user_filter_next_level' => 0]); } } private function initSearchPoints() { $this->setFilterVars(); $user_id = \Auth::user()->id; $query = UserSalesVolume::with('user', 'user.account')->with('shopping_order')->select('user_sales_volumes.*') ->where('user_sales_volumes.user_id', '=', $user_id) ->where('user_sales_volumes.month', '=', Request::get('team_user_points_filter_month')) ->where('user_sales_volumes.year', '=', Request::get('team_user_points_filter_year')); return $query; } public function datatablePoints() { $query = $this->initSearchPoints(); return \DataTables::eloquent($query) ->addColumn('order', function (UserSalesVolume $UserSalesVolume) { if ($UserSalesVolume->shopping_order) { if ($UserSalesVolume->status === 1 && $UserSalesVolume->shopping_order->auth_user_id === $UserSalesVolume->user_id) { return ''.$UserSalesVolume->shopping_order->id.''; } if (($UserSalesVolume->status === 2 || $UserSalesVolume->status === 3) && $UserSalesVolume->shopping_order->member_id === $UserSalesVolume->user_id) { return ''.$UserSalesVolume->shopping_order->id.''; } } return ''; }) ->addColumn('total_net', function (UserSalesVolume $UserSalesVolume) { return formatNumber($UserSalesVolume->total_net).' €'; }) ->addColumn('status_turnover', function (UserSalesVolume $UserSalesVolume) { return ''.$UserSalesVolume->getStatusTurnoverType().''; }) ->addColumn('status', function (UserSalesVolume $UserSalesVolume) { return ''.$UserSalesVolume->getStatusType().''; }) ->addColumn('message', function (UserSalesVolume $UserSalesVolume) { return ''.$UserSalesVolume->message.''; }) ->addColumn('info', function (UserSalesVolume $UserSalesVolume) { return ''.$UserSalesVolume->info.''; }) ->orderColumn('id', 'id $1') ->orderColumn('order', 'order $1') ->orderColumn('status', 'status $1') ->orderColumn('message', 'message $1') ->orderColumn('info', 'info $1') ->rawColumns(['id', 'order', 'status_turnover', 'status', 'message', 'info', 'total_net']) ->make(true); } public function load() { $user = User::find(\Auth::user()->id); $userSalesVolume = $user->getUserSalesVolume(intval(session('team_user_points_filter_month')), intval(session('team_user_points_filter_year')), 'first'); $data = [ 'userSalesVolume' => $userSalesVolume, ]; $html = view('user.team._points_sum', $data)->render(); return response()->json(['response' => true, 'data' => $data, 'html' => $html]); } /** * Formatiert Bytes in lesbare Einheiten (aus BusinessControllerOptimized übernommen) */ private function formatBytes(int $bytes, int $precision = 2): string { $units = ['B', 'KB', 'MB', 'GB', 'TB']; for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) { $bytes /= 1024; } return round($bytes, $precision).' '.$units[$i]; } /** * Holt verfügbare User Level für Filter */ private function getFilterLevels(): array { $levels = [0 => 'Alle Level']; $userLevels = \App\Models\UserLevel::orderBy('pos')->get(['id', 'name']); foreach ($userLevels as $level) { $levels[$level->id] = $level->name; } return $levels; } /** * Holt übersetzte Filter für Aktiv-Status */ private function getFilterActive(): array { return [ 1 => __('team.filter_active'), 2 => __('team.filter_not_active'), 3 => __('team.filter_all'), ]; } /** * Holt übersetzte Filter für Next Level Status */ private function getFilterNextLevel(): array { return [ 0 => __('team.all_status'), 1 => __('team.qualified_green'), 2 => __('team.in_progress_yellow'), 3 => __('team.no_level_red'), ]; } // Performance-optimierte Badge-Generierung wurde in NextLevelBadgeHelper ausgelagert // Alte performance-lastige Methoden wurden entfernt um die Datatable-Performance zu verbessern /** * Extrahiert alle User aus TreeCalcBotOptimized-Struktur für DataTable-Anzeige * Sammelt rekursiv alle User aus der Struktur und macht sie als FLACHE Liste verfügbar */ public function getTeamUsersFromStructure(TreeCalcBotOptimized $treeCalcBot): array { $allUsers = []; $processedIds = []; // Debug: Prüfe TreeCalcBot-Inhalt $businessUsers = $treeCalcBot->getItems(); \Log::info('TeamController: TreeCalcBot root items count: '.count($businessUsers)); // Sammle alle Root-User UND deren verschachtelte businessUserItems foreach ($businessUsers as $businessUser) { // WICHTIG: user_id korrekt über b_user abrufen (Magic Method Problem mit isset()) $userId = $businessUser->user_id; // Über __get() Method \Log::debug('TeamController: Processing root businessUser', [ 'user_id' => $userId, 'businessUserItems_count' => count($businessUser->businessUserItems ?? []), ]); // WICHTIG: Root-User selbst hinzufügen (korrigierte user_id Prüfung) // nur User können auch children haben - businessUserItems if ($userId && ! isset($processedIds[$userId])) { $processedIds[$userId] = true; $businessUser->deep = 0; $allUsers[] = $businessUser; $this->collectAllBusinessUserItemsFlat($businessUser->businessUserItems ?? [], $allUsers, $processedIds, 1); \Log::debug("TeamController: Root-User hinzugefügt: {$userId}"); } } // Sammle parentless User, kann bei usern eigenlich nicht vorkommen, da sie immer teil des baums sind if ($treeCalcBot->isParentless()) { $parentless = $treeCalcBot->__get('parentless'); if (is_array($parentless)) { foreach ($parentless as $businessUser) { if ($businessUser) { // WICHTIG: user_id korrekt über b_user abrufen (Magic Method Problem mit isset()) $userId = $businessUser->user_id; // Über __get() Method if ($userId) { // Prüfe ob dieser parentless User bereits gesammelt wurde if (! isset($processedIds[$userId])) { $processedIds[$userId] = true; $businessUser->deep = 0; $allUsers[] = $businessUser; \Log::debug("TeamController: Parentless-User hinzugefügt: {$userId}"); // Sammle ALLE verschachtelten businessUserItems rekursiv $this->collectAllBusinessUserItemsFlat($businessUser->businessUserItems ?? [], $allUsers, $processedIds, 1); } else { \Log::debug("TeamController: Parentless-User übersprungen: {$userId} (bereits verarbeitet)"); } } else { \Log::warning('TeamController: Parentless BusinessUser ohne user_id übersprungen'); } } } } } \Log::info('TeamController: AllUsers before filtering: '.count($allUsers)); // Filter anwenden $filteredUsers = $this->applyTeamFiltersToBusinessUsers($allUsers); \Log::info('TeamController: AllUsers after filtering: '.count($filteredUsers)); return $filteredUsers; } /** * Wendet Team-Filter auf BusinessUser-Objekte an */ private function applyTeamFiltersToBusinessUsers($businessUsers): array { $activeFilter = Request::get('team_user_filter_active') ?: session('team_user_filter_active', 1); $levelFilter = Request::get('team_user_filter_level') ?: session('team_user_filter_level', 0); $nextLevelFilter = Request::get('team_user_filter_next_level') ?: session('team_user_filter_next_level', 0); \Log::info("TeamController: Applying filters - Active: {$activeFilter}, Level: {$levelFilter}, NextLevel: {$nextLevelFilter}"); // Debug: Zeige verfügbare Eigenschaften des ersten BusinessUsers if (! empty($businessUsers)) { $firstUser = $businessUsers[0]; \Log::debug('TeamController: First BusinessUser properties', [ 'user_id' => $firstUser->user_id ?? 'not set', 'active_account' => $firstUser->active_account ?? 'not set', 'm_level_id' => $firstUser->m_level_id ?? 'not set', 'next_qual_user_level' => isset($firstUser->next_qual_user_level) ? 'set' : 'not set', 'next_can_user_level' => isset($firstUser->next_can_user_level) ? 'set' : 'not set', ]); } $filtered = array_filter($businessUsers, function ($businessUser) use ($activeFilter, $levelFilter, $nextLevelFilter) { // Active Filter anwenden if ($activeFilter == 1) { // Nur aktive if (! $businessUser->active_account) { return false; } } elseif ($activeFilter == 2) { // Nur inaktive if ($businessUser->active_account) { return false; } } // activeFilter == 3 bedeutet alle (keine Einschränkung) // Level Filter anwenden if ($levelFilter && $levelFilter != 0) { if ($businessUser->m_level_id != $levelFilter) { return false; } } // Next Level Filter anwenden if ($nextLevelFilter && $nextLevelFilter != 0) { $hasNextQual = ($businessUser->next_qual_user_level) && $businessUser->next_qual_user_level !== '[]'; $hasNextCan = ($businessUser->next_can_user_level) && $businessUser->next_can_user_level !== '[]'; switch ($nextLevelFilter) { case 1: // Qualifiziert (grün) - hat next_qual_user_level if (! $hasNextQual) { return false; } break; case 2: // In Arbeit (gelb) - hat next_can_user_level aber kein next_qual_user_level if ($hasNextQual || ! $hasNextCan) { return false; } break; case 3: // Kein Level (rot) - hat weder next_qual noch next_can if ($hasNextQual || $hasNextCan) { return false; } break; } } return true; // Alle Filter bestanden }); // Array-Indizes neu setzen für korrekte DataTable-Verarbeitung return array_values($filtered); } /** * NEUE OPTIMIERTE Methode: Sammelt ALLE BusinessUserItems in einer flachen Liste * Perfekt für DataTable-Verarbeitung - keine verschachtelte Struktur */ private function collectAllBusinessUserItemsFlat(array $businessUserItems, &$allUsers, &$processedIds, $depth = 1): void { // Schutz vor zu tiefer Rekursion (maximale Tiefe: 20 Levels) $maxDepth = 20; if ($depth > $maxDepth) { \Log::warning("TeamController: Maximale Sammlungstiefe ({$maxDepth}) erreicht bei Tiefe {$depth}"); return; } foreach ($businessUserItems as $businessUserItem) { if ($businessUserItem) { // WICHTIG: user_id korrekt über b_user abrufen (Magic Method Problem mit isset()) $userId = $businessUserItem->user_id; // Über __get() Method if ($userId) { // KRITISCHER SCHUTZ: Prüfe ob User bereits gesammelt wurde if (isset($processedIds[$userId])) { \Log::debug("TeamController: Überspringe bereits gesammelten User {$userId} (Duplikat verhindert)"); continue; } // User zu flacher Liste hinzufügen $processedIds[$userId] = true; $businessUserItem->deep = $depth; $allUsers[] = $businessUserItem; \Log::debug("TeamController: Flach gesammelt - User ID: {$userId} at depth {$depth}"); // Rekursiv ALLE verschachtelten businessUserItems sammeln if (isset($businessUserItem->businessUserItems) && is_array($businessUserItem->businessUserItems) && ! empty($businessUserItem->businessUserItems)) { \Log::debug('TeamController: Sammle '.count($businessUserItem->businessUserItems)." verschachtelte Items von User {$userId}"); $this->collectAllBusinessUserItemsFlat($businessUserItem->businessUserItems, $allUsers, $processedIds, $depth + 1); } } else { \Log::warning("TeamController: BusinessUserItem ohne user_id bei Tiefe {$depth} übersprungen"); } } } } /** * DEPRECATED: Alte Methode zum rekursiven Sammeln von User-IDs aus BusinessUser-Struktur * Wird durch collectAllBusinessUserItemsFlat() ersetzt */ private function collectUserIdsFromBusinessUser($businessUser, &$allUsers, $deep, $parentless, &$processedIds = []): void { // Schutz vor zu tiefer Rekursion (maximale Tiefe: 20 Levels) $maxDepth = 20; if ($deep > $maxDepth) { \Log::warning("TeamController: Maximale Sammlungstiefe ({$maxDepth}) erreicht"); return; } if (isset($businessUser->businessUserItems) && is_array($businessUser->businessUserItems)) { \Log::debug('TeamController: Collecting from businessUser with '.count($businessUser->businessUserItems)." sub-items at depth {$deep}"); foreach ($businessUser->businessUserItems as $subBusinessUser) { if ($subBusinessUser) { // WICHTIG: user_id korrekt über b_user abrufen (Magic Method Problem mit isset()) $userId = $subBusinessUser->user_id; // Über __get() Method if ($userId) { // KRITISCHER BUGFIX: Prüfe ob User bereits gesammelt wurde if (isset($processedIds[$userId])) { \Log::debug("TeamController: Überspringe bereits gesammelten User {$userId} (zirkuläre Referenz verhindert)"); continue; } $processedIds[$userId] = true; $subBusinessUser->deep = $deep; $allUsers[] = $subBusinessUser; \Log::debug("TeamController: Collected user ID: {$userId} at depth {$deep}"); // Rekursiver Aufruf für weitere Unter-User mit Schutz vor Zyklen $newDeep = $parentless ? 0 : $deep + 1; $this->collectUserIdsFromBusinessUser($subBusinessUser, $allUsers, $newDeep, $parentless, $processedIds); } else { \Log::warning("TeamController: SubBusinessUser ohne user_id bei Tiefe {$deep} übersprungen"); } } } } } /** * KRITISCHE METHODE: Bereinigt BusinessUserItemOptimized Objekte für DataTables * Entfernt zirkuläre Referenzen und extrahiert nur nötige Properties */ private function cleanBusinessUserItemsForDataTable(array $businessUserItems): array { $cleanedUsers = []; foreach ($businessUserItems as $businessUserItem) { if (! $businessUserItem) { continue; } try { // Extrahiere nur die Properties, die für DataTable benötigt werden $cleanedUser = new \stdClass; // Basis Properties (direkt über Magic Method __get) $cleanedUser->user_id = $businessUserItem->user_id; $cleanedUser->m_account = $businessUserItem->m_account; $cleanedUser->email = $businessUserItem->email; $cleanedUser->first_name = $businessUserItem->first_name; $cleanedUser->last_name = $businessUserItem->last_name; $cleanedUser->user_level_name = $businessUserItem->user_level_name; $cleanedUser->active_account = $businessUserItem->active_account; $cleanedUser->active_date = $businessUserItem->active_date; // Sales Volume Properties $cleanedUser->sales_volume_points_KP_sum = $businessUserItem->sales_volume_points_KP_sum ?? 0; $cleanedUser->payline_points_qual_kp = $businessUserItem->payline_points_qual_kp ?? 0; // Depth für Debug/Sortierung (falls gesetzt) $cleanedUser->deep = $businessUserItem->deep ?? 0; // Level-Informationen für Filter $cleanedUser->m_level_id = $businessUserItem->m_level_id; $cleanedUser->next_qual_user_level = $businessUserItem->next_qual_user_level; $cleanedUser->next_can_user_level = $businessUserItem->next_can_user_level; $cleanedUsers[] = $cleanedUser; \Log::debug("TeamController: Cleaned user {$cleanedUser->user_id} for DataTable"); } catch (\Exception $e) { \Log::error('TeamController: Error cleaning BusinessUserItem for DataTable: '.$e->getMessage()); // Skip diesen User, statt alles abzubrechen continue; } } \Log::info('TeamController: Cleaned '.count($cleanedUsers).' users for DataTable (from '.count($businessUserItems).' raw items)'); return $cleanedUsers; } }