feat: Total Cost of Ownership (TCO) per Vehicle #28

Merged
egullickson merged 11 commits from issue-15-add-tco-feature into main 2026-01-14 03:08:35 +00:00
2 changed files with 41 additions and 4 deletions
Showing only changes of commit 47de6898cd - Show all commits

View File

@@ -166,20 +166,20 @@ export class VehiclesController {
try {
const userId = (request as any).user.sub;
const { id } = request.params;
await this.vehiclesService.deleteVehicle(id, userId);
return reply.code(204).send();
} catch (error: any) {
logger.error('Error deleting vehicle', { error, vehicleId: request.params.id, userId: (request as any).user?.sub });
if (error.message === 'Vehicle not found' || error.message === 'Unauthorized') {
return reply.code(404).send({
error: 'Not Found',
message: 'Vehicle not found'
});
}
return reply.code(500).send({
error: 'Internal server error',
message: 'Failed to delete vehicle'
@@ -187,6 +187,37 @@ export class VehiclesController {
}
}
async getTCO(request: FastifyRequest<{ Params: VehicleParams }>, reply: FastifyReply) {
try {
const userId = (request as any).user.sub;
const { id } = request.params;
const tco = await this.vehiclesService.getTCO(id, userId);
return reply.code(200).send(tco);
} catch (error: any) {
logger.error('Error getting vehicle TCO', { error, vehicleId: request.params.id, userId: (request as any).user?.sub });
if (error.statusCode === 404 || error.message === 'Vehicle not found') {
return reply.code(404).send({
error: 'Not Found',
message: 'Vehicle not found'
});
}
if (error.statusCode === 403 || error.message === 'Unauthorized') {
return reply.code(403).send({
error: 'Forbidden',
message: 'Not authorized to access this vehicle'
});
}
return reply.code(500).send({
error: 'Internal server error',
message: 'Failed to calculate TCO'
});
}
}
async getDropdownMakes(request: FastifyRequest<{ Querystring: { year: number } }>, reply: FastifyReply) {
try {
const { year } = request.query;

View File

@@ -100,6 +100,12 @@ export const vehiclesRoutes: FastifyPluginAsync = async (
handler: vehiclesController.deleteImage.bind(vehiclesController)
});
// GET /api/vehicles/:id/tco - Get vehicle Total Cost of Ownership
fastify.get<{ Params: VehicleParams }>('/vehicles/:id/tco', {
preHandler: [fastify.authenticate],
handler: vehiclesController.getTCO.bind(vehiclesController)
});
// Dynamic :id routes MUST come last to avoid matching specific paths like "dropdown"
// GET /api/vehicles/:id - Get specific vehicle
fastify.get<{ Params: VehicleParams }>('/vehicles/:id', {