Files
fotospiel-app/app/Console/Commands/ProcessTenantRetention.php

110 lines
3.5 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Jobs\AnonymizeAccount;
use App\Models\Tenant;
use App\Notifications\InactiveTenantDeletionWarning;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Notification;
class ProcessTenantRetention extends Command
{
protected $signature = 'tenants:retention-scan';
protected $description = 'Scans tenants for Inaktivität, verschickt Warnungen und startet Anonymisierungen.';
public function handle(): int
{
$warningThreshold = now()->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) {
foreach ($tenants as $tenant) {
$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);
}
}