Editing Super Admin permissions and visual changes

This commit is contained in:
2026-09-20 12:07:05 +03:30
parent 4a9fe0b07c
commit 0e580b19c3
9 changed files with 621 additions and 231 deletions
+3
View File
@@ -651,6 +651,7 @@ export class AuthService {
membership?.organization.enabledModules,
),
subscription: sub,
isOrgOwner: role === 'owner',
});
}
@@ -737,6 +738,7 @@ export class AuthService {
avatarStorageKey?: string | null;
enabledModules?: OrgModuleKey[];
subscription?: SubscriptionSummaryDto | null;
isOrgOwner?: boolean;
}): AuthUserDto {
return {
id: user.id,
@@ -749,6 +751,7 @@ export class AuthService {
...(user.mobile ? { mobile: user.mobile } : {}),
...(user.enabledModules ? { enabledModules: user.enabledModules } : {}),
...(user.subscription ? { subscription: user.subscription } : {}),
...(user.isOrgOwner !== undefined ? { isOrgOwner: user.isOrgOwner } : {}),
};
}
}
+25 -10
View File
@@ -94,7 +94,7 @@ export class BillingService {
});
}
/** اشتراک محیط = BillingAccount اولین مالک فعال */
/** اشتراک محیط = BillingAccount اولین مالک فعال — برای عضو زیرمجموعه حساب جدید ساخته نمی‌شود */
async resolveForOrganization(organizationId: string) {
const owner = await this.prisma.membership.findFirst({
where: {
@@ -106,25 +106,25 @@ export class BillingService {
select: { userId: true },
});
if (!owner) return null;
let account = await this.getByOwnerUserId(owner.userId);
if (!account) {
account = await this.ensureTrialForOwner(owner.userId);
}
return account;
return this.getByOwnerUserId(owner.userId);
}
async requireActiveForOrganization(organizationId: string): Promise<SubscriptionSummaryDto> {
const account = await this.resolveForOrganization(organizationId);
if (!account) {
throw new UnauthorizedException('اشتراک این محیط یافت نشد');
throw new UnauthorizedException(
'اشتراک این محیط یافت نشد؛ مالک باید از پنل سوپرادمین پلن داشته باشد',
);
}
return this.assertActive(account);
}
async requireActiveForOwner(ownerUserId: string): Promise<SubscriptionSummaryDto> {
let account = await this.getByOwnerUserId(ownerUserId);
const account = await this.getByOwnerUserId(ownerUserId);
if (!account) {
account = await this.ensureTrialForOwner(ownerUserId);
throw new UnauthorizedException(
'اشتراک یافت نشد؛ ابتدا پلن را از سوپرادمین اعمال کنید',
);
}
return this.assertActive(account);
}
@@ -154,7 +154,12 @@ export class BillingService {
}
async assertCanCreateOrganization(ownerUserId: string) {
const summary = await this.requireActiveForOwner(ownerUserId);
let account = await this.getByOwnerUserId(ownerUserId);
if (!account) {
// اولین محیط مالک: trial خودکار
account = await this.ensureTrialForOwner(ownerUserId, { maxOrganizations: 1 });
}
const summary = await this.assertActive(account);
const ownedCount = await this.prisma.membership.count({
where: { userId: ownerUserId, role: 'owner', isActive: true },
});
@@ -169,6 +174,16 @@ export class BillingService {
ownerUserId: string,
dto: ApplyBillingPlanRequest,
): Promise<BillingAccountDto> {
const ownerMembership = await this.prisma.membership.findFirst({
where: { userId: ownerUserId, role: 'owner', isActive: true },
select: { id: true },
});
if (!ownerMembership) {
throw new BadRequestException(
'اشتراک فقط برای مالک محیط تعریف می‌شود؛ این کاربر مالک هیچ محیطی نیست',
);
}
const planCode = dto.planCode;
const now = new Date();
let expiresAt: Date;
+8 -3
View File
@@ -688,6 +688,9 @@ export class PlatformService {
organization: { id: string; name: string; isActive: boolean };
}>;
}): PlatformUserDto {
const isBillingOwner = row.memberships.some(
(m) => m.role === 'owner' && m.isActive,
);
return {
id: row.id,
email: row.email,
@@ -696,9 +699,11 @@ export class PlatformService {
isActive: row.isActive,
lastLoginAt: row.lastLoginAt?.toISOString() ?? null,
createdAt: row.createdAt.toISOString(),
billing: row.billingAccount
? this.billing.toDto(row.billingAccount)
: null,
isBillingOwner,
billing:
isBillingOwner && row.billingAccount
? this.billing.toDto(row.billingAccount)
: null,
memberships: row.memberships.map((m) => ({
membershipId: m.id,
organizationId: m.organization.id,