feat(platform): Clientes > Tenants e Planos — CRUD real (backend novo)
Não existia NENHUM endpoint pra criar/listar/editar tenant nem plano até aqui — só via script/seed ad hoc. Dois controllers novos: TenantsController (/tenants) e PlansController (/plans), platform-only. POST /tenants cria o tenant E o primeiro usuário (Tenant Admin) numa transação só — sem esse usuário o tenant fica inacessível. Senha gerada e devolvida em texto puro só na resposta de criação (revela uma vez, mesmo padrão de Ramais/SIP). Bug real achado testando o próprio endpoint: GET /tenants calculava memberCount sem contexto de RLS — tenant_memberships tem FORCE RLS, então nem platform admin enxerga linha nenhuma sem app.current_tenant_id setado, o campo sempre voltava 0. Corrigido abrindo o contexto de cada tenant um de cada vez. Frontend: /platform/clientes/tenants (lista+busca), /tenants/new (cria tenant+admin, revela senha), /tenants/:id (troca status/plano, preview dos limites ao vivo), /platform/clientes/planos (CRUD completo dos limites). Testado ponta a ponta: plano novo -> tenant novo com admin real -> troca de plano persistida, confirmada via API. Smoke test nas 19 telas anteriores, todas 200. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
This commit is contained in:
53
apps/api/src/tenants/dto/create-tenant.dto.ts
Normal file
53
apps/api/src/tenants/dto/create-tenant.dto.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { IsEmail, IsIn, IsOptional, IsString, IsUUID, Matches, MaxLength } from "class-validator";
|
||||
|
||||
const TENANT_STATUSES = ["TRIAL", "ACTIVE", "SUSPENDED", "PAST_DUE", "CANCELLED"];
|
||||
|
||||
export class CreateTenantDto {
|
||||
@IsString()
|
||||
@MaxLength(40)
|
||||
@Matches(/^[a-z0-9-]+$/, { message: "code deve ser minusculo, so letras/numeros/hifen (vira tambem o slug)" })
|
||||
code!: string;
|
||||
|
||||
@IsString()
|
||||
@MaxLength(200)
|
||||
legalName!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(200)
|
||||
tradeName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(40)
|
||||
taxId?: string;
|
||||
|
||||
@IsUUID()
|
||||
planId!: string;
|
||||
|
||||
/** Cria o primeiro usuário (tenant_admin) na mesma transação — sem essa
|
||||
* conta o tenant fica inacessível (secao 141: "Tenant Admin é criado
|
||||
* junto com o tenant"). Senha gerada e devolvida uma única vez, mesmo
|
||||
* padrão de `SecretReveal` já usado pra senha SIP de ramal. */
|
||||
@IsEmail()
|
||||
adminEmail!: string;
|
||||
|
||||
@IsString()
|
||||
@MaxLength(120)
|
||||
adminName!: string;
|
||||
}
|
||||
|
||||
export class UpdateTenantDto {
|
||||
@IsOptional()
|
||||
@IsIn(TENANT_STATUSES)
|
||||
status?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
planId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(200)
|
||||
tradeName?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user