diff --git a/apps/edr-passenger-api/src/modules/fleet/fleet.service.ts b/apps/edr-passenger-api/src/modules/fleet/fleet.service.ts index 0662da595..b7f4695a1 100644 --- a/apps/edr-passenger-api/src/modules/fleet/fleet.service.ts +++ b/apps/edr-passenger-api/src/modules/fleet/fleet.service.ts @@ -134,9 +134,28 @@ export class FleetService { } async deleteCoachType(id: string) { - const coachType = await this.prisma.coachType.findUnique({ where: { id } }); + const coachType = await this.prisma.coachType.findUnique({ + where: { id }, + include: { + coaches: true, + seatClasses: true, + }, + }); if (!coachType) throw new NotFoundException('Coach type not found'); + // Check for related records + if (coachType.coaches.length > 0) { + throw new BadRequestException( + `Cannot delete coach type. ${coachType.coaches.length} coach(es) are still using this coach type. Please reassign or delete the coaches first.` + ); + } + + if (coachType.seatClasses.length > 0) { + throw new BadRequestException( + `Cannot delete coach type. ${coachType.seatClasses.length} seat class(es) are still using this coach type. Please reassign or delete the seat classes first.` + ); + } + return this.prisma.coachType.delete({ where: { id } }); } @@ -183,9 +202,29 @@ export class FleetService { } async deleteClass(id: string) { - const seatClass = await this.prisma.seatClass.findUnique({ where: { id } }); + const seatClass = await this.prisma.seatClass.findUnique({ + where: { id }, + include: { + fareRules: true, + routeFareRules: true, + segmentFares: true, + }, + }); if (!seatClass) throw new NotFoundException('Seat class not found'); + // Check for related records + const relatedRecords = [ + ...seatClass.fareRules, + ...seatClass.routeFareRules, + ...seatClass.segmentFares, + ]; + + if (relatedRecords.length > 0) { + throw new BadRequestException( + `Cannot delete seat class. ${relatedRecords.length} fare rule(s) are still using this seat class. Please delete the fare rules first.` + ); + } + return this.prisma.seatClass.delete({ where: { id } }); } @@ -220,8 +259,21 @@ export class FleetService { } async deleteTrain(id: string) { - const train = await this.prisma.train.findUnique({ where: { id } }); + const train = await this.prisma.train.findUnique({ + where: { id }, + include: { + schedules: true, + }, + }); if (!train) throw new NotFoundException('Train not found'); + + // Check for active schedules + if (train.schedules.length > 0) { + throw new BadRequestException( + `Cannot delete train. This train has ${train.schedules.length} schedule(s). Please delete the schedules first.` + ); + } + return this.prisma.train.delete({ where: { id } }); } @@ -305,10 +357,53 @@ export class FleetService { } async deleteCoach(id: string) { - const coach = await this.prisma.coach.findUnique({ where: { id } }); + const coach = await this.prisma.coach.findUnique({ + where: { id }, + include: { + assignments: true, + seats: { + include: { + bookingSeats: true, + blocks: true, + ticketSeats: true, + }, + }, + }, + }); if (!coach) throw new NotFoundException('Coach not found'); - // Delete related seats first + // Check for active assignments + if (coach.assignments.length > 0) { + throw new BadRequestException( + `Cannot delete coach. This coach is assigned to ${coach.assignments.length} schedule(s). Please remove the assignments first.` + ); + } + + // Check for booked seats + const bookedSeats = coach.seats.filter(seat => seat.bookingSeats.length > 0); + if (bookedSeats.length > 0) { + throw new BadRequestException( + `Cannot delete coach. ${bookedSeats.length} seat(s) have active bookings. Please wait for bookings to complete or cancel them first.` + ); + } + + // Check for blocked seats + const blockedSeats = coach.seats.filter(seat => seat.blocks.length > 0); + if (blockedSeats.length > 0) { + throw new BadRequestException( + `Cannot delete coach. ${blockedSeats.length} seat(s) are blocked. Please unblock them first.` + ); + } + + // Check for tickets + const seatsWithTickets = coach.seats.filter(seat => seat.ticketSeats.length > 0); + if (seatsWithTickets.length > 0) { + throw new BadRequestException( + `Cannot delete coach. ${seatsWithTickets.length} seat(s) have issued tickets. Please wait for travel completion.` + ); + } + + // Delete related seats first (now safe to do) await this.prisma.seat.deleteMany({ where: { coachId: id } }); return this.prisma.coach.delete({ where: { id } }); diff --git a/apps/edr-passenger-api/src/modules/schedules/schedules.service.ts b/apps/edr-passenger-api/src/modules/schedules/schedules.service.ts index e816ca8e1..f7ef5487f 100644 --- a/apps/edr-passenger-api/src/modules/schedules/schedules.service.ts +++ b/apps/edr-passenger-api/src/modules/schedules/schedules.service.ts @@ -545,8 +545,13 @@ export class SchedulesService { }); } - if (dto.coaches && dto.coaches.length > 0) { - await this.assignCoaches(id, dto.coaches); + if (dto.coaches !== undefined) { + if (dto.coaches.length > 0) { + await this.assignCoaches(id, dto.coaches); + } else { + // Remove all coach assignments when empty array is sent + await this.prisma.coachAssignment.deleteMany({ where: { scheduleId: id } }); + } } return this.getSchedule(id); diff --git a/apps/edr-passenger-web/backoffice/src/app/schedules/page.tsx b/apps/edr-passenger-web/backoffice/src/app/schedules/page.tsx index e60de15c2..1207a01e4 100644 --- a/apps/edr-passenger-web/backoffice/src/app/schedules/page.tsx +++ b/apps/edr-passenger-web/backoffice/src/app/schedules/page.tsx @@ -204,14 +204,11 @@ export default function SchedulesPage() { departureAt: editForm.departureAt, arrivalAt: editForm.arrivalAt, status: editForm.status, - }; - - if (editForm.coachIds.length > 0) { - payload.coaches = editForm.coachIds.map((coachId: string, idx: number) => ({ + coaches: editForm.coachIds.map((coachId: string, idx: number) => ({ coachId, positionNumber: idx + 1, - })); - } + })), + }; await updateScheduleMutation.mutateAsync({ id: editingSchedule.id, @@ -327,14 +324,20 @@ export default function SchedulesPage() { ), }, { - key: 'originStation.name', - label: 'From', - render: (schedule: Schedule) => {schedule.originStation?.name}, - }, - { - key: 'destinationStation.name', - label: 'To', - render: (schedule: Schedule) => {schedule.destinationStation?.name}, + key: 'route', + label: 'Route', + sortable: true, + render: (schedule: Schedule) => ( +
+ + {schedule.originStation?.name || 'Unknown'} + + + + {schedule.destinationStation?.name || 'Unknown'} + +
+ ), }, { key: 'departureAt', diff --git a/apps/edr-passenger-web/backoffice/src/app/seats/page.tsx b/apps/edr-passenger-web/backoffice/src/app/seats/page.tsx index ba561b24e..8b1ad4072 100644 --- a/apps/edr-passenger-web/backoffice/src/app/seats/page.tsx +++ b/apps/edr-passenger-web/backoffice/src/app/seats/page.tsx @@ -420,7 +420,12 @@ export default function SeatsPage() { const seats = (coach.seats || []).filter((s: any) => s.seatNumber); return seats.length > 0; }) - .sort((a: any, b: any) => (a.sequence || 0) - (b.sequence || 0)); + .sort((a: any, b: any) => { + // Try multiple sequence field possibilities + const seqA = a.positionNumber ?? a.sequence ?? a.coach?.sequence ?? 999; + const seqB = b.positionNumber ?? b.sequence ?? b.coach?.sequence ?? 999; + return seqA - seqB; + }); return (
@@ -460,8 +465,30 @@ export default function SeatsPage() {

Loading seats...

) : coachesWithSeats.length === 0 ? ( -
-

No coaches with seats found for this schedule

+
+
+ + +
+
+

No coaches with seats found for this schedule

+
) : (
@@ -526,7 +553,7 @@ export default function SeatsPage() { const seats = (coach.seats || []).filter((s: any) => s.seatNumber); const isExpanded = expandedCoaches.has(coach.id); const seatOrBedLabel = isBedCoach ? 'beds' : 'seats'; - const sequence = coachData?.sequence ?? coach?.sequence ?? index + 1; + const sequence = coach.positionNumber ?? coach.sequence ?? coachData?.sequence ?? index + 1; return (
diff --git a/apps/edr-passenger-web/backoffice/src/app/stations/page.tsx b/apps/edr-passenger-web/backoffice/src/app/stations/page.tsx index 6c512462e..97a1cf7ba 100644 --- a/apps/edr-passenger-web/backoffice/src/app/stations/page.tsx +++ b/apps/edr-passenger-web/backoffice/src/app/stations/page.tsx @@ -75,7 +75,6 @@ export default function StationsPage() { lat: parseFloat(formData.get('lat') as string) || null, lng: parseFloat(formData.get('lng') as string) || null, timezone: formData.get('timezone') as string, - distance: parseFloat(formData.get('distance') as string) || 0, sequence, isOperational: formData.get('isOperational') === 'true', }; @@ -136,13 +135,7 @@ export default function StationsPage() {
), }, - { - key: 'distance', - label: 'Distance (km)', - render: (station: any) => ( - {station.distance ? `${station.distance}` : '0'} - ), - }, + { key: 'isOperational', label: 'Status', @@ -346,18 +339,6 @@ export default function StationsPage() { ))}
-
- - -