feat(chat): adiciona encerramento automatico por inatividade (12h)
- Sessoes de chat sao encerradas apos 12 horas sem atividade - Criterios de encerramento automatico: 1. Maquina offline (5 min sem heartbeat) 2. Chat inativo (12 horas sem atividade) - NOVO 3. Ticket orfao (sem maquina vinculada) - Log detalhado com contagem por motivo de encerramento - Evento no timeline com reason "inatividade_chat" Isso evita acumular sessoes abertas indefinidamente quando usuario esquece de encerrar o chat. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
29fbbfaa26
commit
86f818c6f3
1 changed files with 47 additions and 7 deletions
|
|
@ -763,15 +763,22 @@ export const getTicketChatHistory = query({
|
||||||
// Timeout de maquina offline: 5 minutos sem heartbeat
|
// Timeout de maquina offline: 5 minutos sem heartbeat
|
||||||
const MACHINE_OFFLINE_TIMEOUT_MS = 5 * 60 * 1000
|
const MACHINE_OFFLINE_TIMEOUT_MS = 5 * 60 * 1000
|
||||||
|
|
||||||
// Mutation interna para encerrar sessões de máquinas offline (chamada pelo cron)
|
// Timeout de inatividade do chat: 12 horas sem atividade
|
||||||
// Nova lógica: só encerra se a MÁQUINA estiver offline, não por inatividade de chat
|
// Isso evita acumular sessoes abertas indefinidamente quando usuario esquece de encerrar
|
||||||
// Isso permite que usuários mantenham o chat aberto sem precisar enviar mensagens
|
const CHAT_INACTIVITY_TIMEOUT_MS = 12 * 60 * 60 * 1000
|
||||||
|
|
||||||
|
// Mutation interna para encerrar sessões inativas (chamada pelo cron)
|
||||||
|
// Critérios de encerramento:
|
||||||
|
// 1. Máquina offline (5 min sem heartbeat)
|
||||||
|
// 2. Chat inativo (12 horas sem atividade) - mesmo se máquina online
|
||||||
|
// 3. Ticket órfão (sem máquina vinculada)
|
||||||
export const autoEndInactiveSessions = mutation({
|
export const autoEndInactiveSessions = mutation({
|
||||||
args: {},
|
args: {},
|
||||||
handler: async (ctx) => {
|
handler: async (ctx) => {
|
||||||
console.log("cron: autoEndInactiveSessions iniciado (verificando maquinas offline)")
|
console.log("cron: autoEndInactiveSessions iniciado")
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const offlineCutoff = now - MACHINE_OFFLINE_TIMEOUT_MS
|
const offlineCutoff = now - MACHINE_OFFLINE_TIMEOUT_MS
|
||||||
|
const inactivityCutoff = now - CHAT_INACTIVITY_TIMEOUT_MS
|
||||||
|
|
||||||
// Limitar a 50 sessões por execução para evitar timeout do cron (30s)
|
// Limitar a 50 sessões por execução para evitar timeout do cron (30s)
|
||||||
const maxSessionsPerRun = 50
|
const maxSessionsPerRun = 50
|
||||||
|
|
@ -784,6 +791,7 @@ export const autoEndInactiveSessions = mutation({
|
||||||
|
|
||||||
let endedCount = 0
|
let endedCount = 0
|
||||||
let checkedCount = 0
|
let checkedCount = 0
|
||||||
|
const reasons: Record<string, number> = {}
|
||||||
|
|
||||||
for (const session of activeSessions) {
|
for (const session of activeSessions) {
|
||||||
checkedCount++
|
checkedCount++
|
||||||
|
|
@ -812,6 +820,36 @@ export const autoEndInactiveSessions = mutation({
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
})
|
})
|
||||||
endedCount++
|
endedCount++
|
||||||
|
reasons["ticket_sem_maquina"] = (reasons["ticket_sem_maquina"] ?? 0) + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar inatividade do chat (12 horas sem atividade)
|
||||||
|
// Isso tem prioridade sobre o status da máquina
|
||||||
|
const chatIsInactive = session.lastActivityAt < inactivityCutoff
|
||||||
|
if (chatIsInactive) {
|
||||||
|
await ctx.db.patch(session._id, {
|
||||||
|
status: "ENDED",
|
||||||
|
endedAt: now,
|
||||||
|
})
|
||||||
|
await ctx.db.insert("ticketEvents", {
|
||||||
|
ticketId: session.ticketId,
|
||||||
|
type: "LIVE_CHAT_ENDED",
|
||||||
|
payload: {
|
||||||
|
sessionId: session._id,
|
||||||
|
agentId: session.agentId,
|
||||||
|
agentName: session.agentSnapshot?.name ?? "Sistema",
|
||||||
|
durationMs: now - session.startedAt,
|
||||||
|
startedAt: session.startedAt,
|
||||||
|
endedAt: now,
|
||||||
|
autoEnded: true,
|
||||||
|
reason: "inatividade_chat",
|
||||||
|
inactiveForMs: now - session.lastActivityAt,
|
||||||
|
},
|
||||||
|
createdAt: now,
|
||||||
|
})
|
||||||
|
endedCount++
|
||||||
|
reasons["inatividade_chat"] = (reasons["inatividade_chat"] ?? 0) + 1
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -819,7 +857,7 @@ export const autoEndInactiveSessions = mutation({
|
||||||
const lastHeartbeatAt = await getLastHeartbeatAt(ctx, ticket.machineId)
|
const lastHeartbeatAt = await getLastHeartbeatAt(ctx, ticket.machineId)
|
||||||
const machineIsOnline = lastHeartbeatAt !== null && lastHeartbeatAt > offlineCutoff
|
const machineIsOnline = lastHeartbeatAt !== null && lastHeartbeatAt > offlineCutoff
|
||||||
|
|
||||||
// Se máquina está online, manter sessão ativa
|
// Se máquina está online e chat está ativo, manter sessão
|
||||||
if (machineIsOnline) {
|
if (machineIsOnline) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -849,10 +887,12 @@ export const autoEndInactiveSessions = mutation({
|
||||||
})
|
})
|
||||||
|
|
||||||
endedCount++
|
endedCount++
|
||||||
|
reasons["maquina_offline"] = (reasons["maquina_offline"] ?? 0) + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`cron: verificadas ${checkedCount} sessoes, encerradas ${endedCount} (maquinas offline)`)
|
const reasonsSummary = Object.entries(reasons).map(([r, c]) => `${r}=${c}`).join(", ")
|
||||||
return { endedCount, checkedCount, hasMore: activeSessions.length === maxSessionsPerRun }
|
console.log(`cron: verificadas ${checkedCount} sessoes, encerradas ${endedCount} (${reasonsSummary || "nenhuma"})`)
|
||||||
|
return { endedCount, checkedCount, reasons, hasMore: activeSessions.length === maxSessionsPerRun }
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue