@@ -22,6 +22,56 @@ import { getEmailPreferences } from '@/lib/messaging/email/unsubscribe'
2222
2323const logger = createLogger ( 'UsageManagement' )
2424
25+ export interface OrgUsageLimitResult {
26+ limit : number
27+ minimum : number
28+ }
29+
30+ /**
31+ * Calculates the effective usage limit for a team or enterprise organization.
32+ * - Enterprise: Uses orgUsageLimit directly (fixed pricing)
33+ * - Team: Uses orgUsageLimit but never below seats × basePrice
34+ */
35+ export async function getOrgUsageLimit (
36+ organizationId : string ,
37+ plan : string ,
38+ seats : number | null
39+ ) : Promise < OrgUsageLimitResult > {
40+ const orgData = await db
41+ . select ( { orgUsageLimit : organization . orgUsageLimit } )
42+ . from ( organization )
43+ . where ( eq ( organization . id , organizationId ) )
44+ . limit ( 1 )
45+
46+ const configured =
47+ orgData . length > 0 && orgData [ 0 ] . orgUsageLimit
48+ ? Number . parseFloat ( orgData [ 0 ] . orgUsageLimit )
49+ : null
50+
51+ if ( plan === 'enterprise' ) {
52+ // Enterprise: Use configured limit directly (no per-seat minimum)
53+ if ( configured !== null ) {
54+ return { limit : configured , minimum : configured }
55+ }
56+ logger . warn ( 'Enterprise org missing usage limit' , { orgId : organizationId } )
57+ return { limit : 0 , minimum : 0 }
58+ }
59+
60+ const { basePrice } = getPlanPricing ( plan )
61+ const minimum = ( seats ?? 0 ) * basePrice
62+
63+ if ( configured !== null ) {
64+ return { limit : Math . max ( configured , minimum ) , minimum }
65+ }
66+
67+ logger . warn ( 'Team org missing usage limit, using seats × basePrice fallback' , {
68+ orgId : organizationId ,
69+ seats,
70+ minimum,
71+ } )
72+ return { limit : minimum , minimum }
73+ }
74+
2575/**
2676 * Handle new user setup when they join the platform
2777 * Creates userStats record with default free credits
@@ -87,22 +137,13 @@ export async function getUserUsageData(userId: string): Promise<UsageData> {
87137 ? Number . parseFloat ( stats . currentUsageLimit )
88138 : getFreeTierLimit ( )
89139 } else {
90- // Team/Enterprise: Use organization limit but never below minimum (seats × cost per seat)
91- const orgData = await db
92- . select ( { orgUsageLimit : organization . orgUsageLimit } )
93- . from ( organization )
94- . where ( eq ( organization . id , subscription . referenceId ) )
95- . limit ( 1 )
96-
97- const { basePrice } = getPlanPricing ( subscription . plan )
98- const minimum = ( subscription . seats ?? 0 ) * basePrice
99-
100- if ( orgData . length > 0 && orgData [ 0 ] . orgUsageLimit ) {
101- const configured = Number . parseFloat ( orgData [ 0 ] . orgUsageLimit )
102- limit = Math . max ( configured , minimum )
103- } else {
104- limit = minimum
105- }
140+ // Team/Enterprise: Use organization limit
141+ const orgLimit = await getOrgUsageLimit (
142+ subscription . referenceId ,
143+ subscription . plan ,
144+ subscription . seats
145+ )
146+ limit = orgLimit . limit
106147 }
107148
108149 const percentUsed = limit > 0 ? Math . min ( ( currentUsage / limit ) * 100 , 100 ) : 0
@@ -159,24 +200,15 @@ export async function getUserUsageLimitInfo(userId: string): Promise<UsageLimitI
159200 minimumLimit = getPerUserMinimumLimit ( subscription )
160201 canEdit = canEditUsageLimit ( subscription )
161202 } else {
162- // Team/Enterprise: Use organization limits (users cannot edit)
163- const orgData = await db
164- . select ( { orgUsageLimit : organization . orgUsageLimit } )
165- . from ( organization )
166- . where ( eq ( organization . id , subscription . referenceId ) )
167- . limit ( 1 )
168-
169- const { basePrice } = getPlanPricing ( subscription . plan )
170- const minimum = ( subscription . seats ?? 0 ) * basePrice
171-
172- if ( orgData . length > 0 && orgData [ 0 ] . orgUsageLimit ) {
173- const configured = Number . parseFloat ( orgData [ 0 ] . orgUsageLimit )
174- currentLimit = Math . max ( configured , minimum )
175- } else {
176- currentLimit = minimum
177- }
178- minimumLimit = minimum
179- canEdit = false // Team/enterprise members cannot edit limits
203+ // Team/Enterprise: Use organization limits
204+ const orgLimit = await getOrgUsageLimit (
205+ subscription . referenceId ,
206+ subscription . plan ,
207+ subscription . seats
208+ )
209+ currentLimit = orgLimit . limit
210+ minimumLimit = orgLimit . minimum
211+ canEdit = false
180212 }
181213
182214 return {
@@ -323,27 +355,23 @@ export async function getUserUsageLimit(userId: string): Promise<number> {
323355
324356 return Number . parseFloat ( userStatsQuery [ 0 ] . currentUsageLimit )
325357 }
326- // Team/Enterprise: Use organization limit but never below minimum
327- const orgData = await db
328- . select ( { orgUsageLimit : organization . orgUsageLimit } )
358+ // Team/Enterprise: Verify org exists then use organization limit
359+ const orgExists = await db
360+ . select ( { id : organization . id } )
329361 . from ( organization )
330362 . where ( eq ( organization . id , subscription . referenceId ) )
331363 . limit ( 1 )
332364
333- if ( orgData . length === 0 ) {
365+ if ( orgExists . length === 0 ) {
334366 throw new Error ( `Organization not found: ${ subscription . referenceId } for user: ${ userId } ` )
335367 }
336368
337- if ( orgData [ 0 ] . orgUsageLimit ) {
338- const configured = Number . parseFloat ( orgData [ 0 ] . orgUsageLimit )
339- const { basePrice } = getPlanPricing ( subscription . plan )
340- const minimum = ( subscription . seats ?? 0 ) * basePrice
341- return Math . max ( configured , minimum )
342- }
343-
344- // If org hasn't set a custom limit, use minimum (seats × cost per seat)
345- const { basePrice } = getPlanPricing ( subscription . plan )
346- return ( subscription . seats ?? 0 ) * basePrice
369+ const orgLimit = await getOrgUsageLimit (
370+ subscription . referenceId ,
371+ subscription . plan ,
372+ subscription . seats
373+ )
374+ return orgLimit . limit
347375}
348376
349377/**
0 commit comments