feat(billing): start demos on the core plan and apply tariff levels
New signups get the core module set with the 7-day demo. Superadmin can apply core, team, organization, or dedicated and the owner's environments take that level's access. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -21,6 +21,9 @@ import {
|
||||
defaultEnabledModules,
|
||||
isPermanentBillingPlan,
|
||||
isOrgModuleKey,
|
||||
modulesForProductPlan,
|
||||
PRODUCT_PLAN_LIMITS,
|
||||
type ProductPlanCode,
|
||||
} from '@mento/shared';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { PlatformAdminAlertService } from './platform-admin-alert.service';
|
||||
@@ -414,7 +417,37 @@ export class BillingService {
|
||||
}
|
||||
|
||||
defaultModulesForNewOrg(): OrgModuleKey[] {
|
||||
return defaultEnabledModules();
|
||||
return modulesForProductPlan('core');
|
||||
}
|
||||
|
||||
/** سطح امکانات را روی همهٔ محیطهای مالک میگذارد و سقف هسته/تیم/سازمان را هم تنظیم میکند. */
|
||||
async applyProductPlan(organizationId: string, code: ProductPlanCode) {
|
||||
const modules = modulesForProductPlan(code);
|
||||
const owner = await this.prisma.membership.findFirst({
|
||||
where: { organizationId, role: 'owner', isActive: true },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
select: { userId: true },
|
||||
});
|
||||
const orgIds = owner ? await this.ownedOrganizationIds(owner.userId) : [organizationId];
|
||||
for (const id of orgIds) {
|
||||
await this.setOrgModules(id, modules);
|
||||
}
|
||||
const limits = PRODUCT_PLAN_LIMITS[code];
|
||||
if (owner && (limits.maxUsers != null || limits.maxOrganizations != null)) {
|
||||
const account = await this.getByOwnerUserId(owner.userId);
|
||||
if (account) {
|
||||
await this.prisma.billingAccount.update({
|
||||
where: { id: account.id },
|
||||
data: {
|
||||
...(limits.maxUsers != null ? { maxUsers: limits.maxUsers } : {}),
|
||||
...(limits.maxOrganizations != null
|
||||
? { maxOrganizations: limits.maxOrganizations }
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
|
||||
async countExpiringSoon() {
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
MinLength,
|
||||
ValidateIf,
|
||||
} from 'class-validator';
|
||||
import { USER_ROLES, type UserRole } from '@mento/shared';
|
||||
import { PRODUCT_PLAN_CODES, USER_ROLES, type ProductPlanCode, type UserRole } from '@mento/shared';
|
||||
|
||||
export class PlatformLoginDto {
|
||||
@IsString()
|
||||
@@ -189,6 +189,11 @@ export class SetStorageQuotaDto {
|
||||
quotaGb!: number | null;
|
||||
}
|
||||
|
||||
export class ApplyProductPlanDto {
|
||||
@IsIn(PRODUCT_PLAN_CODES)
|
||||
planCode!: ProductPlanCode;
|
||||
}
|
||||
|
||||
export class UpdateOrgModulesDto {
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { CurrentUser } from '../auth/current-user.decorator';
|
||||
import {
|
||||
ApplyBillingPlanDto,
|
||||
ApplyProductPlanDto,
|
||||
SetStorageQuotaDto,
|
||||
SetUserCapDto,
|
||||
CreatePlatformAdminDto,
|
||||
@@ -168,6 +169,11 @@ export class PlatformController {
|
||||
return this.platform.setUserCap(id, dto.maxUsers);
|
||||
}
|
||||
|
||||
@Post('organizations/:id/product-plan')
|
||||
applyProductPlan(@Param('id') id: string, @Body() dto: ApplyProductPlanDto) {
|
||||
return this.platform.applyProductPlan(id, dto.planCode);
|
||||
}
|
||||
|
||||
@Patch('organizations/:id/modules')
|
||||
updateOrganizationModules(
|
||||
@Param('id') id: string,
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
PlatformOrganizationDto,
|
||||
PlatformStatsDto,
|
||||
PlatformUserDto,
|
||||
ProductPlanCode,
|
||||
UserRole,
|
||||
} from '@mento/shared';
|
||||
import { isBusinessField } from '@mento/shared';
|
||||
@@ -472,6 +473,16 @@ export class PlatformService {
|
||||
return this.getUser(userId);
|
||||
}
|
||||
|
||||
async applyProductPlan(id: string, planCode: ProductPlanCode) {
|
||||
const existing = await this.prisma.organization.findUnique({
|
||||
where: { id },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!existing) throw new NotFoundException('محیط پیدا نشد');
|
||||
await this.billing.applyProductPlan(id, planCode);
|
||||
return this.getOrganization(id);
|
||||
}
|
||||
|
||||
async updateOrganizationModules(id: string, dto: UpdateOrgModulesDto) {
|
||||
const existing = await this.prisma.organization.findUnique({
|
||||
where: { id },
|
||||
|
||||
Reference in New Issue
Block a user