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:
26
TODO.md
26
TODO.md
@@ -2091,6 +2091,32 @@ mostrar quantos ramais estao online e o status de cada ramal criado")
|
|||||||
hoje é construído à mão no editor genérico de dialplan; backend já
|
hoje é construído à mão no editor genérico de dialplan; backend já
|
||||||
suporta tudo que uma UI assim precisaria gerar
|
suporta tudo que uma UI assim precisaria gerar
|
||||||
|
|
||||||
|
## PHASE 57 — Discagem interna não funcionava em tenant novo (achado real
|
||||||
|
reportado pelo usuário: "a ligacao entre ramais nao esta funcionando")
|
||||||
|
- [x] achado real: não era regressão nenhuma do trabalho desta sessão —
|
||||||
|
o tenant `teste01` (onde o usuário registrou 1501/1502 por conta
|
||||||
|
própria) 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 os testes
|
||||||
|
(Acme). Ou seja: TODO tenant novo nascia sem conseguir ligar entre
|
||||||
|
os próprios ramais, um gap real de produto, não um bug pontual
|
||||||
|
- [x] `DEFAULT_DIALPLAN_EXTENSIONS` (novo, `packages/telephony`) — as
|
||||||
|
mesmas 2 regras já testadas ponta a ponta com chamada real na
|
||||||
|
PHASE 53 (discagem interna com pickup de grupo + `*8`).
|
||||||
|
`TenantsController.create()` agora semeia essas regras + já ativa
|
||||||
|
a versão 1 do contexto `default`, dentro da MESMA transação de
|
||||||
|
criação do tenant — nenhum tenant novo nasce mais sem conseguir
|
||||||
|
discar entre ramais
|
||||||
|
- [x] Reparo pontual do tenant `teste01` já existente (mesmas 2 regras,
|
||||||
|
inseridas direto via SQL — não tem endpoint de "reparar tenant
|
||||||
|
existente", só o seed de tenant NOVO). Testado com uma chamada
|
||||||
|
real: `originate` de 1501 pra 1502 tocou de verdade no aparelho do
|
||||||
|
usuário (`RINGING` → `NO_ANSWER`, não mais 404/dialplan not found)
|
||||||
|
- [x] Testado o auto-seed em si criando um tenant novo de verdade
|
||||||
|
(`testeivr`) via `POST /tenants` — confirmado no banco: versão 1 do
|
||||||
|
contexto `default` já nasce `ACTIVE` com as 2 regras. Tenant de
|
||||||
|
teste suspenso ao final (sem endpoint de delete de tenant)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Riscos conhecidos
|
## Riscos conhecidos
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Body, Controller, ForbiddenException, Get, NotFoundException, Param, Patch, Post, UseGuards } from "@nestjs/common";
|
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 { recordAuditEvent, isPlatformUser, hashPassword, type AccessTokenClaims } from "@b2bcall/auth";
|
||||||
import { generateStrongPassword } from "@b2bcall/shared";
|
import { generateStrongPassword } from "@b2bcall/shared";
|
||||||
|
import { buildDialplanXml, DEFAULT_DIALPLAN_EXTENSIONS } from "@b2bcall/telephony";
|
||||||
import { JwtAuthGuard } from "../common/guards/jwt-auth.guard";
|
import { JwtAuthGuard } from "../common/guards/jwt-auth.guard";
|
||||||
import { PermissionGuard } from "../common/guards/permission.guard";
|
import { PermissionGuard } from "../common/guards/permission.guard";
|
||||||
import { RequirePermission } from "../common/decorators/require-permission.decorator";
|
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" } });
|
const tenantAdminRole = await tx.role.findUniqueOrThrow({ where: { key: "tenant_admin" } });
|
||||||
await tx.userRole.create({ data: { userId: admin.id, roleId: tenantAdminRole.id, tenantId: tenant.id } });
|
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 };
|
return { tenant, admin };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,19 @@ POST /dialplan/versions/:id/activate -- marca ACTIVE, a anterior vira
|
|||||||
**Reativar uma versão antiga é o próprio rollback** — não existe endpoint
|
**Reativar uma versão antiga é o próprio rollback** — não existe endpoint
|
||||||
separado. `GET /dialplan/versions` mostra o histórico completo.
|
separado. `GET /dialplan/versions` mostra o histórico completo.
|
||||||
|
|
||||||
|
## Dialplan padrão pra tenant novo (PHASE 57)
|
||||||
|
|
||||||
|
Achado real reportado pelo usuário: "a ligação entre ramais não está
|
||||||
|
funcionando" — nenhum tenant nascia com nenhuma regra de dialplan, então
|
||||||
|
nenhum tenant novo conseguia discar entre os próprios ramais até alguém
|
||||||
|
configurar manualmente em Telefonia > Dialplan. `DEFAULT_DIALPLAN_EXTENSIONS`
|
||||||
|
(`packages/telephony/src/default-dialplan.ts`) tem as 2 regras mínimas
|
||||||
|
(discagem interna com pickup de grupo + `*8`, já testadas ponta a ponta
|
||||||
|
com chamada real na PHASE 53) — `TenantsController.create()` semeia essas
|
||||||
|
regras e já ativa a versão 1 do contexto `default`, dentro da MESMA
|
||||||
|
transação de criação do tenant. Um tenant criado ANTES desta fase precisa
|
||||||
|
de reparo manual (inserir as mesmas 2 linhas + gerar/ativar uma versão).
|
||||||
|
|
||||||
## Mecanismo: dinâmico por chamada, não arquivo+rescan
|
## Mecanismo: dinâmico por chamada, não arquivo+rescan
|
||||||
|
|
||||||
Ao contrário de Trunks (gateways carregados uma vez no boot/rescan),
|
Ao contrário de Trunks (gateways carregados uma vez no boot/rescan),
|
||||||
|
|||||||
37
packages/telephony/src/default-dialplan.ts
Normal file
37
packages/telephony/src/default-dialplan.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import type { DialplanExtensionInput } from "./dialplan-xml";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dialplan padrão do contexto "default" pra todo tenant novo (PHASE 57)
|
||||||
|
* — achado real: nenhum tenant nascia com isso, então nenhum tenant novo
|
||||||
|
* 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): discagem interna com pickup
|
||||||
|
* de grupo (`callgroup`, `${user_data(...)}`) e `*8` pra capturar.
|
||||||
|
*/
|
||||||
|
export const DEFAULT_DIALPLAN_EXTENSIONS: DialplanExtensionInput[] = [
|
||||||
|
{
|
||||||
|
name: "Capturar chamada do grupo (*8)",
|
||||||
|
conditionField: "destination_number",
|
||||||
|
conditionExpr: "^\\*8$",
|
||||||
|
continueOnFalse: false,
|
||||||
|
order: 5,
|
||||||
|
actions: [
|
||||||
|
{ application: "set", data: "my_callgroup=${user_data(${caller_id_number}@${domain_name} var callgroup)}" },
|
||||||
|
{ application: "pickup", data: "${my_callgroup}" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Discagem interna",
|
||||||
|
conditionField: "destination_number",
|
||||||
|
conditionExpr: "^[0-9]{2,10}$",
|
||||||
|
continueOnFalse: false,
|
||||||
|
order: 10,
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
application: "set",
|
||||||
|
data: "called_party_callgroup=${user_data(${destination_number}@${domain_name} var callgroup)}",
|
||||||
|
},
|
||||||
|
{ application: "bridge", data: "user/${destination_number}@${domain_name},pickup/${called_party_callgroup}" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -4,4 +4,5 @@ export * from "./freeswitch-provider";
|
|||||||
export * from "./directory-xml";
|
export * from "./directory-xml";
|
||||||
export * from "./gateway-xml";
|
export * from "./gateway-xml";
|
||||||
export * from "./dialplan-xml";
|
export * from "./dialplan-xml";
|
||||||
|
export * from "./default-dialplan";
|
||||||
export * from "./queue-xml";
|
export * from "./queue-xml";
|
||||||
|
|||||||
Reference in New Issue
Block a user