Backoffice UAT results addressed, packages and other updates

This commit is contained in:
Stephanos A
2026-06-25 20:10:36 +03:00
parent 9e84a022df
commit cfbbd437e9
28 changed files with 1042 additions and 211 deletions

View File

@@ -153,6 +153,15 @@ export class FleetController {
return this.service.deleteTrain(id);
}
@Patch('trains/:id/restore')
@ApiOperation({ summary: 'Restore (reactivate) a deactivated train' })
@ApiParam({ name: 'id', description: 'Train UUID' })
@ApiResponse({ status: 200, description: 'Train restored' })
@ApiResponse({ status: 404, description: 'Train not found' })
restoreTrain(@Param('id') id: string) {
return this.service.restoreTrain(id);
}
// Coach Endpoints
@Get('coaches')
@ApiOperation({ summary: 'List coaches with seat status summary' })

View File

@@ -7,6 +7,7 @@ export class CreateTrainDto {
@ApiPropertyOptional({ example: 'EDR', description: 'Operator ID (defaults to op_edr)' }) @IsOptional() @IsString() operatorId?: string;
@ApiPropertyOptional({ example: 'Ethiopian-Djibouti Railway' }) @IsOptional() @IsString() operatorName?: string;
@ApiPropertyOptional({ example: 'Addis-Djibouti Express' }) @IsOptional() @IsString() description?: string;
@ApiPropertyOptional({ example: true, description: 'Whether the train is active' }) @IsOptional() @IsBoolean() isActive?: boolean;
}
export class CreateCoachDto {
@@ -26,6 +27,8 @@ export class CreateCoachDto {
description: 'Beds per compartment/room. Must be even (split equally left/right). Defaults: VIP_BED=4, ECONOMY_BED=6. Only applies when bedCategory is set.',
})
@IsOptional() @IsInt() bedsPerRoom?: number;
@ApiPropertyOptional({ example: 1, description: 'Sequence number for ordering coaches in the train' })
@IsOptional() @IsInt() sequence?: number;
}
export class UpdateCoachDto extends PartialType(OmitType(CreateCoachDto, ['number'] as const)) {
@@ -66,6 +69,9 @@ export class CreateClassDto {
@ApiProperty({ example: 'Economy' }) @IsString() name: string;
@IsOptional() @IsString() description?: string;
@ApiProperty({ example: 5000 }) @IsInt() baseFareMinor: number;
@ApiPropertyOptional({ example: 0 }) @IsOptional() @IsInt() premiumMinor?: number;
@ApiPropertyOptional({ example: 0 }) @IsOptional() @IsInt() insuranceFeeMinor?: number;
@ApiPropertyOptional({ example: true }) @IsOptional() @IsBoolean() isActive?: boolean;
}
export class UpdateClassDto {

View File

@@ -224,6 +224,9 @@ export class FleetService {
name: dto.name,
description: dto.description,
baseFareMinor: dto.baseFareMinor,
...(dto.premiumMinor !== undefined && { premiumMinor: dto.premiumMinor }),
...(dto.insuranceFeeMinor !== undefined && { insuranceFeeMinor: dto.insuranceFeeMinor }),
...(dto.isActive !== undefined && { isActive: dto.isActive }),
},
});
}
@@ -307,34 +310,54 @@ export class FleetService {
}
createTrain(dto: CreateTrainDto) {
return this.prisma.train.create({ data: dto });
return this.prisma.train.create({
data: {
number: dto.number,
name: dto.name,
operatorId: dto.operatorId,
operatorName: dto.operatorName,
description: dto.description,
isActive: dto.isActive ?? true,
},
});
}
async updateTrain(id: string, dto: CreateTrainDto) {
const train = await this.prisma.train.findUnique({ where: { id } });
if (!train) throw new NotFoundException('Train not found');
return this.prisma.train.update({ where: { id }, data: dto });
return this.prisma.train.update({
where: { id },
data: {
number: dto.number,
name: dto.name,
operatorId: dto.operatorId,
operatorName: dto.operatorName,
description: dto.description,
...(dto.isActive !== undefined && { isActive: dto.isActive }),
},
});
}
async deleteTrain(id: string) {
const train = await this.prisma.train.findUnique({
where: { id },
include: {
schedules: true,
},
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 } });
}
async restoreTrain(id: string) {
const train = await this.prisma.train.findUnique({ where: { id } });
if (!train) throw new NotFoundException('Train not found');
return this.prisma.train.update({ where: { id }, data: { isActive: true } });
}
async getCoach(id: string) {
const coach = await this.prisma.coach.findUnique({
where: { id },
@@ -370,18 +393,20 @@ export class FleetService {
throw new BadRequestException(`Invalid arrangement format "${dto.arrangement}". Use e.g. "2+2"`);
}
// Get the next sequence number for this coach type
const lastCoach = await this.prisma.coach.findFirst({
where: { coachTypeId: dto.coachTypeId },
orderBy: { sequence: 'desc' },
});
const nextSequence = (lastCoach?.sequence ?? 0) + 1;
// Use user-provided sequence or auto-assign the next one
let resolvedSequence = dto.sequence;
if (resolvedSequence === undefined || resolvedSequence === null) {
const lastCoach = await this.prisma.coach.findFirst({
orderBy: { sequence: 'desc' },
});
resolvedSequence = (lastCoach?.sequence ?? 0) + 1;
}
const coach = await this.prisma.coach.create({
data: {
coachTypeId: dto.coachTypeId,
number: dto.number,
sequence: nextSequence,
sequence: resolvedSequence,
arrangement: dto.arrangement,
capacity: dto.capacity,
status: dto.status || 'ACTIVE',