From 077e9ee3e081876935f30e6c061e18f84d1306de Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Fri, 17 Jul 2026 10:37:36 -0700 Subject: [PATCH] improvement(admin): remove included usage for enterprise provisioning --- .../admin/dashboard/enterprise-provisioning/route.ts | 3 +-- .../sim/lib/api/contracts/v1/admin/dashboard.test.ts | 2 -- apps/sim/lib/api/contracts/v1/admin/dashboard.ts | 1 - apps/sim/lib/billing/enterprise-provisioning.test.ts | 12 +++++++----- apps/sim/lib/billing/enterprise-provisioning.ts | 12 +++++++----- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/apps/sim/app/api/v1/admin/dashboard/enterprise-provisioning/route.ts b/apps/sim/app/api/v1/admin/dashboard/enterprise-provisioning/route.ts index dcd8350f0bf..82881c24eff 100644 --- a/apps/sim/app/api/v1/admin/dashboard/enterprise-provisioning/route.ts +++ b/apps/sim/app/api/v1/admin/dashboard/enterprise-provisioning/route.ts @@ -35,12 +35,11 @@ export const POST = withRouteHandler( if (!parsed.success) return parsed.response try { const actor = await getAdminAuditActor(request) - const { includedMonthlyDollars, usageLimitDollars, ...body } = parsed.data.body + const { usageLimitDollars, ...body } = parsed.data.body return singleResponse( toDashboardProvisioning( await issueEnterpriseProvisioning({ ...body, - includedMonthlyCredits: dollarsToCredits(includedMonthlyDollars), usageLimitCredits: usageLimitDollars === undefined ? undefined : dollarsToCredits(usageLimitDollars), requestedByEmail: actor.email ?? 'admin-api', diff --git a/apps/sim/lib/api/contracts/v1/admin/dashboard.test.ts b/apps/sim/lib/api/contracts/v1/admin/dashboard.test.ts index 952fc48b3f3..97da77abb28 100644 --- a/apps/sim/lib/api/contracts/v1/admin/dashboard.test.ts +++ b/apps/sim/lib/api/contracts/v1/admin/dashboard.test.ts @@ -74,7 +74,6 @@ describe('admin dashboard credit grant contract', () => { adminDashboardIssueEnterpriseBodySchema.safeParse({ ownerUserId: 'owner-1', monthlyInvoiceAmountUsd: 500, - includedMonthlyDollars: 500, seats: 10, concurrencyLimit: 1250, pausePaymentCollection: true, @@ -90,7 +89,6 @@ describe('admin dashboard credit grant contract', () => { adminDashboardIssueEnterpriseBodySchema.safeParse({ ownerUserId: 'owner-1', monthlyInvoiceAmountUsd: 500, - includedMonthlyDollars: 500, seats: 10, concurrencyLimit: null, }).success diff --git a/apps/sim/lib/api/contracts/v1/admin/dashboard.ts b/apps/sim/lib/api/contracts/v1/admin/dashboard.ts index 2e106c5de6e..59a0ee8079a 100644 --- a/apps/sim/lib/api/contracts/v1/admin/dashboard.ts +++ b/apps/sim/lib/api/contracts/v1/admin/dashboard.ts @@ -112,7 +112,6 @@ export const adminDashboardIssueEnterpriseBodySchema = z.object({ ownerUserId: z.string().min(1), organizationName: z.string().trim().min(1).max(120).optional(), monthlyInvoiceAmountUsd: z.number().min(0.01).max(10_000_000).multipleOf(0.01), - includedMonthlyDollars: creditAlignedDollarAmountSchema, usageLimitDollars: creditAlignedDollarAmountSchema.optional(), seats: z.number().int().positive().max(100_000), concurrencyLimit: z.number().int().positive().max(MAX_BILLING_CONCURRENCY_LIMIT).optional(), diff --git a/apps/sim/lib/billing/enterprise-provisioning.test.ts b/apps/sim/lib/billing/enterprise-provisioning.test.ts index 0af8df982f3..45667323064 100644 --- a/apps/sim/lib/billing/enterprise-provisioning.test.ts +++ b/apps/sim/lib/billing/enterprise-provisioning.test.ts @@ -160,11 +160,10 @@ function context() { } describe('Enterprise issuance serialization decisions', () => { - it('preserves legacy request keys when no concurrency override is supplied', () => { + it('derives included usage from invoice amount in the request key', () => { const input = { ownerUserId: 'owner-1', monthlyInvoiceAmountUsd: 125, - includedMonthlyCredits: 20000, usageLimitCredits: 24000, seats: 12, requestedByEmail: 'admin@sim.ai', @@ -172,14 +171,17 @@ describe('Enterprise issuance serialization decisions', () => { } expect(buildEnterpriseProvisioningRequestKey(input, 'org-1')).toBe( - 'enterprise-v2:owner-1:org-1:12500:20000:24000:12' + 'enterprise-v2:owner-1:org-1:12500:25000:24000:12' ) expect( buildEnterpriseProvisioningRequestKey({ ...input, concurrencyLimit: 1250 }, 'org-1') - ).toBe('enterprise-v2:owner-1:org-1:12500:20000:24000:12:1250') + ).toBe('enterprise-v2:owner-1:org-1:12500:25000:24000:12:1250') expect( buildEnterpriseProvisioningRequestKey({ ...input, pausePaymentCollection: true }, 'org-1') - ).toBe('enterprise-v2:owner-1:org-1:12500:20000:24000:12:draft-collection') + ).toBe('enterprise-v2:owner-1:org-1:12500:25000:24000:12:draft-collection') + expect( + buildEnterpriseProvisioningRequestKey({ ...input, usageLimitCredits: undefined }, 'org-1') + ).toBe('enterprise-v2:owner-1:org-1:12500:25000:25000:12') }) it('deduplicates an identical unresolved request to the existing outbox operation', () => { diff --git a/apps/sim/lib/billing/enterprise-provisioning.ts b/apps/sim/lib/billing/enterprise-provisioning.ts index 4c35487dd8f..b126748e8a8 100644 --- a/apps/sim/lib/billing/enterprise-provisioning.ts +++ b/apps/sim/lib/billing/enterprise-provisioning.ts @@ -6,6 +6,7 @@ import { and, count, desc, eq, inArray, isNull, sql } from 'drizzle-orm' import type Stripe from 'stripe' import { parseBillingConcurrencyLimit } from '@/lib/billing/concurrency-defaults' import { getBillingConcurrencyLimit } from '@/lib/billing/concurrency-limits' +import { dollarsToCredits } from '@/lib/billing/credits/conversion' import { deriveEnterpriseOperationStatus, ENTERPRISE_METADATA_SYNC_EVENT_TYPE, @@ -234,7 +235,6 @@ export interface IssueEnterpriseProvisioningInput { ownerUserId: string organizationName?: string monthlyInvoiceAmountUsd: number - includedMonthlyCredits: number usageLimitCredits?: number seats: number concurrencyLimit?: number @@ -281,13 +281,14 @@ export function buildEnterpriseProvisioningRequestKey( input: IssueEnterpriseProvisioningInput, organizationId: string ): string { + const includedMonthlyCredits = dollarsToCredits(input.monthlyInvoiceAmountUsd) const requestTerms: Array = [ 'enterprise-v2', input.ownerUserId, organizationId, Math.round(input.monthlyInvoiceAmountUsd * 100), - input.includedMonthlyCredits, - input.usageLimitCredits ?? input.includedMonthlyCredits, + includedMonthlyCredits, + input.usageLimitCredits ?? includedMonthlyCredits, input.seats, ] if (input.concurrencyLimit !== undefined) requestTerms.push(input.concurrencyLimit) @@ -422,6 +423,7 @@ export async function issueEnterpriseProvisioning( 'Monthly invoice amount must be at least $0.01 and use whole cents' ) } + const includedMonthlyCredits = dollarsToCredits(input.monthlyInvoiceAmountUsd) if ( input.concurrencyLimit !== undefined && parseBillingConcurrencyLimit(input.concurrencyLimit) !== input.concurrencyLimit @@ -548,8 +550,8 @@ export async function issueEnterpriseProvisioning( requestedByEmail: input.requestedByEmail, requestedByUserId: input.requestedByUserId, invoiceAmountCents, - includedMonthlyCredits: input.includedMonthlyCredits, - usageLimitCredits: input.usageLimitCredits ?? input.includedMonthlyCredits, + includedMonthlyCredits, + usageLimitCredits: input.usageLimitCredits ?? includedMonthlyCredits, seats: input.seats, ...(input.concurrencyLimit !== undefined ? { concurrencyLimit: input.concurrencyLimit } : {}), pausePaymentCollection: input.pausePaymentCollection ?? false,