where('is_enabled', true) ->whereHas('tenant', fn ($query) => $query->where('status', 'active')) ->with('tenant'); if (is_array($tenantIdentifiers) && ! empty($tenantIdentifiers)) { $schedulesQuery->whereIn('tenant_id', $this->resolveTenantIds($tenantIdentifiers)); } $createdRuns = 0; $skippedRuns = 0; $scannedSchedules = 0; foreach ($schedulesQuery->cursor() as $schedule) { $scannedSchedules++; $slot = $this->scheduleTimeService->nextRunFor($schedule, $nowUtc->subMinute()); if ($slot === null) { $schedule->forceFill(['next_run_at' => null])->saveQuietly(); continue; } if ($slot->greaterThan($nowUtc)) { if (! $schedule->next_run_at || ! $schedule->next_run_at->equalTo($slot)) { $schedule->forceFill(['next_run_at' => $slot])->saveQuietly(); } continue; } $scheduledFor = $slot->startOfMinute(); $operationRun = $this->operationRunService->ensureRunWithIdentityStrict( tenant: $schedule->tenant, type: OperationRunType::BackupScheduleExecute->value, identityInputs: [ 'backup_schedule_id' => (int) $schedule->id, 'scheduled_for' => $scheduledFor->toDateTimeString(), ], context: [ 'backup_schedule_id' => (int) $schedule->id, 'scheduled_for' => $scheduledFor->toDateTimeString(), 'trigger' => 'scheduled', ], ); if (! $operationRun->wasRecentlyCreated) { $skippedRuns++; Log::debug('Backup schedule operation already dispatched for slot.', [ 'schedule_id' => $schedule->id, 'scheduled_for' => $scheduledFor->toDateTimeString(), 'operation_run_id' => $operationRun->getKey(), ]); $schedule->forceFill([ 'next_run_at' => $this->scheduleTimeService->nextRunFor($schedule, $nowUtc), ])->saveQuietly(); continue; } $createdRuns++; $this->auditLogger->log( tenant: $schedule->tenant, action: 'backup_schedule.run_dispatched', context: [ 'metadata' => [ 'backup_schedule_id' => $schedule->id, 'operation_run_id' => $operationRun->getKey(), 'scheduled_for' => $scheduledFor->toDateTimeString(), ], ], resourceType: 'operation_run', resourceId: (string) $operationRun->getKey(), status: 'success' ); $schedule->forceFill([ 'next_run_at' => $this->scheduleTimeService->nextRunFor($schedule, $nowUtc), ])->saveQuietly(); Bus::dispatch(new RunBackupScheduleJob(0, $operationRun, (int) $schedule->id)); } return [ 'created_runs' => $createdRuns, 'skipped_runs' => $skippedRuns, 'scanned_schedules' => $scannedSchedules, ]; } /** * @param array $tenantIdentifiers * @return array */ private function resolveTenantIds(array $tenantIdentifiers): array { $tenantIds = []; foreach ($tenantIdentifiers as $identifier) { $tenant = Tenant::query() ->where('status', 'active') ->forTenant($identifier) ->first(); if ($tenant) { $tenantIds[] = $tenant->id; } } return array_values(array_unique($tenantIds)); } }