mivita/app/Http/Controllers/AdminUserCleanupController.php
2026-02-20 17:55:06 +01:00

708 lines
30 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\ShoppingUser;
use App\Models\ShoppingUserMemberLog;
use App\Models\UserCleanUpLog;
use App\User;
use Illuminate\Support\Facades\Auth;
class AdminUserCleanupController extends Controller
{
public function __construct()
{
$this->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 '<span class="badge badge-pill badge-danger">'.$user->email.'</span>';
}
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 '<span class="badge badge-danger">Gelöscht</span>';
}
if (! $user->active) {
return '<span class="badge badge-warning">Deaktiviert</span>';
}
return '<span class="badge badge-success">Aktiv</span>';
})
->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 ? '<span class="badge badge-info">'.$count.'</span>' : '0';
})
->addColumn('shopping_users_count', function (User $user) {
$count = ShoppingUser::where('member_id', $user->id)->count();
return $count > 0 ? '<span class="badge badge-info">'.$count.'</span>' : '0';
})
->addColumn('action', function (User $user) {
$html = '';
if ($user->pre_deleted_at) {
$html .= '<a href="'.route('admin_lead_edit', [$user->id]).'" class="btn btn-sm btn-info" title="Details"><i class="fa fa-eye"></i></a> ';
} else {
$html .= '<a href="'.route('admin_lead_edit', [$user->id]).'" class="btn btn-sm btn-primary" title="Bearbeiten"><i class="fa fa-edit"></i></a> ';
}
// Historie-Button
$html .= ' <button class="btn btn-sm btn-secondary btn-user-history" data-id="'.$user->id.'" data-email="'.$user->email.'" title="Historie & Details"><i class="fa fa-history"></i></button>';
// Restore-Button für gelöschte User
if ($user->pre_deleted_at) {
$html .= ' <button class="btn btn-sm btn-success" data-toggle="modal" data-target="#modal-restore-user" data-id="'.$user->id.'" data-email="'.str_replace('delete-', '', $user->email).'" title="Wiederherstellen"><i class="fa fa-undo"></i></button>';
}
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').'<br><small>'.$log->inactive_sponsor->email.'</small>';
}
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').'<br><small>'.$log->child_user->email.'</small>';
}
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 .= '<span class="text-muted"><small>Damals:</small></span><br>';
$html .= ($name ?: 'N/A').'<br><small>'.$log->new_sponsor->email.'</small>';
} 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 .= '<hr class="my-1">';
$html .= '<span class="badge badge-warning">Geändert!</span><br>';
$html .= '<span class="text-success"><small>Aktuell:</small></span><br>';
if ($currentSponsor->account) {
$currentName = trim($currentSponsor->account->first_name.' '.$currentSponsor->account->last_name);
$html .= '<strong>'.($currentName ?: 'N/A').'</strong><br>';
}
$html .= '<small>'.$currentSponsor->email.'</small>';
}
}
}
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').'<br><small>'.$log->pre_member->email.'</small>';
}
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').'<br><small>'.$log->shopping_user->billing_email.'</small>';
}
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 .= '<span class="text-muted"><small>Damals:</small></span><br>';
$html .= ($name ?: 'N/A').'<br><small>'.$log->new_member->email.'</small>';
} 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 .= '<hr class="my-1">';
$html .= '<span class="badge badge-warning">Geändert!</span><br>';
$html .= '<span class="text-success"><small>Aktuell:</small></span><br>';
if ($currentMember->account) {
$currentName = trim($currentMember->account->first_name.' '.$currentMember->account->last_name);
$html .= '<strong>'.($currentName ?: 'N/A').'</strong><br>';
}
$html .= '<small>'.$currentMember->email.'</small>';
}
}
}
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,
]);
}
}