bugfix: a new flow added to re-enable disabled staffs.
This commit is contained in:
@@ -80,6 +80,19 @@ export class StaffController {
|
||||
return this.staffService.updateMember(req.user.id, organizationId, membershipId, dto);
|
||||
}
|
||||
|
||||
@Patch('members/:membershipId/enable')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({
|
||||
summary: 'Re-enable a disabled staff member (uses one plan seat; no new invitation)',
|
||||
})
|
||||
enableMember(
|
||||
@Req() req: { user: { id: string; organizationId?: string } },
|
||||
@Param('membershipId') membershipId: string,
|
||||
) {
|
||||
const organizationId = this.staffService.getOrganizationIdFromUser(req.user);
|
||||
return this.staffService.enableMember(req.user.id, organizationId, membershipId);
|
||||
}
|
||||
|
||||
@Patch('members/:membershipId/disable')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { createHash, randomBytes } from 'crypto';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../../../prisma/prisma.service';
|
||||
import { AcceptStaffInviteDto } from './dto/accept-staff-invite.dto';
|
||||
import { isUnlimitedSeats, normalizeTabPermissions } from '../../common/permissions';
|
||||
@@ -399,6 +400,50 @@ export class StaffService {
|
||||
return { success: true, message: 'Member updated' };
|
||||
}
|
||||
|
||||
async enableMember(actorUserId: string, organizationId: string, membershipId: string) {
|
||||
const actor = await this.getActorMembership(actorUserId, organizationId);
|
||||
if (!actor || !this.canEditStaff(actor)) {
|
||||
throw new ForbiddenException('You cannot manage staff');
|
||||
}
|
||||
|
||||
const target = await this.prisma.membership.findFirst({
|
||||
where: { id: membershipId, organizationId },
|
||||
include: {
|
||||
invitations: { orderBy: { createdAt: 'desc' }, take: 1 },
|
||||
},
|
||||
});
|
||||
|
||||
if (!target) {
|
||||
throw new NotFoundException('Member not found');
|
||||
}
|
||||
if (target.isOwner) {
|
||||
throw new ForbiddenException('Cannot enable the organization owner');
|
||||
}
|
||||
if (target.isActive) {
|
||||
throw new BadRequestException('This member is already active');
|
||||
}
|
||||
|
||||
const invitation = target.invitations[0];
|
||||
if (invitation && !invitation.acceptedAt) {
|
||||
throw new BadRequestException(
|
||||
'This member has not completed their invitation yet. Share the invite link instead.',
|
||||
);
|
||||
}
|
||||
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
await this.assertOrganizationHasAvailableSeat(organizationId, tx);
|
||||
await tx.membership.update({
|
||||
where: { id: membershipId },
|
||||
data: { isActive: true },
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Member enabled. They can sign in to this organization again.',
|
||||
};
|
||||
}
|
||||
|
||||
async disableMember(actorUserId: string, organizationId: string, membershipId: string) {
|
||||
const actor = await this.getActorMembership(actorUserId, organizationId);
|
||||
if (!actor || !this.canEditStaff(actor)) {
|
||||
@@ -459,6 +504,37 @@ export class StaffService {
|
||||
return { success: true, message: 'Member removed' };
|
||||
}
|
||||
|
||||
private async assertOrganizationHasAvailableSeat(
|
||||
organizationId: string,
|
||||
db: Prisma.TransactionClient | PrismaService = this.prisma,
|
||||
) {
|
||||
const org = await db.organization.findUnique({
|
||||
where: { id: organizationId },
|
||||
include: { plan: true },
|
||||
});
|
||||
if (!org) {
|
||||
throw new NotFoundException('Organization not found');
|
||||
}
|
||||
if (!org.plan) {
|
||||
throw new BadRequestException(
|
||||
'This organization has no active subscription. Please choose a plan before adding staff.',
|
||||
);
|
||||
}
|
||||
|
||||
const maxUsers = org.plan.maxUsers;
|
||||
const seatsUsed = await db.membership.count({
|
||||
where: {
|
||||
organizationId,
|
||||
OR: [{ isOwner: true }, { isActive: true }],
|
||||
},
|
||||
});
|
||||
if (!isUnlimitedSeats(maxUsers) && seatsUsed >= maxUsers) {
|
||||
throw new BadRequestException(
|
||||
`Your plan allows ${maxUsers} team members. Free a seat by disabling another member or upgrade your plan.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async getActorMembership(userId: string, organizationId: string) {
|
||||
return this.prisma.membership.findFirst({
|
||||
where: { userId, organizationId },
|
||||
|
||||
Reference in New Issue
Block a user