diff --git a/app/Http/Controllers/Api/AmiEventController.php b/app/Http/Controllers/Api/AmiEventController.php index 93f26e3..02d6859 100644 --- a/app/Http/Controllers/Api/AmiEventController.php +++ b/app/Http/Controllers/Api/AmiEventController.php @@ -26,9 +26,9 @@ public function handle(Request $request) $data = $request->all(); $eventName = $data['Event'] ?? null; $queueNumber = $data['Queue'] ?? null; - + // Tenta achar a interface. Se for MemberStatus, vem em 'Interface'. Se for AgentConnect, vem em 'Interface' ou 'MemberName' - $interface = $data['Interface'] ?? $data['MemberName'] ?? null; + $interface = $data['Interface'] ?? $data['MemberName'] ?? null; // Auto-discovery de Filas e Agentes if ($queueNumber) { @@ -44,8 +44,8 @@ public function handle(Request $request) // Recupera Fila $queue = Queue::where('tenant_id', $tenant->id) - ->where('source_id', $queueNumber) - ->first(); + ->where('source_id', $queueNumber) + ->first(); if (!$queue) { return response()->json(['error' => 'Fila não encontrada'], 404); @@ -59,17 +59,17 @@ public function handle(Request $request) case 'QueueCallerJoin': $this->handleJoin($queue, $data); break; - + case 'AgentConnect': // Foco: Mudar para 'talking' e atualizar métricas - $this->handleConnect($queue, $data); + $this->handleConnect($queue, $data); break; - - case 'AgentComplete': + + case 'AgentComplete': // Foco: Encerrar contagem de tempo de fala - $this->handleComplete($queue, $data); + $this->handleComplete($queue, $data); break; - + case 'QueueCallerAbandon': $this->handleAbandon($queue, $data); break; @@ -98,49 +98,42 @@ public function handle(Request $request) private function handleMemberStatus($agent, $data, $tenantId) { - // 1. A PAUSA TEM PRIORIDADE ABSOLUTA - // Se o Asterisk diz que Paused é '1', o agente está pausado, - // não importa se o telefone está no gancho ou fora do gancho. - if (($data['Paused'] ?? '0') == '1') { - $agent->status = 'paused'; - - // Só atualizamos o motivo se o evento trouxer um motivo novo. - // O QueueMemberStatus as vezes não traz o motivo, então evitamos apagar o existente. - if (!empty($data['PausedReason'])) { - $agent->pause_reason = $data['PausedReason']; - } elseif (!$agent->pause_reason) { - $agent->pause_reason = 'Pausa'; // Fallback se estiver nulo - } + $isPaused = ($data['Paused'] ?? '0') == '1'; + $asteriskStatus = $data['Status'] ?? null; + $targetStatus = 'offline'; + $targetReason = null; + + if ($isPaused) { + $targetStatus = 'paused'; + $targetReason = !empty($data['PausedReason']) ? $data['PausedReason'] : ($agent->pause_reason ?? 'Pausa'); } else { - // 2. SE NÃO ESTÁ PAUSADO, OLHAMOS O STATUS DO DISPOSITIVO - $agent->pause_reason = null; - - $asteriskStatus = $data['Status'] ?? null; - + $targetReason = null; switch ($asteriskStatus) { - case '1': // AST_DEVICE_NOT_INUSE (Livre) - $agent->status = 'available'; + case '1': + $targetStatus = 'available'; break; - - case '2': // AST_DEVICE_INUSE (Em uso / Falando) - case '3': // AST_DEVICE_BUSY (Ocupado) - case '6': // AST_DEVICE_RINGING (Tocando) - // Aqui mantemos 'talking' para ficar azul no dashboard - $agent->status = 'talking'; + case '2': + case '3': + case '6': + $targetStatus = 'talking'; break; - - case '5': // AST_DEVICE_UNAVAILABLE (Offline / Cabo desconectado / Softphone fechado) - $agent->status = 'offline'; + case '5': + $targetStatus = 'offline'; break; - default: - // Se vier status desconhecido (4, 0, etc), mantemos o atual ou definimos offline - // $agent->status = 'offline'; + $targetStatus = 'offline'; break; } } + // TRAVA DE IDEMPOTÊNCIA (Igual ao handlePause) + if ($agent->status === $targetStatus && $agent->pause_reason === $targetReason) { + return; + } + + $agent->status = $targetStatus; + $agent->pause_reason = $targetReason; $agent->last_status_change = now(); $agent->save(); @@ -196,13 +189,25 @@ private function handleComplete($queue, $data) private function handlePause($agent, $data, $tenantId) { - // Esse evento ocorre quando a ação de pausa é disparada $isPaused = isset($data['Paused']) && $data['Paused'] == '1'; + $targetStatus = $isPaused ? 'paused' : 'available'; // Se despausar, assume livre - $agent->status = $isPaused ? 'paused' : 'available'; - $agent->pause_reason = $isPaused ? ($data['Reason'] ?? 'Pausa') : null; + $targetReason = null; + if ($isPaused) { + $targetReason = $data['Reason'] ?? $data['PausedReason'] ?? 'Pausa'; + 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; + } + + $agent->status = $targetStatus; + $agent->pause_reason = $targetReason; $agent->last_status_change = now(); - $agent->save(); broadcast(new DashboardUpdate($tenantId)); @@ -217,7 +222,7 @@ private function handleAbandon($queue, $data) $this->updateMetric($queue, 'abandoned_count', 1); $this->broadcastUpdate($queue); } - + private function handleRingNoAnswer($agent, $tenantId) { $agent->increment('total_ring_no_answer'); @@ -328,4 +333,4 @@ private function broadcastUpdate($queueOrTenantId) $tenantId = is_object($queueOrTenantId) ? $queueOrTenantId->tenant_id : $queueOrTenantId; broadcast(new DashboardUpdate($tenantId)); } -} \ No newline at end of file +}