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:
parent
083d643220
commit
3d0864713b
19 changed files with 635 additions and 90 deletions
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue