Boarding, payment methods, journey direction on seat hold, and more updates

This commit is contained in:
Stephanos A
2026-06-29 08:44:38 +03:00
parent 81ae99cee3
commit c6e56d1c4f
65 changed files with 6437 additions and 1425 deletions

View File

@@ -2,6 +2,7 @@ import { Injectable, NotFoundException, BadRequestException } from '@nestjs/comm
import { PrismaService } from '../../common/prisma.service';
import { CreateTrainDto, CreateCoachDto, UpdateCoachDto, AssignCoachDto, ListCoachesDto, CreateCoachTypeDto, UpdateCoachTypeDto, CreateClassDto, UpdateClassDto, GenerateSeatMapDto } from './fleet.dto';
import { SeatKind } from '@prisma/client';
import { DeleteOperationException } from '../../common/exceptions/delete-operation.exception';
// Parses '2+2' → [2, 2], '2+2+2' → [2, 2, 2]
function parseArrangement(arrangement: string): number[] {
@@ -41,7 +42,7 @@ const DEFAULT_BEDS_PER_ROOM: Record<'ECONOMY_BED' | 'VIP_BED', number> = {
ECONOMY_BED: 6,
};
// Name-based fallback: checks if 'vip' is present for any bed/sleeper coach type
// Name-based fallback: checks if 'vip' is present for any bed coach type
function detectBedCategory(coachTypeName: string): BedCategory {
const name = coachTypeName.toLowerCase();
const isBed = name.includes('bed') || name.includes('berth') || name.includes('sleeper') || name.includes('couchette');
@@ -201,17 +202,25 @@ export class FleetService {
});
if (!coachType) throw new NotFoundException('Coach type not found');
// Check for related records
const constraints = [];
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.`
);
constraints.push({
entityName: 'coach',
count: coachType.coaches.length,
action: 'reassign' as const
});
}
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.`
);
constraints.push({
entityName: 'seat class',
count: coachType.seatClasses.length,
action: 'reassign' as const
});
}
if (constraints.length > 0) {
throw new DeleteOperationException('Coach Type', coachType.name, constraints);
}
return this.prisma.coachType.delete({ where: { id } });
@@ -273,17 +282,19 @@ export class FleetService {
});
if (!seatClass) throw new NotFoundException('Seat class not found');
// Check for related records
const relatedRecords = [
...seatClass.fareRules,
...seatClass.routeFareRules,
...seatClass.segmentFares,
];
const totalFareRules = seatClass.fareRules.length + seatClass.routeFareRules.length + seatClass.segmentFares.length;
const constraints = [];
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.`
);
if (totalFareRules > 0) {
constraints.push({
entityName: 'fare rule',
count: totalFareRules,
action: 'delete' as const
});
}
if (constraints.length > 0) {
throw new DeleteOperationException('Seat Class', seatClass.name, constraints);
}
return this.prisma.seatClass.delete({ where: { id } });
@@ -344,11 +355,20 @@ export class FleetService {
include: { schedules: true },
});
if (!train) throw new NotFoundException('Train not found');
const constraints = [];
if (train.schedules.length > 0) {
throw new BadRequestException(
`Cannot delete train. This train has ${train.schedules.length} schedule(s). Please delete the schedules first.`
);
constraints.push({
entityName: 'schedule',
count: train.schedules.length,
action: 'delete' as const
});
}
if (constraints.length > 0) {
throw new DeleteOperationException('Train', `${train.number} (${train.name})`, constraints);
}
return this.prisma.train.delete({ where: { id } });
}
@@ -460,45 +480,54 @@ export class FleetService {
include: {
bookingSeats: true,
blocks: true,
ticketSeats: true,
tickets: true,
},
},
},
});
if (!coach) throw new NotFoundException('Coach not found');
// 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.`
);
const constraints = [];
if ((coach as any).assignments.length > 0) {
constraints.push({
entityName: 'schedule assignment',
count: (coach as any).assignments.length,
action: 'reassign' as const
});
}
// Check for booked seats
const bookedSeats = coach.seats.filter(seat => seat.bookingSeats.length > 0);
const bookedSeats = (coach as any).seats.filter((seat: any) => 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.`
);
constraints.push({
entityName: 'booked seat',
count: bookedSeats.length,
action: 'complete' as const
});
}
// Check for blocked seats
const blockedSeats = coach.seats.filter(seat => seat.blocks.length > 0);
const blockedSeats = (coach as any).seats.filter((seat: any) => seat.blocks.length > 0);
if (blockedSeats.length > 0) {
throw new BadRequestException(
`Cannot delete coach. ${blockedSeats.length} seat(s) are blocked. Please unblock them first.`
);
constraints.push({
entityName: 'blocked seat',
count: blockedSeats.length,
action: 'delete' as const
});
}
// Check for tickets
const seatsWithTickets = coach.seats.filter(seat => seat.ticketSeats.length > 0);
const seatsWithTickets = (coach as any).seats.filter((seat: any) => seat.tickets.length > 0);
if (seatsWithTickets.length > 0) {
throw new BadRequestException(
`Cannot delete coach. ${seatsWithTickets.length} seat(s) have issued tickets. Please wait for travel completion.`
);
constraints.push({
entityName: 'seat with issued ticket',
count: seatsWithTickets.length,
action: 'complete' as const
});
}
if (constraints.length > 0) {
throw new DeleteOperationException('Coach', coach.number, constraints);
}
// Delete related seats first (now safe to do)
await this.prisma.seat.deleteMany({ where: { coachId: id } });
return this.prisma.coach.delete({ where: { id } });