sending venue details in after booking api

This commit is contained in:
paritosh18
2026-04-27 15:24:04 +05:30
parent ce9a9b6211
commit e164e1c25a
2 changed files with 427 additions and 93 deletions

View File

@@ -43,6 +43,7 @@ export const handler = safeHandler(async (
}
const result = await itineraryService.getActivityDetailsAfterBooking(
userId,
itineraryHeaderXid,
);

View File

@@ -269,6 +269,28 @@ const formatDateKey = (date: Date) => {
const addMinutes = (date: Date, minutes: number) =>
new Date(date.getTime() + minutes * 60 * 1000);
const formatTicketTime = (value?: string | null) => {
if (!value) {
return null;
}
return value.trim().toUpperCase().replace(/\s+(AM|PM)$/i, '$1');
};
const formatTicketTimeRange = (
startTime?: string | null,
endTime?: string | null,
) => {
const formattedStartTime = formatTicketTime(startTime);
const formattedEndTime = formatTicketTime(endTime);
if (formattedStartTime && formattedEndTime) {
return `${formattedStartTime} - ${formattedEndTime}`;
}
return formattedStartTime ?? formattedEndTime ?? null;
};
const getDateRange = (fromDate: Date, toDate: Date) => {
const dates: Date[] = [];
const cursor = startOfDay(fromDate);
@@ -4512,13 +4534,29 @@ export class ItineraryService {
};
}
async getActivityDetailsAfterBooking(itineraryHeaderXid: number) {
// Fetch the itinerary header with complete details
async getActivityDetailsAfterBooking(
userXid: number,
itineraryHeaderXid: number,
) {
const itineraryHeader = await this.prisma.itineraryHeader.findFirst({
where: {
id: itineraryHeaderXid,
isActive: true,
deletedAt: null,
OR: [
{
ownerXid: userXid,
},
{
ItineraryMembers: {
some: {
memberXid: userXid,
isActive: true,
deletedAt: null,
},
},
},
],
},
select: {
id: true,
@@ -4539,6 +4577,220 @@ export class ItineraryService {
mobileNumber: true,
},
},
ItineraryMembers: {
where: {
memberXid: userXid,
isActive: true,
deletedAt: null,
},
select: {
id: true,
memberXid: true,
memberRole: true,
memberStatus: true,
member: {
select: {
id: true,
firstName: true,
lastName: true,
emailAddress: true,
mobileNumber: true,
profileImage: true,
},
},
},
},
ItineraryActivities: {
where: {
activityXid: {
not: null,
},
isActive: true,
deletedAt: null,
},
orderBy: [
{
occurenceDate: 'asc',
},
{
startTime: 'asc',
},
{
displayOrder: 'asc',
},
],
select: {
id: true,
displayOrder: true,
occurenceDate: true,
startTime: true,
endTime: true,
paxCount: true,
totalAmount: true,
bookingStatus: true,
venue: {
select: {
id: true,
venueName: true,
venueLabel: true,
},
},
ItineraryDetails: {
where: {
itineraryKind: 'ACTIVITY',
isActive: true,
deletedAt: null,
itineraryMember: {
is: {
memberXid: userXid,
isActive: true,
deletedAt: null,
},
},
},
orderBy: {
createdAt: 'desc',
},
select: {
id: true,
itineraryMemberXid: true,
offlineCode: true,
description1: true,
description2: true,
activityStatus: true,
itineraryStatus: true,
isPaid: true,
paidOn: true,
createdAt: true,
},
},
itineraryActivitySelections: {
where: {
isActive: true,
deletedAt: null,
itineraryMember: {
is: {
memberXid: userXid,
isActive: true,
deletedAt: null,
},
},
},
select: {
id: true,
itineraryMemberXid: true,
isFoodOpted: true,
isTrainerOpted: true,
isInActivityNavigationOpted: true,
activityNavigationMode: {
select: {
id: true,
navigationModeName: true,
},
},
selectedFoodTypes: {
where: {
isActive: true,
deletedAt: null,
},
select: {
id: true,
activityFoodTypeXid: true,
activityFoodType: {
select: {
id: true,
foodTypeXid: true,
foodType: {
select: {
id: true,
foodTypeName: true,
},
},
},
},
},
},
selectedEquipments: {
where: {
isActive: true,
deletedAt: null,
},
select: {
id: true,
activityEquipmentXid: true,
activityEquipment: {
select: {
id: true,
equipmentName: true,
},
},
},
},
},
},
activity: {
select: {
id: true,
activityTitle: true,
activityDescription: true,
checkInAddress: true,
checkInLat: true,
checkInLong: true,
activityDurationMins: true,
foodAvailable: true,
trainerAvailable: true,
equipmentAvailable: true,
pickUpDropAvailable: true,
inActivityAvailable: true,
ActivitiesMedia: {
where: {
isActive: true,
deletedAt: null,
},
orderBy: {
displayOrder: 'asc',
},
select: {
id: true,
mediaType: true,
mediaFileName: true,
isCoverImage: true,
displayOrder: true,
},
},
ActivityPickUpDetails: {
where: {
isActive: true,
deletedAt: null,
},
select: {
id: true,
isPickUp: true,
locationLat: true,
locationLong: true,
locationAddress: true,
},
},
ActivityOtherDetails: {
where: {
isActive: true,
deletedAt: null,
},
select: {
exclusiveNotes: true,
SafetyInstruction: true,
Cancellations: true,
dosNotes: true,
dontsNotes: true,
tipsNotes: true,
termsAndCondition: true,
},
take: 1,
},
},
},
},
},
},
});
@@ -4546,122 +4798,186 @@ export class ItineraryService {
throw new ApiError(404, 'Itinerary not found');
}
// Find all itinerary activities linked to this itinerary header
const itineraryActivities = await this.prisma.itineraryActivities.findMany({
where: {
itineraryHeaderXid,
activityXid: {
not: null,
},
isActive: true,
deletedAt: null,
},
orderBy: [
{
occurenceDate: 'asc',
},
{
startTime: 'asc',
},
],
select: {
id: true,
occurenceDate: true,
startTime: true,
endTime: true,
activity: {
select: {
id: true,
activityTitle: true,
activityDescription: true,
checkInAddress: true,
checkInLat: true,
checkInLong: true,
activityDurationMins: true,
ActivitiesMedia: {
where: {
isActive: true,
deletedAt: null,
},
orderBy: {
displayOrder: 'asc',
},
select: {
id: true,
mediaType: true,
mediaFileName: true,
isCoverImage: true,
displayOrder: true,
},
},
ActivityOtherDetails: {
where: {
isActive: true,
deletedAt: null,
},
select: {
exclusiveNotes: true,
SafetyInstruction: true,
Cancellations: true,
dosNotes: true,
dontsNotes: true,
tipsNotes: true,
termsAndCondition: true,
},
take: 1,
},
},
},
},
});
const itineraryMember = itineraryHeader.ItineraryMembers[0] ?? null;
if (itineraryActivities.length === 0) {
if (itineraryHeader.ItineraryActivities.length === 0) {
throw new ApiError(404, 'No activities found for this itinerary');
}
// Process all activities
const activities = await Promise.all(
itineraryActivities.map(async (itineraryActivity) => {
// Generate check-in code
const checkInCode = generateCheckInCode();
// Attach presigned URLs to media files
itineraryHeader.ItineraryActivities.map(async (itineraryActivity) => {
const itineraryDetail = itineraryActivity.ItineraryDetails[0] ?? null;
const activitySelection =
itineraryActivity.itineraryActivitySelections[0] ?? null;
const selectedFoodTypes = (
activitySelection?.selectedFoodTypes ?? []
).map(
(selectedFoodType) =>
selectedFoodType.activityFoodType.foodType.foodTypeName,
);
const selectedEquipments = (
activitySelection?.selectedEquipments ?? []
).map(
(selectedEquipment) =>
selectedEquipment.activityEquipment.equipmentName,
);
const selectedNavigationMode =
activitySelection?.activityNavigationMode?.navigationModeName ?? null;
const mediaWithUrls = await Promise.all(
(itineraryActivity.activity?.ActivitiesMedia || []).map(
(itineraryActivity.activity?.ActivitiesMedia ?? []).map(
async (media) => ({
...media,
mediaUrl: await attachPresignedUrl(media.mediaFileName),
}),
),
);
const coverImage =
mediaWithUrls.find((media) => media.isCoverImage) ??
mediaWithUrls[0] ??
null;
const pickUpDetail =
itineraryActivity.activity?.ActivityPickUpDetails.find(
(detail) => detail.isPickUp && detail.locationAddress,
) ??
itineraryActivity.activity?.ActivityPickUpDetails.find(
(detail) => detail.locationAddress,
) ??
null;
const dropDetail =
itineraryActivity.activity?.ActivityPickUpDetails.find(
(detail) => !detail.isPickUp && detail.locationAddress,
) ?? null;
const bookingDate = formatDateKey(itineraryActivity.occurenceDate);
const ticketTime = formatTicketTimeRange(
itineraryActivity.startTime,
itineraryActivity.endTime,
);
const venueName =
itineraryActivity.venue?.venueLabel ??
itineraryActivity.venue?.venueName ??
null;
const foodIncluded = selectedFoodTypes.length > 0 ? 'Yes' : 'No';
const equipmentIncluded = selectedEquipments.length > 0 ? 'Yes' : 'No';
const pickAndDropIncluded = itineraryActivity.activity?.pickUpDropAvailable
? 'Yes'
: 'No';
const inActivityNavigation =
selectedNavigationMode ??
(activitySelection?.isInActivityNavigationOpted ? 'Yes' : 'No');
// Build activity details
return {
itineraryActivityId: itineraryActivity.id,
activityId: itineraryActivity.activity?.id,
activityTitle: itineraryActivity.activity?.activityTitle,
activityDescription: itineraryActivity.activity?.activityDescription,
bookingId: itineraryHeader.itineraryNo,
activityId: itineraryActivity.activity?.id ?? null,
activityTitle: itineraryActivity.activity?.activityTitle ?? null,
activityDescription:
itineraryActivity.activity?.activityDescription ?? null,
occurenceDate: itineraryActivity.occurenceDate,
startTime: itineraryActivity.startTime,
endTime: itineraryActivity.endTime,
duration: itineraryActivity.activity?.activityDurationMins,
checkInCode,
checkInAddress: itineraryActivity.activity?.checkInAddress,
checkInLat: itineraryActivity.activity?.checkInLat,
checkInLong: itineraryActivity.activity?.checkInLong,
duration: itineraryActivity.activity?.activityDurationMins ?? null,
checkInCode: itineraryDetail?.offlineCode ?? null,
qrCodeValue: itineraryDetail?.offlineCode ?? null,
checkInAddress: itineraryActivity.activity?.checkInAddress ?? null,
checkInLat: itineraryActivity.activity?.checkInLat ?? null,
checkInLong: itineraryActivity.activity?.checkInLong ?? null,
bookingStatus:
itineraryDetail?.activityStatus ??
itineraryActivity.bookingStatus ??
null,
description1: itineraryDetail?.description1 ?? null,
description2: itineraryDetail?.description2 ?? null,
itineraryStatus: itineraryDetail?.itineraryStatus ?? null,
isPaid: itineraryDetail?.isPaid ?? false,
paidOn: itineraryDetail?.paidOn ?? null,
paxCount: itineraryActivity.paxCount ?? null,
totalAmount: itineraryActivity.totalAmount ?? null,
venue: {
id: itineraryActivity.venue?.id ?? null,
venueName: itineraryActivity.venue?.venueName ?? null,
venueLabel: itineraryActivity.venue?.venueLabel ?? null,
displayName: venueName,
},
images: mediaWithUrls.map((media) => ({
id: media.id,
type: media.mediaType,
fileName: media.mediaFileName,
url: media.mediaUrl,
isCover: media.isCoverImage,
order: media.displayOrder,
})),
coverImage: mediaWithUrls
.filter((m) => m.isCoverImage)
.map((media) => ({
id: media.id,
url: media.mediaUrl,
})),
coverImage: coverImage
? {
id: coverImage.id,
fileName: coverImage.mediaFileName,
url: coverImage.mediaUrl,
}
: null,
bookingInformation: {
activityName: itineraryActivity.activity?.activityTitle ?? null,
date: bookingDate,
venue: venueName,
venueName: itineraryActivity.venue?.venueName ?? null,
venueLabel: itineraryActivity.venue?.venueLabel ?? null,
time: ticketTime,
startTime: formatTicketTime(itineraryActivity.startTime),
endTime: formatTicketTime(itineraryActivity.endTime),
},
bookingIncluded: {
food: foodIncluded,
selectedFoodTypes,
equipment: equipmentIncluded,
selectedEquipments,
pickAndDrop: pickAndDropIncluded,
pickupLocation:
pickUpDetail?.locationAddress ??
itineraryActivity.activity?.checkInAddress ??
null,
dropLocation: dropDetail?.locationAddress ?? null,
inActivityNavigation: inActivityNavigation ?? 'No',
trainerOrGuide: activitySelection?.isTrainerOpted ? 'Yes' : 'No',
},
ticketCard: {
qrCodeValue: itineraryDetail?.offlineCode ?? null,
checkInCode: itineraryDetail?.offlineCode ?? null,
activityTitle: itineraryActivity.activity?.activityTitle ?? null,
date: bookingDate,
venue: venueName,
time: ticketTime,
food: foodIncluded,
equipments: equipmentIncluded,
pickAndDrop: pickAndDropIncluded,
inActivityNavigation: inActivityNavigation ?? 'No',
},
userSelections: {
isFoodOpted: activitySelection?.isFoodOpted ?? false,
selectedFoodTypes,
isTrainerOpted: activitySelection?.isTrainerOpted ?? false,
isInActivityNavigationOpted:
activitySelection?.isInActivityNavigationOpted ?? false,
selectedNavigationMode: selectedNavigationMode,
selectedEquipments,
},
pickupDetails: {
pickUpDropAvailable:
itineraryActivity.activity?.pickUpDropAvailable ?? false,
pickUpLocation: pickUpDetail
? {
id: pickUpDetail.id,
locationAddress: pickUpDetail.locationAddress,
locationLat: pickUpDetail.locationLat,
locationLong: pickUpDetail.locationLong,
}
: null,
dropLocation: dropDetail
? {
id: dropDetail.id,
locationAddress: dropDetail.locationAddress,
locationLat: dropDetail.locationLat,
locationLong: dropDetail.locationLong,
}
: null,
},
activityInfo: {
exclusiveNotes:
itineraryActivity.activity?.ActivityOtherDetails[0]
@@ -4709,7 +5025,24 @@ export class ItineraryService {
email: itineraryHeader.owner?.emailAddress,
phone: itineraryHeader.owner?.mobileNumber,
},
viewer: itineraryMember
? {
itineraryMemberXid: itineraryMember.id,
memberXid: itineraryMember.memberXid,
memberRole: itineraryMember.memberRole,
memberStatus: itineraryMember.memberStatus,
firstName: itineraryMember.member.firstName,
lastName: itineraryMember.member.lastName,
email: itineraryMember.member.emailAddress,
phone: itineraryMember.member.mobileNumber,
profileImage: itineraryMember.member.profileImage,
profileImagePresignedUrl: await attachPresignedUrl(
itineraryMember.member.profileImage,
),
}
: null,
totalActivities: activities.length,
ticketCards: activities.map((activity) => activity.ticketCard),
activities,
},
};