feat: add checkAvailabilityDetails API endpoint and implement schedule details retrieval logic
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { getPresignedUrl } from '../../../common/middlewares/aws/getPreSignedUrl';
|
||||
import { ACTIVITY_AM_DISPLAY_STATUS, ACTIVITY_AM_INTERNAL_STATUS, ACTIVITY_DISPLAY_STATUS, ACTIVITY_INTERNAL_STATUS, SCHEDULING_TYPE } from '../../../common/utils/constants/host.constant';
|
||||
import ApiError from '../../../common/utils/helper/ApiError';
|
||||
import { ScheduleActivityDTO } from '../../../common/utils/validation/host/createSchedulingOfAct.validation';
|
||||
import { getPresignedUrl } from '../../../common/middlewares/aws/getPreSignedUrl';
|
||||
import config from '../../../config/config';
|
||||
|
||||
|
||||
@@ -317,6 +317,100 @@ export class SchedulingService {
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return full schedule header + venue + slots for a given activity and date
|
||||
*/
|
||||
async getScheduleDetailsForDate(
|
||||
activityXid: number,
|
||||
selectedDate: string
|
||||
) {
|
||||
const date = new Date(selectedDate);
|
||||
|
||||
if (isNaN(date.getTime())) {
|
||||
throw new ApiError(400, 'Invalid date format');
|
||||
}
|
||||
|
||||
const weekDay = date.toLocaleDateString('en-US', { weekday: 'long' }).toUpperCase();
|
||||
const dayOfMonth = date.getDate();
|
||||
|
||||
const scheduleHeaders = await this.prisma.scheduleHeader.findMany({
|
||||
where: {
|
||||
activityXid,
|
||||
isActive: true,
|
||||
startDate: { lte: date },
|
||||
OR: [
|
||||
{ endDate: null },
|
||||
{ endDate: { gte: date } },
|
||||
],
|
||||
},
|
||||
include: {
|
||||
activityVenue: {
|
||||
select: {
|
||||
id: true,
|
||||
venueName: true,
|
||||
venueLabel: true,
|
||||
venueCapacity: true,
|
||||
},
|
||||
},
|
||||
scheduleRecurrences: {
|
||||
where: { isActive: true },
|
||||
},
|
||||
ScheduleDetails: {
|
||||
where: {
|
||||
isActive: true,
|
||||
OR: [
|
||||
{ occurenceDate: date },
|
||||
{ weekDay: weekDay },
|
||||
{ dayOfMonth: dayOfMonth },
|
||||
],
|
||||
},
|
||||
},
|
||||
Cancellations: {
|
||||
where: {
|
||||
occurenceDate: date,
|
||||
isActive: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!scheduleHeaders.length) return [];
|
||||
|
||||
const response = scheduleHeaders.map((header) => {
|
||||
const cancelledSlotIds = new Set(header.Cancellations.map(c => c.slotXid));
|
||||
|
||||
const slots = header.ScheduleDetails
|
||||
.filter(slot => !cancelledSlotIds.has(slot.id))
|
||||
.map(slot => ({
|
||||
slotId: slot.id,
|
||||
occurenceDate: slot.occurenceDate,
|
||||
weekDay: slot.weekDay,
|
||||
dayOfMonth: slot.dayOfMonth,
|
||||
startTime: slot.startTime,
|
||||
endTime: slot.endTime,
|
||||
maxCapacity: slot.maxCapacity,
|
||||
}));
|
||||
|
||||
return {
|
||||
scheduleHeaderXid: header.id,
|
||||
scheduleType: header.scheduleType,
|
||||
startDate: header.startDate,
|
||||
endDate: header.endDate,
|
||||
earlyCheckInMins: header.earlyCheckInMins,
|
||||
bookingCutOffMins: header.bookingCutOffMins,
|
||||
activityVenue: {
|
||||
venueXid: header.activityVenue.id,
|
||||
venueName: header.activityVenue.venueName,
|
||||
venueLabel: header.activityVenue.venueLabel,
|
||||
venueCapacity: header.activityVenue.venueCapacity,
|
||||
},
|
||||
slots,
|
||||
};
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
async getVenueFromVenueXid(venueXid: number, activityXid: number) {
|
||||
return await this.prisma.activityVenues.findUnique({
|
||||
where: { id: venueXid, activityXid: activityXid, isActive: true },
|
||||
|
||||
113
src/modules/user/handlers/activities/checkAvailabilityDetails.ts
Normal file
113
src/modules/user/handlers/activities/checkAvailabilityDetails.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
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 { SchedulingService } from '../../../host/services/activityScheduling.service';
|
||||
import { UserService } from '../../services/user.service';
|
||||
|
||||
const userService = new UserService(prismaClient);
|
||||
const schedulingService = new SchedulingService(prismaClient);
|
||||
|
||||
export const handler = safeHandler(async (
|
||||
event: APIGatewayProxyEvent,
|
||||
context?: Context
|
||||
): Promise<APIGatewayProxyResult> => {
|
||||
// Extract token from headers
|
||||
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.');
|
||||
}
|
||||
|
||||
// Verify token and get user info
|
||||
const userInfo = await verifyUserToken(token);
|
||||
const userId = Number(userInfo.id);
|
||||
|
||||
if (!userId || isNaN(userId)) {
|
||||
throw new ApiError(400, 'Invalid user ID');
|
||||
}
|
||||
|
||||
const activityXid = Number(event.pathParameters?.activity_xid);
|
||||
|
||||
if (!activityXid || isNaN(activityXid)) {
|
||||
throw new ApiError(400, 'Valid activityXid is required');
|
||||
}
|
||||
|
||||
// selected date may be passed as query param `selectedDate`
|
||||
const selectedDate =
|
||||
event.queryStringParameters?.selectedDate ||
|
||||
event.queryStringParameters?.date ||
|
||||
(event.body ? JSON.parse(event.body).selectedDate : undefined);
|
||||
|
||||
if (!selectedDate) {
|
||||
throw new ApiError(400, 'selectedDate query parameter is required');
|
||||
}
|
||||
|
||||
// Fetch activity details (basic) and schedule details for the selected date
|
||||
const activityDetails = await userService.getActivityDetailsById(userId, activityXid);
|
||||
const scheduleDetails = await schedulingService.getScheduleDetailsForDate(activityXid, selectedDate);
|
||||
// Shape response to match UI: only include fields shown in image
|
||||
const activity = activityDetails.activity;
|
||||
|
||||
// Rooms: combine ActivityVenues with schedule info per venue
|
||||
const rooms = (activity.ActivityVenues || []).map((v: any) => {
|
||||
// find schedule header for this venue
|
||||
const header = scheduleDetails.find((h: any) => h.activityVenue?.venueXid === v.id);
|
||||
const slotCount = header ? (header.slots || []).length : 0;
|
||||
|
||||
return {
|
||||
venueXid: v.id,
|
||||
venueName: v.venueName,
|
||||
venueLabel: v.venueLabel,
|
||||
venueCapacity: v.venueCapacity,
|
||||
availableSeats: v.availableSeats ?? null,
|
||||
price: v.ActivityPrices?.[0]?.sellPrice ?? null,
|
||||
slotsCount: slotCount,
|
||||
};
|
||||
});
|
||||
|
||||
// Slots: aggregate slots across scheduleDetails
|
||||
const slots: any[] = [];
|
||||
for (const h of scheduleDetails) {
|
||||
for (const s of h.slots || []) {
|
||||
// status heuristic based on maxCapacity
|
||||
let status = 'Available';
|
||||
if (s.maxCapacity === 0) status = 'Housefull';
|
||||
else if (s.maxCapacity <= 2) status = '2 Slots Left';
|
||||
else if (s.maxCapacity <= 5) status = 'Fast Filling';
|
||||
|
||||
slots.push({
|
||||
slotId: s.slotId,
|
||||
startTime: s.startTime,
|
||||
endTime: s.endTime,
|
||||
status,
|
||||
maxCapacity: s.maxCapacity,
|
||||
venueXid: h.activityVenue?.venueXid,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// derive check-in/out from slot times (earliest start, latest end)
|
||||
const startTimes = slots.map(s => s.startTime).filter(Boolean);
|
||||
const endTimes = slots.map(s => s.endTime).filter(Boolean);
|
||||
const checkInTime = startTimes.length ? startTimes.sort()[0] : null;
|
||||
const checkOutTime = endTimes.length ? endTimes.sort().reverse()[0] : null;
|
||||
|
||||
const responsePayload = {
|
||||
selectedDate,
|
||||
rooms,
|
||||
slots,
|
||||
checkInTime,
|
||||
checkOutTime,
|
||||
};
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({ success: true, data: responsePayload }),
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user