style: run prettier on entire codebase
This commit is contained in:
@@ -1,18 +1,20 @@
|
||||
import { FastifyInstance } from 'fastify';
|
||||
import prisma from '../../config/database';
|
||||
import { requireAuth, requirePermission } from '../../middleware/auth';
|
||||
import { logAudit } from '../../services/audit';
|
||||
import { success, error } from '../../utils/response';
|
||||
import { parsePagination, buildPaginationMeta } from '../../utils/pagination';
|
||||
import { parseBody } from '../../schemas/common';
|
||||
import { CreateTripSchema, UpdateTripSchema } from '../../schemas/trips.schema';
|
||||
import { FastifyInstance } from "fastify";
|
||||
import prisma from "../../config/database";
|
||||
import { requireAuth, requirePermission } from "../../middleware/auth";
|
||||
import { logAudit } from "../../services/audit";
|
||||
import { success, error } from "../../utils/response";
|
||||
import { parsePagination, buildPaginationMeta } from "../../utils/pagination";
|
||||
import { parseBody } from "../../schemas/common";
|
||||
import { CreateTripSchema, UpdateTripSchema } from "../../schemas/trips.schema";
|
||||
|
||||
export default async function tripsRoutes(fastify: FastifyInstance): Promise<void> {
|
||||
fastify.get('/', { preHandler: requireAuth }, async (request, reply) => {
|
||||
export default async function tripsRoutes(
|
||||
fastify: FastifyInstance,
|
||||
): Promise<void> {
|
||||
fastify.get("/", { preHandler: requireAuth }, async (request, reply) => {
|
||||
const query = request.query as Record<string, unknown>;
|
||||
const { page, limit, skip, order } = parsePagination(query);
|
||||
const authData = request.authData!;
|
||||
const isAdmin = authData.permissions.includes('trips.admin');
|
||||
const isAdmin = authData.permissions.includes("trips.admin");
|
||||
|
||||
const where: Record<string, unknown> = {};
|
||||
if (!isAdmin) where.user_id = authData.userId;
|
||||
@@ -23,9 +25,9 @@ export default async function tripsRoutes(fastify: FastifyInstance): Promise<voi
|
||||
if (query.month) {
|
||||
const monthStr = String(query.month);
|
||||
let yr: number, mo: number;
|
||||
if (monthStr.includes('-')) {
|
||||
if (monthStr.includes("-")) {
|
||||
// Combined YYYY-MM format
|
||||
const [yStr, mStr] = monthStr.split('-');
|
||||
const [yStr, mStr] = monthStr.split("-");
|
||||
yr = Number(yStr);
|
||||
mo = Number(mStr);
|
||||
} else if (query.year) {
|
||||
@@ -45,7 +47,10 @@ export default async function tripsRoutes(fastify: FastifyInstance): Promise<voi
|
||||
|
||||
const [trips, total] = await Promise.all([
|
||||
prisma.trips.findMany({
|
||||
where, skip, take: limit, orderBy: { trip_date: order },
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { trip_date: order },
|
||||
include: {
|
||||
users: { select: { id: true, first_name: true, last_name: true } },
|
||||
vehicles: { select: { id: true, name: true, spz: true } },
|
||||
@@ -54,87 +59,111 @@ export default async function tripsRoutes(fastify: FastifyInstance): Promise<voi
|
||||
prisma.trips.count({ where }),
|
||||
]);
|
||||
|
||||
return reply.send({ success: true, data: trips, pagination: buildPaginationMeta(total, page, limit) });
|
||||
return reply.send({
|
||||
success: true,
|
||||
data: trips,
|
||||
pagination: buildPaginationMeta(total, page, limit),
|
||||
});
|
||||
});
|
||||
|
||||
// GET /api/admin/trips/print — print data for trip report
|
||||
fastify.get('/print', { preHandler: requirePermission('trips.admin') }, async (request, reply) => {
|
||||
const query = request.query as Record<string, unknown>;
|
||||
const filterUserId = query.user_id ? Number(query.user_id) : null;
|
||||
const filterVehicleId = query.vehicle_id ? Number(query.vehicle_id) : null;
|
||||
fastify.get(
|
||||
"/print",
|
||||
{ preHandler: requirePermission("trips.admin") },
|
||||
async (request, reply) => {
|
||||
const query = request.query as Record<string, unknown>;
|
||||
const filterUserId = query.user_id ? Number(query.user_id) : null;
|
||||
const filterVehicleId = query.vehicle_id
|
||||
? Number(query.vehicle_id)
|
||||
: null;
|
||||
|
||||
const where: Record<string, unknown> = {};
|
||||
if (filterUserId) where.user_id = filterUserId;
|
||||
if (filterVehicleId) where.vehicle_id = filterVehicleId;
|
||||
if (query.month && query.year) {
|
||||
where.trip_date = {
|
||||
gte: new Date(Number(query.year), Number(query.month) - 1, 1),
|
||||
lt: new Date(Number(query.year), Number(query.month), 1),
|
||||
};
|
||||
}
|
||||
const where: Record<string, unknown> = {};
|
||||
if (filterUserId) where.user_id = filterUserId;
|
||||
if (filterVehicleId) where.vehicle_id = filterVehicleId;
|
||||
if (query.month && query.year) {
|
||||
where.trip_date = {
|
||||
gte: new Date(Number(query.year), Number(query.month) - 1, 1),
|
||||
lt: new Date(Number(query.year), Number(query.month), 1),
|
||||
};
|
||||
}
|
||||
|
||||
const trips = await prisma.trips.findMany({
|
||||
where,
|
||||
include: {
|
||||
users: { select: { id: true, first_name: true, last_name: true } },
|
||||
vehicles: { select: { id: true, name: true, spz: true } },
|
||||
},
|
||||
orderBy: { trip_date: 'asc' },
|
||||
});
|
||||
const trips = await prisma.trips.findMany({
|
||||
where,
|
||||
include: {
|
||||
users: { select: { id: true, first_name: true, last_name: true } },
|
||||
vehicles: { select: { id: true, name: true, spz: true } },
|
||||
},
|
||||
orderBy: { trip_date: "asc" },
|
||||
});
|
||||
|
||||
const vehicles = await prisma.vehicles.findMany({ orderBy: { name: 'asc' } });
|
||||
const users = await prisma.users.findMany({
|
||||
where: { is_active: true },
|
||||
select: { id: true, first_name: true, last_name: true },
|
||||
orderBy: { last_name: 'asc' },
|
||||
});
|
||||
const vehicles = await prisma.vehicles.findMany({
|
||||
orderBy: { name: "asc" },
|
||||
});
|
||||
const users = await prisma.users.findMany({
|
||||
where: { is_active: true },
|
||||
select: { id: true, first_name: true, last_name: true },
|
||||
orderBy: { last_name: "asc" },
|
||||
});
|
||||
|
||||
let totalKm = 0;
|
||||
let businessKm = 0;
|
||||
let privateKm = 0;
|
||||
for (const t of trips) {
|
||||
const dist = Number(t.end_km) - Number(t.start_km);
|
||||
totalKm += dist;
|
||||
if (t.is_business) businessKm += dist;
|
||||
else privateKm += dist;
|
||||
}
|
||||
let totalKm = 0;
|
||||
let businessKm = 0;
|
||||
let privateKm = 0;
|
||||
for (const t of trips) {
|
||||
const dist = Number(t.end_km) - Number(t.start_km);
|
||||
totalKm += dist;
|
||||
if (t.is_business) businessKm += dist;
|
||||
else privateKm += dist;
|
||||
}
|
||||
|
||||
return success(reply, {
|
||||
trips,
|
||||
vehicles,
|
||||
users: users.map(u => ({ id: u.id, name: `${u.first_name} ${u.last_name}`.trim() })),
|
||||
totals: { total_km: totalKm, business_km: businessKm, private_km: privateKm, count: trips.length },
|
||||
});
|
||||
});
|
||||
return success(reply, {
|
||||
trips,
|
||||
vehicles,
|
||||
users: users.map((u) => ({
|
||||
id: u.id,
|
||||
name: `${u.first_name} ${u.last_name}`.trim(),
|
||||
})),
|
||||
totals: {
|
||||
total_km: totalKm,
|
||||
business_km: businessKm,
|
||||
private_km: privateKm,
|
||||
count: trips.length,
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// GET /api/admin/trips/last-km/:vehicleId
|
||||
// Matches PHP: COALESCE(MAX(end_km), vehicle.initial_km, 0)
|
||||
fastify.get<{ Params: { vehicleId: string } }>('/last-km/:vehicleId', { preHandler: requireAuth }, async (request, reply) => {
|
||||
const vehicleId = parseInt(request.params.vehicleId, 10);
|
||||
if (isNaN(vehicleId)) return error(reply, 'Neplatné ID vozidla', 400);
|
||||
fastify.get<{ Params: { vehicleId: string } }>(
|
||||
"/last-km/:vehicleId",
|
||||
{ preHandler: requireAuth },
|
||||
async (request, reply) => {
|
||||
const vehicleId = parseInt(request.params.vehicleId, 10);
|
||||
if (isNaN(vehicleId)) return error(reply, "Neplatné ID vozidla", 400);
|
||||
|
||||
const [lastTrip, vehicle] = await Promise.all([
|
||||
prisma.trips.findFirst({
|
||||
where: { vehicle_id: vehicleId },
|
||||
orderBy: { end_km: 'desc' },
|
||||
select: { end_km: true },
|
||||
}),
|
||||
prisma.vehicles.findUnique({
|
||||
where: { id: vehicleId },
|
||||
select: { initial_km: true },
|
||||
}),
|
||||
]);
|
||||
const [lastTrip, vehicle] = await Promise.all([
|
||||
prisma.trips.findFirst({
|
||||
where: { vehicle_id: vehicleId },
|
||||
orderBy: { end_km: "desc" },
|
||||
select: { end_km: true },
|
||||
}),
|
||||
prisma.vehicles.findUnique({
|
||||
where: { id: vehicleId },
|
||||
select: { initial_km: true },
|
||||
}),
|
||||
]);
|
||||
|
||||
const lastKm = lastTrip
|
||||
? Number(lastTrip.end_km)
|
||||
: Number(vehicle?.initial_km ?? 0);
|
||||
const lastKm = lastTrip
|
||||
? Number(lastTrip.end_km)
|
||||
: Number(vehicle?.initial_km ?? 0);
|
||||
|
||||
return success(reply, { last_km: lastKm });
|
||||
});
|
||||
return success(reply, { last_km: lastKm });
|
||||
},
|
||||
);
|
||||
|
||||
fastify.post('/', { preHandler: requireAuth }, async (request, reply) => {
|
||||
fastify.post("/", { preHandler: requireAuth }, async (request, reply) => {
|
||||
const parsed = parseBody(CreateTripSchema, request.body);
|
||||
if ('error' in parsed) return error(reply, parsed.error, 400);
|
||||
if ("error" in parsed) return error(reply, parsed.error, 400);
|
||||
const body = parsed.data;
|
||||
const authData = request.authData!;
|
||||
|
||||
@@ -147,7 +176,10 @@ export default async function tripsRoutes(fastify: FastifyInstance): Promise<voi
|
||||
end_km: Number(body.end_km),
|
||||
route_from: String(body.route_from),
|
||||
route_to: String(body.route_to),
|
||||
is_business: body.is_business === true || body.is_business === 1 || body.is_business === '1',
|
||||
is_business:
|
||||
body.is_business === true ||
|
||||
body.is_business === 1 ||
|
||||
body.is_business === "1",
|
||||
notes: body.notes ? String(body.notes) : null,
|
||||
},
|
||||
});
|
||||
@@ -158,84 +190,130 @@ export default async function tripsRoutes(fastify: FastifyInstance): Promise<voi
|
||||
data: { actual_km: Number(body.end_km) },
|
||||
});
|
||||
|
||||
await logAudit({ request, authData, action: 'create', entityType: 'trip', entityId: trip.id, description: `Vytvořena jízda` });
|
||||
return success(reply, { id: trip.id }, 201, 'Jízda byla zaznamenána');
|
||||
await logAudit({
|
||||
request,
|
||||
authData,
|
||||
action: "create",
|
||||
entityType: "trip",
|
||||
entityId: trip.id,
|
||||
description: `Vytvořena jízda`,
|
||||
});
|
||||
return success(reply, { id: trip.id }, 201, "Jízda byla zaznamenána");
|
||||
});
|
||||
|
||||
fastify.put<{ Params: { id: string } }>('/:id', { preHandler: requireAuth }, async (request, reply) => {
|
||||
const id = parseInt(request.params.id, 10);
|
||||
if (isNaN(id)) return error(reply, 'Neplatné ID', 400);
|
||||
const parsed = parseBody(UpdateTripSchema, request.body);
|
||||
if ('error' in parsed) return error(reply, parsed.error, 400);
|
||||
const body = parsed.data;
|
||||
const authData = request.authData!;
|
||||
fastify.put<{ Params: { id: string } }>(
|
||||
"/:id",
|
||||
{ preHandler: requireAuth },
|
||||
async (request, reply) => {
|
||||
const id = parseInt(request.params.id, 10);
|
||||
if (isNaN(id)) return error(reply, "Neplatné ID", 400);
|
||||
const parsed = parseBody(UpdateTripSchema, request.body);
|
||||
if ("error" in parsed) return error(reply, parsed.error, 400);
|
||||
const body = parsed.data;
|
||||
const authData = request.authData!;
|
||||
|
||||
const existing = await prisma.trips.findUnique({ where: { id } });
|
||||
if (!existing) return error(reply, 'Jízda nenalezena', 404);
|
||||
const existing = await prisma.trips.findUnique({ where: { id } });
|
||||
if (!existing) return error(reply, "Jízda nenalezena", 404);
|
||||
|
||||
// Ownership check — same as DELETE handler
|
||||
const isAdmin = authData.permissions.includes('trips.admin');
|
||||
if (existing.user_id !== authData.userId && !isAdmin) {
|
||||
return error(reply, 'Nemáte oprávnění upravit tuto jízdu', 403);
|
||||
}
|
||||
// Ownership check — same as DELETE handler
|
||||
const isAdmin = authData.permissions.includes("trips.admin");
|
||||
if (existing.user_id !== authData.userId && !isAdmin) {
|
||||
return error(reply, "Nemáte oprávnění upravit tuto jízdu", 403);
|
||||
}
|
||||
|
||||
const data: Record<string, unknown> = {};
|
||||
if (body.trip_date !== undefined) data.trip_date = new Date(String(body.trip_date));
|
||||
if (body.start_km !== undefined) data.start_km = Number(body.start_km);
|
||||
if (body.end_km !== undefined) data.end_km = Number(body.end_km);
|
||||
if (body.route_from !== undefined) data.route_from = String(body.route_from);
|
||||
if (body.route_to !== undefined) data.route_to = String(body.route_to);
|
||||
if (body.is_business !== undefined) data.is_business = body.is_business === true || body.is_business === 1 || body.is_business === '1';
|
||||
if (body.notes !== undefined) data.notes = body.notes ? String(body.notes) : null;
|
||||
const data: Record<string, unknown> = {};
|
||||
if (body.trip_date !== undefined)
|
||||
data.trip_date = new Date(String(body.trip_date));
|
||||
if (body.start_km !== undefined) data.start_km = Number(body.start_km);
|
||||
if (body.end_km !== undefined) data.end_km = Number(body.end_km);
|
||||
if (body.route_from !== undefined)
|
||||
data.route_from = String(body.route_from);
|
||||
if (body.route_to !== undefined) data.route_to = String(body.route_to);
|
||||
if (body.is_business !== undefined)
|
||||
data.is_business =
|
||||
body.is_business === true ||
|
||||
body.is_business === 1 ||
|
||||
body.is_business === "1";
|
||||
if (body.notes !== undefined)
|
||||
data.notes = body.notes ? String(body.notes) : null;
|
||||
|
||||
await prisma.trips.update({ where: { id }, data });
|
||||
await prisma.trips.update({ where: { id }, data });
|
||||
|
||||
// Update vehicle actual_km if end_km changed
|
||||
if (body.end_km !== undefined) {
|
||||
const vehicleId = existing.vehicle_id;
|
||||
const maxTrip = await prisma.trips.findFirst({
|
||||
where: { vehicle_id: vehicleId },
|
||||
orderBy: { end_km: "desc" },
|
||||
select: { end_km: true },
|
||||
});
|
||||
if (maxTrip) {
|
||||
await prisma.vehicles.update({
|
||||
where: { id: vehicleId },
|
||||
data: { actual_km: Number(maxTrip.end_km) },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await logAudit({
|
||||
request,
|
||||
authData,
|
||||
action: "update",
|
||||
entityType: "trip",
|
||||
entityId: id,
|
||||
description: `Upravena jízda`,
|
||||
});
|
||||
return success(reply, { id }, 200, "Záznam byl aktualizován");
|
||||
},
|
||||
);
|
||||
|
||||
fastify.delete<{ Params: { id: string } }>(
|
||||
"/:id",
|
||||
{ preHandler: requireAuth },
|
||||
async (request, reply) => {
|
||||
const id = parseInt(request.params.id, 10);
|
||||
if (isNaN(id)) return error(reply, "Neplatné ID", 400);
|
||||
const authData = request.authData!;
|
||||
const existing = await prisma.trips.findUnique({ where: { id } });
|
||||
if (!existing) return error(reply, "Jízda nenalezena", 404);
|
||||
|
||||
// Allow users to delete their own trips, admins can delete any
|
||||
const isAdmin = authData.permissions.includes("trips.admin");
|
||||
if (existing.user_id !== authData.userId && !isAdmin) {
|
||||
return error(reply, "Nemáte oprávnění smazat tuto jízdu", 403);
|
||||
}
|
||||
|
||||
// Update vehicle actual_km if end_km changed
|
||||
if (body.end_km !== undefined) {
|
||||
const vehicleId = existing.vehicle_id;
|
||||
await prisma.trips.delete({ where: { id } });
|
||||
|
||||
// Recalculate vehicle actual_km after deletion
|
||||
const maxTrip = await prisma.trips.findFirst({
|
||||
where: { vehicle_id: vehicleId },
|
||||
orderBy: { end_km: 'desc' },
|
||||
orderBy: { end_km: "desc" },
|
||||
select: { end_km: true },
|
||||
});
|
||||
if (maxTrip) {
|
||||
await prisma.vehicles.update({ where: { id: vehicleId }, data: { actual_km: Number(maxTrip.end_km) } });
|
||||
}
|
||||
}
|
||||
const vehicle = await prisma.vehicles.findUnique({
|
||||
where: { id: vehicleId },
|
||||
select: { initial_km: true },
|
||||
});
|
||||
await prisma.vehicles.update({
|
||||
where: { id: vehicleId },
|
||||
data: {
|
||||
actual_km: maxTrip
|
||||
? Number(maxTrip.end_km)
|
||||
: (vehicle?.initial_km ?? 0),
|
||||
},
|
||||
});
|
||||
|
||||
await logAudit({ request, authData, action: 'update', entityType: 'trip', entityId: id, description: `Upravena jízda` });
|
||||
return success(reply, { id }, 200, 'Záznam byl aktualizován');
|
||||
});
|
||||
|
||||
fastify.delete<{ Params: { id: string } }>('/:id', { preHandler: requireAuth }, async (request, reply) => {
|
||||
const id = parseInt(request.params.id, 10);
|
||||
if (isNaN(id)) return error(reply, 'Neplatné ID', 400);
|
||||
const authData = request.authData!;
|
||||
const existing = await prisma.trips.findUnique({ where: { id } });
|
||||
if (!existing) return error(reply, 'Jízda nenalezena', 404);
|
||||
|
||||
// Allow users to delete their own trips, admins can delete any
|
||||
const isAdmin = authData.permissions.includes('trips.admin');
|
||||
if (existing.user_id !== authData.userId && !isAdmin) {
|
||||
return error(reply, 'Nemáte oprávnění smazat tuto jízdu', 403);
|
||||
}
|
||||
|
||||
const vehicleId = existing.vehicle_id;
|
||||
await prisma.trips.delete({ where: { id } });
|
||||
|
||||
// Recalculate vehicle actual_km after deletion
|
||||
const maxTrip = await prisma.trips.findFirst({
|
||||
where: { vehicle_id: vehicleId },
|
||||
orderBy: { end_km: 'desc' },
|
||||
select: { end_km: true },
|
||||
});
|
||||
const vehicle = await prisma.vehicles.findUnique({ where: { id: vehicleId }, select: { initial_km: true } });
|
||||
await prisma.vehicles.update({
|
||||
where: { id: vehicleId },
|
||||
data: { actual_km: maxTrip ? Number(maxTrip.end_km) : (vehicle?.initial_km ?? 0) },
|
||||
});
|
||||
|
||||
await logAudit({ request, authData, action: 'delete', entityType: 'trip', entityId: id, description: `Smazána jízda` });
|
||||
return success(reply, { id }, 200, 'Záznam byl smazán');
|
||||
});
|
||||
await logAudit({
|
||||
request,
|
||||
authData,
|
||||
action: "delete",
|
||||
entityType: "trip",
|
||||
entityId: id,
|
||||
description: `Smazána jízda`,
|
||||
});
|
||||
return success(reply, { id }, 200, "Záznam byl smazán");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user