feat: add call center queues
- packages/database: Agent, AgentSession, AgentStateEvent, AgentPauseEvent, PauseReason, Queue, QueueMember (agente.md secao 53) - apps/api/src/queues: CRUD de filas gerando queues.conf pelo mesmo padrao do dialplan (arquivo compartilhado + module reload app_queue.so), mas SEM membros estaticos no arquivo — membership eh 100% dinamica via AMI (evita duas fontes de verdade conflitantes) - apps/api/src/pause-reasons: CRUD de motivos de pausa - apps/api/src/agents: CRUD administrativo de agentes (1:1 com User) - apps/api/src/agent-console: maquina de estados do agente (LOGGED_IN/AVAILABLE/PAUSED), 'tela do agente' via login/available/pause/unpause/logout, cada transicao aciona AMI QueueAdd/QueuePause/QueueRemove de verdade e registra agent_state_events/agent_pause_events com inicio/fim - apps/api/src/monitoring: GET /api/monitoring/queues (chamadas aguardando, agentes logados/pausados/disponiveis via AMI QueueStatus ao vivo) — metricas historicas (TME/TMA/SLA) ficam para a Fase 7 Testado ponta a ponta contra o Asterisk real: fila criada aparece via 'queue show'; agente loga, fica disponivel (QueueAdd confirmado, membro dinamico visivel), pausa com motivo (confirmado 'paused:Almoco' ao vivo), despausa, desloga (QueueRemove confirmado, fila volta a 'No Members'). Disposicoes de chamada e Callback adiados para a Fase 6 (dependem de haver chamadas de campanha reais para classificar).
This commit is contained in:
@@ -31,6 +31,7 @@ model User {
|
||||
sessions Session[]
|
||||
passwordResetTokens PasswordResetToken[]
|
||||
auditLogs AuditLog[]
|
||||
agent Agent?
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
@@ -251,3 +252,145 @@ model DialplanVersion {
|
||||
|
||||
@@map("dialplan_versions")
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Fase 5 — Call Center. Usuário da aplicação (User) e agente de Call Center
|
||||
// (Agent) são conceitos separados (agente.md seção 16): uma pessoa pode
|
||||
// possuir os dois associados 1:1.
|
||||
// ===========================================================================
|
||||
|
||||
enum QueueStrategy {
|
||||
ringall
|
||||
leastrecent
|
||||
fewestcalls
|
||||
random
|
||||
rrmemory
|
||||
rrordered
|
||||
linear
|
||||
wrandom
|
||||
}
|
||||
|
||||
model Queue {
|
||||
id String @id @default(uuid())
|
||||
name String @unique
|
||||
number String @unique
|
||||
strategy QueueStrategy @default(ringall)
|
||||
timeout Int @default(15)
|
||||
retry Int @default(5)
|
||||
wrapUpTime Int @default(0) @map("wrap_up_time")
|
||||
maxLen Int @default(0) @map("max_len")
|
||||
musicOnHold String @default("default") @map("music_on_hold")
|
||||
announce String?
|
||||
serviceLevel Int @default(60) @map("service_level")
|
||||
autoFill Boolean @default(true) @map("auto_fill")
|
||||
ringInUse Boolean @default(true) @map("ring_in_use")
|
||||
weight Int @default(0)
|
||||
enabled Boolean @default(true)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
members QueueMember[]
|
||||
|
||||
@@map("queues")
|
||||
}
|
||||
|
||||
model QueueMember {
|
||||
id String @id @default(uuid())
|
||||
queueId String @map("queue_id")
|
||||
agentId String @map("agent_id")
|
||||
penalty Int @default(0)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
queue Queue @relation(fields: [queueId], references: [id], onDelete: Cascade)
|
||||
agent Agent @relation(fields: [agentId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([queueId, agentId])
|
||||
@@map("queue_members")
|
||||
}
|
||||
|
||||
model PauseReason {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
code String @unique
|
||||
description String?
|
||||
maxDurationSeconds Int? @map("max_duration_seconds")
|
||||
paid Boolean @default(false)
|
||||
active Boolean @default(true)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
pauseEvents AgentPauseEvent[]
|
||||
|
||||
@@map("pause_reasons")
|
||||
}
|
||||
|
||||
model Agent {
|
||||
id String @id @default(uuid())
|
||||
code String @unique
|
||||
name String
|
||||
userId String @unique @map("user_id")
|
||||
active Boolean @default(true)
|
||||
currentExtension String? @map("current_extension")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
sessions AgentSession[]
|
||||
stateEvents AgentStateEvent[]
|
||||
pauseEvents AgentPauseEvent[]
|
||||
queues QueueMember[]
|
||||
|
||||
@@map("agents")
|
||||
}
|
||||
|
||||
model AgentSession {
|
||||
id String @id @default(uuid())
|
||||
agentId String @map("agent_id")
|
||||
extension String
|
||||
startedAt DateTime @default(now()) @map("started_at")
|
||||
endedAt DateTime? @map("ended_at")
|
||||
|
||||
agent Agent @relation(fields: [agentId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([agentId])
|
||||
@@map("agent_sessions")
|
||||
}
|
||||
|
||||
enum AgentState {
|
||||
OFFLINE
|
||||
LOGGED_IN
|
||||
AVAILABLE
|
||||
RINGING
|
||||
IN_CALL
|
||||
WRAP_UP
|
||||
PAUSED
|
||||
}
|
||||
|
||||
// Trilha de auditoria da máquina de estados do agente (agente.md seção 48).
|
||||
// Sempre exatamente um registro "aberto" (endedAt = null) por agente.
|
||||
model AgentStateEvent {
|
||||
id String @id @default(uuid())
|
||||
agentId String @map("agent_id")
|
||||
state AgentState
|
||||
startedAt DateTime @default(now()) @map("started_at")
|
||||
endedAt DateTime? @map("ended_at")
|
||||
|
||||
agent Agent @relation(fields: [agentId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([agentId, endedAt])
|
||||
@@map("agent_state_events")
|
||||
}
|
||||
|
||||
model AgentPauseEvent {
|
||||
id String @id @default(uuid())
|
||||
agentId String @map("agent_id")
|
||||
pauseReasonId String @map("pause_reason_id")
|
||||
startedAt DateTime @default(now()) @map("started_at")
|
||||
endedAt DateTime? @map("ended_at")
|
||||
|
||||
agent Agent @relation(fields: [agentId], references: [id], onDelete: Cascade)
|
||||
pauseReason PauseReason @relation(fields: [pauseReasonId], references: [id], onDelete: Restrict)
|
||||
|
||||
@@index([agentId, endedAt])
|
||||
@@map("agent_pause_events")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user