117 lines
3.7 KiB
PHP
117 lines
3.7 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Jobs\AnonymizeAccount;
|
|
use App\Models\Tenant;
|
|
use App\Notifications\InactiveTenantDeletionWarning;
|
|
use App\Services\Compliance\RetentionOverrideService;
|
|
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) {
|
|
$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);
|
|
}
|
|
}
|