mirror of
https://github.com/documenso/documenso.git
synced 2026-06-22 04:12:06 +10:00
144 lines
4.8 KiB
TypeScript
144 lines
4.8 KiB
TypeScript
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
|
import { createOrganisationClaimUpsertData } from '@documenso/lib/server-only/organisation/create-organisation';
|
|
import { getSubscriptionClaim } from '@documenso/lib/server-only/subscription/get-subscription-claim';
|
|
import { INTERNAL_CLAIM_ID } from '@documenso/lib/types/subscription';
|
|
import { prisma } from '@documenso/prisma';
|
|
import { SubscriptionStatus } from '@prisma/client';
|
|
|
|
import { adminProcedure } from '../trpc';
|
|
import {
|
|
ZSwapOrganisationSubscriptionRequestSchema,
|
|
ZSwapOrganisationSubscriptionResponseSchema,
|
|
} from './swap-organisation-subscription.types';
|
|
|
|
export const swapOrganisationSubscriptionRoute = adminProcedure
|
|
.input(ZSwapOrganisationSubscriptionRequestSchema)
|
|
.output(ZSwapOrganisationSubscriptionResponseSchema)
|
|
.mutation(async ({ input, ctx }) => {
|
|
const { sourceOrganisationId, targetOrganisationId } = input;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
sourceOrganisationId,
|
|
targetOrganisationId,
|
|
},
|
|
});
|
|
|
|
if (sourceOrganisationId === targetOrganisationId) {
|
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
|
message: 'Source and target organisations must be different',
|
|
});
|
|
}
|
|
|
|
const sourceOrg = await prisma.organisation.findUnique({
|
|
where: { id: sourceOrganisationId },
|
|
include: {
|
|
subscription: true,
|
|
organisationClaim: true,
|
|
},
|
|
});
|
|
|
|
if (!sourceOrg) {
|
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
|
message: 'Source organisation not found',
|
|
});
|
|
}
|
|
|
|
if (
|
|
!sourceOrg.subscription ||
|
|
(sourceOrg.subscription.status !== SubscriptionStatus.ACTIVE &&
|
|
sourceOrg.subscription.status !== SubscriptionStatus.PAST_DUE)
|
|
) {
|
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
|
message: 'Source organisation does not have an active subscription',
|
|
});
|
|
}
|
|
|
|
const targetOrg = await prisma.organisation.findUnique({
|
|
where: { id: targetOrganisationId },
|
|
include: {
|
|
subscription: true,
|
|
organisationClaim: true,
|
|
},
|
|
});
|
|
|
|
if (!targetOrg) {
|
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
|
message: 'Target organisation not found',
|
|
});
|
|
}
|
|
|
|
if (sourceOrg.ownerUserId !== targetOrg.ownerUserId) {
|
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
|
message: 'Both organisations must be owned by the same user',
|
|
});
|
|
}
|
|
|
|
if (
|
|
targetOrg.subscription &&
|
|
(targetOrg.subscription.status === SubscriptionStatus.ACTIVE ||
|
|
targetOrg.subscription.status === SubscriptionStatus.PAST_DUE)
|
|
) {
|
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
|
message: 'Target organisation already has an active subscription',
|
|
});
|
|
}
|
|
|
|
const customerId = sourceOrg.customerId ?? sourceOrg.subscription.customerId;
|
|
|
|
const freeSubscriptionClaim = await getSubscriptionClaim(INTERNAL_CLAIM_ID.FREE);
|
|
|
|
await prisma.$transaction(async (tx) => {
|
|
// Delete stale INACTIVE subscription on target if present.
|
|
if (targetOrg.subscription) {
|
|
await tx.subscription.delete({
|
|
where: { id: targetOrg.subscription.id },
|
|
});
|
|
}
|
|
|
|
// Clear customerId on source org to avoid unique constraint violation.
|
|
await tx.organisation.update({
|
|
where: { id: sourceOrganisationId },
|
|
data: { customerId: null },
|
|
});
|
|
|
|
// Set customerId on target org.
|
|
await tx.organisation.update({
|
|
where: { id: targetOrganisationId },
|
|
data: { customerId },
|
|
});
|
|
|
|
// Move the subscription record to the target org.
|
|
await tx.subscription.update({
|
|
where: { id: sourceOrg.subscription!.id },
|
|
data: { organisationId: targetOrganisationId },
|
|
});
|
|
|
|
// Copy source org's claim entitlements to target org's claim.
|
|
if (sourceOrg.organisationClaim && targetOrg.organisationClaim) {
|
|
await tx.organisationClaim.update({
|
|
where: { id: targetOrg.organisationClaim.id },
|
|
data: {
|
|
originalSubscriptionClaimId: sourceOrg.organisationClaim.originalSubscriptionClaimId,
|
|
teamCount: sourceOrg.organisationClaim.teamCount,
|
|
memberCount: sourceOrg.organisationClaim.memberCount,
|
|
envelopeItemCount: sourceOrg.organisationClaim.envelopeItemCount,
|
|
recipientCount: sourceOrg.organisationClaim.recipientCount,
|
|
flags: sourceOrg.organisationClaim.flags,
|
|
},
|
|
});
|
|
}
|
|
|
|
// Reset source org's claim to FREE.
|
|
if (sourceOrg.organisationClaim) {
|
|
await tx.organisationClaim.update({
|
|
where: { id: sourceOrg.organisationClaim.id },
|
|
data: {
|
|
originalSubscriptionClaimId: INTERNAL_CLAIM_ID.FREE,
|
|
...createOrganisationClaimUpsertData(freeSubscriptionClaim),
|
|
},
|
|
});
|
|
}
|
|
});
|
|
});
|