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:
12
apps/api/src/billing/dto/close-period.dto.ts
Normal file
12
apps/api/src/billing/dto/close-period.dto.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { IsDateString, IsUUID } from "class-validator";
|
||||
|
||||
export class ClosePeriodDto {
|
||||
@IsUUID()
|
||||
tenantId!: string;
|
||||
|
||||
@IsDateString()
|
||||
periodStart!: string;
|
||||
|
||||
@IsDateString()
|
||||
periodEnd!: string;
|
||||
}
|
||||
22
apps/api/src/billing/dto/create-plan-version.dto.ts
Normal file
22
apps/api/src/billing/dto/create-plan-version.dto.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { IsDateString, IsNumber, IsOptional, IsString, IsUUID, MaxLength, Min } from "class-validator";
|
||||
|
||||
export class CreatePlanVersionDto {
|
||||
@IsUUID()
|
||||
planId!: string;
|
||||
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
basePrice!: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(3)
|
||||
currency?: string;
|
||||
|
||||
@IsDateString()
|
||||
effectiveFrom!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
effectiveUntil?: string;
|
||||
}
|
||||
55
apps/api/src/billing/dto/create-price-book.dto.ts
Normal file
55
apps/api/src/billing/dto/create-price-book.dto.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { IsArray, IsBoolean, IsDateString, IsIn, IsNumber, IsOptional, IsString, MaxLength, ValidateNested } from "class-validator";
|
||||
import { Type } from "class-transformer";
|
||||
|
||||
const PRICE_ITEM_TYPES = [
|
||||
"BASE_SUBSCRIPTION",
|
||||
"EXTENSION_MONTH",
|
||||
"AGENT_MONTH",
|
||||
"TRUNK_MONTH",
|
||||
"CALL",
|
||||
"CALL_MINUTE",
|
||||
"FIXED_MINUTE",
|
||||
"MOBILE_MINUTE",
|
||||
"INTERNATIONAL_MINUTE",
|
||||
"AI_TRANSCRIPTION_MINUTE",
|
||||
"AI_ANALYSIS_CALL",
|
||||
"AI_INPUT_TOKEN",
|
||||
"AI_OUTPUT_TOKEN",
|
||||
"RECORDING_GB_MONTH",
|
||||
] as const;
|
||||
|
||||
export class CreatePriceBookItemDto {
|
||||
@IsIn(PRICE_ITEM_TYPES)
|
||||
type!: (typeof PRICE_ITEM_TYPES)[number];
|
||||
|
||||
@IsNumber()
|
||||
unitPrice!: number;
|
||||
|
||||
@IsDateString()
|
||||
effectiveFrom!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
effectiveUntil?: string;
|
||||
}
|
||||
|
||||
export class CreatePriceBookDto {
|
||||
@IsString()
|
||||
@MaxLength(120)
|
||||
name!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(3)
|
||||
currency?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isDefault?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CreatePriceBookItemDto)
|
||||
items?: CreatePriceBookItemDto[];
|
||||
}
|
||||
60
apps/api/src/billing/dto/create-rate-deck.dto.ts
Normal file
60
apps/api/src/billing/dto/create-rate-deck.dto.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { IsArray, IsBoolean, IsDateString, IsIn, IsInt, IsNumber, IsOptional, IsString, Max, MaxLength, Min, ValidateNested } from "class-validator";
|
||||
import { Type } from "class-transformer";
|
||||
|
||||
const DESTINATION_TYPES = ["FIXED", "MOBILE", "INTERNATIONAL"] as const;
|
||||
|
||||
export class CreateRateDeckEntryDto {
|
||||
@IsString()
|
||||
@MaxLength(20)
|
||||
prefix!: string;
|
||||
|
||||
@IsString()
|
||||
@MaxLength(80)
|
||||
destinationName!: string;
|
||||
|
||||
@IsIn(DESTINATION_TYPES)
|
||||
destinationType!: (typeof DESTINATION_TYPES)[number];
|
||||
|
||||
@IsNumber()
|
||||
pricePerMinute!: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(3600)
|
||||
billingIncrementSeconds?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
@Max(3600)
|
||||
minimumSeconds?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
connectionFee?: number;
|
||||
|
||||
@IsDateString()
|
||||
validFrom!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
validUntil?: string;
|
||||
}
|
||||
|
||||
export class CreateRateDeckDto {
|
||||
@IsString()
|
||||
@MaxLength(120)
|
||||
name!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isDefault?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CreateRateDeckEntryDto)
|
||||
entries?: CreateRateDeckEntryDto[];
|
||||
}
|
||||
23
apps/api/src/billing/dto/create-subscription.dto.ts
Normal file
23
apps/api/src/billing/dto/create-subscription.dto.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { IsDateString, IsInt, IsOptional, IsString, IsUUID, Max, MaxLength, Min } from "class-validator";
|
||||
|
||||
export class CreateSubscriptionDto {
|
||||
@IsUUID()
|
||||
tenantId!: string;
|
||||
|
||||
@IsUUID()
|
||||
planVersionId!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
startedAt?: string;
|
||||
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(28)
|
||||
billingCycleAnchor!: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(3)
|
||||
currency?: string;
|
||||
}
|
||||
11
apps/api/src/billing/dto/reopen-period.dto.ts
Normal file
11
apps/api/src/billing/dto/reopen-period.dto.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { IsString, IsUUID, MaxLength, MinLength } from "class-validator";
|
||||
|
||||
export class ReopenPeriodDto {
|
||||
@IsUUID()
|
||||
tenantId!: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(3)
|
||||
@MaxLength(500)
|
||||
reason!: string;
|
||||
}
|
||||
Reference in New Issue
Block a user