Files
cal.diy/packages/features/ee/billing/active-user/services/ActiveUserBillingService.ts
T
Eunjae LeeandClaude Opus 4.6 98b6d63164 refactor: apply biome formatting to packages/features (#27844)
* refactor: apply biome formatting to packages/features (batch 1 - small subdirs)

Format small subdirectories in packages/features: di, flags, holidays, oauth,
settings, users, assignment-reason, selectedCalendar, hashedLink, host, form,
form-builder, availability, data-table, pbac, schedules, troubleshooter,
eventtypes, calendar-subscription, and root-level files.

Also includes straggler apps/web BookEventForm.tsx.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 2 - medium subdirs)

Format medium subdirectories in packages/features: auth, credentials,
calendars, routing-forms, routing-trace, attributes, watchlist, calAIPhone,
tasker, and webhooks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 3 - bookings + insights)

Format bookings and insights subdirectories in packages/features.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 4 - ee)

Format packages/features/ee subdirectory covering billing, workflows,
organizations, teams, managed-event-types, round-robin, dsync,
integration-attribute-sync, and payments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 5 - booking-audit part 1)

Format booking-audit di, actions, common, dto, repository, and types
subdirectories in packages/features/booking-audit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 6 - booking-audit part 2)

Format booking-audit service subdirectory in packages/features/booking-audit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 15:47:14 +01:00

92 lines
2.9 KiB
TypeScript

import type { ActiveUserBillingRepository } from "../repositories/ActiveUserBillingRepository";
export interface IActiveUserBillingServiceDeps {
activeUserBillingRepository: ActiveUserBillingRepository;
}
export class ActiveUserBillingService {
constructor(private readonly deps: IActiveUserBillingServiceDeps) {}
/**
* Count active users for a platform organization (uses PlatformBilling subscription ID).
* A user is "active" if they hosted or attended at least one booking in the period OR managed.
*
* Uses the platform-specific host query that filters on isPlatformManaged + subscriptionId
* to ensure only bookings belonging to this org's managed users are counted.
*/
async getActiveUserCountForPlatformOrg(
subscriptionId: string,
periodStart: Date,
periodEnd: Date
): Promise<number> {
const managedUserEmails =
await this.deps.activeUserBillingRepository.getManagedUserEmailsBySubscriptionId(subscriptionId);
if (managedUserEmails.length === 0) return 0;
const activeHosts = await this.deps.activeUserBillingRepository.getActivePlatformUsersAsHost(
subscriptionId,
periodStart,
periodEnd
);
const activeHostEmails = new Set(activeHosts.map((h) => h.email));
const nonHostEmails = managedUserEmails
.map((u) => u.email)
.filter((email) => !activeHostEmails.has(email));
if (nonHostEmails.length === 0) return activeHostEmails.size;
const activeAttendees = await this.deps.activeUserBillingRepository.getActiveUsersAsAttendee(
nonHostEmails,
periodStart,
periodEnd
);
return activeHostEmails.size + activeAttendees.length;
}
/**
* Count active users for a regular organization (uses org/team ID).
* A user is "active" if they hosted or attended at least one booking in the period.
*/
async getActiveUserCountForOrg(orgId: number, periodStart: Date, periodEnd: Date): Promise<number> {
const memberEmails = await this.deps.activeUserBillingRepository.getOrgMemberEmailsByOrgId(orgId);
return this.countActiveUsers(
memberEmails.map((u) => u.email),
periodStart,
periodEnd
);
}
private async countActiveUsers(
candidateEmails: string[],
periodStart: Date,
periodEnd: Date
): Promise<number> {
if (candidateEmails.length === 0) return 0;
const activeHosts = await this.deps.activeUserBillingRepository.getActiveUsersAsHost(
candidateEmails,
periodStart,
periodEnd
);
const activeHostEmails = new Set(activeHosts.map((h) => h.email));
const nonHostEmails = candidateEmails.filter((email) => !activeHostEmails.has(email));
if (nonHostEmails.length === 0) return activeHostEmails.size;
const activeAttendees = await this.deps.activeUserBillingRepository.getActiveUsersAsAttendee(
nonHostEmails,
periodStart,
periodEnd
);
return activeHostEmails.size + activeAttendees.length;
}
}