( organizationId: string, executor: DbClient = db )
| 90 | * Get comprehensive organization billing and usage data |
| 91 | */ |
| 92 | export async function getOrganizationBillingData( |
| 93 | organizationId: string, |
| 94 | executor: DbClient = db |
| 95 | ): Promise<OrganizationUsageData | null> { |
| 96 | try { |
| 97 | // Get organization info |
| 98 | const orgRecord = await executor |
| 99 | .select() |
| 100 | .from(organization) |
| 101 | .where(eq(organization.id, organizationId)) |
| 102 | .limit(1) |
| 103 | |
| 104 | if (orgRecord.length === 0) { |
| 105 | logger.warn('Organization not found', { organizationId }) |
| 106 | return null |
| 107 | } |
| 108 | |
| 109 | const organizationData = orgRecord[0] |
| 110 | |
| 111 | // Get organization subscription directly (referenceId = organizationId) |
| 112 | const subscription = await getOrganizationSubscription(organizationId, { executor }) |
| 113 | |
| 114 | if (!subscription) { |
| 115 | logger.warn('No subscription found for organization', { organizationId }) |
| 116 | return null |
| 117 | } |
| 118 | |
| 119 | // Get all organization members with their usage data |
| 120 | const membersWithUsage = await executor |
| 121 | .select({ |
| 122 | userId: member.userId, |
| 123 | userName: user.name, |
| 124 | userEmail: user.email, |
| 125 | role: member.role, |
| 126 | joinedAt: member.createdAt, |
| 127 | // User stats fields |
| 128 | currentPeriodCost: userStats.currentPeriodCost, |
| 129 | currentUsageLimit: userStats.currentUsageLimit, |
| 130 | }) |
| 131 | .from(member) |
| 132 | .innerJoin(user, eq(member.userId, user.id)) |
| 133 | .leftJoin(userStats, eq(member.userId, userStats.userId)) |
| 134 | .where(eq(member.organizationId, organizationId)) |
| 135 | |
| 136 | // Per-member current-period usage = userStats baseline + attributed usage_log |
| 137 | // rows. currentPeriodCost is no longer incremented on the hot path, so the |
| 138 | // baseline alone under-reports; add each member's ledger sum for the period. |
| 139 | const billingPeriod = |
| 140 | subscription.periodStart && subscription.periodEnd |
| 141 | ? { start: subscription.periodStart, end: subscription.periodEnd } |
| 142 | : null |
| 143 | const usageByUser = await getOrgMemberLedgerByUser(organizationId, billingPeriod, executor) |
| 144 | |
| 145 | // Process member data |
| 146 | const members: MemberUsageData[] = membersWithUsage.map((memberRecord) => { |
| 147 | const currentUsage = |
| 148 | Number(memberRecord.currentPeriodCost || 0) + (usageByUser.get(memberRecord.userId) ?? 0) |
| 149 | const usageLimit = Number(memberRecord.currentUsageLimit || getFreeTierLimit()) |
no test coverage detected