ErasureRequestStatus::class, 'scheduled_for' => 'datetime', 'cancelled_at' => 'datetime', 'completed_at' => 'datetime', 'report' => 'array', ]; public function subject(): MorphTo { return $this->morphTo(__FUNCTION__, 'subject_type', 'subject_id'); } public function requestedBy(): MorphTo { return $this->morphTo(__FUNCTION__, 'requested_by_type', 'requested_by_id'); } /** * The User erasure request that caused this one to be auto-created, if any — * see Modules\Core\Privacy\Listeners\CascadeCustomerErasureListener. */ public function causedBy(): BelongsTo { return $this->belongsTo(self::class, 'caused_by_request_id'); } /** * Every Customer erasure request THIS request caused (see causedBy()) — * used by CancelErasureOnLoginListener to revert exactly the cascade this * User's own cancellation should undo. */ public function caused(): HasMany { return $this->hasMany(self::class, 'caused_by_request_id'); } public function isForCustomer(): bool { return $this->subject_type === (new Customer)->getMorphClass(); } public function isPending(): bool { return $this->status === ErasureRequestStatus::Pending; } public function isDue(): bool { return $this->isPending() && $this->scheduled_for->isPast(); } /** * A human-readable label for whichever record `$morphable` resolves to * (Customer, User, or Staff — the three concrete types that appear across * subject/requestedBy), since each uses a different name field and there's * no shared interface for it. Falls back to "#id" if the record is gone * (e.g. a Customer erased since this request completed) or the relation is * simply empty. */ public static function displayNameFor(mixed $morphable): string { if (! $morphable) { return '—'; } return match (true) { isset($morphable->full_name) => $morphable->full_name, isset($morphable->name) => $morphable->name, isset($morphable->first_name) => trim("{$morphable->first_name} {$morphable->last_name}"), default => "#{$morphable->getKey()}", }; } }