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:
65
TODO.md
65
TODO.md
@@ -589,8 +589,69 @@ docs/QUALITY_SCORECARDS.md
|
||||
(mesma restrição de todo o módulo de IA); uma `QualityEvaluation`
|
||||
completando de verdade (só via dead-letter, sem rede real)
|
||||
|
||||
## PHASE 22+ — ver `agente.md` seções 120 em diante (Usage Metering
|
||||
completo, Billing, Frontend, Security, Tests)
|
||||
## PHASE 22 — Usage Metering / Billing (agente.md secao 120-139) — ver
|
||||
docs/BILLING.md
|
||||
- [x] Migration `billing` (`PlanVersion`/`TenantSubscription`/`PriceBook`+
|
||||
`PriceBookItem`/`RateDeck`+`RateDeckEntry`/`UsageEvent`/
|
||||
`RatedUsageItem`/`BillingPeriod`/`BillingStatement`+
|
||||
`BillingStatementItem`, RLS nas tenant-scoped) e `packages/billing`
|
||||
(`RatingEngine` puro: longest-prefix match, rating por destino/
|
||||
fallback plano, prorateio de dias ativos, tokens/transcrição de IA,
|
||||
armazenamento) já existiam de uma sessão anterior interrompida —
|
||||
só a orquestração (I/O) e os endpoints faltavam
|
||||
- [x] `apps/api/src/billing/billing-engine.service.ts`:
|
||||
`closeBillingPeriod`/`reopenBillingPeriod` — lê os 2 ledgers
|
||||
imutáveis (`UsageEvent`+`AIUsageRecord`) ainda não tarifados
|
||||
(`ratedUsageItems: { none: {} }`), grava 1 `RatedUsageItem` por
|
||||
evento (nunca agrega antes de ratear), soma `PLAN_BASE` da
|
||||
`TenantSubscription` ativa, agrega por categoria em
|
||||
`BillingStatementItem`. Fechamento imutável (secao 137): `CLOSED`
|
||||
de novo é 409, só reopen explícito (audit trail) permite recalcular
|
||||
- [x] Escritores do ledger `UsageEvent`: `CALL_SECONDS` em
|
||||
`apps/freeswitch-events/src/cdr.ts::finalizeCall` (mesma transação
|
||||
do CDR); `EXTENSION_ACTIVE_DAY`/`AGENT_ACTIVE_DAY`/
|
||||
`TRUNK_ACTIVE_DAY` em `apps/api/src/billing/active-day-sweep.ts`
|
||||
(boot + hora em hora, idempotente por dia)
|
||||
- [x] Controllers: `PriceBooksController`/`RateDecksController`/
|
||||
`PlanVersionsController` (catálogo global, `pricing.manage` +
|
||||
`isPlatformUser`), `SubscriptionsController`/
|
||||
`BillingPeriodsController` (`billing.manage` + `isPlatformUser`,
|
||||
`tenantId` explícito no body — ação de platform admin sobre um
|
||||
tenant arbitrário), `BillingStatementsController` (`billing.view`,
|
||||
sempre o próprio tenant do JWT)
|
||||
- [x] **Bug real, achado no teste desta fase**: `reopenBillingPeriod` lia
|
||||
o período sem tenant context — `billing_periods` tem FORCE RLS,
|
||||
então a leitura nunca via a linha e "not found" virava 500 em vez
|
||||
de 404. Corrigido exigindo `tenantId` explícito no reopen (igual ao
|
||||
close) e lendo dentro de `withTenantContext`.
|
||||
- [x] **Bug real, achado no teste desta fase**: `SubscriptionsController`
|
||||
criava/lia `TenantSubscription` sem `withTenantContext` — RLS
|
||||
rejeitava o create e o list sempre voltava vazio. Corrigido.
|
||||
- [x] Teste unitário do `RatingEngine` (`packages/billing`, 17 casos,
|
||||
`pnpm --filter @b2bcall/billing run test`) + testado ponta a ponta
|
||||
contra a API real e Postgres real com RLS: período fechado com 8
|
||||
`RatedUsageItem`s (chamada, 3 dias de ramal ativo, transcrição,
|
||||
análise, tokens de entrada/saída) e total batendo exatamente com o
|
||||
cálculo manual; fechar 2x = 409; reopen + reclose reusa os itens já
|
||||
tarifados; reopen com tenant errado = 404 (RLS isolando de
|
||||
verdade); tenant admin sem role de plataforma barrado (403) de
|
||||
fechar mas lista as próprias statements normalmente;
|
||||
`runActiveDaySweep` chamado 2x no mesmo dia sem duplicar
|
||||
- [ ] **Lacuna real, conhecida**: `Call.calledNumber` ainda não é
|
||||
populado pelo CDR (PHASE 17) — `CALL_SECONDS` sempre usa o fallback
|
||||
plano (`CALL_MINUTE`), nunca o `RateDeck` por destino real
|
||||
(`longestPrefixMatch`/`rateCallByDestination` só testados
|
||||
isoladamente, não ponta a ponta)
|
||||
- [ ] `RECORDING_BYTES` usa o storage atual no momento do fechamento como
|
||||
proxy do período inteiro (sem histórico de tamanho por dia) —
|
||||
decisão documentada em docs/BILLING.md, não uma média ponderada
|
||||
- [ ] Reajuste de preço no meio de um período aberto (2 vigências
|
||||
sobrepostas de `PriceBookItem`/`RateDeckEntry`) nunca exercitado
|
||||
- [ ] Sem UI de platform admin pra criar tenant/price book/rate deck
|
||||
ainda (fase Frontend) — testado só via API direto
|
||||
|
||||
## PHASE 23+ — ver `agente.md` seções 140 em diante (Frontend, Security,
|
||||
Tests)
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user