feat(platform): Billing > Fechamentos e Relatórios
GET /billing/periods e /billing/statements só serviam o próprio tenant do JWT — sem uso pra um platform admin escolhendo um tenant arbitrário. Adicionado GET .../by-tenant/:tenantId nos dois (mesmo padrão já usado em Subscriptions), e GET /billing/statements/:id ganhou um ?tenantId= opcional só aceito de quem tem role de plataforma. Frontend: /platform/billing/fechamentos (fecha/reabre período por tenant, seletor via querystring pra não duplicar rota) e /relatorios (statements por tenant, detalhe com itens por categoria). Bug real achado testando o fluxo: fechar um período de 01/08 a 31/08 mostrava "31 de jul." a "30 de ago." — meia-noite UTC de uma data-only vira o dia anterior no timezone local do servidor. Corrigido com formatDateUTC novo, usado só em fronteiras de calendário (não em timestamps de verdade, que continuam com formatDate local). Testado ponta a ponta contra a API real: período fechado, statement gerado (R$ 0,00 honesto — Acme sem assinatura/price book ainda), detalhe correto. Smoke test nas 19 telas do tenant + 8 telas platform, todas 200. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
This commit is contained in:
@@ -54,4 +54,19 @@ export class BillingPeriodsController {
|
||||
tx.billingPeriod.findMany({ where: { tenantId }, orderBy: { periodStart: "desc" } }),
|
||||
);
|
||||
}
|
||||
|
||||
/** Platform admin olhando um tenant arbitrário (secao 168, "Billing >
|
||||
* Fechamentos") — `GET /billing/periods` acima só serve o próprio
|
||||
* tenant do JWT, que um platform admin não tem. */
|
||||
@RequirePermission("billing.manage")
|
||||
@Get("by-tenant/:tenantId")
|
||||
async listByTenant(@CurrentUser() user: AccessTokenClaims, @Param("tenantId") tenantId: string) {
|
||||
if (!(await isPlatformUser(user.sub))) {
|
||||
throw new ForbiddenException("So' um usuario com role de plataforma pode ver fechamentos de outro tenant");
|
||||
}
|
||||
const prisma = getPrismaClient();
|
||||
return withTenantContext(prisma, tenantId, (tx) =>
|
||||
tx.billingPeriod.findMany({ where: { tenantId }, orderBy: { periodStart: "desc" } }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Controller, Get, NotFoundException, Param, UseGuards } from "@nestjs/common";
|
||||
import { Controller, ForbiddenException, Get, NotFoundException, Param, Query, UseGuards } from "@nestjs/common";
|
||||
import { getPrismaClient, withTenantContext } from "@b2bcall/database";
|
||||
import type { AccessTokenClaims } from "@b2bcall/auth";
|
||||
import { isPlatformUser, 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";
|
||||
@@ -29,10 +29,39 @@ export class BillingStatementsController {
|
||||
);
|
||||
}
|
||||
|
||||
/** Platform admin olhando um tenant arbitrário (secao 168, "Billing >
|
||||
* Relatórios") — `GET /billing/statements` acima só serve o próprio
|
||||
* tenant do JWT, que um platform admin não tem. */
|
||||
@RequirePermission("billing.manage")
|
||||
@Get("by-tenant/:tenantId")
|
||||
async listByTenant(@CurrentUser() user: AccessTokenClaims, @Param("tenantId") tenantId: string) {
|
||||
if (!(await isPlatformUser(user.sub))) {
|
||||
throw new ForbiddenException("So' um usuario com role de plataforma pode ver relatorios de outro tenant");
|
||||
}
|
||||
const prisma = getPrismaClient();
|
||||
return withTenantContext(prisma, tenantId, (tx) =>
|
||||
tx.billingStatement.findMany({
|
||||
where: { tenantId },
|
||||
include: { billingPeriod: true },
|
||||
orderBy: { generatedAt: "desc" },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/** `tenantId` na query só é aceito de quem tem role de plataforma (secao
|
||||
* 31: nunca confiar em tenant vindo do client sem checar) — um tenant
|
||||
* admin comum sempre olha só o próprio, do JWT, mesmo que tente mandar
|
||||
* outro. */
|
||||
@RequirePermission("billing.view")
|
||||
@Get(":id")
|
||||
async get(@CurrentUser() user: AccessTokenClaims, @Param("id") id: string) {
|
||||
const tenantId = user.tenantId!;
|
||||
async get(@CurrentUser() user: AccessTokenClaims, @Param("id") id: string, @Query("tenantId") queryTenantId?: string) {
|
||||
let tenantId = user.tenantId!;
|
||||
if (queryTenantId && queryTenantId !== tenantId) {
|
||||
if (!(await isPlatformUser(user.sub))) {
|
||||
throw new ForbiddenException("So' um usuario com role de plataforma pode ver statement de outro tenant");
|
||||
}
|
||||
tenantId = queryTenantId;
|
||||
}
|
||||
const prisma = getPrismaClient();
|
||||
const statement = await withTenantContext(prisma, tenantId, (tx) =>
|
||||
tx.billingStatement.findFirst({
|
||||
|
||||
Reference in New Issue
Block a user