Files
vip-coordinator/backend/src/vehicles/vehicles.service.ts
kyle f2b3f34a72 refactor: code efficiency improvements (Issues #9-13, #15, #17-20)
Backend:
- Extract shared hard-delete authorization utility (#9)
- Extract Prisma include constants per entity (#11)
- Fix N+1 query pattern in events findAll (#12)
- Extract shared date utility functions (#13)
- Move vehicle utilization filtering to DB query (#15)
- Add ParseBooleanPipe for query params
- Add CurrentDriver decorator + ResolveDriverInterceptor (#20)

Frontend:
- Extract shared form utilities (toDatetimeLocal) and enum labels (#17)
- Replace browser confirm() with styled ConfirmModal (#18)
- Add centralized query-keys.ts constants (#19)
- Clean up unused imports, add useMemo where needed (#19)
- Standardize filter button styling across list pages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 16:07:19 +01:00

136 lines
3.6 KiB
TypeScript

import { Injectable, NotFoundException, Logger } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateVehicleDto, UpdateVehicleDto } from './dto';
import { executeHardDelete } from '../common/utils';
@Injectable()
export class VehiclesService {
private readonly logger = new Logger(VehiclesService.name);
private readonly vehicleInclude = {
currentDriver: true,
events: {
where: { deletedAt: null },
include: { driver: true, vehicle: true },
orderBy: { startTime: 'asc' as const },
},
} as const;
constructor(private prisma: PrismaService) {}
async create(createVehicleDto: CreateVehicleDto) {
this.logger.log(`Creating vehicle: ${createVehicleDto.name}`);
return this.prisma.vehicle.create({
data: createVehicleDto,
include: this.vehicleInclude,
});
}
async findAll() {
return this.prisma.vehicle.findMany({
where: { deletedAt: null },
include: this.vehicleInclude,
orderBy: { name: 'asc' },
});
}
async findAvailable() {
return this.prisma.vehicle.findMany({
where: {
deletedAt: null,
status: 'AVAILABLE',
},
include: {
currentDriver: true,
},
orderBy: { name: 'asc' },
});
}
async findOne(id: string) {
const vehicle = await this.prisma.vehicle.findFirst({
where: { id, deletedAt: null },
include: this.vehicleInclude,
});
if (!vehicle) {
throw new NotFoundException(`Vehicle with ID ${id} not found`);
}
return vehicle;
}
async update(id: string, updateVehicleDto: UpdateVehicleDto) {
const vehicle = await this.findOne(id);
this.logger.log(`Updating vehicle ${id}: ${vehicle.name}`);
return this.prisma.vehicle.update({
where: { id: vehicle.id },
data: updateVehicleDto,
include: this.vehicleInclude,
});
}
async remove(id: string, hardDelete = false, userRole?: string) {
return executeHardDelete({
id,
hardDelete,
userRole,
findOne: (id) => this.findOne(id),
performHardDelete: (id) => this.prisma.vehicle.delete({ where: { id } }),
performSoftDelete: (id) =>
this.prisma.vehicle.update({
where: { id },
data: { deletedAt: new Date() },
}),
entityName: 'Vehicle',
logger: this.logger,
});
}
/**
* Get vehicle utilization statistics
*/
async getUtilization() {
const now = new Date();
// Fetch vehicles with only upcoming events (filtered at database level)
const vehicles = await this.prisma.vehicle.findMany({
where: { deletedAt: null },
include: {
currentDriver: true,
events: {
where: {
deletedAt: null,
startTime: { gt: now }, // Only fetch upcoming events
},
include: { driver: true, vehicle: true },
orderBy: { startTime: 'asc' },
},
},
orderBy: { name: 'asc' },
});
const stats = vehicles.map((vehicle) => ({
id: vehicle.id,
name: vehicle.name,
type: vehicle.type,
seatCapacity: vehicle.seatCapacity,
status: vehicle.status,
upcomingTrips: vehicle.events.length, // Already filtered at DB level
currentDriver: vehicle.currentDriver?.name,
}));
return {
totalVehicles: vehicles.length,
available: vehicles.filter((v) => v.status === 'AVAILABLE').length,
inUse: vehicles.filter((v) => v.status === 'IN_USE').length,
maintenance: vehicles.filter((v) => v.status === 'MAINTENANCE').length,
reserved: vehicles.filter((v) => v.status === 'RESERVED').length,
vehicles: stats,
};
}
}