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:
parent
728dde4c76
commit
90c2188ffa
12 changed files with 631 additions and 88 deletions
|
|
@ -48,6 +48,10 @@ return [
|
|||
'gdpr_error_is_admin' => 'DSGVO-Löschung nicht möglich: Der Account hat Admin-Rechte. Bitte entziehe zuerst die Admin-Rechte.',
|
||||
'gdpr_error_active_abo' => 'DSGVO-Löschung nicht möglich: Es besteht noch ein aktives Abo. Bitte kündige das Abo zuerst.',
|
||||
'gdpr_error_customer_is_member' => 'DSGVO-Löschung nicht möglich: Dieser Kundendatensatz gehört zu einem Berater-Account. Bitte nutze die DSGVO-Löschung in der User-Verwaltung.',
|
||||
'delete_confirm_checkbox_missing' => 'Bitte bestätige die Löschung über die Checkbox.',
|
||||
'gdpr_purge_customer_button' => 'Kunde komplett löschen',
|
||||
'gdpr_purge_customer_success' => 'Kunde wurde komplett gelöscht: :count Kundeneintrag/-einträge entfernt.',
|
||||
'gdpr_error_customer_has_orders' => 'Komplett löschen nicht möglich: Der Kunde hat bereits Bestellungen. Es ist nur eine DSGVO-Löschung möglich.',
|
||||
];
|
||||
/*
|
||||
{{ __('msg.') }}
|
||||
|
|
|
|||
|
|
@ -50,4 +50,8 @@ return [
|
|||
'gdpr_error_is_admin' => 'GDPR deletion not possible: The account has admin rights. Please remove the admin rights first.',
|
||||
'gdpr_error_active_abo' => 'GDPR deletion not possible: There is still an active subscription. Please cancel the subscription first.',
|
||||
'gdpr_error_customer_is_member' => 'GDPR deletion not possible: This customer record belongs to a consultant account. Please use the GDPR deletion in the user administration.',
|
||||
'delete_confirm_checkbox_missing' => 'Please confirm the deletion via the checkbox.',
|
||||
'gdpr_purge_customer_button' => 'Delete customer completely',
|
||||
'gdpr_purge_customer_success' => 'Customer deleted completely: :count customer record(s) removed.',
|
||||
'gdpr_error_customer_has_orders' => 'Complete deletion not possible: The customer already has orders. Only a GDPR deletion is possible.',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -50,4 +50,8 @@ return [
|
|||
'gdpr_error_is_admin' => 'Eliminación RGPD no posible: La cuenta tiene derechos de administrador. Por favor retira primero los derechos de administrador.',
|
||||
'gdpr_error_active_abo' => 'Eliminación RGPD no posible: Todavía existe una suscripción activa. Por favor cancela primero la suscripción.',
|
||||
'gdpr_error_customer_is_member' => 'Eliminación RGPD no posible: Este registro de cliente pertenece a una cuenta de asesor. Por favor utiliza la eliminación RGPD en la administración de usuarios.',
|
||||
'delete_confirm_checkbox_missing' => 'Confirma la eliminación mediante la casilla.',
|
||||
'gdpr_purge_customer_button' => 'Eliminar cliente por completo',
|
||||
'gdpr_purge_customer_success' => 'Cliente eliminado por completo: :count registro(s) de cliente eliminado(s).',
|
||||
'gdpr_error_customer_has_orders' => 'Eliminación completa no posible: el cliente ya tiene pedidos. Solo es posible una eliminación RGPD.',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -48,4 +48,8 @@ return [
|
|||
'gdpr_error_is_admin' => "Suppression RGPD impossible : Le compte dispose de droits d'administrateur. Veuillez d'abord retirer les droits d'administrateur.",
|
||||
'gdpr_error_active_abo' => "Suppression RGPD impossible : Un abonnement actif existe encore. Veuillez d'abord résilier l'abonnement.",
|
||||
'gdpr_error_customer_is_member' => "Suppression RGPD impossible : Cette fiche client appartient à un compte de conseiller. Veuillez utiliser la suppression RGPD dans l'administration des utilisateurs.",
|
||||
'delete_confirm_checkbox_missing' => 'Veuillez confirmer la suppression via la case à cocher.',
|
||||
'gdpr_purge_customer_button' => 'Supprimer complètement le client',
|
||||
'gdpr_purge_customer_success' => 'Client supprimé complètement : :count enregistrement(s) client supprimé(s).',
|
||||
'gdpr_error_customer_has_orders' => 'Suppression complète impossible : le client a déjà des commandes. Seule une suppression RGPD est possible.',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<a class="btn btn-sm btn-secondary mt-2" href="{{route('admin_customer_edit', [$shopping_user->id])}}">{{ __('customer.edit_customer_data') }}</a>
|
||||
<a class="btn btn-sm btn-danger mt-2" href="{{route('admin_customer_delete', [$shopping_user->id])}}" onclick="return confirm('{{ __('customer.delete_customer_confirm') }}')">{{ __('customer.delete_customer') }}</a>
|
||||
<button type="button" class="btn btn-sm btn-danger mt-2" data-toggle="modal" data-target="#modals-customer-delete">{{ __('customer.delete_customer') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
|
|
|
|||
|
|
@ -9,40 +9,102 @@
|
|||
@include('admin.customer._detail')
|
||||
<a href="{{route('admin_customers')}}" class="btn btn-sm btn-default mt-2 float-right">{{ __('back') }}</a>
|
||||
|
||||
@if (Auth::user()->isSuperAdmin() && !$shopping_user->auth_user_id)
|
||||
<button type="button" class="btn btn-sm btn-danger mt-2" data-toggle="modal"
|
||||
data-target="#modals-customer-gdpr-delete">{{ __('msg.gdpr_delete_customer_button') }}</button>
|
||||
{{-- Kunden löschen: ein Modal für beide Wege (dev/_tickets/2026-09-08.md, Ticket #2) --}}
|
||||
@php($canGdprDelete = Auth::user()->isSuperAdmin())
|
||||
<div class="modal fade" id="modals-customer-delete">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{{ __('customer.delete_customer') }}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@if ($deletion['is_member'])
|
||||
<div class="alert alert-warning mb-0">{{ __('msg.gdpr_error_customer_is_member') }}</div>
|
||||
@else
|
||||
<p>
|
||||
Der Kunde ist <b>{{ $deletion['rows']->count() }}-mal</b> angelegt
|
||||
@if ($deletion['email'])
|
||||
(E-Mail <b>{{ $deletion['email'] }}</b>, Kundennummer {{ $shopping_user->number ?: '-' }}).
|
||||
@else
|
||||
(Kundennummer {{ $shopping_user->number ?: '-' }}; keine gültige E-Mail zur Duplikat-Suche).
|
||||
@endif
|
||||
Alle folgenden Einträge werden gemeinsam behandelt:
|
||||
</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered" id="customer-delete-rows">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Kd-Nr.</th>
|
||||
<th>Name</th>
|
||||
<th>Berater</th>
|
||||
<th>Bestellungen</th>
|
||||
<th>Angelegt</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($deletion['rows'] as $row)
|
||||
<tr class="{{ $row->id === $shopping_user->id ? 'font-weight-bold' : '' }}">
|
||||
<td>{{ $row->id }}</td>
|
||||
<td>{{ $row->number ?: '-' }}</td>
|
||||
<td>{{ trim($row->billing_firstname.' '.$row->billing_lastname) }}
|
||||
@if ($row->deleted_at) <span class="badge badge-secondary">gelöscht</span> @endif
|
||||
</td>
|
||||
<td>{{ $row->member->email ?? '-' }}</td>
|
||||
<td>{{ $deletion['orders_by_row'][$row->id] ?? 0 }}</td>
|
||||
<td>{{ $row->created_at ? $row->created_at->format('d.m.Y') : '-' }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Modal DSGVO-Löschung -->
|
||||
<div class="modal fade" id="modals-customer-gdpr-delete">
|
||||
<div class="modal-dialog">
|
||||
<form class="modal-content" action="{{ route('admin_customer_gdpr_delete', [$shopping_user->id]) }}"
|
||||
method="post">
|
||||
@csrf
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{{ __('msg.gdpr_delete_customer_button') }}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Alle personenbezogenen Daten dieses Kunden werden <b>unwiderruflich anonymisiert</b>
|
||||
(alle Einträge mit der Kundennummer {{ $shopping_user->number }}).
|
||||
Bei Bestellungen mit Rechnung bleiben Name und Rechnungsadresse wegen der
|
||||
gesetzlichen Aufbewahrungspflichten erhalten; alle Kontaktdaten
|
||||
(E-Mail, Telefon, Notizen, Newsletter) werden entfernt.
|
||||
Aktive Abos müssen vorher gekündigt werden.</p>
|
||||
<label class="custom-control custom-checkbox m-0">
|
||||
<input type="checkbox" class="custom-control-input" name="gdpr_delete_customer" value="1">
|
||||
<span class="custom-control-label">DSGVO-Löschung unwiderruflich ausführen?</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ __('close') }}</button>
|
||||
<button type="submit"
|
||||
class="btn btn-danger">{{ __('msg.gdpr_delete_customer_button') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
@if ($deletion['orders_count'] > 0)
|
||||
<div class="alert alert-warning">
|
||||
Der Kunde hat schon <b>{{ $deletion['orders_count'] }} Bestellung(en)</b>. Es ist nur <b>DSGVO löschen</b> möglich:
|
||||
Alle personenbezogenen Daten werden unwiderruflich anonymisiert. Bei Einträgen mit Bestellung bleiben Name und
|
||||
Rechnungsadresse wegen der gesetzlichen Aufbewahrungspflichten erhalten; alle Kontaktdaten (E-Mail, Telefon,
|
||||
Notizen, Newsletter) sowie die Portal-Logins werden entfernt.
|
||||
@if ($deletion['active_abos'] > 0)
|
||||
<br><b>{{ __('msg.gdpr_error_active_abo') }}</b>
|
||||
@endif
|
||||
</div>
|
||||
@if ($canGdprDelete)
|
||||
<form action="{{ route('admin_customer_gdpr_delete', [$shopping_user->id]) }}" method="post" id="form-customer-gdpr-delete">
|
||||
@csrf
|
||||
<label class="custom-control custom-checkbox mb-3">
|
||||
<input type="checkbox" class="custom-control-input" name="gdpr_delete_customer" value="1">
|
||||
<span class="custom-control-label">DSGVO-Löschung unwiderruflich ausführen?</span>
|
||||
</label>
|
||||
<button type="submit" class="btn btn-danger" {{ $deletion['active_abos'] > 0 ? 'disabled' : '' }}>{{ __('msg.gdpr_delete_customer_button') }}</button>
|
||||
</form>
|
||||
@else
|
||||
<p class="text-muted mb-0">Die DSGVO-Löschung kann nur von einem Superadmin ausgeführt werden.</p>
|
||||
@endif
|
||||
@elseif ($deletion['active_abos'] > 0)
|
||||
<div class="alert alert-warning mb-0">{{ __('msg.gdpr_error_active_abo') }}</div>
|
||||
@else
|
||||
<div class="alert alert-info">
|
||||
Der Kunde hat <b>keine Bestellungen</b> und kann <b>komplett gelöscht</b> werden: Alle oben aufgeführten Einträge
|
||||
werden vollständig anonymisiert und entfernt, inklusive Portal-Login und inaktiver Abos.
|
||||
</div>
|
||||
<form action="{{ route('admin_customer_delete', [$shopping_user->id]) }}" method="post" id="form-customer-delete">
|
||||
@csrf
|
||||
<label class="custom-control custom-checkbox mb-3">
|
||||
<input type="checkbox" class="custom-control-input" name="delete_customer" value="1">
|
||||
<span class="custom-control-label">Kunden mit allen {{ $deletion['rows']->count() }} Einträgen unwiderruflich löschen?</span>
|
||||
</label>
|
||||
<button type="submit" class="btn btn-danger">{{ __('msg.gdpr_purge_customer_button') }}</button>
|
||||
</form>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ __('close') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
Loading…
Add table
Add a link
Reference in a new issue