Compare commits

..

2 Commits

Author SHA1 Message Date
lukidev 31fa9a00e8
fix: Status de agentes V0.1.
fix: Status de agentes V0.1.
2025-12-21 14:27:54 -03:00
lukibeg c9466b9d2c fix: Status de agentes V0.1. 2025-12-21 14:25:20 -03:00
1 changed files with 34 additions and 27 deletions

View File

@ -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' // 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 // 1. Auto-discovery (Com proteção para não salvar a fila fake do Auto-Hangup)
if ($queueNumber) { if ($queueNumber && $queueNumber !== 'Auto-Hangup') {
$this->saveQueues($queueNumber, $tenant); $this->saveQueues($queueNumber, $tenant);
} }
if ($interface) { if ($interface) {
@ -42,7 +42,19 @@ public function handle(Request $request)
return response()->json(['status' => 'ignored']); 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) $queue = Queue::where('tenant_id', $tenant->id)
->where('source_id', $queueNumber) ->where('source_id', $queueNumber)
->first(); ->first();
@ -51,21 +63,16 @@ public function handle(Request $request)
return response()->json(['error' => 'Fila não encontrada'], 404); return response()->json(['error' => 'Fila não encontrada'], 404);
} }
$agent = $this->findAgent($tenant->id, $data);
switch ($eventName) { switch ($eventName) {
case 'QueueCallerJoin': case 'QueueCallerJoin':
$this->handleJoin($queue, $data); $this->handleJoin($queue, $data);
break; break;
case 'AgentConnect': case 'AgentConnect':
// Foco: Mudar para 'talking' e atualizar métricas
$this->handleConnect($queue, $data); $this->handleConnect($queue, $data);
break; break;
case 'AgentComplete': case 'AgentComplete':
// Foco: Encerrar contagem de tempo de fala
$this->handleComplete($queue, $data); $this->handleComplete($queue, $data);
break; break;
@ -74,7 +81,6 @@ public function handle(Request $request)
break; break;
case 'QueueMemberPause': case 'QueueMemberPause':
// Evento explícito de pausa (Alguém clicou no botão de pausa)
if ($agent) $this->handlePause($agent, $data, $tenant->id); if ($agent) $this->handlePause($agent, $data, $tenant->id);
break; break;
@ -83,7 +89,6 @@ public function handle(Request $request)
break; break;
case 'QueueMemberStatus': case 'QueueMemberStatus':
// Foco: Atualizar Presença (Offline, Disponível, Pausado, Busy)
if ($agent) $this->handleMemberStatus($agent, $data, $tenant->id); if ($agent) $this->handleMemberStatus($agent, $data, $tenant->id);
break; break;
} }
@ -95,6 +100,20 @@ public function handle(Request $request)
// HANDLERS // 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) private function handleMemberStatus($agent, $data, $tenantId)
{ {
$isPaused = ($data['Paused'] ?? '0') == '1'; $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) { if ($agent->status === $targetStatus && $agent->pause_reason === $targetReason) {
return; return;
} }
@ -141,12 +159,10 @@ private function handleMemberStatus($agent, $data, $tenantId)
private function handleConnect($queue, $data) private function handleConnect($queue, $data)
{ {
// 1. Remove da Lista de Espera
WaitingList::where('queue_id', $queue->id) WaitingList::where('queue_id', $queue->id)
->where('caller_number', $data['CallerIDNum']) ->where('caller_number', $data['CallerIDNum'])
->delete(); ->delete();
// 2. Atualiza Métricas da Fila
$metric = $this->getTodayMetric($queue); $metric = $this->getTodayMetric($queue);
$holdTime = intval($data['HoldTime'] ?? 0); $holdTime = intval($data['HoldTime'] ?? 0);
$newAvg = (($metric->avg_wait_time * $metric->answered_count) + $holdTime) / ($metric->answered_count + 1); $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->answered_count += 1;
$metric->save(); $metric->save();
// 3. Atualiza Agente para Talking (Feedback Visual Imediato)
$agent = $this->findAgent($queue->tenant_id, $data); $agent = $this->findAgent($queue->tenant_id, $data);
if ($agent) { if ($agent) {
$agent->status = 'talking'; $agent->status = 'talking';
@ -169,7 +184,6 @@ private function handleConnect($queue, $data)
private function handleComplete($queue, $data) private function handleComplete($queue, $data)
{ {
// Atualiza tempo médio falado
$talkTime = intval($data['TalkTime'] ?? 0); $talkTime = intval($data['TalkTime'] ?? 0);
$metric = $this->getTodayMetric($queue); $metric = $this->getTodayMetric($queue);
@ -179,17 +193,13 @@ private function handleComplete($queue, $data)
$metric->save(); $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); $this->broadcastUpdate($queue);
} }
private function handlePause($agent, $data, $tenantId) private function handlePause($agent, $data, $tenantId)
{ {
$isPaused = isset($data['Paused']) && $data['Paused'] == '1'; $isPaused = isset($data['Paused']) && $data['Paused'] == '1';
$targetStatus = $isPaused ? 'paused' : 'available'; // Se despausar, assume livre $targetStatus = $isPaused ? 'paused' : 'available';
$targetReason = null; $targetReason = null;
if ($isPaused) { if ($isPaused) {
@ -197,9 +207,6 @@ private function handlePause($agent, $data, $tenantId)
if (trim($targetReason) === '') $targetReason = '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) { if ($agent->status === $targetStatus && $agent->pause_reason === $targetReason) {
return; return;
} }
@ -248,13 +255,11 @@ private function handleJoin($queue, $data)
private function saveAgent($interface, $tenant) private function saveAgent($interface, $tenant)
{ {
$exists = Agent::where('tenant_id', $tenant->id) $exists = Agent::where('tenant_id', $tenant->id)
->where('interface', $interface) ->where('interface', $interface)
->exists(); ->exists();
if (!$exists) { if (!$exists) {
// Lógica de extração de nome
$name = $interface; $name = $interface;
if (strpos($interface, '/') !== false) { if (strpos($interface, '/') !== false) {
$parts = explode('/', $interface); $parts = explode('/', $interface);
@ -272,6 +277,9 @@ private function saveAgent($interface, $tenant)
private function saveQueues($queueSourceId, $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) $exists = Queue::where('tenant_id', $tenant->id)
->where('source_id', $queueSourceId) ->where('source_id', $queueSourceId)
->exists(); ->exists();
@ -291,7 +299,6 @@ private function findAgent($tenantId, $data)
{ {
$interface = $data['Interface'] ?? $data['MemberName'] ?? null; $interface = $data['Interface'] ?? $data['MemberName'] ?? null;
return Agent::where('tenant_id', $tenantId) return Agent::where('tenant_id', $tenantId)
->where('interface', $interface) ->where('interface', $interface)
->first(); ->first();
@ -324,4 +331,4 @@ private function broadcastUpdate($queueOrTenantId)
$tenantId = is_object($queueOrTenantId) ? $queueOrTenantId->tenant_id : $queueOrTenantId; $tenantId = is_object($queueOrTenantId) ? $queueOrTenantId->tenant_id : $queueOrTenantId;
broadcast(new DashboardUpdate($tenantId)); broadcast(new DashboardUpdate($tenantId));
} }
} }