Kunden loeschen: DSGVO-geloeschte Kunden ausblenden und sperren; Checkout-Herkunft freiwillig

Kunden loeschen (Rueckmeldung "es wird nichts geloescht"):
- DSGVO-geloeschte Eintraege blieben mit Name in den Listen stehen; Kunde
  61009 wurde deshalb achtmal geloescht, bei Eintrag 45013 wurde die E-Mail
  danach ueber ein offenes Bearbeiten-Formular zurueckgeschrieben
- neue Spalte shopping_users.gdpr_deleted_at (Bestand per Migration markiert)
- Admin-Kundenliste blendet geloeschte Kunden aus (Filter: ausblenden /
  mit anzeigen / nur geloeschte), Berater-Kundenliste immer
- Bearbeiten/Speichern im Admin und beim Berater gesperrt; ShoppingUser
  verweigert das Zurueckschreiben von E-Mail und Telefon
- Modal und Erfolgsmeldung (de/en/es/fr) sagen klar, dass Name und
  Rechnungsadresse wegen der Aufbewahrungspflicht bleiben
- gdpr:repair-customers [--execute] zieht protokollierte Loeschungen nach
- DSGVO-Loeschung prueft Superadmin serverseitig; Berater duerfen eine
  Kunden-E-Mail nur beim eigenen Kunden nachtragen
- setFakerMail setzt faker_mail bei geloeschten Eintraegen nicht zurueck

Checkout: "Wie bist du auf uns aufmerksam geworden?" ist kein Pflichtfeld
mehr und wird farbig hervorgehoben.

Deploy: php artisan migrate && php artisan gdpr:repair-customers --execute

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kevin 2026-10-06 12:36:02 +00:00
parent 083d643220
commit 3d0864713b
19 changed files with 635 additions and 90 deletions

View file

@ -0,0 +1,50 @@
<?php
namespace App\Console\Commands;
use App\Services\GdprDeletionService;
use Illuminate\Console\Command;
class GdprRepairCustomers extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'gdpr:repair-customers
{--execute : Änderungen wirklich ausführen (ohne Option nur Vorschau)}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Zieht protokollierte DSGVO-Löschungen von Endkunden nach: Einträge, deren Kontaktdaten nach der Löschung wieder befüllt wurden, werden erneut anonymisiert und gesperrt.';
/**
* Execute the console command.
*/
public function handle(GdprDeletionService $gdprDeletionService): int
{
$execute = (bool) $this->option('execute');
$rows = $gdprDeletionService->repairLoggedShoppingUserDeletions(! $execute);
if ($rows === []) {
$this->info('Keine Einträge zu reparieren.');
return 0;
}
$this->table(['ShoppingUser-ID', 'Kd-Nr.', 'Löschung protokolliert am'], array_map(
fn (array $row) => [$row['id'], $row['number'] ?: '-', $row['deletion_logged_at']],
$rows
));
$this->info($execute
? count($rows).' Eintrag/Einträge erneut anonymisiert und gesperrt.'
: count($rows).' Eintrag/Einträge betroffen. Vorschau – mit --execute ausführen.');
return 0;
}
}

View file

