Kunden loeschen: Modal mit Komplett- oder DSGVO-Loeschung ueber alle Dubletten

Ticket dev/_tickets/2026-09-08.md #2:
- "Kunde loeschen" oeffnet ein Modal statt direkt per GET zu loeschen. Es
  zeigt alle Eintraege desselben Kunden (Kundennummer oder E-Mail, ohne
  Platzhalter-Adressen) mit Anzahl Bestellungen.
- Ohne Bestellungen: Komplett-Loeschung (anonymisiert und soft-geloescht,
  Portal-Logins und Stripe-Kunden entfernt, Zahlungs-Tokens inaktiver Abos
  geleert). Mit Bestellungen nur DSGVO-Loeschung (Superadmin); die arbeitet
  jetzt ebenfalls auf der E-Mail-Gruppe und entfernt Portal-Logins.
- Bei aktivem Abo oder Berater-Datensatz wird abgelehnt.
- Die alte "Loeschung" (CustomerRepository::deleteCustomer) entfaellt.
- Fehlermeldungen nutzen alert-error; alert-danger zeigte kein Layout an.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin 2026-09-23 15:43:35 +00:00
parent 728dde4c76
commit 90c2188ffa
12 changed files with 631 additions and 88 deletions

View file

@ -45,6 +45,7 @@ class CustomerController extends Controller
$shopping_user = ShoppingUser::findOrFail($id);
$data = [
'shopping_user' => $shopping_user,
'deletion' => app(GdprDeletionService::class)->analyzeShoppingUser($shopping_user),
'isAdmin' => true,
'isView' => 'customer',
];
@ -52,16 +53,26 @@ class CustomerController extends Controller
return view('admin.customer.detail', $data);
}
/**
* Komplett-Löschung (nur ohne Bestellungen), dev/_tickets/2026-09-08.md #2.
*/
public function delete($id)
{
$shopping_user = ShoppingUser::findOrFail($id);
$result = $this->customerRepository->deleteCustomer($shopping_user);
if (! $result) {
return back()->with('alert-error', 'Kunde hat Bestellungen. Löschen nicht möglich.');
} else {
\Session()->flash('alert-success', 'Kunde wurde gelöscht');
$shopping_user = ShoppingUser::withTrashed()->findOrFail($id);
return redirect(route('admin_customers'));
if (! Request::get('delete_customer')) {
\Session()->flash('alert-error', __('msg.delete_confirm_checkbox_missing'));
return redirect(route('admin_customer_detail', [$shopping_user->id]));
}
try {
$summary = app(GdprDeletionService::class)->purgeShoppingUser($shopping_user, Auth::id());
\Session()->flash('alert-success', __('msg.gdpr_purge_customer_success', ['count' => $summary['shopping_users_purged']]));
} catch (GdprDeletionException $e) {
\Session()->flash('alert-error', $e->getMessage());
return redirect(route('admin_customer_detail', [$shopping_user->id]));
}
return redirect(route('admin_customers'));
@ -72,7 +83,7 @@ class CustomerController extends Controller
$shopping_user = ShoppingUser::withTrashed()->findOrFail($id);
if (! Request::get('gdpr_delete_customer')) {
\Session()->flash('alert-danger', __('msg.gdpr_confirm_checkbox_missing'));
\Session()->flash('alert-error', __('msg.gdpr_confirm_checkbox_missing'));
return redirect(route('admin_customer_detail', [$shopping_user->id]));
}
@ -81,7 +92,7 @@ class CustomerController extends Controller
app(GdprDeletionService::class)->deleteShoppingUser($shopping_user, Auth::id());
\Session()->flash('alert-success', __('msg.gdpr_delete_customer_success'));
} catch (GdprDeletionException $e) {
\Session()->flash('alert-danger', $e->getMessage());
\Session()->flash('alert-error', $e->getMessage());
return redirect(route('admin_customer_detail', [$shopping_user->id]));
}