Files
B2BCall-dialer/packages/database/prisma/migrations/20260830170000_inbound_routes/migration.sql
Matheus ed4ae4421e feat(telefonia): rotas de entrada por DID — fundação real pro IVR
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
2026-08-30 16:45:18 -03:00

38 lines
1.7 KiB
SQL

-- PHASE 56: rotas de entrada por DID (chamada de tronco -> tenant/destino)
CREATE TABLE "inbound_routes" (
"id" UUID NOT NULL,
"tenant_id" UUID NOT NULL,
"did_number" TEXT NOT NULL,
"description" TEXT,
"destination_context" TEXT NOT NULL DEFAULT 'default',
"destination_number" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"deleted_at" TIMESTAMP(3),
CONSTRAINT "inbound_routes_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "inbound_routes_did_number_key" ON "inbound_routes"("did_number");
-- CreateIndex
CREATE INDEX "inbound_routes_tenant_id_idx" ON "inbound_routes"("tenant_id");
-- AddForeignKey
ALTER TABLE "inbound_routes" ADD CONSTRAINT "inbound_routes_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- Tabela de negocio tenant-scoped: RLS obrigatorio (ver docs/TENANT_ISOLATION.md).
-- didNumber e' @unique GLOBAL de proposito (mesma excecao ja aceita em
-- Tenant.telephonyDomain) -- e' a UNICA forma de descobrir de qual tenant
-- e' uma chamada de entrada ANTES de identificar o tenant. A resolucao em
-- apps/freeswitch-config faz fan-out sobre tenants ativos com
-- withTenantContext (nunca uma query sem contexto), entao RLS continua
-- valendo de verdade aqui, igual toda outra tabela de negocio.
ALTER TABLE "inbound_routes" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "inbound_routes" FORCE ROW LEVEL SECURITY;
CREATE POLICY "tenant_isolation" ON "inbound_routes"
USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::uuid);