feat(billing): cap active users on an owner subscription

Super admin can set a seat limit of 3, 10, 20, or a negotiated number so an owner cannot add users past that cap.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-02 18:33:51 +03:30
co-authored by Cursor
parent 06977d27ed
commit acb5d51f24
9 changed files with 220 additions and 2 deletions
+7
View File
@@ -23,6 +23,7 @@ import { normalizeMobile } from '../auth/auth.service';
import { PrismaService } from '../prisma/prisma.service';
import { OrgRolesService } from '../org-roles/org-roles.service';
import { MembershipsService } from '../memberships/memberships.service';
import { BillingService } from '../billing/billing.service';
import { LocalFileStorage } from '../storage/local-file.storage';
import * as path from 'path';
@@ -70,6 +71,7 @@ export class UsersService {
private readonly orgRoles: OrgRolesService,
private readonly memberships: MembershipsService,
private readonly files: LocalFileStorage,
private readonly billing: BillingService,
) {}
async list(organizationId: string): Promise<OrgUserDto[]> {
@@ -105,6 +107,7 @@ export class UsersService {
return this.addExistingUser(organizationId, existingUser.id, resolved);
}
await this.billing.assertSeatAvailable(organizationId, null);
const password = dto.password?.trim() ?? '';
if (password.length < 8) {
throw new BadRequestException(
@@ -506,6 +509,9 @@ export class UsersService {
throw new BadRequestException('حداقل یک مالک فعال لازم است');
}
}
if (isActive && !target.isActive) {
await this.billing.assertSeatAvailable(organizationId, targetId);
}
await this.prisma.membership.update({
where: { id: target.id },
data: { isActive },
@@ -533,6 +539,7 @@ export class UsersService {
userId: string,
resolved: { role: UserRole; orgRoleId: string },
): Promise<OrgUserDto> {
await this.billing.assertSeatAvailable(organizationId, userId);
const existing = await this.prisma.membership.findFirst({
where: { userId, organizationId },
});