feat(frontend): Relatórios > Consumo (tenant)
GET /reports/consumo agrega os 2 ledgers imutáveis de uso (UsageEvent + AIUsageRecord — os mesmos que o RatingEngine usa pra faturar) por meter/tipo no mês corrente: chamadas, minutos, dias ativos, armazenamento de gravação, tokens de IA. Nunca calcula valor em dinheiro (isso é billing/RatingEngine, platform-only) — decisão deliberada pra não duplicar essa lógica fora dele. Devolve também os limites do plano (maxMonthlyCalls/maxRecordingStorageGb) pra comparação. Tela /app/relatorios/consumo reaproveita o InstrumentTile do dashboard, zeros honestos em vez de esconder seção. Testado ponta a ponta contra o tenant Acme real (2 dias-tronco já ledgerados aparecem certos, resto zerado por não ter chamada rodada ainda). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
This commit is contained in:
@@ -343,4 +343,67 @@ export class ReportsController {
|
||||
bottomAgents: agentRanking.slice(-5).reverse(),
|
||||
};
|
||||
}
|
||||
|
||||
/** "Relatórios > Consumo" (secao 169) — uso bruto (minutos, dias
|
||||
* ativos, armazenamento, tokens de IA), nunca valor em dinheiro: isso é
|
||||
* trabalho do RatingEngine/BillingStatement (packages/billing,
|
||||
* platform-only), não deste endpoint. Sem `from`/`to`, olha o mês
|
||||
* corrente (mesmo corte usado por `Tenant.billingCurrency`/período de
|
||||
* fechamento) — é "quanto eu já usei este mês", não um relatório
|
||||
* histórico livre. Os 2 ledgers imutáveis (`UsageEvent`+
|
||||
* `AIUsageRecord`) são a mesma fonte que o billing usa, então o número
|
||||
* aqui bate exatamente com o que vira fatura depois. */
|
||||
@RequirePermission("reports.view")
|
||||
@Get("consumo")
|
||||
async consumo(@CurrentUser() user: AccessTokenClaims, @Query("from") from?: string, @Query("to") to?: string) {
|
||||
const prisma = getPrismaClient();
|
||||
const tenantId = user.tenantId!;
|
||||
|
||||
const now = new Date();
|
||||
const defaultFrom = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
|
||||
const range = { gte: from ? new Date(from) : defaultFrom, lte: to ? new Date(to) : now };
|
||||
|
||||
const [usageByMeter, aiUsageByType, tenant] = await withTenantContext(prisma, tenantId, (tx) =>
|
||||
Promise.all([
|
||||
tx.usageEvent.groupBy({ by: ["meter"], where: { tenantId, occurredAt: range }, _sum: { quantity: true } }),
|
||||
tx.aIUsageRecord.groupBy({ by: ["type"], where: { tenantId, occurredAt: range }, _sum: { quantity: true } }),
|
||||
tx.tenant.findFirst({ where: { id: tenantId }, select: { plan: true } }),
|
||||
]),
|
||||
);
|
||||
|
||||
const usage: Record<string, number> = {};
|
||||
for (const row of usageByMeter) usage[row.meter] = row._sum.quantity ?? 0;
|
||||
const aiUsage: Record<string, number> = {};
|
||||
for (const row of aiUsageByType) aiUsage[row.type] = row._sum.quantity ?? 0;
|
||||
|
||||
const plan = tenant?.plan ?? null;
|
||||
const callCount = usage["CALL_COUNT"] ?? 0;
|
||||
const recordingBytes = usage["RECORDING_BYTES"] ?? 0;
|
||||
|
||||
return {
|
||||
period: { from: range.gte.toISOString(), to: range.lte.toISOString() },
|
||||
usage: {
|
||||
callCount,
|
||||
callSeconds: usage["CALL_SECONDS"] ?? 0,
|
||||
extensionActiveDays: usage["EXTENSION_ACTIVE_DAY"] ?? 0,
|
||||
agentActiveDays: usage["AGENT_ACTIVE_DAY"] ?? 0,
|
||||
trunkActiveDays: usage["TRUNK_ACTIVE_DAY"] ?? 0,
|
||||
recordingBytes,
|
||||
},
|
||||
aiUsage: {
|
||||
transcriptionSeconds: aiUsage["AI_TRANSCRIPTION_SECONDS"] ?? 0,
|
||||
analysisRequests: aiUsage["AI_ANALYSIS_REQUEST"] ?? 0,
|
||||
inputTokens: aiUsage["AI_INPUT_TOKENS"] ?? 0,
|
||||
outputTokens: aiUsage["AI_OUTPUT_TOKENS"] ?? 0,
|
||||
},
|
||||
limits: plan && {
|
||||
maxMonthlyCalls: plan.maxMonthlyCalls,
|
||||
maxRecordingStorageGb: plan.maxRecordingStorageGb,
|
||||
callCountUsedRatio: plan.maxMonthlyCalls ? callCount / plan.maxMonthlyCalls : null,
|
||||
recordingStorageUsedRatio: plan.maxRecordingStorageGb
|
||||
? recordingBytes / (plan.maxRecordingStorageGb * 1024 ** 3)
|
||||
: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user