Package booking new rules update, cascade delete option updates

This commit is contained in:
Stephanos A
2026-07-06 22:12:04 +03:00
parent f52d7442ac
commit 36055d0d82
31 changed files with 777 additions and 339 deletions

View File

@@ -51,9 +51,10 @@ Route stops carry distanceKm for fare-by-distance calculations.`,
@UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Delete a route' })
@ApiParam({ name: 'id', description: 'Route UUID' })
@ApiQuery({ name: 'cascade', required: false, type: Boolean, description: 'Force delete with all related data' })
@ApiResponse({ status: 200, description: 'Route deleted' })
@ApiResponse({ status: 404, description: 'Route not found' })
deleteRoute(@Param('id') id: string) { return this.service.deleteRoute(id); }
deleteRoute(@Param('id') id: string, @Query('cascade') cascade?: string) { return this.service.deleteRoute(id, cascade === 'true'); }
// ── Route Stops ────────────────────────────────────────────────────────────

View File

@@ -110,7 +110,7 @@ export class RoutesService {
});
}
async deleteRoute(id: string) {
async deleteRoute(id: string, cascade = false) {
const route = await this.prisma.route.findUnique({
where: { id },
include: {
@@ -120,19 +120,77 @@ export class RoutesService {
});
if (!route) throw new NotFoundException('Route not found');
const constraints = [];
if (route.schedules.length > 0) {
constraints.push({
entityName: 'schedule',
count: route.schedules.length,
action: 'delete' as const
});
if (!cascade) {
const constraints = [];
if (route.schedules.length > 0) {
constraints.push({
entityName: 'schedule',
count: route.schedules.length,
action: 'delete' as const
});
}
if (constraints.length > 0) {
throw new DeleteOperationException('Route', `${route.code} (${route.name})`, constraints);
}
}
if (constraints.length > 0) {
throw new DeleteOperationException('Route', `${route.code} (${route.name})`, constraints);
if (cascade) {
const scheduleIds = route.schedules.map(s => s.id);
if (scheduleIds.length > 0) {
await this.prisma.tripStopTime.deleteMany({ where: { scheduleId: { in: scheduleIds } } });
await this.prisma.coachAssignment.deleteMany({ where: { scheduleId: { in: scheduleIds } } });
await this.prisma.tripLiveStatus.deleteMany({ where: { scheduleId: { in: scheduleIds } } });
await this.prisma.menuItem.deleteMany({ where: { scheduleId: { in: scheduleIds } } });
await this.prisma.journeySegment.deleteMany({ where: { scheduleId: { in: scheduleIds } } });
const bookings = await this.prisma.booking.findMany({
where: { OR: [{ scheduleId: { in: scheduleIds } }, { returnScheduleId: { in: scheduleIds } }] },
select: { id: true },
});
if (bookings.length > 0) {
const bookingIds = bookings.map(b => b.id);
const tickets = await this.prisma.ticket.findMany({ where: { bookingId: { in: bookingIds } }, select: { id: true } });
if (tickets.length > 0) {
await this.prisma.gateValidationLog.deleteMany({ where: { ticketId: { in: tickets.map(t => t.id) } } });
}
await this.prisma.ticket.deleteMany({ where: { bookingId: { in: bookingIds } } });
const foodOrders = await this.prisma.foodOrder.findMany({ where: { bookingId: { in: bookingIds } }, select: { id: true } });
if (foodOrders.length > 0) {
await this.prisma.foodOrderItem.deleteMany({ where: { orderId: { in: foodOrders.map(o => o.id) } } });
}
await this.prisma.foodOrder.deleteMany({ where: { bookingId: { in: bookingIds } } });
const paymentIntents = await this.prisma.paymentIntent.findMany({ where: { bookingId: { in: bookingIds } }, select: { id: true } });
if (paymentIntents.length > 0) {
await this.prisma.paymentRefund.deleteMany({ where: { paymentIntentId: { in: paymentIntents.map(p => p.id) } } });
}
await this.prisma.paymentIntent.deleteMany({ where: { bookingId: { in: bookingIds } } });
await this.prisma.bookingSeat.deleteMany({ where: { bookingId: { in: bookingIds } } });
await this.prisma.agentBooking.deleteMany({ where: { bookingId: { in: bookingIds } } });
await this.prisma.bookingModification.deleteMany({ where: { bookingId: { in: bookingIds } } });
await this.prisma.bookingCancellation.deleteMany({ where: { bookingId: { in: bookingIds } } });
await this.prisma.baggageBooking.deleteMany({ where: { bookingId: { in: bookingIds } } });
await this.prisma.excessBaggageCharge.deleteMany({ where: { bookingId: { in: bookingIds } } });
await this.prisma.journey.deleteMany({ where: { bookingId: { in: bookingIds } } });
await this.prisma.booking.deleteMany({ where: { id: { in: bookingIds } } });
}
const packages = await this.prisma.travelPackage.findMany({
where: { OR: [{ outboundScheduleId: { in: scheduleIds } }, { returnScheduleId: { in: scheduleIds } }] },
select: { id: true },
});
if (packages.length > 0) {
const packageIds = packages.map(p => p.id);
await this.prisma.packagePriceTier.deleteMany({ where: { packageId: { in: packageIds } } });
await this.prisma.travelPackage.deleteMany({ where: { id: { in: packageIds } } });
}
await this.prisma.trainSchedule.deleteMany({ where: { id: { in: scheduleIds } } });
}
await this.prisma.routeStop.deleteMany({ where: { routeId: id } });
await this.prisma.routeCoachTemplate.deleteMany({ where: { routeId: id } });
}
await this.prisma.route.delete({ where: { id } });
return { deleted: true, id };
}

View File

@@ -113,7 +113,8 @@ export class SchedulesController {
@UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Delete a schedule' })
@ApiParam({ name: 'id', description: 'TrainSchedule UUID' })
deleteSchedule(@Param('id') id: string) { return this.service.deleteSchedule(id); }
@ApiQuery({ name: 'cascade', required: false, type: Boolean })
deleteSchedule(@Param('id') id: string, @Query('cascade') cascade?: string) { return this.service.deleteSchedule(id, cascade === 'true'); }
@Get(':id/stops')
@IsPublic()

View File

@@ -332,7 +332,7 @@ export class SchedulesService {
return this.prisma.trainSchedule.update({ where: { id }, data: { status: dto.status } });
}
async deleteSchedule(id: string) {
async deleteSchedule(id: string, cascade = false) {
const schedule = await this.prisma.trainSchedule.findUnique({
where: { id },
include: {
@@ -344,18 +344,20 @@ export class SchedulesService {
});
if (!schedule) throw new NotFoundException('Schedule not found');
const constraints = [];
if ((schedule as any)._count.bookings > 0) {
constraints.push({
entityName: 'booking',
count: (schedule as any)._count.bookings,
action: 'cancel' as const
});
}
if (constraints.length > 0) {
const scheduleName = `${schedule.train.number} (${schedule.originStation.name}${schedule.destinationStation.name})`;
throw new DeleteOperationException('Schedule', scheduleName, constraints);
if (!cascade) {
const constraints = [];
if ((schedule as any)._count.bookings > 0) {
constraints.push({
entityName: 'booking',
count: (schedule as any)._count.bookings,
action: 'cancel' as const
});
}
if (constraints.length > 0) {
const scheduleName = `${schedule.train.number} (${schedule.originStation.name}${schedule.destinationStation.name})`;
throw new DeleteOperationException('Schedule', scheduleName, constraints);
}
}
await this.prisma.tripStopTime.deleteMany({ where: { scheduleId: id } });
@@ -363,30 +365,49 @@ export class SchedulesService {
await this.prisma.tripLiveStatus.deleteMany({ where: { scheduleId: id } });
await this.prisma.menuItem.deleteMany({ where: { scheduleId: id } });
await this.prisma.journeySegment.deleteMany({ where: { scheduleId: id } });
// Delete travel packages that reference this schedule (required fields cannot be nulled)
// First get packages that reference this schedule
const packagesToDelete = await this.prisma.travelPackage.findMany({
where: {
OR: [
{ outboundScheduleId: id },
{ returnScheduleId: id }
]
},
select: { id: true }
// Delete bookings and all their children
const bookings = await this.prisma.booking.findMany({
where: { OR: [{ scheduleId: id }, { returnScheduleId: id }] },
select: { id: true },
});
if (bookings.length > 0) {
const bookingIds = bookings.map(b => b.id);
// Leaf tables first
const tickets = await this.prisma.ticket.findMany({ where: { bookingId: { in: bookingIds } }, select: { id: true } });
if (tickets.length > 0) {
await this.prisma.gateValidationLog.deleteMany({ where: { ticketId: { in: tickets.map(t => t.id) } } });
}
await this.prisma.ticket.deleteMany({ where: { bookingId: { in: bookingIds } } });
const foodOrders = await this.prisma.foodOrder.findMany({ where: { bookingId: { in: bookingIds } }, select: { id: true } });
if (foodOrders.length > 0) {
await this.prisma.foodOrderItem.deleteMany({ where: { orderId: { in: foodOrders.map(o => o.id) } } });
}
await this.prisma.foodOrder.deleteMany({ where: { bookingId: { in: bookingIds } } });
const paymentIntents = await this.prisma.paymentIntent.findMany({ where: { bookingId: { in: bookingIds } }, select: { id: true } });
if (paymentIntents.length > 0) {
await this.prisma.paymentRefund.deleteMany({ where: { paymentIntentId: { in: paymentIntents.map(p => p.id) } } });
}
await this.prisma.paymentIntent.deleteMany({ where: { bookingId: { in: bookingIds } } });
await this.prisma.bookingSeat.deleteMany({ where: { bookingId: { in: bookingIds } } });
await this.prisma.agentBooking.deleteMany({ where: { bookingId: { in: bookingIds } } });
await this.prisma.bookingModification.deleteMany({ where: { bookingId: { in: bookingIds } } });
await this.prisma.bookingCancellation.deleteMany({ where: { bookingId: { in: bookingIds } } });
await this.prisma.baggageBooking.deleteMany({ where: { bookingId: { in: bookingIds } } });
await this.prisma.excessBaggageCharge.deleteMany({ where: { bookingId: { in: bookingIds } } });
await this.prisma.journey.deleteMany({ where: { bookingId: { in: bookingIds } } });
await this.prisma.booking.deleteMany({ where: { id: { in: bookingIds } } });
}
// Delete travel packages that reference this schedule
const packagesToDelete = await this.prisma.travelPackage.findMany({
where: { OR: [{ outboundScheduleId: id }, { returnScheduleId: id }] },
select: { id: true },
});
// Delete price tiers first (they have foreign key to packages)
if (packagesToDelete.length > 0) {
const packageIds = packagesToDelete.map(p => p.id);
await this.prisma.packagePriceTier.deleteMany({
where: { packageId: { in: packageIds } }
});
// Now delete the packages
await this.prisma.travelPackage.deleteMany({
where: { id: { in: packageIds } }
});
await this.prisma.packagePriceTier.deleteMany({ where: { packageId: { in: packageIds } } });
await this.prisma.travelPackage.deleteMany({ where: { id: { in: packageIds } } });
}
return this.prisma.trainSchedule.delete({ where: { id } });
}