subMonths(23); $deletionThreshold = now()->subMonths(24); Tenant::query() ->whereNull('anonymized_at') ->with(['user']) ->withMax('events as last_event_activity', 'updated_at') ->withMax('purchases as last_purchase_activity', 'purchased_at') ->withMax('photos as last_photo_activity', 'created_at') ->chunkById(100, function ($tenants) use ($warningThreshold, $deletionThreshold) { $overrides = app(RetentionOverrideService::class); foreach ($tenants as $tenant) { if ($overrides->tenantOnHold($tenant)) { continue; } $lastActivity = $this->determineLastActivity($tenant); if (! $lastActivity) { $lastActivity = $tenant->created_at ?? now(); } if ($this->hasActiveSubscription($tenant)) { continue; } if ($lastActivity->lte($deletionThreshold)) { $this->dispatchAnonymization($tenant); continue; } if ($lastActivity->lte($warningThreshold) && $tenant->deletion_warning_sent_at === null) { $this->sendWarning($tenant, $lastActivity->copy()->addMonths(24)); } } }); $this->info(__('profile.retention.scan_complete')); return self::SUCCESS; } protected function determineLastActivity(Tenant $tenant): ?Carbon { $candidates = collect([ $tenant->last_activity_at, $tenant->last_event_activity ? Carbon::parse($tenant->last_event_activity) : null, $tenant->last_purchase_activity ? Carbon::parse($tenant->last_purchase_activity) : null, $tenant->last_photo_activity ? Carbon::parse($tenant->last_photo_activity) : null, ])->filter(); if ($candidates->isEmpty()) { return $tenant->created_at ? $tenant->created_at->copy() : null; } return $candidates->sort()->last(); } protected function hasActiveSubscription(Tenant $tenant): bool { return $tenant->tenantPackages() ->where('active', true) ->whereHas('package', fn ($query) => $query->where('type', 'reseller')) ->exists(); } protected function sendWarning(Tenant $tenant, Carbon $plannedDeletion): void { $email = $tenant->contact_email ?? $tenant->email ?? $tenant->user?->email; if (! $email) { return; } Notification::route('mail', $email) ->notify(new InactiveTenantDeletionWarning($tenant, $plannedDeletion)); $tenant->forceFill([ 'deletion_warning_sent_at' => now(), 'pending_deletion_at' => $plannedDeletion, ])->save(); } protected function dispatchAnonymization(Tenant $tenant): void { if ($tenant->anonymized_at) { return; } AnonymizeAccount::dispatch($tenant->user?->id, $tenant->id); } }