@ -10,6 +10,7 @@ use App\Services\GdprDeletionService;
use App\Services\HTMLHelper;
use App\Services\Shop;
use Auth;
use Illuminate\Http\RedirectResponse;
use Request;
use Validator;
@ -29,6 +30,7 @@ class CustomerController extends Controller
if (Request::get('reset') === 'filter') {
set_user_attr('filter_member_id', null);
set_user_attr('filter_customer_member', null);
set_user_attr('filter_gdpr_deleted', null);
return redirect(route('admin_customers'));
}
@ -82,6 +84,10 @@ class CustomerController extends Controller
{
$shopping_user = ShoppingUser::withTrashed()->findOrFail($id);
if (! Auth::user()->isSuperAdmin()) {
abort(403);
}
if (! Request::get('gdpr_delete_customer')) {
\Session()->flash('alert-error', __('msg.gdpr_confirm_checkbox_missing'));
@ -89,8 +95,8 @@ class CustomerController extends Controller
}
try {
app(GdprDeletionService::class)->deleteShoppingUser($shopping_user, Auth::id());
\Session()->flash('alert-success', __('msg.gdpr_delete_customer_success'));
$summary = app(GdprDeletionService::class)->deleteShoppingUser($shopping_user, Auth::id());
\Session()->flash('alert-success', __('msg.gdpr_delete_customer_success', ['count' => $summary['shopping_users_anonymized']]));
} catch (GdprDeletionException $e) {
\Session()->flash('alert-error', $e->getMessage());
@ -107,6 +113,9 @@ class CustomerController extends Controller
$shopping_user->id = 'new';
} else {
$shopping_user = ShoppingUser::findOrFail($id);
if ($shopping_user->isGdprDeleted()) {
return $this->redirectGdprLocked($shopping_user);
}
}
$data = [
'shopping_user' => $shopping_user,
@ -168,6 +177,9 @@ class CustomerController extends Controller
}
$shopping_user = ShoppingUser::findOrFail($id);
if ($shopping_user->isGdprDeleted()) {
return $this->redirectGdprLocked($shopping_user);
}
$data['language'] = isset($data['language']) ? $data['language'] : \App::getLocale();
$data['has_buyed'] = isset($data['has_buyed']) ? true : false;
@ -196,6 +208,13 @@ class CustomerController extends Controller
{
$query = ShoppingUser::select('shopping_users.*')->where('auth_user_id', '=', null);
set_user_attr('filter_gdpr_deleted', Request::get('filter_gdpr_deleted'));
if (Request::get('filter_gdpr_deleted') === 'only') {
$query->whereNotNull('gdpr_deleted_at');
} elseif (Request::get('filter_gdpr_deleted') !== 'all') {
$query->withoutGdprDeleted();
}
set_user_attr('filter_member_id', Request::get('filter_member_id'));
if (Request::get('filter_member_id') != '') {
$query->where('member_id', '=', Request::get('filter_member_id'));
@ -224,7 +243,8 @@ class CustomerController extends Controller
return $ShoppingUser->billing_country ? $ShoppingUser->billing_country->getLocated() : '';
})
->addColumn('isMember', function (ShoppingUser $ShoppingUser) {
return get_active_badge($ShoppingUser->auth_user_id).($ShoppingUser->mode === 'dev' ? ' <span class="badge badge-warning">dev</span>' : '');
return get_active_badge($ShoppingUser->auth_user_id).($ShoppingUser->mode === 'dev' ? ' <span class="badge badge-warning">dev</span>' : '')
.($ShoppingUser->isGdprDeleted() ? ' <span class="badge badge-secondary">DSGVO-gelöscht</span>' : '');
})
->addColumn('member_id', function (ShoppingUser $ShoppingUser) {
if ($ShoppingUser->is_like) {
@ -263,4 +283,11 @@ class CustomerController extends Controller
->rawColumns(['id', 'subscribed', 'isMember', 'member_id'])
->make(true);
}
protected function redirectGdprLocked(ShoppingUser $shopping_user): RedirectResponse
{
\Session()->flash('alert-error', __('msg.gdpr_customer_locked'));
return redirect(route('admin_customer_detail', [$shopping_user->id]));
}
}

View file

@ -11,6 +11,7 @@ use App\Services\HTMLHelper;
use App\Services\Shop;
use App\Services\ShoppingUserService;
use App\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\DB;
use Request;
use Validator;
@ -61,6 +62,9 @@ class CustomerController extends Controller
if ($shopping_user->member_id != \Auth::user()->id) {
abort(404);
}
if ($shopping_user->isGdprDeleted()) {
return $this->redirectGdprLocked($shopping_user);
}
$data = [
'shopping_user' => $shopping_user,
'isAdmin' => false,
@ -81,6 +85,9 @@ class CustomerController extends Controller
if ($shopping_user->member_id != \Auth::user()->id) {
abort(404);
}
if ($shopping_user->isGdprDeleted()) {
return $this->redirectGdprLocked($shopping_user);
}
}
$billing_email = null;
@ -142,6 +149,12 @@ class CustomerController extends Controller
if ($action === 'save') {
$shopping_user = ShoppingUser::findOrFail($id);
if ($shopping_user->member_id != \Auth::user()->id) {
abort(404);
}
if ($shopping_user->isGdprDeleted()) {
return $this->redirectGdprLocked($shopping_user);
}
$shopping_user->faker_mail = false;
$shopping_user->billing_email = Request::get($email);
$shopping_user->save();
@ -213,6 +226,9 @@ class CustomerController extends Controller
if ($shopping_user->member_id != \Auth::user()->id) {
abort(404);
}
if ($shopping_user->isGdprDeleted()) {
return $this->redirectGdprLocked($shopping_user);
}
CustomerPriority::checkChangeOne($shopping_user, $data, true);
$shopping_user->fill($data);
$shopping_user->save();
@ -261,7 +277,8 @@ class CustomerController extends Controller
$query->select(DB::raw('MAX(id)'))
->from('shopping_users')
->groupBy('billing_email');
})->where('shopping_users.member_id', '=', $user->id)->where('shopping_users.auth_user_id', '=', null);
})->where('shopping_users.member_id', '=', $user->id)->where('shopping_users.auth_user_id', '=', null)
->withoutGdprDeleted();
if (Request::get('isfor') === 'ot-member') { // Bestellung für Kunden
}
@ -327,4 +344,11 @@ class CustomerController extends Controller
->rawColumns(['send_to', 'id', 'subscribed', 'extras', 'status'])
->make(true);
}
protected function redirectGdprLocked(ShoppingUser $shopping_user): RedirectResponse
{
\Session()->flash('alert-error', __('msg.gdpr_customer_locked'));
return redirect(route('user_customer_detail', [$shopping_user->id]));
}
}

