feat: add outbound routes (Issabel-style dial pattern masking)
Nova aba 'Dialplan -> Rotas de Saída': abstração amigável sobre o dialplan bruto, no mesmo espírito das Outbound Routes do Issabel/FreePBX. O usuário informa (prepend) + prefix | match pattern (sintaxe de padrão do Asterisk: X/Z/N/faixas/coringas, sem o '_' inicial) e escolhe o tronco, sem escrever exten=>/Dial() à mão. - packages/database: model OutboundRoute + migration - apps/api/src/outbound-routes: gerador determinístico (contexto b2bcall-outbound-routes, incluído em b2bcall-agents — contexto padrão de Extension.context), service/controller CRUD reaproveitando as permissões dialplans.*, 6 testes unitários - apps/api/src/dialplan/dialplan.service.ts: publish() agora gera e verifica também o contexto das rotas, no mesmo pipeline de versionamento/rollback automático das entradas de dialplan brutas - apps/frontend: aba com formulário (prepend/prefix/padrão/tronco) e preview ao vivo da máscara resultante O prefixo é sempre removido do número antes de discar e substituído pelo prepend — o tronco nunca vê o prefixo digitado pelo agente, só o número já mascarado. Não usado pelo discador preditivo (campanhas já sabem o tronco via Campaign.trunkId diretamente). Build/lint/testes (api+frontend) verificados; teste end-to-end contra o Asterisk real ficou pendente porque a senha do super_admin foi trocada durante a sessão (acesso legítimo do usuário) — validação via UI delegada ao usuário.
This commit is contained in:
@@ -14,6 +14,10 @@ import { TELEPHONY_PROVIDER } from '../telephony/telephony.module';
|
||||
import { CreateDialplanEntryDto } from './dto/create-dialplan-entry.dto';
|
||||
import { UpdateDialplanEntryDto } from './dto/update-dialplan-entry.dto';
|
||||
import { generateDialplanConfig } from './dialplan-generator';
|
||||
import {
|
||||
generateOutboundRoutesConfig,
|
||||
OUTBOUND_ROUTES_CONTEXT,
|
||||
} from '../outbound-routes/outbound-route-generator';
|
||||
|
||||
const DIALPLAN_FILE_PATH = '/etc/asterisk-generated/b2bcall-dialplan.conf';
|
||||
|
||||
@@ -155,7 +159,19 @@ export class DialplanService {
|
||||
const entries = await this.prisma.dialplanEntry.findMany({
|
||||
where: { enabled: true },
|
||||
});
|
||||
const newConfig = generateDialplanConfig(entries);
|
||||
const outboundRoutes = await this.prisma.outboundRoute.findMany({
|
||||
where: { enabled: true },
|
||||
include: { trunk: { select: { name: true } } },
|
||||
});
|
||||
const newConfig =
|
||||
generateDialplanConfig(entries) +
|
||||
'\n' +
|
||||
generateOutboundRoutesConfig(outboundRoutes);
|
||||
const verifyContexts = [
|
||||
...entries.map((e) => ({ context: e.context })),
|
||||
{ context: OUTBOUND_ROUTES_CONTEXT },
|
||||
{ context: 'b2bcall-agents' },
|
||||
];
|
||||
|
||||
const previous = await this.prisma.dialplanVersion.findFirst({
|
||||
where: { status: DialplanVersionStatus.APPLIED },
|
||||
@@ -173,7 +189,7 @@ export class DialplanService {
|
||||
);
|
||||
}
|
||||
|
||||
const failureReason = await this.verify(entries);
|
||||
const failureReason = await this.verify(verifyContexts);
|
||||
|
||||
if (failureReason) {
|
||||
// Rollback automático — nunca deixa uma configuração inválida ativa.
|
||||
|
||||
Reference in New Issue
Block a user