Merge branch 'dev' into tests

This commit is contained in:
mulish77
2026-07-27 14:59:00 +03:00
committed by GitHub
360 changed files with 39014 additions and 2448 deletions

View File

@@ -148,6 +148,20 @@ export class RoutesService {
travelMinutesToStop: s.travelMinutesToStop ?? null,
})),
});
// Propagate new stop timing to all future schedules on this route so that
// per-stop check-in cutoffs reflect the updated travelMinutesToStop values.
const futureSchedules = await this.prisma.trainSchedule.findMany({
where: { routeId: id, status: { in: ['SCHEDULED', 'BOARDING'] }, departureAt: { gt: new Date() } },
select: { id: true, departureAt: true, arrivalAt: true },
});
const stopsForTiming = dto.stops
.map(s => ({ sequence: s.sequence, distanceKm: s.distanceKm ?? null, travelMinutesToStop: s.travelMinutesToStop ?? null, checkinMinutesBefore: s.checkinMinutesBefore ?? null }))
.sort((a, b) => a.sequence - b.sequence);
for (const sched of futureSchedules) {
const times = computePlannedStopTimes({ id, stops: stopsForTiming }, new Date(sched.departureAt), new Date(sched.arrivalAt));
await this.applyRouteToSchedule(id, sched.id, Object.fromEntries(times.map(t => [t.sequence, t])));
}
}
await this.auditService.log({ action: 'UPDATE', entityType: 'Route', entityId: id, newData: { name: dto.name, active: dto.active } });

View File

@@ -154,6 +154,12 @@ export class SchedulesController {
@ApiQuery({ name: 'cascade', required: false, type: Boolean })
deleteSchedule(@Param('id') id: string, @Query('cascade') cascade?: string) { return this.service.deleteSchedule(id, cascade === 'true'); }
@Post(':id/recalculate-stops')
@PassengerStaff([PASSENGER_PERMS.schedules.manage, PASSENGER_PERMS.admin]) @ApiBearerAuth('IAM-auth')
@ApiOperation({ summary: 'Recompute TripStopTime records from current route travelMinutesToStop values' })
@ApiParam({ name: 'id', description: 'TrainSchedule UUID' })
recalculateStops(@Param('id') id: string) { return this.service.recalculateStopTimes(id); }
@Get(':id/stops')
@IsPublic()
@ApiOperation({ summary: 'List all stops for a schedule' })

View File

@@ -702,6 +702,24 @@ export class SchedulesService {
return { synced, errors };
}
async recalculateStopTimes(scheduleId: string) {
const schedule = await this.prisma.trainSchedule.findUnique({
where: { id: scheduleId },
include: { route: { include: { stops: { orderBy: { sequence: 'asc' } } } } },
});
if (!schedule) throw new NotFoundException('Schedule not found');
if (!schedule.routeId || !schedule.route) throw new BadRequestException('Schedule has no associated route');
const plannedTimes = computePlannedStopTimes(
schedule.route,
new Date(schedule.departureAt),
new Date(schedule.arrivalAt),
);
const plannedTimesMap = Object.fromEntries(plannedTimes.map(t => [t.sequence, t]));
await this.routesService.applyRouteToSchedule(schedule.routeId, scheduleId, plannedTimesMap);
return { recalculated: true, scheduleId, stopCount: plannedTimes.length };
}
async assignCoaches(scheduleId: string, coaches: Array<{ coachId: string; positionNumber: number }>) {
const schedule = await this.prisma.trainSchedule.findUnique({ where: { id: scheduleId } });
if (!schedule) throw new NotFoundException('Schedule not found');