diff --git a/serverless/functions/user.yml b/serverless/functions/user.yml index 6c15442..99cb242 100644 --- a/serverless/functions/user.yml +++ b/serverless/functions/user.yml @@ -498,6 +498,21 @@ getAllUserSavedItineraries: path: /itinerary/get-all-user-saved-itineraries method: get +cancelUserItinerary: + handler: src/modules/user/handlers/itinerary/cancelUserItinerary.handler + memorySize: 512 + package: + patterns: + - 'src/modules/user/**' + - ${file(./serverless/patterns/base.yml):pattern1} + - ${file(./serverless/patterns/base.yml):pattern2} + - ${file(./serverless/patterns/base.yml):pattern3} + - ${file(./serverless/patterns/base.yml):pattern4} + events: + - httpApi: + path: /itinerary/cancel-itinerary + method: post + createRazorpayOrder: handler: src/modules/user/handlers/payment/createOrder.handler memorySize: 512 diff --git a/src/modules/user/handlers/itinerary/cancelUserItinerary.ts b/src/modules/user/handlers/itinerary/cancelUserItinerary.ts new file mode 100644 index 0000000..929dd4a --- /dev/null +++ b/src/modules/user/handlers/itinerary/cancelUserItinerary.ts @@ -0,0 +1,64 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; +import { prismaClient } from '../../../../common/database/prisma.lambda.service'; +import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser'; +import { safeHandler } from '../../../../common/utils/handlers/safeHandler'; +import ApiError from '../../../../common/utils/helper/ApiError'; +import { ItineraryService } from '../../services/itinerary.service'; + +const itineraryService = new ItineraryService(prismaClient); + +export const handler = safeHandler(async ( + event: APIGatewayProxyEvent, + context?: Context, +): Promise => { + const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']; + if (!token) { + throw new ApiError( + 400, + 'This is a protected route. Please provide a valid token.', + ); + } + + const userInfo = await verifyUserToken(token); + const userId = Number(userInfo.id); + + if (!userId || Number.isNaN(userId)) { + throw new ApiError(400, 'Invalid user ID'); + } + + let body: Record = {}; + if (event.body) { + try { + body = JSON.parse(event.body); + } catch { + throw new ApiError(400, 'Invalid JSON body'); + } + } + + const itineraryHeaderXid = + body.itineraryHeaderXid !== undefined && body.itineraryHeaderXid !== null + ? Number(body.itineraryHeaderXid) + : NaN; + + if (!Number.isInteger(itineraryHeaderXid) || itineraryHeaderXid <= 0) { + throw new ApiError(400, 'Invalid itineraryHeaderXid.'); + } + + const result = await itineraryService.cancelUserItinerary( + userId, + itineraryHeaderXid, + ); + + return { + statusCode: 200, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, + body: JSON.stringify({ + success: true, + message: 'Itinerary cancelled successfully', + data: result, + }), + }; +}); diff --git a/src/modules/user/services/itinerary.service.ts b/src/modules/user/services/itinerary.service.ts index 37b3821..a0e7b0d 100644 --- a/src/modules/user/services/itinerary.service.ts +++ b/src/modules/user/services/itinerary.service.ts @@ -2462,6 +2462,209 @@ export class ItineraryService { }; } + async cancelUserItinerary(userXid: number, itineraryHeaderXid: number) { + return this.prisma.$transaction(async (tx) => { + const itinerary = await tx.itineraryHeader.findFirst({ + where: { + id: itineraryHeaderXid, + ownerXid: userXid, + isActive: true, + deletedAt: null, + }, + select: { + id: true, + itineraryNo: true, + title: true, + itineraryStatus: true, + }, + }); + + if (!itinerary) { + throw new ApiError( + 404, + 'Active itinerary not found for the logged-in user.', + ); + } + + const itineraryActivityIds = ( + await tx.itineraryActivities.findMany({ + where: { + itineraryHeaderXid, + isActive: true, + deletedAt: null, + }, + select: { + id: true, + }, + }) + ).map((item) => item.id); + + const itineraryDetailIds = itineraryActivityIds.length + ? ( + await tx.itineraryDetails.findMany({ + where: { + itineraryActivityXid: { + in: itineraryActivityIds, + }, + isActive: true, + deletedAt: null, + }, + select: { + id: true, + }, + }) + ).map((item) => item.id) + : []; + + const itinerarySelectionIds = itineraryActivityIds.length + ? ( + await tx.itineraryActivitySelection.findMany({ + where: { + itineraryActivityXid: { + in: itineraryActivityIds, + }, + isActive: true, + deletedAt: null, + }, + select: { + id: true, + }, + }) + ).map((item) => item.id) + : []; + + if (itineraryDetailIds.length) { + await tx.itineraryDetailTaxes.updateMany({ + where: { + itineraryDetailXid: { + in: itineraryDetailIds, + }, + isActive: true, + deletedAt: null, + }, + data: { + isActive: false, + }, + }); + + await tx.itineraryDetails.updateMany({ + where: { + id: { + in: itineraryDetailIds, + }, + isActive: true, + deletedAt: null, + }, + data: { + isActive: false, + itineraryStatus: 'cancelled', + }, + }); + } + + if (itinerarySelectionIds.length) { + await tx.itineraryActivitySelectionFoodType.updateMany({ + where: { + itineraryActivitySelectionXid: { + in: itinerarySelectionIds, + }, + isActive: true, + deletedAt: null, + }, + data: { + isActive: false, + }, + }); + + await tx.itineraryActivitySelectionEquipment.updateMany({ + where: { + itineraryActivitySelectionXid: { + in: itinerarySelectionIds, + }, + isActive: true, + deletedAt: null, + }, + data: { + isActive: false, + }, + }); + + await tx.itineraryActivitySelection.updateMany({ + where: { + id: { + in: itinerarySelectionIds, + }, + isActive: true, + deletedAt: null, + }, + data: { + isActive: false, + }, + }); + } + + await tx.itineraryActivities.updateMany({ + where: { + itineraryHeaderXid, + isActive: true, + deletedAt: null, + }, + data: { + isActive: false, + bookingStatus: 'cancelled', + }, + }); + + await tx.itineraryStartStopDetails.updateMany({ + where: { + itineraryHeaderXid, + isActive: true, + deletedAt: null, + }, + data: { + isActive: false, + }, + }); + + await tx.itineraryMembers.updateMany({ + where: { + itineraryHeaderXid, + isActive: true, + deletedAt: null, + }, + data: { + isActive: false, + memberStatus: 'cancelled', + }, + }); + + const cancelledItinerary = await tx.itineraryHeader.update({ + where: { + id: itineraryHeaderXid, + }, + data: { + isActive: false, + itineraryStatus: 'cancelled', + }, + select: { + id: true, + itineraryNo: true, + title: true, + itineraryStatus: true, + isActive: true, + }, + }); + + return { + itineraryHeaderXid: cancelledItinerary.id, + itineraryNo: cancelledItinerary.itineraryNo, + title: cancelledItinerary.title, + itineraryStatus: cancelledItinerary.itineraryStatus, + isActive: cancelledItinerary.isActive, + }; + }); + } + async bookItineraryAfterPayment( tx: Prisma.TransactionClient, userXid: number,