fixed the issues of the saveuseritinerary file
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user