View file

@ -273,7 +273,7 @@ class CheckoutController extends Controller
];
if (Request::get('is_from') === 'shopping') {
$rules['customer_order_source_comment'] = 'required|string|max:500';
$rules['customer_order_source_comment'] = 'nullable|string|max:500';
}
if (Request::get('same_as_billing')) {
@ -287,9 +287,7 @@ class CheckoutController extends Controller
]);
}
return Validator::make(Request::all(), $rules, [
'customer_order_source_comment.required' => __('order.source_comment_required'),
]);
return Validator::make(Request::all(), $rules);
}
/**

View file

@ -2,8 +2,10 @@
namespace App\Models;
use App\Exceptions\GdprDeletionException;
use App\Services\ShoppingUserService;
use App\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
@ -122,6 +124,7 @@ use Illuminate\Support\Carbon;
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ShoppingUser whereMode($value)
*
* @property bool|null $faker_mail
* @property Carbon|null $gdpr_deleted_at
* @property string|null $shipping_email
* @property string|null $is_for
* @property string|null $is_from
@ -215,8 +218,57 @@ class ShoppingUser extends Model
'has_buyed' => 'bool',
'subscribed' => 'bool',
'wp_order_number' => 'int',
'gdpr_deleted_at' => 'datetime',
];
/**
* Kontaktfelder, die nach einer DSGVO-Löschung nicht wieder befüllt werden dürfen.
*
* @var array<int, string>
*/
public const GDPR_LOCKED_CONTACT_FIELDS = ['billing_email', 'billing_phone', 'shipping_email', 'shipping_phone'];
/**
* Schutz gegen das Zurückschreiben von Kontaktdaten in DSGVO-gelöschte Einträge,
* z. B. durch ein noch offenes Bearbeiten-Formular (dev/_tickets/2026-10-06).
*/
protected static function booted(): void
{
static::updating(function (ShoppingUser $shoppingUser) {
if ($shoppingUser->getOriginal('gdpr_deleted_at') === null) {
return;
}
foreach (self::GDPR_LOCKED_CONTACT_FIELDS as $field) {
if ($shoppingUser->isDirty($field) && ! self::isGdprPlaceholderValue($field, $shoppingUser->{$field})) {
throw new GdprDeletionException(__('msg.gdpr_customer_locked'));
}
}
});
}
protected static function isGdprPlaceholderValue(string $field, mixed $value): bool
{
if ($value === null || $value === '') {
return true;
}
return $field === 'billing_email' && str_ends_with((string) $value, '@dsgvo.invalid');
}
public function isGdprDeleted(): bool
{
return $this->gdpr_deleted_at !== null;
}
/**
* @param Builder<ShoppingUser> $query
*/
public function scopeWithoutGdprDeleted($query): void
{
$query->whereNull($query->qualifyColumn('gdpr_deleted_at'));
}
// can null
public function member()
{

View file

@ -4,6 +4,7 @@ namespace App\Services;
use App\Exceptions\GdprDeletionException;
use App\Models\Customer;
use App\Models\Logger;
use App\Models\ShoppingOrder;
use App\Models\ShoppingUser;
use App\Models\UserAbo;
@ -13,6 +14,7 @@ use App\Models\UserShop;
use App\Models\UserUpdateEmail;
use App\Services\Payment\Stripe\StripeCustomerService;
use App\User;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
@ -220,7 +222,10 @@ class GdprDeletionService
* orders_count: int,
* orders_by_row: array<int, int>,
* active_abos: int,
* can_purge: bool
* can_purge: bool,
* gdpr_deleted_rows: int,
* gdpr_deleted_at: Carbon|null,
* all_gdpr_deleted: bool
* }
*/
public function analyzeShoppingUser(ShoppingUser $shoppingUser): array
@ -239,6 +244,7 @@ class GdprDeletionService
$isMember = $rows->contains(fn (ShoppingUser $row) => (int) $row->auth_user_id > 0);
$ordersCount = array_sum($ordersByRow);
$activeAbos = UserAbo::whereIn('shopping_user_id', $rowIds)->where('active', true)->count();
$gdprDeletedRows = $rows->filter(fn (ShoppingUser $row) => $row->isGdprDeleted());
return [
'rows' => $rows,
@ -248,9 +254,76 @@ class GdprDeletionService
'orders_by_row' => $ordersByRow,
'active_abos' => $activeAbos,
'can_purge' => ! $isMember && $ordersCount === 0 && $activeAbos === 0,
'gdpr_deleted_rows' => $gdprDeletedRows->count(),
'gdpr_deleted_at' => $gdprDeletedRows->max('gdpr_deleted_at'),
'all_gdpr_deleted' => $rows->isNotEmpty() && $gdprDeletedRows->count() === $rows->count(),
];
}
/**
* Protokollierte DSGVO-Löschungen von Endkunden nachziehen: Einträge, die vor
* der Löschung angelegt waren und deren Kontaktdaten danach wieder befüllt
* wurden (z. B. durch ein offenes Bearbeiten-Formular), werden erneut
* anonymisiert und markiert. Später angelegte Einträge (neue Bestellung nach
* der Löschung) bleiben unberührt.
*
* @return array<int, array{id: int, number: int|null, deletion_logged_at: string}>
*/
public function repairLoggedShoppingUserDeletions(bool $dryRun = true): array
{
$deletions = Logger::query()
->whereIn('action', ['gdpr-delete-customer', 'gdpr-purge-customer'])
->where('model', ShoppingUser::class)
->selectRaw('model_id, MAX(created_at) as deleted_at')
->groupBy('model_id')
->get();
$repaired = [];
foreach ($deletions as $deletion) {
$target = ShoppingUser::withTrashed()->find($deletion->model_id);
if (! $target || (int) $target->auth_user_id > 0) {
continue;
}
$rows = ShoppingUser::withTrashed()
->where(function ($query) use ($target) {
$query->where('id', $target->id);
if ($target->number > 0) {
$query->orWhere('number', $target->number);
}
})
->where(function ($query) {
$query->whereNull('auth_user_id')->orWhere('auth_user_id', 0);
})
->where('created_at', '<=', $deletion->deleted_at)
->get()
->filter(fn (ShoppingUser $row) => ! $row->isGdprDeleted() || ! str_ends_with((string) $row->billing_email, '@dsgvo.invalid'))
->reject(fn (ShoppingUser $row) => isset($repaired[$row->id]));
if ($rows->isEmpty()) {
continue;
}
foreach ($rows as $row) {
$repaired[$row->id] = [
'id' => $row->id,
'number' => $row->number,
'deletion_logged_at' => (string) $deletion->deleted_at,
];
}
if (! $dryRun) {
DB::transaction(fn () => $this->anonymizeShoppingUserRows($rows));
$this->logDeletion('gdpr-repair-customer', $target->id, ShoppingUser::class, null, null, [
'shopping_user_id' => $target->id,
'shopping_users_anonymized' => $rows->count(),
]);
}
}
return array_values($repaired);
}
/**
* Alle Datensätze eines Endkunden, inkl. soft-deleted: der Eintrag selbst,
* alle Einträge mit derselben Kundennummer und alle Endkunden-Einträge mit
@ -494,6 +567,7 @@ class GdprDeletionService
$row->notice = [];
$row->subscribed = false;
$row->faker_mail = true;
$row->gdpr_deleted_at ??= now();
$row->save();
}

View file

@ -1,78 +1,82 @@
<?php
namespace App\Services;
use App\Models\ShoppingOrder;
use App\Models\ShoppingUser;
use App\User;
use Yard;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\DB;
class ShoppingUserService
{
/**
* Gibt alle Bestellungen eines Mitglieds zurück
*
* @param ShoppingUser $shopping_user Das Mitglied, dessen Bestellungen zurückgegeben werden sollen
*
* @param ShoppingUser $shopping_user Das Mitglied, dessen Bestellungen zurückgegeben werden sollen
* @return Collection Die Bestellungen des Mitglieds
*/
public static function getAllOrdersByMember($shopping_user){
public static function getAllOrdersByMember($shopping_user)
{
$users = ShoppingUser::where('billing_email', '=', $shopping_user->billing_email)->where('member_id', '=', $shopping_user->member_id)->get();
return $users->flatMap(function($user) {
return $users->flatMap(function ($user) {
return $user->shopping_orders;
})->sortByDesc('created_at');
})->sortByDesc('created_at');
}
public static function syncOrdersByEmail($user)
{
$maxAttempts = 3;
$attempt = 1;
while ($attempt <= $maxAttempts) {
try {
DB::beginTransaction();
$shopping_user = ShoppingUser::where('member_id', $user->id)
->first();
if (!$shopping_user) {
if (! $shopping_user) {
DB::commit();
return;
}
$shopping_users = ShoppingUser::where('billing_email', '=', $shopping_user->billing_email)
->whereHas('shopping_order', function($q) {
->whereHas('shopping_order', function ($q) {
$q->where('txaction', 'paid')
->orWhere('txaction', 'appointed')
->orWhere('txaction', 'extern')
->orWhere('txaction', 'invoice_open')
->orWhere('txaction', 'invoice_paid');
->orWhere('txaction', 'appointed')
->orWhere('txaction', 'extern')
->orWhere('txaction', 'invoice_open')
->orWhere('txaction', 'invoice_paid');
})
->lockForUpdate()
->get();
$order_count = $shopping_users->count();
foreach($shopping_users as $user) {
foreach ($shopping_users as $user) {
$user->orders = $order_count;
$user->save();
}
DB::commit();
return; // Erfolgreicher Fall
} catch (QueryException $e) {
DB::rollBack();
// Wenn es ein Deadlock ist, versuchen wir es erneut
if ($e->getCode() == 40001 && $attempt < $maxAttempts) {
$sleepTime = pow(2, $attempt) * 100000; // Exponentielles Backoff in Mikrosekunden
usleep($sleepTime);
$attempt++;
continue;
}
throw $e; // Andere Fehler oder zu viele Versuche
}
}
@ -91,52 +95,55 @@ class ShoppingUserService
}*/
/**
* Synchronisiert die Bestellungen eines ShoppingOrders mit den Bestellungen des zugehörigen ShoppingUsers
*
* @param ShoppingOrder $shopping_order Der zu synchronisierende ShoppingOrder
*/
public static function snycOrdersByShoppingOrder(ShoppingOrder $shopping_order) {
*
* @param ShoppingOrder $shopping_order Der zu synchronisierende ShoppingOrder
*/
public static function snycOrdersByShoppingOrder(ShoppingOrder $shopping_order)
{
$shopping_user = $shopping_order->shopping_user ? $shopping_order->shopping_user : null;
if($shopping_user){
if ($shopping_user) {
self::snycOrdersByShoppingUser($shopping_user);
}
}
/**
* Synchronisiert die Bestellungen eines ShoppingUsers mit den Bestellungen des zugehörigen Benutzers
*
* @param ShoppingUser $shopping_user Der zu synchronisierende ShoppingUser
*/
public static function snycOrdersByShoppingUser(ShoppingUser $shopping_user) {
*
* @param ShoppingUser $shopping_user Der zu synchronisierende ShoppingUser
*/
public static function snycOrdersByShoppingUser(ShoppingUser $shopping_user)
{
// Aktualisiere alle Benutzer mit einer einzigen Datenbankabfrage
ShoppingUser::where('billing_email', $shopping_user->billing_email)
->update([
'orders' => ShoppingUser::where('billing_email', $shopping_user->billing_email)
->whereHas('shopping_order', function($q) {
$q->whereIn('txaction', ['paid', 'appointed', 'extern', 'invoice_open', 'invoice_paid']);
})
->count()
]);
->update([
'orders' => ShoppingUser::where('billing_email', $shopping_user->billing_email)
->whereHas('shopping_order', function ($q) {
$q->whereIn('txaction', ['paid', 'appointed', 'extern', 'invoice_open', 'invoice_paid']);
})
->count(),
]);
}
public static function snycOrdersByNumber($number){
if($number <= 0) {
public static function snycOrdersByNumber($number)
{
if ($number <= 0) {
return;
}
// Aktualisiere alle Benutzer mit einer einzigen Datenbankabfrage
ShoppingUser::where('number', '=', $number)
->update([
'orders' => ShoppingUser::where('number', '=', $number)
->whereHas('shopping_order', function($q) {
$q->whereIn('txaction', ['paid', 'appointed', 'extern', 'invoice_open', 'invoice_paid']);
})
->count()
]);
->update([
'orders' => ShoppingUser::where('number', '=', $number)
->whereHas('shopping_order', function ($q) {
$q->whereIn('txaction', ['paid', 'appointed', 'extern', 'invoice_open', 'invoice_paid']);
})
->count(),
]);
// Setze orders auf NULL für alle anderen
ShoppingUser::where('number', '=', $number)
->whereDoesntHave('shopping_order', function($q) {
->whereDoesntHave('shopping_order', function ($q) {
$q->whereIn('txaction', ['paid', 'appointed', 'extern', 'extern_paid']);
})
->update(['orders' => null]);
@ -144,22 +151,23 @@ class ShoppingUserService
/**
* Synchronisiert die Nummern aller ShoppingUser eines Benutzers anhand der E-Mails
*
* @param User $user Der Benutzer, dessen ShoppingUser synchronisiert werden sollen
*/
public static function syncNumbersByEmail($user) {
*
* @param User $user Der Benutzer, dessen ShoppingUser synchronisiert werden sollen
*/
public static function syncNumbersByEmail($user)
{
// Hole alle ShoppingUser mit E-Mail gruppiert
$emailGroups = ShoppingUser::where('member_id', $user->id)
->whereNotNull('billing_email')
->where('billing_email', '!=', '')
->get()
->groupBy('billing_email');
foreach($emailGroups as $email => $users) {
foreach ($emailGroups as $email => $users) {
// Suche ob es bereits eine Nummer für diese E-Mail gibt
$existingNumber = ($users->whereNotNull('number')->first()) ? $users->whereNotNull('number')->first()->number : null;
if($existingNumber) {
if ($existingNumber) {
// Wenn eine Nummer existiert, nutze diese für alle User mit der E-Mail
ShoppingUser::where('member_id', $user->id)
->where('billing_email', $email)
@ -169,20 +177,21 @@ class ShoppingUserService
// Wenn keine Nummer existiert, generiere eine neue
$maxNumber = ShoppingUser::max('number') ?: 1000;
$newNumber = $maxNumber + 1;
ShoppingUser::where('member_id', $user->id)
->where('billing_email', $email)
->update(['number' => $newNumber]);
}
}
}
/**
* Setzt alle Faker-E-Mails für einen Benutzer auf 1
*
* @param User $user Der Benutzer, dessen Faker-E-Mails gesetzt werden sollen
*/
public static function setFakerMail($user){
*
* @param User $user Der Benutzer, dessen Faker-E-Mails gesetzt werden sollen
*/
public static function setFakerMail($user)
{
// Aktualisiere alle Faker-E-Mails für den Benutzer in einem einzigen Update
// Schneller da nur ein einzelner SQL Query ausgeführt wird
ShoppingUser::where('member_id', $user->id)
@ -193,6 +202,7 @@ class ShoppingUserService
// Setze alle anderen auf NULL zurück
ShoppingUser::where('member_id', $user->id)
->where('billing_email', 'NOT LIKE', '%faker@mivita.care')
->whereNull('gdpr_deleted_at')
->update(['faker_mail' => null]);
}
}
}