Skip to content

Commit 75dbed5

Browse files
committed
address comments
1 parent a49e535 commit 75dbed5

23 files changed

Lines changed: 303 additions & 227 deletions

File tree

apps/sim/app/api/billing/route.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,6 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
140140
members: rawBillingData.members.map((m) => ({
141141
...m,
142142
joinedAt: m.joinedAt.toISOString(),
143-
lastActive: m.lastActive?.toISOString() || null,
144143
})),
145144
}
146145

apps/sim/app/api/organizations/[id]/members/[memberId]/route.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { parseRequest } from '@/lib/api/server'
1212
import { getSession } from '@/lib/auth'
1313
import { setActiveOrganizationForCurrentSession } from '@/lib/auth/active-organization'
1414
import { getUserUsageData } from '@/lib/billing/core/usage'
15+
import { getBillingPeriodUsageCostByUser } from '@/lib/billing/core/usage-log'
1516
import {
1617
removeExternalUserFromOrganizationWorkspaces,
1718
removeUserFromOrganization,
@@ -101,10 +102,25 @@ export const GET = withRouteHandler(
101102
const computed = await getUserUsageData(memberId)
102103

103104
if (usageData.length > 0) {
105+
// currentPeriodCost is only a baseline; add this member's attributed
106+
// usage_log for the period. (getUserUsageData returns the org POOL for
107+
// org-scoped members, so it can't supply the per-member figure.)
108+
const memberLedger =
109+
computed.billingPeriodStart && computed.billingPeriodEnd
110+
? ((
111+
await getBillingPeriodUsageCostByUser(
112+
{ type: 'organization', id: organizationId },
113+
{ start: computed.billingPeriodStart, end: computed.billingPeriodEnd }
114+
)
115+
).get(memberId) ?? 0)
116+
: 0
104117
memberData = {
105118
...memberData,
106119
usage: {
107120
...usageData[0],
121+
currentPeriodCost: (
122+
Number(usageData[0].currentPeriodCost ?? 0) + memberLedger
123+
).toString(),
108124
billingPeriodStart: computed.billingPeriodStart,
109125
billingPeriodEnd: computed.billingPeriodEnd,
110126
},

apps/sim/app/api/organizations/[id]/members/route.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
} from '@/lib/api/contracts/organization'
1818
import { getValidationErrorMessage, parseRequest } from '@/lib/api/server'
1919
import { getSession } from '@/lib/auth'
20+
import { getBillingPeriodUsageCostByUser } from '@/lib/billing/core/usage-log'
2021
import { ENTITLED_SUBSCRIPTION_STATUSES } from '@/lib/billing/subscriptions/utils'
2122
import { validateSeatAvailability } from '@/lib/billing/validation/seat-management'
2223
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
@@ -139,8 +140,21 @@ export const GET = withRouteHandler(
139140
const billingPeriodStart = orgSub?.periodStart ?? null
140141
const billingPeriodEnd = orgSub?.periodEnd ?? null
141142

143+
// currentPeriodCost is only a baseline; add each member's attributed
144+
// usage_log for the period (batched, one query) so the roster shows real
145+
// usage rather than the frozen baseline.
146+
const usageByUser =
147+
billingPeriodStart && billingPeriodEnd
148+
? await getBillingPeriodUsageCostByUser(
149+
{ type: 'organization', id: organizationId },
150+
{ start: billingPeriodStart, end: billingPeriodEnd }
151+
)
152+
: new Map<string, number>()
153+
142154
const membersWithUsage = base.map((row) => ({
143155
...row,
156+
currentPeriodCost:
157+
Number(row.currentPeriodCost ?? 0) + (usageByUser.get(row.userId) ?? 0),
144158
billingPeriodStart,
145159
billingPeriodEnd,
146160
}))

apps/sim/app/api/v1/admin/organizations/[id]/members/[memberId]/route.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import {
3535
adminV1UpdateOrganizationMemberContract,
3636
} from '@/lib/api/contracts/v1/admin'
3737
import { parseRequest } from '@/lib/api/server'
38+
import { getOrganizationSubscription } from '@/lib/billing/core/billing'
39+
import { getBillingPeriodUsageCostByUser } from '@/lib/billing/core/usage-log'
3840
import { removeUserFromOrganization } from '@/lib/billing/organizations/membership'
3941
import { isBillingEnabled } from '@/lib/core/config/feature-flags'
4042
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
@@ -86,7 +88,6 @@ export const GET = withRouteHandler(
8688
userEmail: user.email,
8789
currentPeriodCost: userStats.currentPeriodCost,
8890
currentUsageLimit: userStats.currentUsageLimit,
89-
lastActive: userStats.lastActive,
9091
billingBlocked: userStats.billingBlocked,
9192
})
9293
.from(member)
@@ -99,6 +100,20 @@ export const GET = withRouteHandler(
99100
return notFoundResponse('Member')
100101
}
101102

103+
// currentPeriodCost is only a baseline; add this member's attributed
104+
// usage_log for the org's period so admin shows real current usage.
105+
const subscription = await getOrganizationSubscription(organizationId)
106+
const billingPeriod =
107+
subscription?.periodStart && subscription?.periodEnd
108+
? { start: subscription.periodStart, end: subscription.periodEnd }
109+
: null
110+
const ledgerByUser = billingPeriod
111+
? await getBillingPeriodUsageCostByUser(
112+
{ type: 'organization', id: organizationId },
113+
billingPeriod
114+
)
115+
: new Map<string, number>()
116+
102117
const data: AdminMemberDetail = {
103118
id: memberData.id,
104119
userId: memberData.userId,
@@ -107,9 +122,10 @@ export const GET = withRouteHandler(
107122
createdAt: memberData.createdAt.toISOString(),
108123
userName: memberData.userName,
109124
userEmail: memberData.userEmail,
110-
currentPeriodCost: memberData.currentPeriodCost ?? '0',
125+
currentPeriodCost: (
126+
Number(memberData.currentPeriodCost ?? 0) + (ledgerByUser.get(memberData.userId) ?? 0)
127+
).toString(),
111128
currentUsageLimit: memberData.currentUsageLimit,
112-
lastActive: memberData.lastActive?.toISOString() ?? null,
113129
billingBlocked: memberData.billingBlocked ?? false,
114130
}
115131

apps/sim/app/api/v1/admin/organizations/[id]/members/route.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ import {
3737
adminV1ListOrganizationMembersContract,
3838
} from '@/lib/api/contracts/v1/admin'
3939
import { parseRequest } from '@/lib/api/server'
40+
import { getOrganizationSubscription } from '@/lib/billing/core/billing'
41+
import { getBillingPeriodUsageCostByUser } from '@/lib/billing/core/usage-log'
4042
import { addUserToOrganization } from '@/lib/billing/organizations/membership'
4143
import { isBillingEnabled } from '@/lib/core/config/feature-flags'
4244
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
@@ -96,7 +98,6 @@ export const GET = withRouteHandler(
9698
userEmail: user.email,
9799
currentPeriodCost: userStats.currentPeriodCost,
98100
currentUsageLimit: userStats.currentUsageLimit,
99-
lastActive: userStats.lastActive,
100101
billingBlocked: userStats.billingBlocked,
101102
})
102103
.from(member)
@@ -109,6 +110,21 @@ export const GET = withRouteHandler(
109110
])
110111

111112
const total = countResult[0].count
113+
114+
// currentPeriodCost is only a baseline; add each member's attributed
115+
// usage_log for the org's period so admin shows real current usage.
116+
const subscription = await getOrganizationSubscription(organizationId)
117+
const billingPeriod =
118+
subscription?.periodStart && subscription?.periodEnd
119+
? { start: subscription.periodStart, end: subscription.periodEnd }
120+
: null
121+
const usageByUser = billingPeriod
122+
? await getBillingPeriodUsageCostByUser(
123+
{ type: 'organization', id: organizationId },
124+
billingPeriod
125+
)
126+
: new Map<string, number>()
127+
112128
const data: AdminMemberDetail[] = membersData.map((m) => ({
113129
id: m.id,
114130
userId: m.userId,
@@ -117,9 +133,10 @@ export const GET = withRouteHandler(
117133
createdAt: m.createdAt.toISOString(),
118134
userName: m.userName,
119135
userEmail: m.userEmail,
120-
currentPeriodCost: m.currentPeriodCost ?? '0',
136+
currentPeriodCost: (
137+
Number(m.currentPeriodCost ?? 0) + (usageByUser.get(m.userId) ?? 0)
138+
).toString(),
121139
currentUsageLimit: m.currentUsageLimit,
122-
lastActive: m.lastActive?.toISOString() ?? null,
123140
billingBlocked: m.billingBlocked ?? false,
124141
}))
125142

apps/sim/app/api/v1/admin/organizations/[id]/seats/route.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,6 @@ export const GET = withRouteHandler(
5151
subscriptionPlan: analytics.subscriptionPlan,
5252
canAddSeats: analytics.canAddSeats,
5353
utilizationRate: analytics.utilizationRate,
54-
activeMembers: analytics.activeMembers,
55-
inactiveMembers: analytics.inactiveMembers,
56-
memberActivity: analytics.memberActivity.map((m) => ({
57-
userId: m.userId,
58-
userName: m.userName,
59-
userEmail: m.userEmail,
60-
role: m.role,
61-
joinedAt: m.joinedAt.toISOString(),
62-
lastActive: m.lastActive?.toISOString() ?? null,
63-
})),
6454
}
6555

6656
logger.info(`Admin API: Retrieved seat analytics for organization ${organizationId}`)

apps/sim/app/api/v1/admin/types.ts

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,6 @@ export interface AdminMemberDetail extends AdminMember {
543543
// Billing/usage info from userStats
544544
currentPeriodCost: string
545545
currentUsageLimit: string | null
546-
lastActive: string | null
547546
billingBlocked: boolean
548547
}
549548

@@ -574,20 +573,15 @@ interface AdminUserBilling {
574573
userEmail: string
575574
stripeCustomerId: string | null
576575
// Usage stats
577-
totalCost: string
578576
currentUsageLimit: string | null
579577
currentPeriodCost: string
580578
lastPeriodCost: string | null
581579
billedOverageThisPeriod: string
582580
storageUsedBytes: number
583-
lastActive: string | null
584581
billingBlocked: boolean
585-
// Copilot usage
586-
totalCopilotCost: string
582+
// Copilot usage (active per-period baselines)
587583
currentPeriodCopilotCost: string
588584
lastPeriodCopilotCost: string | null
589-
totalCopilotTokens: number
590-
totalCopilotCalls: number
591585
}
592586

593587
export interface AdminUserBillingWithSubscription extends AdminUserBilling {
@@ -635,16 +629,6 @@ export interface AdminSeatAnalytics {
635629
subscriptionPlan: string
636630
canAddSeats: boolean
637631
utilizationRate: number
638-
activeMembers: number
639-
inactiveMembers: number
640-
memberActivity: Array<{
641-
userId: string
642-
userName: string
643-
userEmail: string
644-
role: string
645-
joinedAt: string
646-
lastActive: string | null
647-
}>
648632
}
649633

650634
export interface AdminDeploymentVersion {

apps/sim/app/api/v1/admin/users/[id]/billing/route.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
} from '@/lib/api/contracts/v1/admin'
3030
import { parseRequest } from '@/lib/api/server'
3131
import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
32+
import { getUserUsageData } from '@/lib/billing/core/usage'
3233
import { isOrgScopedSubscription } from '@/lib/billing/subscriptions/utils'
3334
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
3435
import { withAdminAuthParams } from '@/app/api/v1/admin/middleware'
@@ -78,6 +79,11 @@ export const GET = withRouteHandler(
7879

7980
const [stats] = await db.select().from(userStats).where(eq(userStats.userId, userId)).limit(1)
8081

82+
// currentPeriodCost is now only a baseline; canonical current-period usage
83+
// (baseline + attributed usage_log, refresh-adjusted) comes from the same
84+
// helper users see, so admin reflects real usage instead of a stale 0.
85+
const usage = await getUserUsageData(userId)
86+
8187
const memberOrgs = await db
8288
.select({
8389
organizationId: member.organizationId,
@@ -107,19 +113,14 @@ export const GET = withRouteHandler(
107113
userName: userData.name,
108114
userEmail: userData.email,
109115
stripeCustomerId: userData.stripeCustomerId,
110-
totalCost: stats?.totalCost ?? '0',
111116
currentUsageLimit: stats?.currentUsageLimit ?? null,
112-
currentPeriodCost: stats?.currentPeriodCost ?? '0',
117+
currentPeriodCost: usage.currentUsage.toString(),
113118
lastPeriodCost: stats?.lastPeriodCost ?? null,
114119
billedOverageThisPeriod: stats?.billedOverageThisPeriod ?? '0',
115120
storageUsedBytes: stats?.storageUsedBytes ?? 0,
116-
lastActive: stats?.lastActive?.toISOString() ?? null,
117121
billingBlocked: stats?.billingBlocked ?? false,
118-
totalCopilotCost: stats?.totalCopilotCost ?? '0',
119122
currentPeriodCopilotCost: stats?.currentPeriodCopilotCost ?? '0',
120123
lastPeriodCopilotCost: stats?.lastPeriodCopilotCost ?? null,
121-
totalCopilotTokens: stats?.totalCopilotTokens ?? 0,
122-
totalCopilotCalls: stats?.totalCopilotCalls ?? 0,
123124
subscriptions: subscriptions.map(toAdminSubscription),
124125
organizationMemberships: memberOrgs.map((m) => ({
125126
organizationId: m.organizationId,

apps/sim/lib/api/contracts/subscription.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ export const organizationBillingMemberSchema = z
103103
userName: z.string().nullable().optional(),
104104
userEmail: z.string().nullable().optional(),
105105
joinedAt: z.string().nullable().optional(),
106-
lastActive: z.string().nullable().optional(),
107106
})
108107
.passthrough()
109108

apps/sim/lib/api/contracts/v1/admin/organizations.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ export const adminV1MemberSchema = z.object({
4343
export const adminV1MemberDetailSchema = adminV1MemberSchema.extend({
4444
currentPeriodCost: z.string(),
4545
currentUsageLimit: z.string().nullable(),
46-
lastActive: z.string().nullable(),
4746
billingBlocked: z.boolean(),
4847
})
4948

@@ -75,18 +74,6 @@ export const adminV1SeatAnalyticsSchema = z.object({
7574
subscriptionPlan: z.string(),
7675
canAddSeats: z.boolean(),
7776
utilizationRate: z.number(),
78-
activeMembers: z.number(),
79-
inactiveMembers: z.number(),
80-
memberActivity: z.array(
81-
z.object({
82-
userId: z.string(),
83-
userName: z.string(),
84-
userEmail: z.string(),
85-
role: z.string(),
86-
joinedAt: z.string(),
87-
lastActive: z.string().nullable(),
88-
})
89-
),
9077
})
9178

9279
export const adminV1CreateOrganizationBodySchema = z.object({

0 commit comments

Comments
 (0)