find($this->bulkRunId); if (! $run || $run->status !== 'pending') { return; } $service->start($run); $itemCount = 0; $succeeded = 0; $failed = 0; $skipped = 0; $skipReasons = []; $chunkSize = 10; $totalItems = $run->total_items ?: count($run->item_ids ?? []); $failureThreshold = (int) floor($totalItems / 2); foreach (($run->item_ids ?? []) as $backupSetId) { $itemCount++; try { /** @var BackupSet|null $backupSet */ $backupSet = BackupSet::withTrashed() ->where('tenant_id', $run->tenant_id) ->whereKey($backupSetId) ->first(); if (! $backupSet) { $service->recordFailure($run, (string) $backupSetId, 'Backup set not found'); $failed++; if ($failed > $failureThreshold) { $service->abort($run, 'Circuit breaker: more than 50% of items failed.'); if ($run->user) { Notification::make() ->title('Bulk Force Delete Aborted') ->body('Circuit breaker triggered: too many failures (>50%).') ->icon('heroicon-o-exclamation-triangle') ->danger() ->sendToDatabase($run->user) ->send(); } return; } continue; } if (! $backupSet->trashed()) { $service->recordSkippedWithReason($run, (string) $backupSet->id, 'Not archived'); $skipped++; $skipReasons['Not archived'] = ($skipReasons['Not archived'] ?? 0) + 1; continue; } if ($backupSet->restoreRuns()->withTrashed()->exists()) { $service->recordSkippedWithReason($run, (string) $backupSet->id, 'Referenced by restore runs'); $skipped++; $skipReasons['Referenced by restore runs'] = ($skipReasons['Referenced by restore runs'] ?? 0) + 1; continue; } $backupSet->items()->withTrashed()->forceDelete(); $backupSet->forceDelete(); $service->recordSuccess($run); $succeeded++; } catch (Throwable $e) { $service->recordFailure($run, (string) $backupSetId, $e->getMessage()); $failed++; if ($failed > $failureThreshold) { $service->abort($run, 'Circuit breaker: more than 50% of items failed.'); if ($run->user) { Notification::make() ->title('Bulk Force Delete Aborted') ->body('Circuit breaker triggered: too many failures (>50%).') ->icon('heroicon-o-exclamation-triangle') ->danger() ->sendToDatabase($run->user) ->send(); } return; } } if ($itemCount % $chunkSize === 0) { $run->refresh(); } } $service->complete($run); if (! $run->user) { return; } $message = "Force deleted {$succeeded} backup sets"; if ($skipped > 0) { $message .= " ({$skipped} skipped)"; } if ($failed > 0) { $message .= " ({$failed} failed)"; } if (! empty($skipReasons)) { $summary = collect($skipReasons) ->sortDesc() ->map(fn (int $count, string $reason) => "{$reason} ({$count})") ->take(3) ->implode(', '); if ($summary !== '') { $message .= " Skip reasons: {$summary}."; } } $message .= '.'; Notification::make() ->title('Bulk Force Delete Completed') ->body($message) ->icon('heroicon-o-check-circle') ->success() ->sendToDatabase($run->user) ->send(); } }