middleware('superadmin'); } /** * Übersicht deaktivierter und gelöschter User */ public function index() { return view('admin.user.cleanup.index'); } /** * Protokoll der User-Cleanup-Logs (Downline-Übertragungen) */ public function logs() { return view('admin.user.cleanup.logs'); } /** * Protokoll der Shopping-User-Member-Logs (Kunden-Übertragungen) */ public function shoppingLogs() { return view('admin.user.cleanup.shopping_logs'); } /** * DataTable für deaktivierte/gelöschte User */ public function getInactiveUsers() { // Deaktivierte User (active=false) ODER gelöschte User (mit pre_deleted_at) $query = User::withTrashed() ->where(function ($q) { $q->where('active', false) ->orWhere('pre_deleted_at', '!=', null); }) ->with('account') ->select('users.*') ->where('users.admin', '<', 5); return \DataTables::eloquent($query) ->addColumn('user_id', function (User $user) { return $user->id; }) ->addColumn('first_name', function (User $user) { return $user->account ? $user->account->first_name : ''; }) ->addColumn('last_name', function (User $user) { return $user->account ? $user->account->last_name : ''; }) ->addColumn('email', function (User $user) { if ($user->pre_deleted_at) { return ''.$user->email.''; } return $user->email; }) ->addColumn('m_account', function (User $user) { return $user->account ? $user->account->m_account : ''; }) ->addColumn('status', function (User $user) { if ($user->pre_deleted_at) { return 'Gelöscht'; } if (! $user->active) { return 'Deaktiviert'; } return 'Aktiv'; }) ->addColumn('deleted_at', function (User $user) { if ($user->pre_deleted_at) { return \Carbon\Carbon::parse($user->pre_deleted_at)->format('d.m.Y H:i'); } return '-'; }) ->addColumn('payment_account', function (User $user) { return $user->getPaymentAccountDateFormat(); }) ->addColumn('m_sponsor', function (User $user) { if ($user->m_sponsor) { $sponsor = User::find($user->m_sponsor); return $sponsor ? $sponsor->email : 'ID: '.$user->m_sponsor; } return '-'; }) ->addColumn('pre_sponsor', function (User $user) { if ($user->pre_sponsor) { $sponsor = User::withTrashed()->find($user->pre_sponsor); return $sponsor ? $sponsor->email : 'ID: '.$user->pre_sponsor; } return '-'; }) ->addColumn('childs_count', function (User $user) { $count = User::where('m_sponsor', $user->id)->count(); return $count > 0 ? ''.$count.'' : '0'; }) ->addColumn('shopping_users_count', function (User $user) { $count = ShoppingUser::where('member_id', $user->id)->count(); return $count > 0 ? ''.$count.'' : '0'; }) ->addColumn('action', function (User $user) { $html = ''; if ($user->pre_deleted_at) { $html .= ' '; } else { $html .= ' '; } // Historie-Button $html .= ' '; // Restore-Button für gelöschte User if ($user->pre_deleted_at) { $html .= ' '; } return $html; }) ->orderColumn('user_id', 'id $1') ->orderColumn('email', 'email $1') ->orderColumn('status', 'active $1') ->rawColumns(['email', 'status', 'childs_count', 'shopping_users_count', 'action']) ->make(true); } /** * DataTable für UserCleanUpLogs (Downline-Übertragungen) */ public function getCleanupLogs() { $query = UserCleanUpLog::with(['inactive_sponsor.account', 'child_user.account', 'new_sponsor.account']) ->select('user_clean_up_logs.*'); return \DataTables::eloquent($query) ->addColumn('id', function (UserCleanUpLog $log) { return $log->id; }) ->addColumn('inactive_sponsor', function (UserCleanUpLog $log) { if ($log->inactive_sponsor && $log->inactive_sponsor->account) { $name = trim($log->inactive_sponsor->account->first_name.' '.$log->inactive_sponsor->account->last_name); return ($name ?: 'N/A').'
'.$log->inactive_sponsor->email.''; } return 'ID: '.$log->inactive_sponsor_id; }) ->addColumn('child_user', function (UserCleanUpLog $log) { if ($log->child_user && $log->child_user->account) { $name = trim($log->child_user->account->first_name.' '.$log->child_user->account->last_name); return ($name ?: 'N/A').'
'.$log->child_user->email.''; } return 'ID: '.$log->child_user_id; }) ->addColumn('new_sponsor', function (UserCleanUpLog $log) { $html = ''; // Original-Sponsor aus dem Log if ($log->new_sponsor && $log->new_sponsor->account) { $name = trim($log->new_sponsor->account->first_name.' '.$log->new_sponsor->account->last_name); $html .= 'Damals:
'; $html .= ($name ?: 'N/A').'
'.$log->new_sponsor->email.''; } else { $html .= 'ID: '.$log->new_sponsor_id; } // Prüfe aktuellen Sponsor des child_user if ($log->child_user) { $currentSponsorId = $log->child_user->m_sponsor; // Wenn aktueller Sponsor ANDERS ist als im Log if ($currentSponsorId && $currentSponsorId != $log->new_sponsor_id) { $currentSponsor = User::with('account')->find($currentSponsorId); if ($currentSponsor) { $html .= '
'; $html .= 'Geändert!
'; $html .= 'Aktuell:
'; if ($currentSponsor->account) { $currentName = trim($currentSponsor->account->first_name.' '.$currentSponsor->account->last_name); $html .= ''.($currentName ?: 'N/A').'
'; } $html .= ''.$currentSponsor->email.''; } } } return $html; }) ->addColumn('created_at', function (UserCleanUpLog $log) { return \Carbon\Carbon::parse($log->created_at)->format('d.m.Y H:i'); }) ->orderColumn('id', 'id $1') ->rawColumns(['inactive_sponsor', 'child_user', 'new_sponsor']) ->make(true); } /** * DataTable für ShoppingUserMemberLogs (Kunden-Übertragungen) */ public function getShoppingLogs() { $query = ShoppingUserMemberLog::with(['pre_member.account', 'shopping_user', 'new_member.account']) ->select('shopping_user_member_logs.*'); return \DataTables::eloquent($query) ->addColumn('id', function (ShoppingUserMemberLog $log) { return $log->id; }) ->addColumn('pre_member', function (ShoppingUserMemberLog $log) { if ($log->pre_member && $log->pre_member->account) { $name = trim($log->pre_member->account->first_name.' '.$log->pre_member->account->last_name); return ($name ?: 'N/A').'
'.$log->pre_member->email.''; } return 'ID: '.$log->pre_member_id; }) ->addColumn('shopping_user', function (ShoppingUserMemberLog $log) { if ($log->shopping_user) { $name = trim($log->shopping_user->billing_firstname.' '.$log->shopping_user->billing_lastname); return ($name ?: 'N/A').'
'.$log->shopping_user->billing_email.''; } return 'ID: '.$log->shopping_user_id; }) ->addColumn('new_member', function (ShoppingUserMemberLog $log) { $html = ''; // Original-Berater aus dem Log if ($log->new_member && $log->new_member->account) { $name = trim($log->new_member->account->first_name.' '.$log->new_member->account->last_name); $html .= 'Damals:
'; $html .= ($name ?: 'N/A').'
'.$log->new_member->email.''; } else { $html .= 'ID: '.$log->new_member_id; } // Prüfe aktuellen Berater des Kunden if ($log->shopping_user) { $currentMemberId = $log->shopping_user->member_id; // Wenn aktueller Berater ANDERS ist als im Log if ($currentMemberId && $currentMemberId != $log->new_member_id) { $currentMember = User::with('account')->find($currentMemberId); if ($currentMember) { $html .= '
'; $html .= 'Geändert!
'; $html .= 'Aktuell:
'; if ($currentMember->account) { $currentName = trim($currentMember->account->first_name.' '.$currentMember->account->last_name); $html .= ''.($currentName ?: 'N/A').'
'; } $html .= ''.$currentMember->email.''; } } } return $html; }) ->addColumn('created_at', function (ShoppingUserMemberLog $log) { return \Carbon\Carbon::parse($log->created_at)->format('d.m.Y H:i'); }) ->orderColumn('id', 'id $1') ->rawColumns(['pre_member', 'shopping_user', 'new_member']) ->make(true); } /** * User über Artisan Command wiederherstellen */ public function restore(\Illuminate\Http\Request $request) { $userId = $request->input('user_id'); if (! $userId) { return response()->json([ 'success' => false, 'message' => 'Keine User-ID angegeben', ], 400); } try { // Führe Artisan Command aus \Artisan::call('user:restore', ['user_id' => $userId]); $output = \Artisan::output(); // Prüfe ob Command erfolgreich war (Exit Code 0) $exitCode = \Artisan::call('user:restore', ['user_id' => $userId]); \Log::channel('cleanup')->info('AdminUserCleanupController restore via web: user_id='.$userId.' | exitCode='.$exitCode); if ($exitCode === 0) { return response()->json([ 'success' => true, 'message' => 'User wurde erfolgreich wiederhergestellt', 'output' => $output, ]); } else { return response()->json([ 'success' => false, 'message' => 'Fehler beim Wiederherstellen (Exit Code: '.$exitCode.')', 'output' => $output, ], 500); } } catch (\Exception $e) { \Log::channel('cleanup')->error('AdminUserCleanupController restore failed: '.$e->getMessage()); return response()->json([ 'success' => false, 'message' => 'Exception: '.$e->getMessage(), ], 500); } } /** * Suche nach aktiven Sponsoren für Select2 */ public function searchSponsors(\Illuminate\Http\Request $request) { $search = $request->input('q'); $userId = $request->input('exclude_user_id'); // User selbst ausschließen $loadAll = $request->input('load_all', false); // Alle Sponsoren laden $query = User::where('active', true) ->where('admin', '<', 5) ->where('blocked', false) ->where('payment_account', '>=', now()) ->with('account') ->orderBy('email', 'asc'); if ($userId) { $query->where('id', '!=', $userId); } // Nur filtern wenn Suche vorhanden und nicht load_all if ($search && ! $loadAll) { $query->where(function ($q) use ($search) { $q->where('email', 'like', '%'.$search.'%') ->orWhereHas('account', function ($q2) use ($search) { $q2->where('first_name', 'like', '%'.$search.'%') ->orWhere('last_name', 'like', '%'.$search.'%') ->orWhere('m_account', 'like', '%'.$search.'%'); }); }); } // Limit nur wenn nicht alle geladen werden sollen if (! $loadAll) { $query->limit(20); } $users = $query->get()->map(function ($user) { $name = ''; if ($user->account) { $name = trim($user->account->first_name.' '.$user->account->last_name); if ($name) { $name .= ' | '; } } return [ 'id' => $user->id, 'text' => $name.$user->email.($user->account && $user->account->m_account ? ' #'.$user->account->m_account : ''), ]; }); return response()->json(['results' => $users]); } /** * Sponsor manuell neu zuweisen */ public function reassignSponsor(\Illuminate\Http\Request $request) { $userId = $request->input('user_id'); $newSponsorId = $request->input('new_sponsor_id'); // Boolean-Werte korrekt konvertieren (auch wenn sie als String ankommen) $transferDownline = filter_var($request->input('transfer_downline', false), FILTER_VALIDATE_BOOLEAN); $transferCustomers = filter_var($request->input('transfer_customers', false), FILTER_VALIDATE_BOOLEAN); if (! $userId || ! $newSponsorId) { return response()->json([ 'success' => false, 'message' => 'User-ID und neuer Sponsor sind erforderlich', ], 400); } $user = User::withTrashed()->find($userId); $newSponsor = User::find($newSponsorId); if (! $user) { return response()->json([ 'success' => false, 'message' => 'User nicht gefunden', ], 404); } if (! $newSponsor || ! $newSponsor->active) { return response()->json([ 'success' => false, 'message' => 'Neuer Sponsor nicht gefunden oder nicht aktiv', ], 404); } \DB::beginTransaction(); try { $oldSponsorId = $user->m_sponsor; $logs = []; // 1. Downline neu zuweisen (aus Logs - bereits übertragene) $childrenTransferred = 0; if ($transferDownline) { // Hole die Kinder aus den vorherigen Cleanup-Logs (die bereits VON diesem User weg übertragen wurden) $cleanupLogs = UserCleanUpLog::where('inactive_sponsor_id', $userId)->get(); \Log::channel('cleanup')->info('Reassigning downline from logs: found '.$cleanupLogs->count().' log entries for user_id='.$userId); foreach ($cleanupLogs as $oldLog) { $child = User::find($oldLog->child_user_id); if (! $child) { \Log::channel('cleanup')->warning('Child user not found: '.$oldLog->child_user_id); continue; } // Neuen Log erstellen für die Neu-Zuweisung UserCleanUpLog::create([ 'inactive_sponsor_id' => $child->m_sponsor, // Aktueller Sponsor (wohin es vorher übertragen wurde) 'child_user_id' => $child->id, 'new_sponsor_id' => $newSponsorId, // Neuer Sponsor ]); // Sponsor ändern $child->m_sponsor = $newSponsorId; $child->save(); $childrenTransferred++; $logs[] = 'Downline: '.$child->email.' → Neuer Sponsor: '.$newSponsor->email; } \Log::channel('cleanup')->info('Children reassigned: '.$childrenTransferred); } // 2. Shopping-Kunden neu zuweisen (aus Logs - bereits übertragene) $customersTransferred = 0; if ($transferCustomers) { // Hole die Kunden aus den vorherigen Shopping-Logs (die bereits VON diesem User weg übertragen wurden) $shoppingLogs = ShoppingUserMemberLog::where('pre_member_id', $userId)->get(); \Log::channel('cleanup')->info('Reassigning customers from logs: found '.$shoppingLogs->count().' log entries for user_id='.$userId); foreach ($shoppingLogs as $oldLog) { $customer = ShoppingUser::find($oldLog->shopping_user_id); if (! $customer) { \Log::channel('cleanup')->warning('Shopping user not found: '.$oldLog->shopping_user_id); continue; } // Neuen Log erstellen für die Neu-Zuweisung ShoppingUserMemberLog::create([ 'pre_member_id' => $customer->member_id, // Aktueller Berater (wohin es vorher übertragen wurde) 'shopping_user_id' => $customer->id, 'new_member_id' => $newSponsorId, // Neuer Berater ]); // Member ändern $customer->member_id = $newSponsorId; $customer->save(); $customersTransferred++; $logs[] = 'Kunde: '.$customer->billing_email.' → Neuer Berater: '.$newSponsor->email; } \Log::channel('cleanup')->info('Customers reassigned: '.$customersTransferred); } // 3. User selbst dem neuen Sponsor zuweisen $user->m_sponsor = $newSponsorId; $user->save(); \DB::commit(); // Cleanup-Log \Log::channel('cleanup')->info('Manual reassign sponsor: user_id='.$userId.' | old_sponsor='.$oldSponsorId.' | new_sponsor='.$newSponsorId.' | transfer_downline='.(int) $transferDownline.' | transfer_customers='.(int) $transferCustomers.' | by_admin='.Auth::id()); return response()->json([ 'success' => true, 'message' => 'Sponsor erfolgreich neu zugewiesen', 'logs' => $logs, 'transferred' => [ 'downline' => $childrenTransferred, 'customers' => $customersTransferred, ], ]); } catch (\Exception $e) { \DB::rollBack(); \Log::channel('cleanup')->error('Manual reassign sponsor failed: user_id='.$userId.' | error='.$e->getMessage()); return response()->json([ 'success' => false, 'message' => 'Fehler beim Neu-Zuweisen: '.$e->getMessage(), ], 500); } } /** * Lade User-Historie: Downline-Position und Shopping-Kunden */ public function getUserHistory($userId) { $user = User::withTrashed()->with('account')->find($userId); if (! $user) { return response()->json([ 'success' => false, 'message' => 'User nicht gefunden', ], 404); } // Aktueller/Vorheriger Sponsor $sponsor = null; $preSponsor = null; if ($user->m_sponsor) { $sponsor = User::withTrashed()->with('account')->find($user->m_sponsor); } if ($user->pre_sponsor) { $preSponsor = User::withTrashed()->with('account')->find($user->pre_sponsor); } // Direkte Downline (Kinder) - nur die, die aktuell m_sponsor haben // pre_sponsor sind bereits deaktiviert und würden doppelt gezählt $children = User::withTrashed() ->with('account') ->where('m_sponsor', $userId) ->get() ->map(function ($child) use ($userId) { return [ 'id' => $child->id, 'name' => $child->account ? trim($child->account->first_name.' '.$child->account->last_name) : 'N/A', 'email' => $child->email, 'active' => $child->active, 'deleted' => $child->pre_deleted_at ? true : false, 'is_pre_sponsor' => $child->pre_sponsor == $userId, ]; }); // Shopping-Kunden $shoppingUsers = ShoppingUser::where('member_id', $userId) ->get() ->map(function ($customer) { return [ 'id' => $customer->id, 'name' => trim($customer->billing_firstname.' '.$customer->billing_lastname), 'email' => $customer->billing_email, 'city' => $customer->billing_city, 'created_at' => \Carbon\Carbon::parse($customer->created_at)->format('d.m.Y'), ]; }); // Downline-Übertragungen (wo dieser User betroffen war) $cleanupLogs = UserCleanUpLog::with(['inactive_sponsor.account', 'child_user.account', 'new_sponsor.account']) ->where(function ($query) use ($userId) { $query->where('inactive_sponsor_id', $userId) ->orWhere('child_user_id', $userId); }) ->orderBy('created_at', 'desc') ->get() ->map(function ($log) use ($userId) { $data = [ 'type' => $log->inactive_sponsor_id == $userId ? 'as_inactive' : 'as_child', 'child_user' => $log->child_user ? [ 'id' => $log->child_user->id, 'name' => $log->child_user->account ? trim($log->child_user->account->first_name.' '.$log->child_user->account->last_name) : 'N/A', 'email' => $log->child_user->email, 'active' => $log->child_user->active, 'deleted' => $log->child_user->pre_deleted_at ? true : false, ] : null, 'inactive_sponsor' => $log->inactive_sponsor ? [ 'id' => $log->inactive_sponsor->id, 'name' => $log->inactive_sponsor->account ? trim($log->inactive_sponsor->account->first_name.' '.$log->inactive_sponsor->account->last_name) : 'N/A', 'email' => $log->inactive_sponsor->email, ] : null, 'new_sponsor' => $log->new_sponsor ? [ 'id' => $log->new_sponsor->id, 'name' => $log->new_sponsor->account ? trim($log->new_sponsor->account->first_name.' '.$log->new_sponsor->account->last_name) : 'N/A', 'email' => $log->new_sponsor->email, ] : null, 'created_at' => \Carbon\Carbon::parse($log->created_at)->format('d.m.Y H:i'), ]; // Prüfe aktuellen Sponsor des child_user (falls geändert) if ($log->child_user && $log->child_user->m_sponsor && $log->child_user->m_sponsor != $log->new_sponsor_id) { $currentSponsor = User::with('account')->find($log->child_user->m_sponsor); if ($currentSponsor) { $data['current_sponsor'] = [ 'id' => $currentSponsor->id, 'name' => $currentSponsor->account ? trim($currentSponsor->account->first_name.' '.$currentSponsor->account->last_name) : 'N/A', 'email' => $currentSponsor->email, ]; } } return $data; }); // Kunden-Übertragungen $shoppingLogs = ShoppingUserMemberLog::with(['shopping_user', 'new_member.account']) ->where('pre_member_id', $userId) ->orderBy('created_at', 'desc') ->get() ->map(function ($log) { $data = [ 'customer_name' => $log->shopping_user ? trim($log->shopping_user->billing_firstname.' '.$log->shopping_user->billing_lastname) : 'N/A', 'customer_email' => $log->shopping_user ? $log->shopping_user->billing_email : 'N/A', 'new_member' => $log->new_member ? [ 'id' => $log->new_member->id, 'name' => $log->new_member->account ? trim($log->new_member->account->first_name.' '.$log->new_member->account->last_name) : 'N/A', 'email' => $log->new_member->email, ] : null, 'created_at' => \Carbon\Carbon::parse($log->created_at)->format('d.m.Y H:i'), ]; // Prüfe aktuellen Berater des Kunden (falls geändert) if ($log->shopping_user && $log->shopping_user->member_id && $log->shopping_user->member_id != $log->new_member_id) { $currentMember = User::with('account')->find($log->shopping_user->member_id); if ($currentMember) { $data['current_member'] = [ 'id' => $currentMember->id, 'name' => $currentMember->account ? trim($currentMember->account->first_name.' '.$currentMember->account->last_name) : 'N/A', 'email' => $currentMember->email, ]; } } return $data; }); return response()->json([ 'success' => true, 'user' => [ 'id' => $user->id, 'name' => $user->account ? trim($user->account->first_name.' '.$user->account->last_name) : 'N/A', 'email' => $user->email, 'm_account' => $user->account ? $user->account->m_account : 'N/A', 'active' => $user->active, 'deleted' => $user->pre_deleted_at ? true : false, 'deleted_at' => $user->pre_deleted_at ? \Carbon\Carbon::parse($user->pre_deleted_at)->format('d.m.Y H:i') : null, ], 'sponsor' => $sponsor ? [ 'id' => $sponsor->id, 'name' => $sponsor->account ? trim($sponsor->account->first_name.' '.$sponsor->account->last_name) : 'N/A', 'email' => $sponsor->email, 'active' => $sponsor->active, ] : null, 'pre_sponsor' => $preSponsor ? [ 'id' => $preSponsor->id, 'name' => $preSponsor->account ? trim($preSponsor->account->first_name.' '.$preSponsor->account->last_name) : 'N/A', 'email' => $preSponsor->email, 'active' => $preSponsor->active, ] : null, 'children' => $children, 'shopping_users' => $shoppingUsers, 'cleanup_logs' => $cleanupLogs, 'shopping_logs' => $shoppingLogs, ]); } }