fix(telefonia): tenant novo nasce sem conseguir discar entre ramais

Achado real reportado pelo usuário: "a ligacao entre ramais nao esta
funcionando". Não era regressão de nada desta sessão — o tenant onde o
usuário registrou os próprios ramais de teste tinha ZERO regras de
dialplan e ZERO versão ativa. Nenhum tenant nascia com a regra de
"discagem interna": só existia nos tenants onde eu mesmo configurei
manualmente durante testes anteriores (Acme). Todo tenant novo nascia
incapaz de ligar entre os próprios ramais até alguém configurar isso à
mão em Telefonia > Dialplan — um gap real de produto.

`DEFAULT_DIALPLAN_EXTENSIONS` (novo, packages/telephony) tem as mesmas 2
regras já testadas ponta a ponta com chamada real (PHASE 53): discagem
interna com pickup de grupo + `*8`. `TenantsController.create()` agora
semeia essas regras e já ativa a versão 1 do contexto default, na mesma
transação de criação do tenant.

Reparo pontual aplicado no tenant existente do usuário (mesmas 2 regras,
via SQL direto — não existe endpoint de "reparar tenant já criado").
Testado com uma chamada real: originate de um ramal pro outro tocou de
verdade no aparelho do usuário (RINGING -> NO_ANSWER, não mais dialplan
not found). Auto-seed testado criando um tenant novo de verdade via
POST /tenants — confirmado no banco que a versão 1 já nasce ACTIVE.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
This commit is contained in:
2026-08-30 17:10:15 -03:00
parent 7763afacc0
commit 83eb2c5aea
5 changed files with 110 additions and 1 deletions

View File

@@ -1,7 +1,8 @@
import { Body, Controller, ForbiddenException, Get, NotFoundException, Param, Patch, Post, UseGuards } from "@nestjs/common";
import { getPrismaClient, withTenantContext, type TenantStatus } from "@b2bcall/database";
import { getPrismaClient, withTenantContext, type Prisma, type TenantStatus } from "@b2bcall/database";
import { recordAuditEvent, isPlatformUser, hashPassword, type AccessTokenClaims } from "@b2bcall/auth";
import { generateStrongPassword } from "@b2bcall/shared";
import { buildDialplanXml, DEFAULT_DIALPLAN_EXTENSIONS } from "@b2bcall/telephony";
import { JwtAuthGuard } from "../common/guards/jwt-auth.guard";
import { PermissionGuard } from "../common/guards/permission.guard";
import { RequirePermission } from "../common/decorators/require-permission.decorator";
@@ -75,6 +76,37 @@ export class TenantsController {
const tenantAdminRole = await tx.role.findUniqueOrThrow({ where: { key: "tenant_admin" } });
await tx.userRole.create({ data: { userId: admin.id, roleId: tenantAdminRole.id, tenantId: tenant.id } });
// Discagem interna + pickup de grupo (PHASE 57, docs/DIALPLAN.md)
// — achado real: nenhum tenant nascia com isso, então nenhum
// conseguia discar entre os próprios ramais até alguém configurar a
// regra manualmente em Telefonia > Dialplan. Mesmas 2 regras já
// testadas ponta a ponta com chamada real (PHASE 53).
for (const ext of DEFAULT_DIALPLAN_EXTENSIONS) {
await tx.dialplanExtension.create({
data: {
tenantId: tenant.id,
context: "default",
name: ext.name,
conditionField: ext.conditionField,
conditionExpr: ext.conditionExpr,
actions: ext.actions as unknown as Prisma.InputJsonValue,
continueOnFalse: ext.continueOnFalse,
order: ext.order,
},
});
}
await tx.dialplanVersion.create({
data: {
tenantId: tenant.id,
context: "default",
version: 1,
generatedXml: buildDialplanXml("default", DEFAULT_DIALPLAN_EXTENSIONS),
status: "ACTIVE",
createdByUserId: user.sub,
activatedAt: new Date(),
},
});
return { tenant, admin };
});