From d65f7f536879be9f7e77ee3095121e00b66c2f98 Mon Sep 17 00:00:00 2001 From: paritosh18 Date: Mon, 24 Nov 2025 23:25:20 +0530 Subject: [PATCH] resolved Merge --- serverless.yml | 14 + .../minglaradmin/handlers/addPQQSuggestion.ts | 91 +++++ .../minglaradmin/services/minglar.service.ts | 314 ++++++++++++------ 3 files changed, 313 insertions(+), 106 deletions(-) create mode 100644 src/modules/minglaradmin/handlers/addPQQSuggestion.ts diff --git a/serverless.yml b/serverless.yml index a0b3d2c..93724de 100644 --- a/serverless.yml +++ b/serverless.yml @@ -676,3 +676,17 @@ functions: - httpApi: path: /host/submit-final-pqq-ans method: patch + + addPQQSuggestion: + handler: src/modules/minglar/handlers/addPQQSuggestion.handler + package: + patterns: + - 'src/modules/minglar/handlers/addPQQSuggestion.*' + - 'src/modules/minglar/services/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' + events: + - httpApi: + path: /minglar/add-Pqq-suggestion + method: post diff --git a/src/modules/minglaradmin/handlers/addPQQSuggestion.ts b/src/modules/minglaradmin/handlers/addPQQSuggestion.ts new file mode 100644 index 0000000..8d3971e --- /dev/null +++ b/src/modules/minglaradmin/handlers/addPQQSuggestion.ts @@ -0,0 +1,91 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; +import { safeHandler } from '../../../common/utils/handlers/safeHandler'; +import { PrismaService } from '../../../common/database/prisma.service'; +import { MinglarService } from '../services/minglar.service'; +import ApiError from '../../../common/utils/helper/ApiError'; +import { verifyMinglarAdminToken } from '../../../common/middlewares/jwt/authForMinglarAdmin'; +import { HOST_SUGGESTION_TITLES } from '../../../common/utils/constants/minglar.constant'; + +const prismaService = new PrismaService(); +const minglarService = new MinglarService(prismaService); + +interface AddSuggestionBody { + hostXid: number; + title: string; + comments: string; + activity_pqq_header_xid:number +} + +/** + * Add suggestion handler for host applications + * Allows Minglar Admin, Co_Admin, and Account Manager to add suggestions + * Types: Setup Profile, Review Account, Add Payment Details, Agreement + */ +export const handler = safeHandler(async ( + event: APIGatewayProxyEvent, + context?: Context +): Promise => { + // Verify authentication token + const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']; + if (!token) { + throw new ApiError(401, 'This is a protected route. Please provide a valid token.'); + } + + // Verify token and get user info + const userInfo = await verifyMinglarAdminToken(token); + + // Get user details + const user = await prismaService.user.findUnique({ + where: { id: userInfo.id }, + select: { id: true, roleXid: true } + }); + + if (!user) { + throw new ApiError(404, 'User not found'); + } + + // Parse request body + let body: AddSuggestionBody; + + try { + body = event.body ? JSON.parse(event.body) : {}; + } catch (error) { + throw new ApiError(400, 'Invalid JSON in request body'); + } + + const { title, comments , activity_pqq_header_xid} = body; + + if (!title) { + throw new ApiError(400, 'Title is required'); + } + + if (!comments) { + throw new ApiError(400, 'Comments are required'); + } + + if(!activity_pqq_header_xid){ + throw new ApiError(400 , "Activity Pqq HeaderXid Required"); + } + + // Validate title is one of the allowed types + const allowedTitles = Object.values(HOST_SUGGESTION_TITLES); + if (!allowedTitles.includes(title)) { + throw new ApiError(400, `Invalid title. Allowed values: ${allowedTitles.join(', ')}`); + } + + // Add suggestion using service + await minglarService.addPqqSuggestion(title, comments, activity_pqq_header_xid); + + return { + statusCode: 200, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, + body: JSON.stringify({ + success: true, + message: 'Suggestion added successfully', + data: null, + }), + }; +}); diff --git a/src/modules/minglaradmin/services/minglar.service.ts b/src/modules/minglaradmin/services/minglar.service.ts index 255e6b0..7c428ac 100644 --- a/src/modules/minglaradmin/services/minglar.service.ts +++ b/src/modules/minglaradmin/services/minglar.service.ts @@ -1,6 +1,14 @@ import { ROLE, USER_STATUS } from '@/common/utils/constants/common.constant'; -import { HOST_STATUS_DISPLAY, HOST_STATUS_INTERNAL, STEPPER } from '@/common/utils/constants/host.constant'; -import { MINGLAR_INVITATION_STATUS, MINGLAR_STATUS_DISPLAY, MINGLAR_STATUS_INTERNAL } from '@/common/utils/constants/minglar.constant'; +import { + HOST_STATUS_DISPLAY, + HOST_STATUS_INTERNAL, + STEPPER, +} from '@/common/utils/constants/host.constant'; +import { + MINGLAR_INVITATION_STATUS, + MINGLAR_STATUS_DISPLAY, + MINGLAR_STATUS_INTERNAL, +} from '@/common/utils/constants/minglar.constant'; import { Injectable } from '@nestjs/common'; import { User } from '@prisma/client'; import * as bcrypt from 'bcryptjs'; @@ -9,10 +17,9 @@ import ApiError from '../../../common/utils/helper/ApiError'; import { CreateMinglarDto, UpdateMinglarDto } from '../dto/minglar.dto'; import { sendAMEmailForHostAssign } from './AMEmail.service'; - @Injectable() export class MinglarService { - constructor(private prisma: PrismaService) { } + constructor(private prisma: PrismaService) {} async createPassword(user_xid: number, password: string): Promise { // Find user by id @@ -35,8 +42,8 @@ export class MinglarService { invitation_status: MINGLAR_INVITATION_STATUS.ACCEPTED, accepted_on: new Date(), is_accepted: true, - } - }) + }, + }); } if (!user) { @@ -45,7 +52,10 @@ export class MinglarService { // Check if password already exists if (user.userPassword) { - throw new ApiError(400, 'Password already exists. Use update password instead.'); + throw new ApiError( + 400, + 'Password already exists. Use update password instead.', + ); } // Hash the password @@ -55,7 +65,11 @@ export class MinglarService { // Update user with hashed password await this.prisma.user.update({ where: { id: user.id }, - data: { userPassword: hashedPassword, userStatus: USER_STATUS.ACTIVE, isEmailVerfied: true }, + data: { + userPassword: hashedPassword, + userStatus: USER_STATUS.ACTIVE, + isEmailVerfied: true, + }, }); return true; @@ -86,8 +100,8 @@ export class MinglarService { async getUserDetails(id: number) { return await this.prisma.user.findUnique({ - where: { id: id } - }) + where: { id: id }, + }); } async verifyHostOtp(email: string, otp: string): Promise { @@ -138,18 +152,29 @@ export class MinglarService { async loginForMinglar(emailAddress: string, userPassword: string) { const existingUser = await this.prisma.user.findUnique({ - where: { emailAddress: emailAddress, isActive: true, userStatus: USER_STATUS.ACTIVE } + where: { + emailAddress: emailAddress, + isActive: true, + userStatus: USER_STATUS.ACTIVE, + }, }); if (!existingUser) { throw new ApiError(404, 'User not found'); } - if (existingUser.roleXid !== ROLE.MINGLAR_ADMIN && existingUser.roleXid !== ROLE.CO_ADMIN && existingUser.roleXid !== ROLE.ACCOUNT_MANAGER) { + if ( + existingUser.roleXid !== ROLE.MINGLAR_ADMIN && + existingUser.roleXid !== ROLE.CO_ADMIN && + existingUser.roleXid !== ROLE.ACCOUNT_MANAGER + ) { throw new ApiError(403, 'Access denied.'); } - const matchPassword = await bcrypt.compare(userPassword, existingUser.userPassword); + const matchPassword = await bcrypt.compare( + userPassword, + existingUser.userPassword, + ); if (!matchPassword) { throw new ApiError(401, 'Invalid credentials'); } @@ -159,7 +184,7 @@ export class MinglarService { async checkUserExists(emailAddress: string) { return await this.prisma.user.findUnique({ - where: { emailAddress: emailAddress, isActive: true } + where: { emailAddress: emailAddress, isActive: true }, }); } @@ -168,23 +193,31 @@ export class MinglarService { data: { emailAddress: emailAddress, roleXid: roleXid, - userStatus: USER_STATUS.INVITED - } + userStatus: USER_STATUS.INVITED, + }, }); } - async createUserRevenue(userXid: number, isFixedSalary: boolean, perValue: number) { + async createUserRevenue( + userXid: number, + isFixedSalary: boolean, + perValue: number, + ) { return await this.prisma.userRevenue.create({ data: { userXid: userXid, is_fixed_salary: isFixedSalary, per_value: perValue || 0, - isActive: true - } + isActive: true, + }, }); } - async createInviteDetails(userXid: number, invitedBy: number, invitationStatus: string) { + async createInviteDetails( + userXid: number, + invitedBy: number, + invitationStatus: string, + ) { return await this.prisma.inviteDetails.create({ data: { userXid: userXid, @@ -195,7 +228,7 @@ export class MinglarService { invitation_status: invitationStatus, isActive: true, isMinglarInvitation: true, - } + }, }); } @@ -208,7 +241,7 @@ export class MinglarService { roleXid: number, isFixedSalary: boolean, perValue: number, - invitedBy: number + invitedBy: number, ) { return await this.prisma.$transaction(async (tx) => { // Check existing user @@ -274,7 +307,7 @@ export class MinglarService { cityXid?: number; pinCode?: string; }, - documents: Array<{ fileName: string; filePath: string }> + documents: Array<{ fileName: string; filePath: string }>, ) { try { return await this.prisma.$transaction(async (tx) => { @@ -282,9 +315,15 @@ export class MinglarService { // 1. Update User table (optimized) const userUpdateData: any = {}; - const userFields = ['firstName', 'lastName', 'mobileNumber', 'dateOfBirth', 'profileImage']; + const userFields = [ + 'firstName', + 'lastName', + 'mobileNumber', + 'dateOfBirth', + 'profileImage', + ]; - userFields.forEach(field => { + userFields.forEach((field) => { if (userData[field as keyof typeof userData] !== undefined) { if (field === 'dateOfBirth' && userData.dateOfBirth) { userUpdateData[field] = new Date(userData.dateOfBirth); @@ -308,15 +347,23 @@ export class MinglarService { const existingAddress = await tx.userAddressDetails.findFirst({ where: { userXid: userId, isActive: true }, - select: { id: true } // Only select needed field + select: { id: true }, // Only select needed field }); const addressUpdateData: any = {}; - const addressFields = ['address1', 'address2', 'stateXid', 'countryXid', 'cityXid', 'pinCode']; + const addressFields = [ + 'address1', + 'address2', + 'stateXid', + 'countryXid', + 'cityXid', + 'pinCode', + ]; - addressFields.forEach(field => { + addressFields.forEach((field) => { if (addressData[field as keyof typeof addressData] !== undefined) { - addressUpdateData[field] = addressData[field as keyof typeof addressData]; + addressUpdateData[field] = + addressData[field as keyof typeof addressData]; } }); @@ -327,11 +374,22 @@ export class MinglarService { }); } else { // Validate required fields - const requiredFields = ['address1', 'stateXid', 'countryXid', 'cityXid', 'pinCode']; - const missingFields = requiredFields.filter(field => !addressData[field as keyof typeof addressData]); + const requiredFields = [ + 'address1', + 'stateXid', + 'countryXid', + 'cityXid', + 'pinCode', + ]; + const missingFields = requiredFields.filter( + (field) => !addressData[field as keyof typeof addressData], + ); if (missingFields.length > 0) { - throw new ApiError(400, `Missing required address fields: ${missingFields.join(', ')}`); + throw new ApiError( + 400, + `Missing required address fields: ${missingFields.join(', ')}`, + ); } await tx.userAddressDetails.create({ @@ -354,7 +412,7 @@ export class MinglarService { if (documents.length > 0) { await tx.userDocuments.createMany({ - data: documents.map(doc => ({ + data: documents.map((doc) => ({ userXid: userId, fileName: doc.filePath, isActive: true, @@ -384,14 +442,14 @@ export class MinglarService { countryXid: true, cityXid: true, pinCode: true, - } + }, }, userDocuments: { where: { isActive: true }, select: { id: true, fileName: true, - } + }, }, }, }); @@ -407,14 +465,24 @@ export class MinglarService { if (updatedUser.profileImage) percentage += 15; // Name and Phone Number: 15% - if (updatedUser.firstName && updatedUser.lastName && updatedUser.mobileNumber) { + if ( + updatedUser.firstName && + updatedUser.lastName && + updatedUser.mobileNumber + ) { percentage += 15; } // Location Info: 25% if (updatedUser.userAddressDetails.length > 0) { const address = updatedUser.userAddressDetails[0]; - if (address.address1 && address.stateXid && address.countryXid && address.cityXid && address.pinCode) { + if ( + address.address1 && + address.stateXid && + address.countryXid && + address.cityXid && + address.pinCode + ) { percentage += 25; } } @@ -432,7 +500,7 @@ export class MinglarService { if (profilePercentage > 80) { await tx.user.update({ where: { id: userId }, - data: { isProfileUpdated: true } + data: { isProfileUpdated: true }, }); } @@ -477,12 +545,12 @@ export class MinglarService { select: { id: true, roleName: true, - } - } - } - } - } - }) + }, + }, + }, + }, + }, + }); } async getAllHostApplications( @@ -609,10 +677,10 @@ export class MinglarService { where: { roleXid: { in: [ - ROLE.MINGLAR_ADMIN, // Admin - ROLE.CO_ADMIN, // Co-Admin - ROLE.ACCOUNT_MANAGER // AM - ] + ROLE.MINGLAR_ADMIN, // Admin + ROLE.CO_ADMIN, // Co-Admin + ROLE.ACCOUNT_MANAGER, // AM + ], }, isActive: true, userStatus: USER_STATUS.ACTIVE, @@ -633,9 +701,9 @@ export class MinglarService { // 2. Count assigned hosts for ANY user (Admin / Co-Admin / AM) const groupedHosts = await this.prisma.hostHeader.groupBy({ - by: ["accountManagerXid"], + by: ['accountManagerXid'], where: { - accountManagerXid: { in: userIds }, // assigned user + accountManagerXid: { in: userIds }, // assigned user isActive: true, }, _count: { @@ -662,14 +730,14 @@ export class MinglarService { where: { roleXid: { in: [ - ROLE.MINGLAR_ADMIN, // Admin - ROLE.CO_ADMIN, // Co-Admin - ROLE.ACCOUNT_MANAGER // AM - ] + ROLE.MINGLAR_ADMIN, // Admin + ROLE.CO_ADMIN, // Co-Admin + ROLE.ACCOUNT_MANAGER, // AM + ], }, isActive: true, userStatus: { - not: USER_STATUS.DE_ACTIVATED // Exclude DE_ACTIVATED status + not: USER_STATUS.DE_ACTIVATED, // Exclude DE_ACTIVATED status }, }, include: { @@ -683,11 +751,14 @@ export class MinglarService { }); } - - async assignAMToHost(userId: number, hostXid: number, accountManagerXid: number) { + async assignAMToHost( + userId: number, + hostXid: number, + accountManagerXid: number, + ) { const hostDetails = await this.prisma.hostHeader.findFirst({ where: { id: hostXid }, - }) + }); if (!hostDetails) { throw new ApiError(404, 'Host not found'); @@ -697,8 +768,11 @@ export class MinglarService { throw new ApiError(400, 'AM already assigned to this host'); } - if (hostDetails.adminStatusInternal !== MINGLAR_STATUS_INTERNAL.AM_NOT_ASSIGNED && - hostDetails.adminStatusDisplay !== MINGLAR_STATUS_DISPLAY.AM_NOT_ASSIGNED) { + if ( + hostDetails.adminStatusInternal !== + MINGLAR_STATUS_INTERNAL.AM_NOT_ASSIGNED && + hostDetails.adminStatusDisplay !== MINGLAR_STATUS_DISPLAY.AM_NOT_ASSIGNED + ) { throw new ApiError(400, 'Invalid host status'); } @@ -711,7 +785,7 @@ export class MinglarService { hostStatusDisplay: HOST_STATUS_DISPLAY.UNDER_REVIEW, adminStatusInternal: MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW, adminStatusDisplay: MINGLAR_STATUS_DISPLAY.TO_REVIEW, - } + }, }); return true; } @@ -729,7 +803,9 @@ export class MinglarService { }); if (!amUser || !amUser.emailAddress) { - console.warn(`AM notification skipped: user not found or missing email for id=${accountManagerXid}`); + console.warn( + `AM notification skipped: user not found or missing email for id=${accountManagerXid}`, + ); return false; } @@ -742,13 +818,18 @@ export class MinglarService { } } - async addHostSuggestion(hostXid: number, title: string, comments: string, reviewedByXid: number) { + async addHostSuggestion( + hostXid: number, + title: string, + comments: string, + reviewedByXid: number, + ) { // Check if host exists const hostHeader = await this.prisma.hostHeader.findUnique({ where: { id: hostXid }, - select: { id: true } + select: { id: true }, }); - console.log(hostHeader) + console.log(hostHeader); if (!hostHeader) { throw new ApiError(404, 'Host not found'); @@ -764,18 +845,46 @@ export class MinglarService { isreviewed: false, reviewedByXid: reviewedByXid, reviewOn: null, - isActive: true - } + isActive: true, + }, + }); + + return true; + } + async addPqqSuggestion( + title: string, + comments: string, + activity_pqq_header_xid: number, + ) { + // Check if host exists + const ActivityHeader = await this.prisma.activityPQQheader.findUnique({ + where: { id: activity_pqq_header_xid }, + select: { id: true }, + }); + + if (!ActivityHeader) { + throw new ApiError(404, 'Host not found'); + } + + await this.prisma.activityPQQSuggestions.create({ + data: { + title: title, + comments: comments, + isReviewed: false, + reviewedOn: null, + isActive: true, + activityPqqHeaderXid: activity_pqq_header_xid, + reviewedByXid: null, + }, }); return true; } async getHostSuggestions(userId: number) { - const hostDetail = await this.prisma.hostHeader.findFirst({ - where: { userXid: userId, isActive: true } - }) + where: { userXid: userId, isActive: true }, + }); const suggestions = await this.prisma.hostSuggestion.findMany({ where: { hostXid: hostDetail.id, isreviewed: false, isActive: true }, @@ -788,8 +897,8 @@ export class MinglarService { reviewOn: true, }, orderBy: { - id: 'asc' - } + id: 'asc', + }, }); return suggestions; @@ -804,7 +913,7 @@ export class MinglarService { amountPerBooking: number, durationFrequency: string, payoutDurationNum: number, - payoutDurationFrequency: string + payoutDurationFrequency: string, ) { return await this.prisma.hostHeader.update({ where: { id: host_xid }, @@ -816,9 +925,9 @@ export class MinglarService { commisionPer: commisionPer ? Number(commisionPer) : null, // Convert to number if exists amountPerBooking: amountPerBooking ? Number(amountPerBooking) : null, // Convert to number if exists payoutDurationNum: Number(payoutDurationNum), // Convert to number - payoutDurationFrequency: payoutDurationFrequency - } - }) + payoutDurationFrequency: payoutDurationFrequency, + }, + }); } async acceptHostApplication(host_xid: number, user_xid: number) { @@ -828,19 +937,18 @@ export class MinglarService { hostStatusInternal: HOST_STATUS_INTERNAL.HOST_SUBMITTED, hostStatusDisplay: HOST_STATUS_DISPLAY.UNDER_REVIEW, adminStatusInternal: MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW, - adminStatusDisplay: MINGLAR_STATUS_DISPLAY.TO_REVIEW + adminStatusDisplay: MINGLAR_STATUS_DISPLAY.TO_REVIEW, }, data: { hostStatusInternal: HOST_STATUS_INTERNAL.APPROVED, hostStatusDisplay: HOST_STATUS_DISPLAY.APPROVED, adminStatusInternal: MINGLAR_STATUS_INTERNAL.AM_APPROVED, adminStatusDisplay: MINGLAR_STATUS_DISPLAY.APPROVED, - stepper: STEPPER.COMPANY_DETAILS_APPROVED - } - }) + stepper: STEPPER.COMPANY_DETAILS_APPROVED, + }, + }); } - async acceptHostApplicationMinglarAdmin(host_xid: number, user_xid: number) { return await this.prisma.hostHeader.update({ where: { @@ -848,7 +956,7 @@ export class MinglarService { hostStatusInternal: HOST_STATUS_INTERNAL.HOST_SUBMITTED, hostStatusDisplay: HOST_STATUS_DISPLAY.UNDER_REVIEW, adminStatusInternal: MINGLAR_STATUS_INTERNAL.ADMIN_TO_REVIEW, - adminStatusDisplay: MINGLAR_STATUS_DISPLAY.NEW + adminStatusDisplay: MINGLAR_STATUS_DISPLAY.NEW, }, data: { isApproved: true, @@ -856,68 +964,62 @@ export class MinglarService { hostStatusDisplay: HOST_STATUS_DISPLAY.UNDER_REVIEW, adminStatusInternal: MINGLAR_STATUS_INTERNAL.AM_NOT_ASSIGNED, adminStatusDisplay: MINGLAR_STATUS_DISPLAY.AM_NOT_ASSIGNED, - } - }) + }, + }); } async rejectHostApplication(host_xid: number, user_xid: number) { await this.prisma.$transaction(async (tx) => { const hostDetails = await tx.hostHeader.findFirst({ where: { id: host_xid }, - select: { id: true, userXid: true } - }) + select: { id: true, userXid: true }, + }); if (!hostDetails) { - throw new Error("Host not found"); + throw new Error('Host not found'); } await tx.hostHeader.update({ where: { id: host_xid, hostStatusInternal: HOST_STATUS_INTERNAL.HOST_SUBMITTED, - hostStatusDisplay: HOST_STATUS_DISPLAY.UNDER_REVIEW + hostStatusDisplay: HOST_STATUS_DISPLAY.UNDER_REVIEW, }, data: { hostStatusInternal: HOST_STATUS_INTERNAL.REJECTED, hostStatusDisplay: HOST_STATUS_DISPLAY.REJECTED, adminStatusInternal: MINGLAR_STATUS_INTERNAL.ADMIN_REJECTED, adminStatusDisplay: MINGLAR_STATUS_DISPLAY.REJECTED, - - } - }) + }, + }); await tx.user.update({ where: { id: hostDetails.userXid }, data: { - userStatus: USER_STATUS.REJECTED - } - }) - }) + userStatus: USER_STATUS.REJECTED, + }, + }); + }); } - async rejectHostApplicationAM(host_xid: number, user_xid: number) { const hostDetails = await this.prisma.hostHeader.findFirst({ where: { id: host_xid }, - select: { id: true, userXid: true } - }) + select: { id: true, userXid: true }, + }); if (!hostDetails) { - throw new Error("Host not found"); + throw new Error('Host not found'); } await this.prisma.hostHeader.update({ where: { id: host_xid, hostStatusInternal: HOST_STATUS_INTERNAL.HOST_SUBMITTED, - hostStatusDisplay: HOST_STATUS_DISPLAY.UNDER_REVIEW + hostStatusDisplay: HOST_STATUS_DISPLAY.UNDER_REVIEW, }, data: { hostStatusInternal: HOST_STATUS_INTERNAL.REJECTED, hostStatusDisplay: HOST_STATUS_DISPLAY.REJECTED, adminStatusInternal: MINGLAR_STATUS_INTERNAL.ADMIN_REJECTED, adminStatusDisplay: MINGLAR_STATUS_DISPLAY.REJECTED, - - } - }) + }, + }); } - - - }