Files
MinglarBackendNestJS/src/modules/host/services/activityScheduling.service.ts

287 lines
9.7 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Injectable } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import { 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';
@Injectable()
export class SchedulingService {
constructor(private prisma: PrismaClient) { }
async addSchedulingForActivity(data: ScheduleActivityDTO) {
const {
activityXid,
scheduleType,
dateRange,
rules,
venues,
earlyCheckInMins,
bookingCutOffMins,
} = data;
return this.prisma.$transaction(async (tx) => {
const createdHeaders: number[] = [];
for (const venue of venues) {
const header = await tx.scheduleHeader.create({
data: {
activityXid,
activityVenueXid: venue.venueXid,
scheduleType,
startDate: new Date(dateRange.startDate),
endDate: dateRange.endDate ? new Date(dateRange.endDate) : null,
earlyCheckInMins,
bookingCutOffMins,
isActive: true,
},
});
createdHeaders.push(header.id);
// WEEKLY
if (scheduleType === SCHEDULING_TYPE.WEEKLY) {
await tx.scheduleRecurrence.createMany({
data: rules.weekdays!.map(day => ({
scheduleHeaderXid: header.id,
weekDay: day,
isActive: true,
})),
});
}
// MONTHLY
if (scheduleType === SCHEDULING_TYPE.MONTHLY) {
await tx.scheduleRecurrence.createMany({
data: rules.monthDates!.map(day => ({
scheduleHeaderXid: header.id,
dayOfMonth: day,
isActive: true,
})),
});
}
// CUSTOM / ONCE
if (scheduleType === SCHEDULING_TYPE.CUSTOM || scheduleType === SCHEDULING_TYPE.ONCE) {
await tx.scheduleOccurences.createMany({
data: rules.customDates!.map(d => ({
scheduleHeaderXid: header.id,
occurenceDate: new Date(d),
isActive: true,
})),
});
}
// Slots
for (const slot of venue.slots) {
await tx.scheduleDetails.create({
data: {
scheduleHeaderXid: header.id,
occurenceDate: slot.occurrenceDate ? new Date(slot.occurrenceDate) : null,
weekDay: slot.weekDay ?? null,
dayOfMonth: slot.dayOfMonth ?? null,
startTime: slot.startTime,
endTime: slot.endTime,
maxCapacity: slot.maxCapacity,
isActive: true,
},
});
}
}
return { success: true, scheduleHeaderIds: createdHeaders };
});
}
async getAvailableSlotsForDate(
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();
/* --------------------------------
1⃣ FETCH ACTIVE SCHEDULE HEADERS
-------------------------------- */
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 }, // ONLY_ONCE / CUSTOM
{ weekDay: weekDay }, // WEEKLY
{ dayOfMonth: dayOfMonth }, // MONTHLY
],
},
},
Cancellations: {
where: {
occurenceDate: date,
isActive: true,
},
},
},
});
if (!scheduleHeaders.length) {
return [];
}
/* --------------------------------
2⃣ BUILD RESPONSE
-------------------------------- */
const response = [];
for (const header of scheduleHeaders) {
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,
startTime: slot.startTime,
endTime: slot.endTime,
maxCapacity: slot.maxCapacity,
}));
if (!slots.length) continue;
response.push({
venueXid: header.activityVenue.id,
venueName: header.activityVenue.venueName,
venueLabel: header.activityVenue.venueLabel,
slots,
});
}
return response;
}
async getVenueFromVenueXid(venueXid: number, activityXid: number) {
return await this.prisma.activityVenues.findUnique({
where: { id: venueXid, activityXid: activityXid, isActive: true },
select: {
id: true,
venueName: true,
venueLabel: true,
venueCapacity: true,
},
});
}
async getActivityByXid(activityXid: number) {
return await this.prisma.activities.findUnique({
where: { id: activityXid, isActive: true },
select: {
id: true,
activityTitle: true,
}
});
}
/**
* Get activities by status and host ID
* @param hostId - ID of the host
* @param status - Filter by status (Listed, Unlisted, Not_Listed) - optional
* @returns Array of activities matching the criteria
*/
async getActivitiesByStatus(hostId: number, status?: string) {
// Build where clause
const whereClause: any = {
hostXid: hostId,
isActive: true,
deletedAt: null,
activityInternalStatus: {
in: [ACTIVITY_INTERNAL_STATUS.ACTIVITY_APPROVED, ACTIVITY_INTERNAL_STATUS.ACTIVITY_UNLISTED, ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED]
}
};
// Add status filter if provided
if (status) {
whereClause.activityInternalStatus = status;
}
// Query activities
const activities = await this.prisma.activities.findMany({
where: whereClause,
select: {
id: true,
activityRefNumber: true,
activityTitle: true,
activityDescription: true,
activityDisplayStatus: true,
activityInternalStatus: true,
ActivitiesMedia: {
select: {
id: true,
mediaFileName: true,
mediaType: true,
},
},
},
orderBy: {
createdAt: 'desc',
},
});
// Transform response
return activities.map((activity) => ({
activityId: activity.id,
activityRefNumber: activity.activityRefNumber,
activityName: activity.activityTitle,
activityDescription: activity.activityDescription,
activityInternalStatus: activity.activityInternalStatus,
activityDisplayStatus: activity.activityDisplayStatus,
media: activity.ActivitiesMedia,
}));
}
async getVenueDurationByAct(activityXid: number, hostId: number) {
const result = await this.prisma.activities.findUnique({
where: { id: activityXid, hostXid: hostId, isActive: true },
select: {
id: true,
activityDurationMins: true,
ActivityVenues: {
select: {
id: true,
venueName: true,
venueLabel: true,
}
}
}
})
return result;
}
}