feat: enhance scheduling service to support instant booking and late check-in options, and improve activity listing logic

This commit is contained in:
2026-02-05 16:07:43 +05:30
parent 93fb58f4f4
commit 00d53adf3d
3 changed files with 249 additions and 53 deletions

View File

@@ -41,6 +41,8 @@ export class SchedulingService {
venues,
earlyCheckInMins,
bookingCutOffMins,
isLateCheckingAllowed,
isInstantBooking
} = data;
return this.prisma.$transaction(async (tx) => {
@@ -90,8 +92,32 @@ export class SchedulingService {
---------------------------------- */
const createdHeaders: number[] = [];
if (isInstantBooking !== undefined || isLateCheckingAllowed !== undefined) {
await tx.activities.update({
where: { id: activityXid, isActive: true },
data: { isInstantBooking, isLateCheckingAllowed },
});
}
if (listNow) {
await tx.activities.update({
where: { id: activityXid, isActive: true },
data: {
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
activityDisplayStatus: ACTIVITY_DISPLAY_STATUS.ACTIVITY_LISTED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
amDisplayStatus: ACTIVITY_AM_DISPLAY_STATUS.ACTIVITY_LISTED
}
})
}
for (const venue of venues) {
if (!venue.slots || venue.slots.length === 0) {
continue;
}
const header = await tx.scheduleHeader.create({
data: {
activityXid,
@@ -109,8 +135,20 @@ export class SchedulingService {
// WEEKLY
if (scheduleType === SCHEDULING_TYPE.WEEKLY) {
const uniqueWeekdays = [
...new Set(
venue.slots
.map(s => s.weekDay)
.filter((d): d is "MONDAY" | "TUESDAY" | "WEDNESDAY" | "THURSDAY" | "FRIDAY" | "SATURDAY" | "SUNDAY" => !!d)
),
];
if (!uniqueWeekdays.length) {
throw new ApiError(400, 'Weekly schedule requires weekDay in slots');
}
await tx.scheduleRecurrence.createMany({
data: rules.weekdays!.map(day => ({
data: uniqueWeekdays.map(day => ({
scheduleHeaderXid: header.id,
weekDay: day,
isActive: true,
@@ -118,10 +156,23 @@ export class SchedulingService {
});
}
// MONTHLY
if (scheduleType === SCHEDULING_TYPE.MONTHLY) {
const uniqueDays = [
...new Set(
venue.slots
.map(s => s.dayOfMonth)
.filter((d): d is number => d !== null && d !== undefined)
),
];
if (!uniqueDays.length) {
throw new ApiError(400, 'Monthly schedule requires dayOfMonth in slots');
}
await tx.scheduleRecurrence.createMany({
data: rules.monthDates!.map(day => ({
data: uniqueDays.map(day => ({
scheduleHeaderXid: header.id,
dayOfMonth: day,
isActive: true,
@@ -129,17 +180,27 @@ export class SchedulingService {
});
}
// CUSTOM / ONCE
if (scheduleType === SCHEDULING_TYPE.CUSTOM || scheduleType === SCHEDULING_TYPE.ONCE) {
const uniqueDates = [
...new Set(
venue.slots
.map(s => s.occurrenceDate)
.filter(Boolean)
),
];
await tx.scheduleOccurences.createMany({
data: rules.customDates!.map(d => ({
data: uniqueDates.map(d => ({
scheduleHeaderXid: header.id,
occurenceDate: new Date(d),
occurenceDate: new Date(d!),
isActive: true,
})),
});
}
// Slots
for (const slot of venue.slots) {
await tx.scheduleDetails.create({
@@ -155,18 +216,6 @@ export class SchedulingService {
},
});
}
if (listNow) {
await tx.activities.update({
where: { id: activityXid, isActive: true },
data: {
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
activityDisplayStatus: ACTIVITY_DISPLAY_STATUS.ACTIVITY_LISTED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
amDisplayStatus: ACTIVITY_AM_DISPLAY_STATUS.ACTIVITY_LISTED
}
})
}
}
return { success: true, scheduleHeaderIds: createdHeaders };
@@ -330,6 +379,15 @@ export class SchedulingService {
mediaType: true,
},
},
ScheduleHeader: {
where: { isActive: true },
select: {
id: true,
scheduleType: true,
startDate: true
},
orderBy: { createdAt: 'desc' }
}
},
orderBy: {
createdAt: 'desc',
@@ -360,6 +418,8 @@ export class SchedulingService {
activityInternalStatus: activity.activityInternalStatus,
activityDisplayStatus: activity.activityDisplayStatus,
media: activity.ActivitiesMedia,
scheduleType: activity.ScheduleHeader?.length ? activity.ScheduleHeader[0].scheduleType : null,
scheduleStartDate: activity.ScheduleHeader?.length ? activity.ScheduleHeader[0].startDate : null,
}));
}

View File

@@ -3263,7 +3263,7 @@ export class HostService {
if (!isDraft && isChargeable && totalPrice <= 0) {
throw new ApiError(
400,
'transportTotalPrice must be > 0 when pickup/drop is chargeable',
'Pick-up and drop-off price is required.',
);
}