Files
calendar/packages/lib/server/repository/organizationOnboarding.ts
T
Joe Au-YeungGitHubjoe@cal.com <joe@cal.com>cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>joe@cal.com <joe@cal.com>Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>joe@cal.com <joe@cal.com>Benny Joocubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>Hariom
6ed197d35c fix: Org creation for self-hosters (#21994)
* 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>
2025-06-25 16:58:18 +03:00

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,
},
});
}
}