Configuring access rights in the CRM Super Admin
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsEmail,
|
||||
IsIn,
|
||||
IsInt,
|
||||
IsOptional,
|
||||
IsString,
|
||||
MaxLength,
|
||||
Min,
|
||||
MinLength,
|
||||
ValidateIf,
|
||||
} from 'class-validator';
|
||||
@@ -119,3 +122,32 @@ export class ResetPlatformUserPasswordDto {
|
||||
@MaxLength(120)
|
||||
password!: string;
|
||||
}
|
||||
|
||||
export class ApplyBillingPlanDto {
|
||||
@IsIn(['trial', 'monthly', 'quarterly', 'semiannual', 'yearly', 'custom'])
|
||||
planCode!: 'trial' | 'monthly' | 'quarterly' | 'semiannual' | 'yearly' | 'custom';
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
maxOrganizations?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
expiresAt?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(500)
|
||||
notes?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsIn(['active', 'expired', 'suspended'])
|
||||
status?: 'active' | 'expired' | 'suspended';
|
||||
}
|
||||
|
||||
export class UpdateOrgModulesDto {
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
enabledModules!: string[];
|
||||
}
|
||||
|
||||
@@ -10,10 +10,12 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import { CurrentUser } from '../auth/current-user.decorator';
|
||||
import {
|
||||
ApplyBillingPlanDto,
|
||||
CreatePlatformAdminDto,
|
||||
CreatePlatformOrganizationDto,
|
||||
ResetPlatformAdminPasswordDto,
|
||||
ResetPlatformUserPasswordDto,
|
||||
UpdateOrgModulesDto,
|
||||
UpdatePlatformMembershipRoleDto,
|
||||
UpdatePlatformOrganizationDto,
|
||||
UpdatePlatformStatusDto,
|
||||
@@ -105,6 +107,19 @@ export class PlatformController {
|
||||
return this.platform.resetUserPassword(id, dto);
|
||||
}
|
||||
|
||||
@Post('users/:id/billing/apply-plan')
|
||||
applyBillingPlan(@Param('id') id: string, @Body() dto: ApplyBillingPlanDto) {
|
||||
return this.platform.applyBillingPlan(id, dto);
|
||||
}
|
||||
|
||||
@Patch('organizations/:id/modules')
|
||||
updateOrganizationModules(
|
||||
@Param('id') id: string,
|
||||
@Body() dto: UpdateOrgModulesDto,
|
||||
) {
|
||||
return this.platform.updateOrganizationModules(id, dto);
|
||||
}
|
||||
|
||||
@Patch('memberships/:id')
|
||||
setMembershipActive(
|
||||
@Param('id') id: string,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { AuthModule } from '../auth/auth.module';
|
||||
import { MembershipsModule } from '../memberships/memberships.module';
|
||||
import { OrgRolesModule } from '../org-roles/org-roles.module';
|
||||
import { PipelinesModule } from '../pipelines/pipelines.module';
|
||||
import { BillingModule } from '../billing/billing.module';
|
||||
import { PlatformAuthController } from './platform-auth.controller';
|
||||
import { PlatformAuthGuard } from './platform-auth.guard';
|
||||
import { PlatformAuthService } from './platform-auth.service';
|
||||
@@ -17,6 +18,7 @@ import { PlatformService } from './platform.service';
|
||||
MembershipsModule,
|
||||
OrgRolesModule,
|
||||
PipelinesModule,
|
||||
BillingModule,
|
||||
PassportModule,
|
||||
],
|
||||
controllers: [PlatformAuthController, PlatformController],
|
||||
|
||||
@@ -18,12 +18,15 @@ import { normalizeMobile } from '../auth/auth.service';
|
||||
import { MembershipsService } from '../memberships/memberships.service';
|
||||
import { OrgRolesService } from '../org-roles/org-roles.service';
|
||||
import { PipelinesService } from '../pipelines/pipelines.service';
|
||||
import { BillingService } from '../billing/billing.service';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import type {
|
||||
ApplyBillingPlanDto,
|
||||
CreatePlatformAdminDto,
|
||||
CreatePlatformOrganizationDto,
|
||||
ResetPlatformAdminPasswordDto,
|
||||
ResetPlatformUserPasswordDto,
|
||||
UpdateOrgModulesDto,
|
||||
UpdatePlatformOrganizationDto,
|
||||
} from './dto';
|
||||
|
||||
@@ -45,6 +48,7 @@ export class PlatformService {
|
||||
private readonly memberships: MembershipsService,
|
||||
private readonly orgRoles: OrgRolesService,
|
||||
private readonly pipelines: PipelinesService,
|
||||
private readonly billing: BillingService,
|
||||
) {}
|
||||
|
||||
async stats(): Promise<PlatformStatsDto> {
|
||||
@@ -61,12 +65,18 @@ export class PlatformService {
|
||||
this.prisma.user.count({ where: { isActive: true } }),
|
||||
this.prisma.membership.count(),
|
||||
]);
|
||||
const [expiringSoonCount, expiredBillingCount] = await Promise.all([
|
||||
this.billing.countExpiringSoon(),
|
||||
this.billing.countExpired(),
|
||||
]);
|
||||
return {
|
||||
organizationCount,
|
||||
activeOrganizationCount,
|
||||
userCount,
|
||||
activeUserCount,
|
||||
membershipCount,
|
||||
expiringSoonCount,
|
||||
expiredBillingCount,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -89,6 +99,7 @@ export class PlatformService {
|
||||
businessField: true,
|
||||
isActive: true,
|
||||
createdAt: true,
|
||||
enabledModules: true,
|
||||
_count: {
|
||||
select: {
|
||||
memberships: true,
|
||||
@@ -111,6 +122,7 @@ export class PlatformService {
|
||||
businessField: true,
|
||||
isActive: true,
|
||||
createdAt: true,
|
||||
enabledModules: true,
|
||||
_count: {
|
||||
select: {
|
||||
memberships: true,
|
||||
@@ -213,6 +225,7 @@ export class PlatformService {
|
||||
name,
|
||||
slug,
|
||||
businessField,
|
||||
enabledModules: this.billing.defaultModulesForNewOrg(),
|
||||
users: {
|
||||
create: {
|
||||
email: ownerEmail,
|
||||
@@ -238,6 +251,7 @@ export class PlatformService {
|
||||
organizationId: organization.id,
|
||||
role: 'owner',
|
||||
});
|
||||
await this.billing.ensureTrialForOwner(owner.id, { maxOrganizations: 1 });
|
||||
await this.orgRoles.ensureDefaultRoles(organization.id);
|
||||
await this.pipelines.ensureDefaultPipeline(organization.id);
|
||||
|
||||
@@ -331,6 +345,7 @@ export class PlatformService {
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 300,
|
||||
include: {
|
||||
billingAccount: true,
|
||||
memberships: {
|
||||
include: {
|
||||
organization: {
|
||||
@@ -356,12 +371,33 @@ export class PlatformService {
|
||||
},
|
||||
orderBy: { createdAt: 'asc' },
|
||||
},
|
||||
billingAccount: true,
|
||||
},
|
||||
});
|
||||
if (!row) throw new NotFoundException('کاربر پیدا نشد');
|
||||
return this.userToDto(row);
|
||||
}
|
||||
|
||||
async applyBillingPlan(userId: string, dto: ApplyBillingPlanDto) {
|
||||
const existing = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!existing) throw new NotFoundException('کاربر پیدا نشد');
|
||||
await this.billing.applyPlan(userId, dto);
|
||||
return this.getUser(userId);
|
||||
}
|
||||
|
||||
async updateOrganizationModules(id: string, dto: UpdateOrgModulesDto) {
|
||||
const existing = await this.prisma.organization.findUnique({
|
||||
where: { id },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!existing) throw new NotFoundException('محیط پیدا نشد');
|
||||
await this.billing.setOrgModules(id, dto.enabledModules ?? []);
|
||||
return this.getOrganization(id);
|
||||
}
|
||||
|
||||
async setUserActive(id: string, isActive: boolean): Promise<PlatformUserDto> {
|
||||
const existing = await this.prisma.user.findUnique({
|
||||
where: { id },
|
||||
@@ -609,6 +645,7 @@ export class PlatformService {
|
||||
businessField: string | null;
|
||||
isActive: boolean;
|
||||
createdAt: Date;
|
||||
enabledModules?: string[];
|
||||
_count: { memberships: number; people: number; deals: number };
|
||||
}): PlatformOrganizationDto {
|
||||
return {
|
||||
@@ -621,6 +658,7 @@ export class PlatformService {
|
||||
memberCount: row._count.memberships,
|
||||
personCount: row._count.people,
|
||||
dealCount: row._count.deals,
|
||||
enabledModules: this.billing.normalizeModules(row.enabledModules),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -632,6 +670,17 @@ export class PlatformService {
|
||||
isActive: boolean;
|
||||
lastLoginAt: Date | null;
|
||||
createdAt: Date;
|
||||
billingAccount?: {
|
||||
id: string;
|
||||
ownerUserId: string;
|
||||
planCode: string;
|
||||
status: string;
|
||||
startsAt: Date;
|
||||
expiresAt: Date;
|
||||
maxOrganizations: number;
|
||||
externalPaymentRef: string | null;
|
||||
notes: string | null;
|
||||
} | null;
|
||||
memberships: Array<{
|
||||
id: string;
|
||||
role: PlatformUserDto['memberships'][number]['role'];
|
||||
@@ -647,6 +696,9 @@ export class PlatformService {
|
||||
isActive: row.isActive,
|
||||
lastLoginAt: row.lastLoginAt?.toISOString() ?? null,
|
||||
createdAt: row.createdAt.toISOString(),
|
||||
billing: row.billingAccount
|
||||
? this.billing.toDto(row.billingAccount)
|
||||
: null,
|
||||
memberships: row.memberships.map((m) => ({
|
||||
membershipId: m.id,
|
||||
organizationId: m.organization.id,
|
||||
|
||||
Reference in New Issue
Block a user