made cancel slot api and modified get by id api to send all the data of scheduling and taking listNow flag in create scheduling api while deleting all the records before creating new ones

This commit is contained in:
2026-01-29 17:47:48 +05:30
parent 1d90675d19
commit 8be2eebaba
5 changed files with 315 additions and 3 deletions

View File

@@ -1523,7 +1523,7 @@ model Cancellations {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
scheduleHeaderXid Int @map("schedule_header_xid") scheduleHeaderXid Int @map("schedule_header_xid")
scheduleHeader ScheduleHeader @relation(fields: [scheduleHeaderXid], references: [id], onDelete: Cascade) scheduleHeader ScheduleHeader @relation(fields: [scheduleHeaderXid], references: [id], onDelete: Cascade)
occurenceDate DateTime @map("occurence_date") occurenceDate DateTime? @map("occurence_date")
slotXid Int @map("slot_xid") slotXid Int @map("slot_xid")
slot ScheduleDetails @relation(fields: [slotXid], references: [id], onDelete: Cascade) slot ScheduleDetails @relation(fields: [slotXid], references: [id], onDelete: Cascade)
cancellationReason String @map("cancellation_reason") cancellationReason String @map("cancellation_reason")

View File

@@ -492,4 +492,19 @@ getVenueDurationByAct:
events: events:
- httpApi: - httpApi:
path: /scheduling/get-venue-duration/{activityXid} path: /scheduling/get-venue-duration/{activityXid}
method: get method: get
cancelSlotForActivity:
handler: src/modules/host/handlers/Activity_Hub/Scheduling/cancelSlot.handler
memorySize: 512
package:
patterns:
- 'src/modules/host/handlers/Activity_Hub/Scheduling/**'
- ${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: /scheduling/cancel-slot
method: post

View File

@@ -13,6 +13,7 @@ const WeekdayEnum = z.enum([
export const scheduleActivity = z.object({ export const scheduleActivity = z.object({
activityXid: z.number(), activityXid: z.number(),
listNow: z.boolean(),
scheduleType: z.enum([ scheduleType: z.enum([
SCHEDULING_TYPE.ONCE, SCHEDULING_TYPE.ONCE,

View File

@@ -0,0 +1,83 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { SchedulingService } from '../../../services/activityScheduling.service';
import { HostService } from '../../../services/host.service';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { scheduleActivity } from '../../../../../common/utils/validation/host/createSchedulingOfAct.validation';
import { z } from 'zod';
const schedulingService = new SchedulingService(prismaClient);
const hostService = new HostService(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.');
}
// Authenticate user using the shared authForHost function
const userInfo = await verifyHostToken(token);
const hostId = userInfo.id;
if (Number.isNaN(hostId)) {
throw new ApiError(400, 'Host id must be a number');
}
const host = await hostService.getHostIdByUserXid(hostId);
if (!host) {
throw new ApiError(404, 'Host not found');
}
let body: { activityXid: number; venueXid: number; scheduleHeaderXid: number; slotXid?: number; cancellationReason?: string };
try {
body = event.body ? JSON.parse(event.body) : {};
} catch {
throw new ApiError(400, 'Invalid JSON payload');
}
const activity = await schedulingService.getActivityByXid(body.activityXid);
if (!activity) {
throw new ApiError(404, "Activity not found");
}
const venueExists = await schedulingService.getVenueFromVenueXid(
body.venueXid,
body.activityXid
);
if (!venueExists) {
throw new ApiError(
404,
`Venue not found for this activity`
)
}
await schedulingService.cancelSlotForActivity(
Number(body.scheduleHeaderXid),
Number(body.slotXid),
body.cancellationReason || 'No reason provided'
);
const result = await schedulingService.getVenueDurationByAct(Number(body.activityXid), Number(hostId));
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Scheduling details updated successfully',
data: result
}),
};
});

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
import { ACTIVITY_INTERNAL_STATUS, SCHEDULING_TYPE } from '../../../common/utils/constants/host.constant'; 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 ApiError from '../../../common/utils/helper/ApiError';
import { ScheduleActivityDTO } from '../../../common/utils/validation/host/createSchedulingOfAct.validation'; import { ScheduleActivityDTO } from '../../../common/utils/validation/host/createSchedulingOfAct.validation';
import { getPresignedUrl } from '../../../common/middlewares/aws/getPreSignedUrl'; import { getPresignedUrl } from '../../../common/middlewares/aws/getPreSignedUrl';
@@ -16,6 +16,7 @@ export class SchedulingService {
async addSchedulingForActivity(data: ScheduleActivityDTO) { async addSchedulingForActivity(data: ScheduleActivityDTO) {
const { const {
activityXid, activityXid,
listNow,
scheduleType, scheduleType,
dateRange, dateRange,
rules, rules,
@@ -25,6 +26,50 @@ export class SchedulingService {
} = data; } = data;
return this.prisma.$transaction(async (tx) => { return this.prisma.$transaction(async (tx) => {
const venueXids = venues.map(v => v.venueXid);
/* ----------------------------------
🧹 0⃣ DELETE OLD SCHEDULING (PER ACTIVITY + VENUES)
---------------------------------- */
const oldHeaders = await tx.scheduleHeader.findMany({
where: {
activityXid,
activityVenueXid: { in: venueXids },
isActive: true,
},
select: { id: true },
});
const headerIds = oldHeaders.map(h => h.id);
if (headerIds.length) {
// Delete in correct FK order
await tx.cancellations.deleteMany({
where: { scheduleHeaderXid: { in: headerIds } },
});
await tx.scheduleDetails.deleteMany({
where: { scheduleHeaderXid: { in: headerIds } },
});
await tx.scheduleOccurences.deleteMany({
where: { scheduleHeaderXid: { in: headerIds } },
});
await tx.scheduleRecurrence.deleteMany({
where: { scheduleHeaderXid: { in: headerIds } },
});
await tx.scheduleHeader.deleteMany({
where: { id: { in: headerIds } },
});
}
/* ----------------------------------
1⃣ CREATE NEW SCHEDULING
---------------------------------- */
const createdHeaders: number[] = []; const createdHeaders: number[] = [];
for (const venue of venues) { for (const venue of venues) {
@@ -92,6 +137,18 @@ 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 }; return { success: true, scheduleHeaderIds: createdHeaders };
@@ -293,15 +350,171 @@ export class SchedulingService {
select: { select: {
id: true, id: true,
activityDurationMins: true, activityDurationMins: true,
activityTitle: true,
activityRefNumber: true,
ActivityVenues: { ActivityVenues: {
select: { select: {
id: true, id: true,
venueName: true, venueName: true,
venueLabel: true, venueLabel: true,
ScheduleHeader: {
select: {
id: true,
scheduleType: true,
startDate: true,
endDate: true,
earlyCheckInMins: true,
bookingCutOffMins: true,
ScheduleDetails: {
select: {
id: true,
occurenceDate: true,
weekDay: true,
dayOfMonth: true,
startTime: true,
endTime: true,
}
},
Cancellations: {
select: {
id: true,
slotXid: true,
cancellationReason: true,
slot: {
select: {
id: true,
occurenceDate: true,
startTime: true,
endTime: true,
weekDay: true,
dayOfMonth: true,
}
}
}
},
scheduleOccurences: {
select: {
id: true,
occurenceDate: true
}
},
scheduleRecurrences: {
select: {
id: true,
weekDay: true,
dayOfMonth: true
}
}
}
}
}
},
ActivitiesMedia: {
select: {
id: true,
mediaFileName: true,
mediaType: true,
} }
} }
} }
}) })
if (!result) return null;
if (result.ActivitiesMedia?.length) {
for (const media of result.ActivitiesMedia) {
if (!media.mediaFileName) continue;
const key = media.mediaFileName.startsWith('http')
? media.mediaFileName.split('.com/')[1]
: media.mediaFileName;
(media as any).presignedUrl = await getPresignedUrl(bucket, key);
}
}
for (const venue of result.ActivityVenues ?? []) {
for (const header of venue.ScheduleHeader ?? []) {
/* -------------------------------
🚫 SLOT CANCELLATION FLAG
-------------------------------- */
const cancelledSlotIds = new Set(
header.Cancellations?.map(c => c.slotXid)
);
for (const slot of header.ScheduleDetails ?? []) {
(slot as any).isCancelled = cancelledSlotIds.has(slot.id);
}
/* -------------------------------
📅 FRONTEND FRIENDLY META
-------------------------------- */
// WEEKLY → send weekdays
if (header.scheduleType === 'WEEKLY') {
(header as any).scheduleDays = [
...new Set(
header.scheduleRecurrences
?.map(r => r.weekDay)
.filter(Boolean)
),
];
}
// MONTHLY → send dates (131)
if (header.scheduleType === 'MONTHLY') {
(header as any).scheduleDates = [
...new Set(
header.scheduleRecurrences
?.map(r => r.dayOfMonth)
.filter(Boolean)
),
];
}
// CUSTOM / ONCE → send exact dates
if (
header.scheduleType === 'CUSTOM' ||
header.scheduleType === 'ONCE'
) {
(header as any).scheduleDates = [
...new Set(
header.scheduleOccurences
?.map(o =>
o.occurenceDate
?.toISOString()
.split('T')[0]
)
.filter(Boolean)
),
];
}
}
}
(result as any).availableScheduleTypes = [
'ONCE',
'WEEKLY',
'MONTHLY',
'CUSTOM',
];
return result; return result;
} }
async cancelSlotForActivity(
scheduleHeaderXid: number,
slotXid?: number,
cancellationReason?: string
){
return await this.prisma.cancellations.create({
data: {
scheduleHeaderXid,
slotXid,
cancellationReason
}
})
}
} }