diff --git a/src/modules/minglaradmin/handlers/inviteTeammate.ts b/src/modules/minglaradmin/handlers/inviteTeammate.ts index 8f1cee2..546ba79 100644 --- a/src/modules/minglaradmin/handlers/inviteTeammate.ts +++ b/src/modules/minglaradmin/handlers/inviteTeammate.ts @@ -78,29 +78,14 @@ export const handler = safeHandler(async ( throw new ApiError(400, 'Per value must be greater than 0'); } - // Run user creation, revenue and invite details inside a transaction - const createdUser = await prismaService.$transaction(async (tx) => { - // create a transaction-scoped MinglarService instance - const txMinglarService = new MinglarService(tx as unknown as PrismaService); - - // Check if user already exists within transaction to avoid race - const existingUser = await txMinglarService.checkUserExists(emailAddress); - if (existingUser) { - throw new ApiError(400, 'User already exists.'); - } - - // Create new user - const user = await txMinglarService.createUserForInvite(emailAddress, roleXid); - - // Create user revenue - await txMinglarService.createUserRevenue(user.id, isFixedSalary, perValue || 0); - - // Create invite details - await txMinglarService.createInviteDetails(user.id, userInfo.id, MINGLAR_INVITATION_STATUS.INVITED); - - // return created user from transaction - return user; - }); + // Use single service method that encapsulates the transaction + const createdUser = await minglarService.inviteTeammate( + emailAddress, + roleXid, + isFixedSalary, + perValue || 0, + userInfo.id + ); // send email after transaction commits await sendInvitationEmailForMinglarAdmin(emailAddress); diff --git a/src/modules/minglaradmin/services/minglar.service.ts b/src/modules/minglaradmin/services/minglar.service.ts index a1e215b..99c8655 100644 --- a/src/modules/minglaradmin/services/minglar.service.ts +++ b/src/modules/minglaradmin/services/minglar.service.ts @@ -192,6 +192,64 @@ export class MinglarService { }); } + /** + * Invite teammate flow: checks existing user, creates user, revenue and invite details + * All operations are performed inside a single DB transaction to avoid races. + */ + async inviteTeammate( + emailAddress: string, + roleXid: number, + isFixedSalary: boolean, + perValue: number, + invitedBy: number + ) { + return await this.prisma.$transaction(async (tx) => { + // Check existing user + const existingUser = await tx.user.findFirst({ + where: { emailAddress: emailAddress, isActive: true }, + }); + + if (existingUser) { + throw new ApiError(400, 'User already exists.'); + } + + // Create user with INVITED status + const user = await tx.user.create({ + data: { + emailAddress: emailAddress, + roleXid: roleXid, + userStatus: USER_STATUS.INVITED, + }, + }); + + // Create revenue record + await tx.userRevenue.create({ + data: { + userXid: user.id, + is_fixed_salary: isFixedSalary, + per_value: perValue || 0, + isActive: true, + }, + }); + + // Create invite details + await tx.inviteDetails.create({ + data: { + userXid: user.id, + is_invited: true, + invited_by: invitedBy, + invited_on: new Date(), + is_accepted: false, + invitation_status: MINGLAR_INVITATION_STATUS.INVITED, + isActive: true, + isMinglarInvitation: true, + }, + }); + + return user; + }); + } + async updateProfile( userId: number, userData: {