import { getPrismaClient, type Plan } from "@b2bcall/database"; /** * Entitlements por plano (agente.md secao 56-61): "criar sistema genérico... * não espalhar `if plan == PRO` pelo código". Todo tenant tem exatamente um * Plan (nunca null) — um campo de limite null significa "sem limite", nunca * "sem plano". */ export class QuotaExceededError extends Error { constructor( public readonly quotaKey: QuotaKey, public readonly limit: number, ) { super(`Quota excedida: ${quotaKey} (limite do plano: ${limit})`); this.name = "QuotaExceededError"; } } export class FeatureNotEnabledError extends Error { constructor(public readonly featureKey: FeatureKey) { super(`Recurso nao habilitado no plano: ${featureKey}`); this.name = "FeatureNotEnabledError"; } } /** Campos de limite (contagem) do Plan — null = sem limite. */ export type QuotaKey = | "maxExtensions" | "maxAgents" | "maxTrunks" | "maxQueues" | "maxCampaigns" | "maxConcurrentCalls" | "maxDailyCalls" | "maxMonthlyCalls"; /** Campos booleanos de feature flag do Plan. */ export type FeatureKey = | "recordingEnabled" | "aiEnabled" | "aiTranscriptionEnabled" | "aiAnalysisEnabled" | "apiAccessEnabled"; export async function getPlanForTenant(tenantId: string): Promise { const prisma = getPrismaClient(); const tenant = await prisma.tenant.findUniqueOrThrow({ where: { id: tenantId }, include: { plan: true }, }); return tenant.plan; } /** * Lança QuotaExceededError se `currentCount` já atingiu o limite do plano * pra `key`. Chamar ANTES de criar a linha (currentCount = contagem atual, * sem contar a nova) — nunca depois, pra não criar e ter que desfazer. */ export async function assertQuota(tenantId: string, key: QuotaKey, currentCount: number): Promise { const plan = await getPlanForTenant(tenantId); const limit = plan[key]; if (limit === null || limit === undefined) return; if (currentCount >= limit) { throw new QuotaExceededError(key, limit); } } /** Lança FeatureNotEnabledError se o plano não habilita `key`. */ export async function assertFeatureEnabled(tenantId: string, key: FeatureKey): Promise { const plan = await getPlanForTenant(tenantId); if (!plan[key]) { throw new FeatureNotEnabledError(key); } } /** Versão não-lançante de `assertFeatureEnabled` — pra decisões em * background (ex.: "devo enfileirar este job de IA?") onde lançar não faz * sentido, só pular silenciosamente. */ export async function isFeatureEnabled(tenantId: string, key: FeatureKey): Promise { const plan = await getPlanForTenant(tenantId); return Boolean(plan[key]); }