From c9466b9d2c9ccb36c5625a89bc487f7bf9a6f113 Mon Sep 17 00:00:00 2001 From: lukibeg Date: Sun, 21 Dec 2025 14:25:20 -0300 Subject: [PATCH] fix: Status de agentes V0.1. --- .../Controllers/Api/AmiEventController.php | 61 +++++++++++-------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/app/Http/Controllers/Api/AmiEventController.php b/app/Http/Controllers/Api/AmiEventController.php index 835ee43..01ec229 100644 --- a/app/Http/Controllers/Api/AmiEventController.php +++ b/app/Http/Controllers/Api/AmiEventController.php @@ -30,8 +30,8 @@ public function handle(Request $request) // Tenta achar a interface. Se for MemberStatus, vem em 'Interface'. Se for AgentConnect, vem em 'Interface' ou 'MemberName' $interface = $data['Interface'] ?? $data['MemberName'] ?? null; - // Auto-discovery de Filas e Agentes - if ($queueNumber) { + // 1. Auto-discovery (Com proteção para não salvar a fila fake do Auto-Hangup) + if ($queueNumber && $queueNumber !== 'Auto-Hangup') { $this->saveQueues($queueNumber, $tenant); } if ($interface) { @@ -42,7 +42,19 @@ public function handle(Request $request) return response()->json(['status' => 'ignored']); } - // Recupera Fila + // 2. Busca o Agente (Movi para cima, pois precisamos dele para o AgentFinished) + $agent = $this->findAgent($tenant->id, $data); + + // 3. INTERCEPTADOR ANTI-FANTASMA + // Processa o AgentFinished ANTES de validar a fila, pois 'Auto-Hangup' não existe no BD + if ($eventName === 'AgentFinished') { + if ($agent) { + $this->handleFinished($agent, $tenant->id); + } + return response()->json(['status' => 'fixed']); + } + + // 4. Recupera Fila (Fluxo normal) $queue = Queue::where('tenant_id', $tenant->id) ->where('source_id', $queueNumber) ->first(); @@ -51,21 +63,16 @@ public function handle(Request $request) return response()->json(['error' => 'Fila não encontrada'], 404); } - $agent = $this->findAgent($tenant->id, $data); - - switch ($eventName) { case 'QueueCallerJoin': $this->handleJoin($queue, $data); break; case 'AgentConnect': - // Foco: Mudar para 'talking' e atualizar métricas $this->handleConnect($queue, $data); break; case 'AgentComplete': - // Foco: Encerrar contagem de tempo de fala $this->handleComplete($queue, $data); break; @@ -74,7 +81,6 @@ public function handle(Request $request) break; case 'QueueMemberPause': - // Evento explícito de pausa (Alguém clicou no botão de pausa) if ($agent) $this->handlePause($agent, $data, $tenant->id); break; @@ -83,7 +89,6 @@ public function handle(Request $request) break; case 'QueueMemberStatus': - // Foco: Atualizar Presença (Offline, Disponível, Pausado, Busy) if ($agent) $this->handleMemberStatus($agent, $data, $tenant->id); break; } @@ -95,6 +100,20 @@ public function handle(Request $request) // HANDLERS // ========================================================================= + // --- NOVO HANDLER: Solução para Agentes Fantasmas --- + private function handleFinished($agent, $tenantId) + { + // Só forçamos 'available' se ele estiver 'talking'. + // Se ele estiver 'paused', não mexemos (para respeitar pausas feitas durante chamada) + if ($agent->status === 'talking') { + $agent->status = 'available'; + $agent->last_status_change = now(); + $agent->save(); + + broadcast(new DashboardUpdate($tenantId)); + } + } + private function handleMemberStatus($agent, $data, $tenantId) { $isPaused = ($data['Paused'] ?? '0') == '1'; @@ -126,7 +145,6 @@ private function handleMemberStatus($agent, $data, $tenantId) } } - // TRAVA DE IDEMPOTÊNCIA (Igual ao handlePause) if ($agent->status === $targetStatus && $agent->pause_reason === $targetReason) { return; } @@ -141,12 +159,10 @@ private function handleMemberStatus($agent, $data, $tenantId) private function handleConnect($queue, $data) { - // 1. Remove da Lista de Espera WaitingList::where('queue_id', $queue->id) ->where('caller_number', $data['CallerIDNum']) ->delete(); - // 2. Atualiza Métricas da Fila $metric = $this->getTodayMetric($queue); $holdTime = intval($data['HoldTime'] ?? 0); $newAvg = (($metric->avg_wait_time * $metric->answered_count) + $holdTime) / ($metric->answered_count + 1); @@ -155,7 +171,6 @@ private function handleConnect($queue, $data) $metric->answered_count += 1; $metric->save(); - // 3. Atualiza Agente para Talking (Feedback Visual Imediato) $agent = $this->findAgent($queue->tenant_id, $data); if ($agent) { $agent->status = 'talking'; @@ -169,7 +184,6 @@ private function handleConnect($queue, $data) private function handleComplete($queue, $data) { - // Atualiza tempo médio falado $talkTime = intval($data['TalkTime'] ?? 0); $metric = $this->getTodayMetric($queue); @@ -179,17 +193,13 @@ private function handleComplete($queue, $data) $metric->save(); } - // NOTA: Removemos a lógica de mudar status para 'available' aqui. - // Deixamos o 'QueueMemberStatus' cuidar disso quando o telefone for colocado no gancho. - // Isso evita conflitos se o agente desligar e imediatamente receber outra chamada. - $this->broadcastUpdate($queue); } private function handlePause($agent, $data, $tenantId) { $isPaused = isset($data['Paused']) && $data['Paused'] == '1'; - $targetStatus = $isPaused ? 'paused' : 'available'; // Se despausar, assume livre + $targetStatus = $isPaused ? 'paused' : 'available'; $targetReason = null; if ($isPaused) { @@ -197,9 +207,6 @@ private function handlePause($agent, $data, $tenantId) if (trim($targetReason) === '') $targetReason = 'Pausa'; } - // --- TRAVA DE IDEMPOTÊNCIA --- - // Se o agente está em 10 filas, esse evento dispara 10 vezes. - // Se o status já for igual, IGNORA para não spamar o WebSocket. if ($agent->status === $targetStatus && $agent->pause_reason === $targetReason) { return; } @@ -248,13 +255,11 @@ private function handleJoin($queue, $data) private function saveAgent($interface, $tenant) { - $exists = Agent::where('tenant_id', $tenant->id) ->where('interface', $interface) ->exists(); if (!$exists) { - // Lógica de extração de nome $name = $interface; if (strpos($interface, '/') !== false) { $parts = explode('/', $interface); @@ -272,6 +277,9 @@ private function saveAgent($interface, $tenant) private function saveQueues($queueSourceId, $tenant) { + // Proteção extra para não criar fila suja + if ($queueSourceId === 'Auto-Hangup') return; + $exists = Queue::where('tenant_id', $tenant->id) ->where('source_id', $queueSourceId) ->exists(); @@ -291,7 +299,6 @@ private function findAgent($tenantId, $data) { $interface = $data['Interface'] ?? $data['MemberName'] ?? null; - return Agent::where('tenant_id', $tenantId) ->where('interface', $interface) ->first(); @@ -324,4 +331,4 @@ private function broadcastUpdate($queueOrTenantId) $tenantId = is_object($queueOrTenantId) ? $queueOrTenantId->tenant_id : $queueOrTenantId; broadcast(new DashboardUpdate($tenantId)); } -} +} \ No newline at end of file