with(['tenant.workspace']) ->find($this->backupScheduleId); if (! $schedule || ! $schedule->tenant) { return; } $operationRun = OperationRun::query()->create([ 'workspace_id' => (int) $schedule->tenant->workspace_id, 'tenant_id' => (int) $schedule->tenant_id, 'user_id' => null, 'initiator_name' => 'System', 'type' => 'backup_schedule_retention', 'status' => OperationRunStatus::Running->value, 'outcome' => OperationRunOutcome::Pending->value, 'run_identity_hash' => hash('sha256', (string) $schedule->tenant_id.':backup_schedule_retention:'.$schedule->id.':'.Str::uuid()->toString()), 'context' => [ 'backup_schedule_id' => (int) $schedule->id, ], 'started_at' => now(), ]); $keepLast = $schedule->retention_keep_last; $retentionFloor = 1; if ($schedule->tenant->workspace instanceof \App\Models\Workspace) { $resolvedFloor = $settingsResolver->resolveValue( workspace: $schedule->tenant->workspace, domain: 'backup', key: 'retention_min_floor', tenant: $schedule->tenant, ); if (is_numeric($resolvedFloor)) { $retentionFloor = max(1, (int) $resolvedFloor); } } if ($keepLast === null && $schedule->tenant->workspace instanceof \App\Models\Workspace) { $resolvedDefault = $settingsResolver->resolveValue( workspace: $schedule->tenant->workspace, domain: 'backup', key: 'retention_keep_last_default', tenant: $schedule->tenant, ); if (is_numeric($resolvedDefault)) { $keepLast = (int) $resolvedDefault; } } if (! is_numeric($keepLast)) { $keepLast = 30; } $keepLast = (int) $keepLast; if ($keepLast < $retentionFloor) { $keepLast = $retentionFloor; } /** @var Collection $keepBackupSetIds */ $keepBackupSetIds = OperationRun::query() ->where('tenant_id', (int) $schedule->tenant_id) ->where('type', 'backup_schedule_run') ->where('status', OperationRunStatus::Completed->value) ->where('context->backup_schedule_id', (int) $schedule->id) ->whereNotNull('context->backup_set_id') ->orderByDesc('completed_at') ->orderByDesc('id') ->limit($keepLast) ->get() ->map(fn (OperationRun $run): ?int => is_numeric(data_get($run->context, 'backup_set_id')) ? (int) data_get($run->context, 'backup_set_id') : null) ->filter(fn (?int $id): bool => is_int($id) && $id > 0) ->values(); /** @var Collection $allBackupSetIds */ $allBackupSetIds = OperationRun::query() ->where('tenant_id', (int) $schedule->tenant_id) ->where('type', 'backup_schedule_run') ->where('status', OperationRunStatus::Completed->value) ->where('context->backup_schedule_id', (int) $schedule->id) ->whereNotNull('context->backup_set_id') ->get() ->map(fn (OperationRun $run): ?int => is_numeric(data_get($run->context, 'backup_set_id')) ? (int) data_get($run->context, 'backup_set_id') : null) ->filter(fn (?int $id): bool => is_int($id) && $id > 0) ->unique() ->values(); /** @var Collection $deleteBackupSetIds */ $deleteBackupSetIds = $allBackupSetIds ->reject(fn (int $backupSetId): bool => $keepBackupSetIds->contains($backupSetId)) ->values(); $deletedCount = 0; if ($deleteBackupSetIds->isNotEmpty()) { BackupSet::query() ->where('tenant_id', $schedule->tenant_id) ->whereIn('id', $deleteBackupSetIds->all()) ->whereNull('deleted_at') ->chunkById(200, function (Collection $sets) use (&$deletedCount): void { foreach ($sets as $set) { $set->delete(); $deletedCount++; } }); } $operationRun->update([ 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::Succeeded->value, 'summary_counts' => [ 'total' => (int) $deleteBackupSetIds->count(), 'processed' => (int) $deleteBackupSetIds->count(), 'succeeded' => $deletedCount, 'failed' => max(0, (int) $deleteBackupSetIds->count() - $deletedCount), 'updated' => $deletedCount, ], 'completed_at' => now(), ]); $auditLogger->log( tenant: $schedule->tenant, action: 'backup_schedule.retention_applied', resourceType: 'backup_schedule', resourceId: (string) $schedule->id, status: 'success', context: [ 'metadata' => [ 'keep_last' => $keepLast, 'retention_floor' => $retentionFloor, 'deleted_backup_sets' => $deletedCount, 'operation_run_id' => (int) $operationRun->getKey(), ], ], ); } }