* Restart form if admin is not org admin * Show pricing is admin and hosted * Only show billing specific fields when enabled * Create user onboarding only if valid valid license when self hosting * Reset store when org creation is completed * Create schema for trpc input when creating an org * Add `organization.createSelfHosted` trpc endpoint * `createOrganizationFromOnboarding` do not require stripe data * Create organization without billing if self-hosted admin * Update organizationOnboarding record as completed * Handle updating subscription for self hosters * Refactor `isBillingEnabled` to store * Type fix * Fix admin org creation for hosted * Hide UI from admin * Add tests * fix: Fix LicenseKeySingleton mocking in organization tests - Add proper vi.mock() module-level mocking for LicenseKeySingleton - Replace incorrect casting syntax with vi.mocked() approach - Add LicenseKeySingleton mock to hosted tests in intentToCreateOrg.handler.test.ts - Fix IS_SELF_HOSTED mocking for hosted vs self-hosted test scenarios - All organization creation tests now pass locally Co-Authored-By: joe@cal.com <joe@cal.com> * Fix tests * Fix tests * Test fixes * Type fix * Type fix * Type fix * Update packages/features/ee/organizations/lib/server/createOrganizationFromOnboarding.test.ts Fix typo Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * fix: Add IS_SELF_HOSTED mocking for E2E organization creation tests Co-Authored-By: joe@cal.com <joe@cal.com> * Fix E2E test * Address feedback * Type fix * Fix e2e tests * Remove unused code * Small fixes * fix unit tests * review fixes --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: joe@cal.com <joe@cal.com> Co-authored-by: Benny Joo <sldisek783@gmail.com> Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> Co-authored-by: Hariom <hariombalhara@gmail.com>
140 lines
4.0 KiB
TypeScript
140 lines
4.0 KiB
TypeScript
import logger from "@calcom/lib/logger";
|
|
import { safeStringify } from "@calcom/lib/safeStringify";
|
|
import { prisma } from "@calcom/prisma";
|
|
import type { BillingPeriod } from "@calcom/prisma/enums";
|
|
|
|
type OnboardingId = string;
|
|
|
|
export type CreateOrganizationOnboardingInput = {
|
|
createdById: number;
|
|
organizationId?: number | null;
|
|
billingPeriod: BillingPeriod;
|
|
pricePerSeat: number;
|
|
seats: number;
|
|
orgOwnerEmail: string;
|
|
name: string;
|
|
slug: string;
|
|
logo?: string | null;
|
|
bio?: string | null;
|
|
stripeCustomerId?: string;
|
|
stripeSubscriptionId?: string;
|
|
stripeSubscriptionItemId?: string;
|
|
invitedMembers?: { email: string; name?: string }[];
|
|
teams?: { id: number; name: string; isBeingMigrated: boolean; slug: string | null }[];
|
|
error?: string | null;
|
|
isDomainConfigured?: boolean;
|
|
isComplete?: boolean;
|
|
};
|
|
|
|
export class OrganizationOnboardingRepository {
|
|
static async create(data: CreateOrganizationOnboardingInput) {
|
|
logger.debug("Creating organization onboarding", safeStringify(data));
|
|
|
|
return await prisma.organizationOnboarding.create({
|
|
// HEKOP
|
|
data: {
|
|
billingPeriod: data.billingPeriod,
|
|
pricePerSeat: data.pricePerSeat,
|
|
seats: data.seats,
|
|
orgOwnerEmail: data.orgOwnerEmail,
|
|
name: data.name,
|
|
slug: data.slug,
|
|
logo: data.logo,
|
|
bio: data.bio,
|
|
stripeCustomerId: data.stripeCustomerId,
|
|
stripeSubscriptionId: data.stripeSubscriptionId,
|
|
invitedMembers: data.invitedMembers || [],
|
|
teams: data.teams || [],
|
|
createdById: data.createdById,
|
|
},
|
|
});
|
|
}
|
|
|
|
static async findByStripeCustomerId(stripeCustomerId: string) {
|
|
logger.debug(
|
|
"Finding organization onboarding by stripe customer id",
|
|
safeStringify({ stripeCustomerId })
|
|
);
|
|
return await prisma.organizationOnboarding.findUnique({
|
|
where: {
|
|
stripeCustomerId,
|
|
},
|
|
});
|
|
}
|
|
|
|
static async findById(id: OnboardingId) {
|
|
logger.debug("Finding organization onboarding by id", safeStringify({ id }));
|
|
return await prisma.organizationOnboarding.findUnique({
|
|
where: {
|
|
id,
|
|
},
|
|
});
|
|
}
|
|
|
|
static async findByOrgOwnerEmail(email: string) {
|
|
logger.debug("Finding organization onboarding by org owner email", safeStringify({ email }));
|
|
return await prisma.organizationOnboarding.findUnique({
|
|
where: {
|
|
orgOwnerEmail: email,
|
|
},
|
|
});
|
|
}
|
|
|
|
// TODO: This method should be moved to OrganizationOnboardingService
|
|
static async markAsComplete(id: OnboardingId) {
|
|
logger.debug("Marking organization onboarding as complete", { id });
|
|
return await prisma.organizationOnboarding.update({
|
|
where: {
|
|
id,
|
|
},
|
|
data: {
|
|
error: null,
|
|
isComplete: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
static async update(id: OnboardingId, data: Partial<CreateOrganizationOnboardingInput>) {
|
|
logger.debug("Updating organization onboarding", safeStringify({ id, data }));
|
|
// We don't want to update the createdById field in update
|
|
const { organizationId, createdById: _, ...rest } = data;
|
|
|
|
return await prisma.organizationOnboarding.update({
|
|
where: {
|
|
id,
|
|
},
|
|
data: {
|
|
...rest,
|
|
...(organizationId ? { organization: { connect: { id: organizationId } } } : {}),
|
|
updatedAt: new Date(),
|
|
},
|
|
});
|
|
}
|
|
|
|
static async findByOrganizationId(organizationId: number) {
|
|
logger.debug("Finding organization onboarding by organization id", safeStringify({ organizationId }));
|
|
return await prisma.organizationOnboarding.findUnique({
|
|
where: {
|
|
organizationId,
|
|
},
|
|
});
|
|
}
|
|
|
|
static async findAllBySlug(slug: string) {
|
|
logger.debug("Finding all organization onboardings by slug", safeStringify({ slug }));
|
|
return await prisma.organizationOnboarding.findMany({
|
|
where: {
|
|
slug,
|
|
},
|
|
});
|
|
}
|
|
static async delete(id: OnboardingId) {
|
|
logger.debug("Deleting organization onboarding", { id });
|
|
return await prisma.organizationOnboarding.delete({
|
|
where: {
|
|
id,
|
|
},
|
|
});
|
|
}
|
|
}
|