*/ public function future(Lead $lead, ?string $exceptTask = null): Builder { return CrmTask::availableForWork()->where('tasks.lead_id', $lead->id)->where('tasks.due_at', '>', now()) ->when($exceptTask !== null, fn ($query) => $query->where('tasks.id', '!=', $exceptTask)); } /** Both write paths lock the dossier (when linked), lead, then task before checking. */ public function assertMayFinish(?Lead $lead, CrmTask $task, string $status): void { if ($lead !== null && ! $lead->trashed() && in_array($lead->status, ['new', 'qualified'], true) && in_array($status, ['done', 'cancelled'], true) && ! $this->future($lead, $task->id)->exists()) { throw ValidationException::withMessages(['status' => 'Crie a próxima acção com prazo futuro antes de encerrar a última tarefa de um acompanhamento activo. Tarefas num dossier encerrado não estão disponíveis.']); } } }