feat: Call Center > Agentes + Telefonia > Dialplan (novo endpoint GET /users)

Fecha a lacuna de backend que travava Agentes: POST /agents exigia um
userId de um usuário já existente do tenant, mas não havia nenhum endpoint
pra listar usuários. Novo GET /users (gated por users.manage, não
agents.manage — listar identidade de login é administração de usuário)
via TenantMembership, mesmo padrão de RLS já usado em listUserTenants.

Duas telas novas: Call Center > Agentes (criar/listar/remover, seletor de
usuário só mostra quem ainda não é agente) e Telefonia > Dialplan (editor
estruturado do contexto default — regras com condição/ações dinâmicas,
painel de Versões com gerar/ativar, reativar versão antiga = rollback).

Testado ponta a ponta contra a API real do tenant Acme, incluindo o ciclo
completo de dialplan (criar regra -> gerar v1 -> ativar -> badge "Ativa").
Smoke test de regressão nas 13 telas anteriores do tenant + platform.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
This commit is contained in:
2026-08-29 16:10:45 -03:00
parent ca49504f0a
commit f1cadddc91
23 changed files with 1025 additions and 12 deletions

View File

@@ -6,6 +6,7 @@ import { TrunksModule } from "./trunks/trunks.module";
import { DialplanModule } from "./dialplan/dialplan.module";
import { QueuesModule } from "./queues/queues.module";
import { AgentsModule } from "./agents/agents.module";
import { UsersModule } from "./users/users.module";
import { PauseReasonsModule } from "./pause-reasons/pause-reasons.module";
import { RealtimeModule } from "./realtime/realtime.module";
import { CampaignsModule } from "./campaigns/campaigns.module";
@@ -29,6 +30,7 @@ import { BillingModule } from "./billing/billing.module";
DialplanModule,
QueuesModule,
AgentsModule,
UsersModule,
PauseReasonsModule,
RealtimeModule,
CampaignsModule,

View File

@@ -0,0 +1,41 @@
import { Controller, Get, UseGuards } from "@nestjs/common";
import { getPrismaClient, withTenantContext } from "@b2bcall/database";
import type { AccessTokenClaims } from "@b2bcall/auth";
import { JwtAuthGuard } from "../common/guards/jwt-auth.guard";
import { PermissionGuard } from "../common/guards/permission.guard";
import { RequirePermission } from "../common/decorators/require-permission.decorator";
import { CurrentUser } from "../common/decorators/current-user.decorator";
/**
* Usuários do tenant ativo — hoje só o list existe, e só pra alimentar o
* seletor de "Call Center > Agentes" (provisionar agente precisa de um
* `userId` já existente, secao 45). Convite/criação de usuário continua
* só via script (mesma lacuna documentada na PHASE 22 pra tenant/plano).
* Gated por `users.manage` (não `agents.manage`) porque listar identidade
* de login de outras pessoas é uma ação de administração de usuários, não
* de call center — um supervisor com `agents.manage` mas sem
* `users.manage` gerencia agentes já provisionados, mas não lista
* usuários pra provisionar um novo.
*/
@UseGuards(JwtAuthGuard, PermissionGuard)
@Controller("users")
export class UsersController {
@RequirePermission("users.manage")
@Get()
async list(@CurrentUser() user: AccessTokenClaims) {
const prisma = getPrismaClient();
const tenantId = user.tenantId!;
const memberships = await withTenantContext(prisma, tenantId, (tx) =>
tx.tenantMembership.findMany({
where: { tenantId },
include: { user: { select: { id: true, email: true, name: true, status: true, deletedAt: true } } },
orderBy: { createdAt: "asc" },
}),
);
return memberships
.filter((m) => m.user.deletedAt == null)
.map((m) => ({ id: m.user.id, email: m.user.email, name: m.user.name, status: m.user.status }));
}
}

View File

@@ -0,0 +1,7 @@
import { Module } from "@nestjs/common";
import { UsersController } from "./users.controller";
@Module({
controllers: [UsersController],
})
export class UsersModule {}