Pedido do usuário: "em vez de o campo de destino ser aberto tem que ter
um dropdown listando todos os ramais do tennant e tb todas ivr e filas
e grupos de ramais do tennant" — até aqui o destino era texto livre.
Construir o dropdown revelou que fila e grupo NUNCA tinham sido
implementados de verdade como destino possível — só ramal/IVR
funcionavam. Oferecer as duas opções sem o mecanismo por trás seria
mostrar um dropdown mentiroso, então:
InboundRoute.destinationType (novo enum EXTENSION/IVR/QUEUE/CALL_GROUP)
decide como buildInboundRouteXml interpreta o destino:
- EXTENSION/IVR: inalterado, o mesmo transfer já testado (PHASE 56/58).
- QUEUE: destinationNumber guarda o Queue.id; a resolução de entrada
emite `answer` + `callcenter data="<queueId>@<domain>"` direto, sem
tocar no dialplan "default". `callcenter` entrou no allowlist de
applications com o mesmo risco zero de `pickup`.
- CALL_GROUP: destinationNumber guarda o Extension.callGroup; a
resolução consulta AGORA (nunca um snapshot salvo) todos os ramais
com esse callGroup e emite um `bridge` multi-leg — toca todos ao
mesmo tempo, quem atender primeiro cancela os outros. Trocar quem
está no grupo depois de criar a rota já vale na próxima chamada.
Testado ponta a ponta com chamadas reais nos 2 mecanismos novos: fila —
softphone externo discou o DID, show channels confirmou a chamada
dentro da application callcenter com o nome certo da fila; grupo — 2
ramais reais no mesmo callGroup, a chamada tocou nos DOIS ao mesmo
tempo (mesmo call_uuid, ambas RINGING), atender em um cancelou o outro
automaticamente — ring group de verdade.
Tela: "Tipo de destino" + um segundo dropdown com as opções reais do
tenant pra cada tipo (ramais, menus de IVR, filas, ou os valores
distintos de callGroup já usados em algum ramal). Testado com
Playwright: os 4 tipos aparecem, e trocar o tipo atualiza as opções do
segundo dropdown corretamente.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
Pedido do usuário: "pode iniciar a montar o IVR e as rotas de entrada".
Investigando antes de escrever qualquer XML de IVR, achei que NENHUMA
chamada de entrada por tronco tinha como funcionar hoje, IVR ou não:
nenhuma carregava `b2bcall_tenant_id` (só REGISTER de ramal e discagem de
saída setam essa variable), e mesmo corrigindo isso, o profile "external"
apontava pro contexto "public" vanilla — um arquivo ESTÁTICO, que sempre
ganha de uma consulta ao mod_xml_curl, então nunca seria dinâmico
enquanto se chamasse "public".
Perguntei ao usuário a granularidade certa (por DID ou por tronco) antes
de desenhar o schema — escolheu por DID, mais flexível (um tronco pode
carregar vários números com destinos diferentes). `InboundRoute` nova
(RLS real, FORCE ROW LEVEL SECURITY): `didNumber` único GLOBAL entre
tenants (mesma exceção já aceita em Tenant.telephonyDomain) — é a ÚNICA
forma de descobrir de qual tenant é uma chamada de entrada ANTES de
identificar o tenant. Resolvido por fan-out sobre tenants ativos, nunca
uma query sem contexto de RLS.
Dockerfile repontou o profile external pra context="inbound" (sem
arquivo estático, cai no mod_xml_curl de verdade). O XML gerado pra esse
contexto injeta b2bcall_tenant_id + domain_name (achado real: sem setar
domain_name explicitamente, o bridge da "Discagem interna" resolvia pro
domínio GLOBAL default, nunca pro do tenant) e transfere pro dialplan
real do tenant — reaproveita 100% do que já existe, inclusive pickup de
grupo (PHASE 53).
CRUD completo (InboundRoutesController, permissions novas no seed) + tela
"Telefonia > Rotas de Entrada" no frontend.
Testado com uma chamada REAL: um softphone registrado como ramal normal,
outro discando direto pro profile external (porta 5080, sem registrar —
exatamente como um provedor de tronco manda) um DID cadastrado. `show
channels` confirma: tenant certo, contexto certo, domínio certo no
bridge, codec PCMU negociado nos dois lados, ramal tocou e atendeu de
verdade. Detalhes completos, inclusive uma tentativa de teste que falhou
por limitação do canal `loopback` (não um bug), em docs/INBOUND_ROUTES.md.
O IVR em si (menu com play_and_get_digits) fica pra próxima fase — esta
é a fundação sem a qual nada de chamada de entrada funcionava.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8