feat(billing): rating engine, fechamento de periodo, dashboard platform (fase 22)
Fecha a orquestracao de Billing (agente.md secao 120-139) sobre o schema/ RatingEngine puro ja existentes: escritores do ledger UsageEvent (CALL_SECONDS no CDR, ACTIVE_DAY via sweep diario), closeBillingPeriod/ reopenBillingPeriod (fechamento imutavel com audit trail), e os controllers de price books/rate decks/plan versions/subscriptions/ periods/statements. Corrige 2 bugs reais de RLS achados no teste ponta a ponta (reopen sem tenant context, subscriptions sem withTenantContext) e adiciona teste unitario do RatingEngine (17 casos). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWHKmcVJtstQFErbZ1AanY
This commit is contained in:
@@ -92,7 +92,7 @@ export async function persistCallEvent(normalized: NormalizedEvent): Promise<voi
|
||||
});
|
||||
|
||||
if (normalized.type === "CALL_ENDED") {
|
||||
await finalizeCall(tx, callId);
|
||||
await finalizeCall(tx, callId, tenantId);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -155,9 +155,10 @@ function buildPatch(normalized: NormalizedEvent): CallPatch {
|
||||
|
||||
/** Calcula os agregados em segundos (secao 155-156) uma vez que a chamada
|
||||
* terminou — nunca antes, pra não gravar valores parciais. */
|
||||
async function finalizeCall(tx: Prisma.TransactionClient, callId: string): Promise<void> {
|
||||
async function finalizeCall(tx: Prisma.TransactionClient, callId: string, tenantId: string): Promise<void> {
|
||||
const call = await tx.call.findUniqueOrThrow({ where: { id: callId } });
|
||||
const talkTime = seconds(call.bridgeAt, call.endAt);
|
||||
const billableSeconds = talkTime ?? 0;
|
||||
|
||||
await tx.call.update({
|
||||
where: { id: callId },
|
||||
@@ -166,7 +167,27 @@ async function finalizeCall(tx: Prisma.TransactionClient, callId: string): Promi
|
||||
waitTime: seconds(call.queueEnterAt, call.agentAnswerAt),
|
||||
talkTime,
|
||||
durationSeconds: seconds(call.createdAt, call.endAt),
|
||||
billableSeconds: talkTime ?? 0,
|
||||
billableSeconds,
|
||||
},
|
||||
});
|
||||
|
||||
// "Chamada faturável" (agente.md secao 133) gera o UsageEvent que o
|
||||
// RatingEngine (packages/billing) consome no fechamento do período —
|
||||
// nunca calcula o valor aqui, só registra o fato bruto (segundos
|
||||
// faturáveis). Chamada sem talk time (nunca bridgeou) não gera evento —
|
||||
// nada a cobrar.
|
||||
if (billableSeconds > 0) {
|
||||
await tx.usageEvent.create({
|
||||
data: {
|
||||
tenantId,
|
||||
callId,
|
||||
meter: "CALL_SECONDS",
|
||||
quantity: billableSeconds,
|
||||
unit: "seconds",
|
||||
sourceType: "call",
|
||||
sourceId: callId,
|
||||
occurredAt: call.endAt ?? new Date(),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user