fixed the issues of the saveuseritinerary file

This commit is contained in:
2026-04-02 16:03:12 +05:30
parent d32915c865
commit e22c37bc65

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import { Prisma, PrismaClient } from '@prisma/client';
import { getPresignedUrl } from '../../../common/middlewares/aws/getPreSignedUrl';
import ApiError from '../../../common/utils/helper/ApiError';
import {
@@ -144,6 +144,18 @@ const parseTimeValue = (value: string) => {
return { hours, minutes, seconds };
};
const normalizeTimeValue = (value: string) => {
const parsed = parseTimeValue(value);
if (!parsed) {
return null;
}
return `${String(parsed.hours).padStart(2, '0')}:${String(
parsed.minutes,
).padStart(2, '0')}:${String(parsed.seconds).padStart(2, '0')}`;
};
const combineDateAndTime = (dateValue: string | Date, timeValue: string) => {
const date = parseDateValue(dateValue);
const time = parseTimeValue(timeValue);
@@ -577,29 +589,31 @@ export class ItineraryService {
`${itineraryType} must fall inside the itinerary date range.`,
);
}
const customActivityData: Prisma.ItineraryActivitiesCreateInput = {
itineraryHeader: {
connect: { id: itineraryHeader.id },
},
itineraryType,
occurenceDate: startOfDay(customStartDateTime),
startTime: activityItem.selectedStartTime,
endTime: activityItem.selectedEndTime,
endDate: customEndDateTime,
locationLat: activityItem.locationLat ?? null,
locationLong: activityItem.locationLong ?? null,
locationAddress:
(activityItem.locationAddress as Prisma.InputJsonValue | null) ??
null,
travelMode: activityItem.modeOfTravel,
kmForNextPoint: activityItem.kmForNextPoint,
timeForNextPointMins: activityItem.travelTimeBetweenPointsMins,
paxCount: activityItem.paxCount ?? 1,
totalAmount: activityItem.totalAmount ?? null,
bookingStatus: 'pending',
isActive: true,
};
return tx.itineraryActivities.create({
data: {
itineraryHeaderXid: itineraryHeader.id,
itineraryType,
activityXid: null,
scheduledHeaderXid: null,
occurenceDate: startOfDay(customStartDateTime),
startTime: activityItem.selectedStartTime,
endTime: activityItem.selectedEndTime,
endDate: customEndDateTime,
venueXid: null,
locationLat: activityItem.locationLat ?? null,
locationLong: activityItem.locationLong ?? null,
locationAddress: (activityItem.locationAddress as string | null) ?? null,
travelMode: activityItem.modeOfTravel,
kmForNextPoint: activityItem.kmForNextPoint,
timeForNextPointMins: activityItem.travelTimeBetweenPointsMins,
paxCount: activityItem.paxCount ?? 1,
totalAmount: activityItem.totalAmount ?? null,
bookingStatus: 'pending',
isActive: true,
},
data: customActivityData,
select: {
id: true,
itineraryType: true,
@@ -718,7 +732,17 @@ export class ItineraryService {
}).filter(Boolean) as string[],
);
const candidateSlots = scheduleHeader.ScheduleDetails.flatMap((slot) =>
const requestedOccurrenceDate = activityItem.occurenceDate
? formatDateKey(parseDateValue(activityItem.occurenceDate))
: null;
const requestedStartTime = activityItem.selectedStartTime
? normalizeTimeValue(activityItem.selectedStartTime)
: null;
const requestedEndTime = activityItem.selectedEndTime
? normalizeTimeValue(activityItem.selectedEndTime)
: null;
const expandedSlots = scheduleHeader.ScheduleDetails.flatMap((slot) =>
getUniqueDatesForScheduleDetail(
{
occurenceDate: slot.occurenceDate,
@@ -727,72 +751,128 @@ export class ItineraryService {
},
scheduleRangeStart,
scheduleRangeEnd,
)
.map((slotDate) => {
const slotStart = combineDateAndTime(slotDate, slot.startTime);
if (!slotStart) {
return null;
}
const slotEnd = scheduleHeader.activity.activityDurationMins
).map((slotDate) => {
const slotStart = combineDateAndTime(slotDate, slot.startTime);
const slotEnd = slotStart
? combineDateAndTime(slotDate, slot.endTime) ??
(scheduleHeader.activity.activityDurationMins
? addMinutes(
slotStart,
scheduleHeader.activity.activityDurationMins,
)
: combineDateAndTime(slotDate, slot.endTime);
: null)
: null;
if (!slotEnd) {
return null;
}
const normalizedSlotDate = formatDateKey(slotDate);
const normalizedSlotStartTime = normalizeTimeValue(slot.startTime);
const normalizedSlotEndTime = normalizeTimeValue(slot.endTime);
const cancellationKey = `${normalizedSlotDate}|${slot.startTime}|${slot.endTime}`;
const cancellationKey = `${formatDateKey(slotDate)}|${slot.startTime}|${slot.endTime}`;
const mismatchReasons: string[] = [];
if (cancelledSlots.has(cancellationKey)) {
return null;
}
if (!slotStart) {
mismatchReasons.push('invalid_slot_start_time');
}
if (!slotEnd) {
mismatchReasons.push('invalid_slot_end_time');
}
if (slotStart && slotEnd) {
if (
slotStart < itineraryStartDateTime ||
slotEnd > itineraryEndDateTime
) {
return null;
mismatchReasons.push('outside_itinerary_window');
}
}
if (
activityItem.occurenceDate &&
formatDateKey(slotDate) !==
formatDateKey(parseDateValue(activityItem.occurenceDate))
) {
return null;
}
if (cancelledSlots.has(cancellationKey)) {
mismatchReasons.push('slot_cancelled');
}
if (
activityItem.selectedStartTime &&
slot.startTime !== activityItem.selectedStartTime
) {
return null;
}
if (
requestedOccurrenceDate &&
normalizedSlotDate !== requestedOccurrenceDate
) {
mismatchReasons.push('occurrence_date_mismatch');
}
if (
activityItem.selectedEndTime &&
slot.endTime !== activityItem.selectedEndTime
) {
return null;
}
if (
requestedStartTime &&
normalizedSlotStartTime !== requestedStartTime
) {
mismatchReasons.push('start_time_mismatch');
}
return {
slotId: slot.id,
occurenceDate: startOfDay(slotDate),
startTime: slot.startTime,
endTime: slot.endTime,
endDate: slotEnd,
};
})
.filter(Boolean),
if (
requestedEndTime &&
normalizedSlotEndTime !== requestedEndTime
) {
mismatchReasons.push('end_time_mismatch');
}
return {
slotId: slot.id,
occurenceDate: startOfDay(slotDate),
startTime: slot.startTime,
endTime: slot.endTime,
endDate: slotEnd,
debug: {
slotDate: normalizedSlotDate,
normalizedStartTime: normalizedSlotStartTime,
normalizedEndTime: normalizedSlotEndTime,
mismatchReasons,
},
};
}),
);
const candidateSlots = expandedSlots
.filter((slot) => slot.endDate && slot.debug.mismatchReasons.length === 0)
.map(({ debug, ...slot }) => slot);
if (!candidateSlots.length) {
if (
requestedOccurrenceDate ||
requestedStartTime ||
requestedEndTime
) {
const availableSlots = expandedSlots
.filter(
(slot) =>
slot.endDate &&
!slot.debug.mismatchReasons.includes('outside_itinerary_window') &&
!slot.debug.mismatchReasons.includes('slot_cancelled'),
)
.map((slot) => ({
slotId: slot.slotId,
occurenceDate: formatDateKey(slot.occurenceDate),
startTime: slot.startTime,
endTime: slot.endTime,
}));
throw new ApiError(
400,
`Requested slot does not exist for activity ${activityItem.activityXid}. Please choose a valid occurenceDate/startTime/endTime combination.`,
[],
true,
undefined,
undefined,
{
activityXid: activityItem.activityXid,
venueXid: activityItem.venueXid,
scheduleHeaderXid: activityItem.scheduleHeaderXid,
requestedSlot: {
occurenceDate: activityItem.occurenceDate ?? null,
selectedStartTime: activityItem.selectedStartTime ?? null,
selectedEndTime: activityItem.selectedEndTime ?? null,
},
availableSlots,
},
);
}
throw new ApiError(
400,
`No valid slot found for activity ${activityItem.activityXid} in the selected itinerary range.`,
@@ -808,37 +888,47 @@ export class ItineraryService {
const selectedSlot = candidateSlots[0]!;
return tx.itineraryActivities.create({
data: {
itineraryHeaderXid: itineraryHeader.id,
itineraryType,
activityXid: activityItem.activityXid,
scheduledHeaderXid: activityItem.scheduleHeaderXid,
occurenceDate: selectedSlot.occurenceDate,
startTime: selectedSlot.startTime,
endTime: selectedSlot.endTime,
endDate: selectedSlot.endDate,
venueXid: activityItem.venueXid,
locationLat:
activityItem.locationLat ??
scheduleHeader.activity.checkInLat ??
null,
locationLong:
activityItem.locationLong ??
scheduleHeader.activity.checkInLong ??
null,
locationAddress:
activityItem.locationAddress ??
(scheduleHeader.activity.checkInAddress as any) ??
undefined,
travelMode: activityItem.modeOfTravel,
kmForNextPoint: activityItem.kmForNextPoint,
timeForNextPointMins: activityItem.travelTimeBetweenPointsMins,
paxCount: activityItem.paxCount ?? 1,
totalAmount: activityItem.totalAmount ?? null,
bookingStatus: 'pending',
isActive: true,
const activityData: Prisma.ItineraryActivitiesCreateInput = {
itineraryHeader: {
connect: { id: itineraryHeader.id },
},
itineraryType,
activity: {
connect: { id: activityItem.activityXid },
},
scheduledHeader: {
connect: { id: activityItem.scheduleHeaderXid },
},
venue: {
connect: { id: activityItem.venueXid },
},
occurenceDate: selectedSlot.occurenceDate,
startTime: selectedSlot.startTime,
endTime: selectedSlot.endTime,
endDate: selectedSlot.endDate,
locationLat:
activityItem.locationLat ??
scheduleHeader.activity.checkInLat ??
null,
locationLong:
activityItem.locationLong ??
scheduleHeader.activity.checkInLong ??
null,
locationAddress:
(activityItem.locationAddress as Prisma.InputJsonValue | undefined) ??
((scheduleHeader.activity.checkInAddress as Prisma.InputJsonValue | null) ??
undefined),
travelMode: activityItem.modeOfTravel,
kmForNextPoint: activityItem.kmForNextPoint,
timeForNextPointMins: activityItem.travelTimeBetweenPointsMins,
paxCount: activityItem.paxCount ?? 1,
totalAmount: activityItem.totalAmount ?? null,
bookingStatus: 'pending',
isActive: true,
};
return tx.itineraryActivities.create({
data: activityData,
select: {
id: true,
activityXid: true,