queuedThresholdMinutes > 0) { return $this->queuedThresholdMinutes; } return max(1, (int) config('tenantpilot.system_console.stuck_thresholds.queued_minutes', 15)); } public function runningThresholdMinutes(): int { if ($this->runningThresholdMinutes > 0) { return $this->runningThresholdMinutes; } return max(1, (int) config('tenantpilot.system_console.stuck_thresholds.running_minutes', 30)); } public function apply(Builder $query, ?CarbonImmutable $now = null): Builder { $now ??= CarbonImmutable::now(); $queuedCutoff = $now->subMinutes($this->queuedThresholdMinutes()); $runningCutoff = $now->subMinutes($this->runningThresholdMinutes()); return $query->where(function (Builder $stuckQuery) use ($queuedCutoff, $runningCutoff): void { $stuckQuery ->where(function (Builder $queuedQuery) use ($queuedCutoff): void { $queuedQuery ->where('status', OperationRunStatus::Queued->value) ->whereNull('started_at') ->where('created_at', '<=', $queuedCutoff); }) ->orWhere(function (Builder $runningQuery) use ($runningCutoff): void { $runningQuery ->where('status', OperationRunStatus::Running->value) ->where('started_at', '<=', $runningCutoff); }); }); } public function classify(OperationRun $run, ?CarbonImmutable $now = null): ?string { $now ??= CarbonImmutable::now(); if ($run->status === OperationRunStatus::Queued->value) { if ($run->started_at !== null) { return null; } if ($run->created_at === null) { return null; } return $run->created_at->lte($now->subMinutes($this->queuedThresholdMinutes())) ? OperationRunStatus::Queued->value : null; } if ($run->status === OperationRunStatus::Running->value && $run->started_at !== null) { return $run->started_at->lte($now->subMinutes($this->runningThresholdMinutes())) ? OperationRunStatus::Running->value : null; } return null; } }