mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 07:38:10 +00:00
Boarding, payment methods, journey direction on seat hold, and more updates
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Injectable, NotFoundException, ConflictException, BadRequestException } from '@nestjs/common';
|
||||
import { PrismaService } from '../../common/prisma.service';
|
||||
import { CreateRouteDto, AddRouteStopDto, UpdateRouteDto } from './routes.dto';
|
||||
import { DeleteOperationException } from '../../common/exceptions/delete-operation.exception';
|
||||
|
||||
@Injectable()
|
||||
export class RoutesService {
|
||||
@@ -93,8 +94,28 @@ export class RoutesService {
|
||||
}
|
||||
|
||||
async deleteRoute(id: string) {
|
||||
const route = await this.prisma.route.findUnique({ where: { id } });
|
||||
const route = await this.prisma.route.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
schedules: true,
|
||||
stops: true
|
||||
}
|
||||
});
|
||||
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 (constraints.length > 0) {
|
||||
throw new DeleteOperationException('Route', `${route.code} (${route.name})`, constraints);
|
||||
}
|
||||
|
||||
await this.prisma.route.delete({ where: { id } });
|
||||
return { deleted: true, id };
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import { PrismaService } from '../../common/prisma.service';
|
||||
import { RoutesService } from './routes.service';
|
||||
import { FareEngineService } from '../fare-engine/fare-engine.service';
|
||||
import { CreateScheduleDto, UpdateScheduleDto, CreateFareRuleDto, UpdateScheduleStatusDto, UpdateStopTimeDto, ListSchedulesDto, BulkCreateSchedulesDto } from './schedules.dto';
|
||||
import { DeleteOperationException } from '../../common/exceptions/delete-operation.exception';
|
||||
import { parseEthiopianTime, startOfDayEAT, startOfNextDayEAT } from '../../common/utils/timezone.utils';
|
||||
|
||||
@Injectable()
|
||||
export class SchedulesService {
|
||||
@@ -13,7 +15,7 @@ export class SchedulesService {
|
||||
) { }
|
||||
|
||||
async bulkGenerateSchedules(dto: BulkCreateSchedulesDto) {
|
||||
const startDate = new Date(dto.startDateTime);
|
||||
const startDate = parseEthiopianTime(dto.startDateTime);
|
||||
const endDate = new Date(startDate.getTime() + dto.forNextDays * 24 * 60 * 60 * 1000);
|
||||
const errors: string[] = [];
|
||||
const scheduleIds: string[] = [];
|
||||
@@ -66,8 +68,8 @@ export class SchedulesService {
|
||||
const where: any = {};
|
||||
|
||||
if (dto.date) {
|
||||
const date = new Date(dto.date);
|
||||
const nextDay = new Date(date.getTime() + 86_400_000);
|
||||
const date = parseEthiopianTime(dto.date);
|
||||
const nextDay = startOfNextDayEAT(date);
|
||||
where.departureAt = { gte: date, lt: nextDay };
|
||||
}
|
||||
if (dto.routeId) where.routeId = dto.routeId;
|
||||
@@ -93,8 +95,9 @@ export class SchedulesService {
|
||||
}
|
||||
|
||||
async createSchedule(dto: CreateScheduleDto) {
|
||||
const dep = new Date(dto.departureAt);
|
||||
const arr = new Date(dto.arrivalAt);
|
||||
// Parse dates in local Ethiopian time (EAT - UTC+3)
|
||||
const dep = parseEthiopianTime(dto.departureAt);
|
||||
const arr = parseEthiopianTime(dto.arrivalAt);
|
||||
if (arr <= dep) throw new BadRequestException('arrivalAt must be after departureAt');
|
||||
|
||||
const route = await this.prisma.route.findUnique({
|
||||
@@ -105,10 +108,9 @@ export class SchedulesService {
|
||||
if (!route.active) throw new BadRequestException('Route is not active');
|
||||
if (route.stops.length < 2) throw new BadRequestException('Route must have at least 2 stops');
|
||||
|
||||
const depDate = new Date(dep);
|
||||
depDate.setHours(0, 0, 0, 0);
|
||||
const nextDay = new Date(depDate);
|
||||
nextDay.setDate(nextDay.getDate() + 1);
|
||||
// Check for existing schedule on the same day (local Ethiopian time)
|
||||
const depDate = startOfDayEAT(dep);
|
||||
const nextDay = startOfNextDayEAT(dep);
|
||||
|
||||
const existingSchedule = await this.prisma.trainSchedule.findFirst({
|
||||
where: { trainId: dto.trainId, routeId: dto.routeId, departureAt: { gte: depDate, lt: nextDay } },
|
||||
@@ -240,8 +242,9 @@ export class SchedulesService {
|
||||
const schedule = await this.prisma.trainSchedule.findUnique({ where: { id } });
|
||||
if (!schedule) throw new NotFoundException('Schedule not found');
|
||||
|
||||
const dep = new Date(dto.departureAt);
|
||||
const arr = new Date(dto.arrivalAt);
|
||||
// Parse dates in local Ethiopian time (EAT - UTC+3)
|
||||
const dep = parseEthiopianTime(dto.departureAt);
|
||||
const arr = parseEthiopianTime(dto.arrivalAt);
|
||||
if (arr <= dep) throw new BadRequestException('arrivalAt must be after departureAt');
|
||||
|
||||
const route = await this.prisma.route.findUnique({
|
||||
@@ -308,16 +311,59 @@ export class SchedulesService {
|
||||
async deleteSchedule(id: string) {
|
||||
const schedule = await this.prisma.trainSchedule.findUnique({
|
||||
where: { id },
|
||||
include: { _count: { select: { bookings: true } } },
|
||||
include: {
|
||||
_count: { select: { bookings: true } },
|
||||
train: true,
|
||||
originStation: true,
|
||||
destinationStation: true
|
||||
},
|
||||
});
|
||||
if (!schedule) throw new NotFoundException('Schedule not found');
|
||||
|
||||
const constraints = [];
|
||||
if ((schedule as any)._count.bookings > 0) {
|
||||
throw new BadRequestException(
|
||||
`Cannot delete schedule. It has ${(schedule as any)._count.bookings} booking(s). Cancel all bookings before deleting.`,
|
||||
);
|
||||
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 } });
|
||||
await this.prisma.coachAssignment.deleteMany({ where: { scheduleId: id } });
|
||||
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 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 } }
|
||||
});
|
||||
}
|
||||
return this.prisma.trainSchedule.delete({ where: { id } });
|
||||
}
|
||||
|
||||
@@ -338,8 +384,8 @@ export class SchedulesService {
|
||||
return this.prisma.tripStopTime.update({
|
||||
where: { scheduleId_sequence: { scheduleId, sequence } },
|
||||
data: {
|
||||
plannedArrivalAt: dto.plannedArrivalAt ? new Date(dto.plannedArrivalAt) : undefined,
|
||||
plannedDepartureAt: dto.plannedDepartureAt ? new Date(dto.plannedDepartureAt) : undefined,
|
||||
plannedArrivalAt: dto.plannedArrivalAt ? parseEthiopianTime(dto.plannedArrivalAt) : undefined,
|
||||
plannedDepartureAt: dto.plannedDepartureAt ? parseEthiopianTime(dto.plannedDepartureAt) : undefined,
|
||||
status: dto.status,
|
||||
},
|
||||
include: { station: true },
|
||||
@@ -353,8 +399,8 @@ export class SchedulesService {
|
||||
...rest,
|
||||
tripId: scheduleId,
|
||||
nationality,
|
||||
validFrom: new Date(validFrom),
|
||||
validUntil: validUntil ? new Date(validUntil) : null,
|
||||
validFrom: parseEthiopianTime(validFrom),
|
||||
validUntil: validUntil ? parseEthiopianTime(validUntil) : null,
|
||||
},
|
||||
include: { seatClass: true },
|
||||
});
|
||||
@@ -371,8 +417,8 @@ export class SchedulesService {
|
||||
...rest,
|
||||
...(scheduleId !== undefined && { tripId: scheduleId }),
|
||||
...(nationality !== undefined && { nationality }),
|
||||
...(validFrom && { validFrom: new Date(validFrom) }),
|
||||
...(validUntil !== undefined && { validUntil: validUntil ? new Date(validUntil) : null }),
|
||||
...(validFrom && { validFrom: parseEthiopianTime(validFrom) }),
|
||||
...(validUntil !== undefined && { validUntil: validUntil ? parseEthiopianTime(validUntil) : null }),
|
||||
},
|
||||
include: { seatClass: true },
|
||||
});
|
||||
@@ -390,8 +436,8 @@ export class SchedulesService {
|
||||
return this.prisma.segmentFareRule.create({
|
||||
data: {
|
||||
...rest,
|
||||
validFrom: new Date(validFrom),
|
||||
validUntil: validUntil ? new Date(validUntil) : null,
|
||||
validFrom: parseEthiopianTime(validFrom),
|
||||
validUntil: validUntil ? parseEthiopianTime(validUntil) : null,
|
||||
},
|
||||
include: { seatClass: true, route: true },
|
||||
});
|
||||
@@ -415,8 +461,8 @@ export class SchedulesService {
|
||||
where: { id },
|
||||
data: {
|
||||
...rest,
|
||||
validFrom: validFrom ? new Date(validFrom) : undefined,
|
||||
validUntil: validUntil ? new Date(validUntil) : null,
|
||||
validFrom: validFrom ? parseEthiopianTime(validFrom) : undefined,
|
||||
validUntil: validUntil ? parseEthiopianTime(validUntil) : null,
|
||||
},
|
||||
include: { seatClass: true, route: true },
|
||||
});
|
||||
@@ -523,8 +569,8 @@ export class SchedulesService {
|
||||
const updateData: any = {};
|
||||
|
||||
if (dto.departureAt || dto.arrivalAt) {
|
||||
const dep = dto.departureAt ? new Date(dto.departureAt) : new Date(schedule.departureAt);
|
||||
const arr = dto.arrivalAt ? new Date(dto.arrivalAt) : new Date(schedule.arrivalAt);
|
||||
const dep = dto.departureAt ? parseEthiopianTime(dto.departureAt) : new Date(schedule.departureAt);
|
||||
const arr = dto.arrivalAt ? parseEthiopianTime(dto.arrivalAt) : new Date(schedule.arrivalAt);
|
||||
if (arr <= dep) throw new BadRequestException('Arrival time must be after departure time');
|
||||
updateData.departureAt = dep;
|
||||
updateData.arrivalAt = arr;
|
||||
|
||||
Reference in New Issue
Block a user