Files
app/api/admin/handlers/trips-handlers.php
Simon 5ef6fc8064 refactor: odstraneni PSR-1 SideEffects warningu
- Handler funkce extrahovany z API souboru do api/admin/handlers/
- config.php rozdeleny na helpers.php (funkce) a constants.php (konstanty)
- require_once odstranen z class souboru (AuditLog, JWTAuth, LeaveNotification)
- vendor/autoload.php presunuto do config.php bootstrap
- totp-handlers.php: pridany use deklarace pro TwoFactorAuth
- phpstan.neon: bootstrapFiles, scanDirectories, dynamicConstantNames
- Opraveny chybejici routing bloky v totp.php a session.php

Vysledek: phpcs 0 errors 0 warnings, PHPStan 0 errors, ESLint 0 errors

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 14:29:21 +01:00

661 lines
19 KiB
PHP

<?php
declare(strict_types=1);
function getLastKmForVehicle(PDO $pdo, int $vehicleId): int
{
$stmt = $pdo->prepare('
SELECT COALESCE(
(SELECT MAX(end_km) FROM trips WHERE vehicle_id = ?),
(SELECT initial_km FROM vehicles WHERE id = ?),
0
) as last_km
');
$stmt->execute([$vehicleId, $vehicleId]);
$result = $stmt->fetch();
return $result ? (int)$result['last_km'] : 0;
}
function formatKm(int $km): string
{
return number_format($km, 0, ',', ' ') . ' km';
}
// ============================================================================
// GET Handlers
// ============================================================================
/**
* GET - Current month trips (filtered to current user)
*/
function handleGetCurrent(PDO $pdo, int $userId): void
{
$month = validateMonth();
$vehicleId = isset($_GET['vehicle_id']) ? (int)$_GET['vehicle_id'] : null;
$startDate = "{$month}-01";
$endDate = date('Y-m-t', strtotime($startDate));
$sql = "
SELECT t.*, v.spz, v.name as vehicle_name, v.brand, v.model,
CONCAT(u.first_name, ' ', u.last_name) as driver_name
FROM trips t
JOIN vehicles v ON t.vehicle_id = v.id
JOIN users u ON t.user_id = u.id
WHERE t.trip_date BETWEEN ? AND ?
AND t.user_id = ?
";
$params = [$startDate, $endDate, $userId];
if ($vehicleId) {
$sql .= ' AND t.vehicle_id = ?';
$params[] = $vehicleId;
}
$sql .= ' ORDER BY t.trip_date DESC, t.start_km DESC';
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$trips = $stmt->fetchAll();
// Get active vehicles for selection
$stmt = $pdo->query('SELECT id, spz, name, brand, model FROM vehicles WHERE is_active = 1 ORDER BY name');
$vehicles = $stmt->fetchAll();
// Calculate totals
$totalDistance = 0;
$businessDistance = 0;
$privateDistance = 0;
foreach ($trips as $trip) {
$totalDistance += $trip['distance'];
if ($trip['is_business']) {
$businessDistance += $trip['distance'];
} else {
$privateDistance += $trip['distance'];
}
}
successResponse([
'trips' => $trips,
'vehicles' => $vehicles,
'month' => $month,
'totals' => [
'total' => $totalDistance,
'business' => $businessDistance,
'private' => $privateDistance,
'count' => count($trips),
],
]);
}
/**
* GET - Trip history with filters (filtered to current user)
*/
function handleGetHistory(PDO $pdo, int $userId): void
{
$month = validateMonth();
$vehicleId = isset($_GET['vehicle_id']) ? (int)$_GET['vehicle_id'] : null;
$startDate = "{$month}-01";
$endDate = date('Y-m-t', strtotime($startDate));
$sql = "
SELECT t.*, v.spz, v.name as vehicle_name, v.brand, v.model,
CONCAT(u.first_name, ' ', u.last_name) as driver_name
FROM trips t
JOIN vehicles v ON t.vehicle_id = v.id
JOIN users u ON t.user_id = u.id
WHERE t.trip_date BETWEEN ? AND ?
AND t.user_id = ?
";
$params = [$startDate, $endDate, $userId];
if ($vehicleId) {
$sql .= ' AND t.vehicle_id = ?';
$params[] = $vehicleId;
}
$sql .= ' ORDER BY t.trip_date DESC, t.start_km DESC';
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$trips = $stmt->fetchAll();
// Get vehicles for filter
$stmt = $pdo->query('SELECT id, spz, name FROM vehicles WHERE is_active = 1 ORDER BY name');
$vehicles = $stmt->fetchAll();
// Calculate totals
$totalDistance = 0;
$businessDistance = 0;
foreach ($trips as $trip) {
$totalDistance += $trip['distance'];
if ($trip['is_business']) {
$businessDistance += $trip['distance'];
}
}
successResponse([
'trips' => $trips,
'vehicles' => $vehicles,
'month' => $month,
'totals' => [
'total' => $totalDistance,
'business' => $businessDistance,
'count' => count($trips),
],
]);
}
/**
* GET - Admin view of all trips
*/
function handleGetAdmin(PDO $pdo): void
{
$dateFrom = $_GET['date_from'] ?? null;
$dateTo = $_GET['date_to'] ?? null;
$vehicleId = isset($_GET['vehicle_id']) ? (int)$_GET['vehicle_id'] : null;
$filterUserId = isset($_GET['user_id']) ? (int)$_GET['user_id'] : null;
// Default to current month if no dates provided
if (!$dateFrom || !$dateTo) {
$month = date('Y-m');
$startDate = "{$month}-01";
$endDate = date('Y-m-t', strtotime($startDate));
} else {
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateFrom) || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateTo)) {
errorResponse('Neplatný formát data (očekáváno YYYY-MM-DD)');
}
$startDate = $dateFrom;
$endDate = $dateTo;
}
$sql = "
SELECT t.*, v.spz, v.name as vehicle_name,
CONCAT(u.first_name, ' ', u.last_name) as driver_name
FROM trips t
JOIN vehicles v ON t.vehicle_id = v.id
JOIN users u ON t.user_id = u.id
WHERE t.trip_date BETWEEN ? AND ?
";
$params = [$startDate, $endDate];
if ($vehicleId) {
$sql .= ' AND t.vehicle_id = ?';
$params[] = $vehicleId;
}
if ($filterUserId) {
$sql .= ' AND t.user_id = ?';
$params[] = $filterUserId;
}
$sql .= ' ORDER BY t.trip_date DESC, t.start_km DESC';
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$trips = $stmt->fetchAll();
// Get vehicles for filter
$stmt = $pdo->query('SELECT id, spz, name FROM vehicles ORDER BY name');
$vehicles = $stmt->fetchAll();
// Get users for filter
$stmt = $pdo->query(
"SELECT id, CONCAT(first_name, ' ', last_name) as name FROM users WHERE is_active = 1 ORDER BY last_name"
);
$users = $stmt->fetchAll();
// Calculate totals
$totalDistance = 0;
$businessDistance = 0;
foreach ($trips as $trip) {
$totalDistance += $trip['distance'];
if ($trip['is_business']) {
$businessDistance += $trip['distance'];
}
}
successResponse([
'trips' => $trips,
'vehicles' => $vehicles,
'users' => $users,
'date_from' => $startDate,
'date_to' => $endDate,
'totals' => [
'total' => $totalDistance,
'business' => $businessDistance,
'count' => count($trips),
],
]);
}
/**
* GET - All vehicles (admin)
*/
function handleGetVehicles(PDO $pdo): void
{
$stmt = $pdo->query('
SELECT v.*, COUNT(t.id) as trip_count,
COALESCE(MAX(t.end_km), v.initial_km) as current_km
FROM vehicles v
LEFT JOIN trips t ON t.vehicle_id = v.id
GROUP BY v.id
ORDER BY v.is_active DESC, v.name
');
$vehicles = $stmt->fetchAll();
successResponse(['vehicles' => $vehicles]);
}
/**
* GET - Active vehicles for selection
*/
function handleGetActiveVehicles(PDO $pdo): void
{
$stmt = $pdo->query('
SELECT v.id, v.spz, v.name, v.brand, v.model,
COALESCE(MAX(t.end_km), v.initial_km) as current_km
FROM vehicles v
LEFT JOIN trips t ON t.vehicle_id = v.id
WHERE v.is_active = 1
GROUP BY v.id
ORDER BY v.name
');
$vehicles = $stmt->fetchAll();
successResponse(['vehicles' => $vehicles]);
}
/**
* GET - Last km for vehicle
*/
function handleGetLastKm(PDO $pdo): void
{
$vehicleId = (int)($_GET['vehicle_id'] ?? 0);
if (!$vehicleId) {
errorResponse('Vehicle ID je povinné');
}
$lastKm = getLastKmForVehicle($pdo, $vehicleId);
successResponse(['last_km' => $lastKm]);
}
// ============================================================================
// POST Handlers
// ============================================================================
/**
* POST - Create trip
*/
function handleCreateTrip(PDO $pdo, int $userId): void
{
$input = getJsonInput();
$vehicleId = (int)($input['vehicle_id'] ?? 0);
$tripDate = $input['trip_date'] ?? '';
$startKm = (int)($input['start_km'] ?? 0);
$endKm = (int)($input['end_km'] ?? 0);
$routeFrom = trim($input['route_from'] ?? '');
$routeTo = trim($input['route_to'] ?? '');
$isBusiness = (int)($input['is_business'] ?? 1);
$notes = trim($input['notes'] ?? '');
// Validation
if (!$vehicleId) {
errorResponse('Vyberte vozidlo');
}
if (!$tripDate) {
errorResponse('Datum jízdy je povinné');
}
if (!$startKm) {
errorResponse('Počáteční stav km je povinný');
}
if (!$endKm) {
errorResponse('Konečný stav km je povinný');
}
if (!$routeFrom) {
errorResponse('Místo odjezdu je povinné');
}
if (!$routeTo) {
errorResponse('Místo příjezdu je povinné');
}
if ($endKm <= $startKm) {
errorResponse('Konečný stav km musí být větší než počáteční');
}
// Check vehicle exists
$stmt = $pdo->prepare('SELECT id FROM vehicles WHERE id = ? AND is_active = 1');
$stmt->execute([$vehicleId]);
if (!$stmt->fetch()) {
errorResponse('Vozidlo neexistuje nebo není aktivní');
}
$stmt = $pdo->prepare('
INSERT INTO trips (vehicle_id, user_id, trip_date, start_km, end_km, route_from, route_to, is_business, notes)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
');
$stmt->execute([
$vehicleId, $userId, $tripDate, $startKm, $endKm,
$routeFrom, $routeTo, $isBusiness, $notes ?: null,
]);
$newId = (int)$pdo->lastInsertId();
AuditLog::logCreate('trips', $newId, $input, 'Vytvořen záznam jízdy');
successResponse(['id' => $newId], 'Jízda byla zaznamenána');
}
/**
* POST - Create/update vehicle (admin)
*/
function handleVehicle(PDO $pdo): void
{
$input = getJsonInput();
$id = (int)($input['id'] ?? 0);
$spz = strtoupper(trim($input['spz'] ?? ''));
$name = trim($input['name'] ?? '');
$brand = trim($input['brand'] ?? '');
$model = trim($input['model'] ?? '');
$initialKm = (int)($input['initial_km'] ?? 0);
$isActive = isset($input['is_active']) ? (int)$input['is_active'] : 1;
if (!$spz) {
errorResponse('SPZ je povinná');
}
if (!$name) {
errorResponse('Název je povinný');
}
if ($id) {
// Update
$stmt = $pdo->prepare('
UPDATE vehicles
SET spz = ?, name = ?, brand = ?, model = ?, initial_km = ?, is_active = ?
WHERE id = ?
');
$stmt->execute([$spz, $name, $brand ?: null, $model ?: null, $initialKm, $isActive, $id]);
AuditLog::logUpdate('vehicles', $id, [], $input, 'Upraveno vozidlo');
successResponse(null, 'Vozidlo bylo aktualizováno');
} else {
// Create
$stmt = $pdo->prepare('
INSERT INTO vehicles (spz, name, brand, model, initial_km, is_active)
VALUES (?, ?, ?, ?, ?, ?)
');
try {
$stmt->execute([$spz, $name, $brand ?: null, $model ?: null, $initialKm, $isActive]);
$newId = (int)$pdo->lastInsertId();
AuditLog::logCreate('vehicles', $newId, $input, 'Vytvořeno vozidlo');
successResponse(['id' => $newId], 'Vozidlo bylo vytvořeno');
} catch (PDOException $e) {
if ($e->getCode() == 23000) {
errorResponse('Vozidlo s touto SPZ již existuje');
}
throw $e;
}
}
}
// ============================================================================
// PUT Handler
// ============================================================================
/**
* PUT - Update trip
*
* @param array<string, mixed> $authData
*/
function handleUpdateTrip(PDO $pdo, int $id, int $userId, array $authData): void
{
$stmt = $pdo->prepare('SELECT * FROM trips WHERE id = ?');
$stmt->execute([$id]);
$trip = $stmt->fetch();
if (!$trip) {
errorResponse('Záznam nebyl nalezen', 404);
}
// Check permission - own trips or trips.admin
if ($trip['user_id'] !== $userId && !hasPermission($authData, 'trips.admin')) {
errorResponse('Nemáte oprávnění upravit tento záznam', 403);
}
$input = getJsonInput();
$vehicleId = (int)($input['vehicle_id'] ?? $trip['vehicle_id']);
$tripDate = $input['trip_date'] ?? $trip['trip_date'];
$startKm = (int)($input['start_km'] ?? $trip['start_km']);
$endKm = (int)($input['end_km'] ?? $trip['end_km']);
$routeFrom = trim($input['route_from'] ?? $trip['route_from']);
$routeTo = trim($input['route_to'] ?? $trip['route_to']);
$isBusiness = isset($input['is_business']) ? (int)$input['is_business'] : $trip['is_business'];
$notes = trim($input['notes'] ?? $trip['notes'] ?? '');
if ($endKm <= $startKm) {
errorResponse('Konečný stav km musí být větší než počáteční');
}
$stmt = $pdo->prepare('
UPDATE trips
SET vehicle_id = ?, trip_date = ?, start_km = ?, end_km = ?,
route_from = ?, route_to = ?, is_business = ?, notes = ?
WHERE id = ?
');
$stmt->execute([$vehicleId, $tripDate, $startKm, $endKm, $routeFrom, $routeTo, $isBusiness, $notes ?: null, $id]);
AuditLog::logUpdate('trips', $id, $trip, $input, 'Upraven záznam jízdy');
successResponse(null, 'Záznam byl aktualizován');
}
// ============================================================================
// DELETE Handlers
// ============================================================================
/**
* DELETE - Delete trip
*
* @param array<string, mixed> $authData
*/
function handleDeleteTrip(PDO $pdo, int $id, int $userId, array $authData): void
{
$stmt = $pdo->prepare('SELECT * FROM trips WHERE id = ?');
$stmt->execute([$id]);
$trip = $stmt->fetch();
if (!$trip) {
errorResponse('Záznam nebyl nalezen', 404);
}
// Check permission - own trips or trips.admin
if ($trip['user_id'] !== $userId && !hasPermission($authData, 'trips.admin')) {
errorResponse('Nemáte oprávnění smazat tento záznam', 403);
}
$stmt = $pdo->prepare('DELETE FROM trips WHERE id = ?');
$stmt->execute([$id]);
AuditLog::logDelete('trips', $id, $trip, 'Smazán záznam jízdy');
successResponse(null, 'Záznam byl smazán');
}
/**
* DELETE - Delete vehicle (admin)
*/
function handleDeleteVehicle(PDO $pdo, int $id): void
{
if (!$id) {
errorResponse('ID je povinné');
}
$stmt = $pdo->prepare('SELECT * FROM vehicles WHERE id = ?');
$stmt->execute([$id]);
$vehicle = $stmt->fetch();
if (!$vehicle) {
errorResponse('Vozidlo nebylo nalezeno', 404);
}
// Check if vehicle has trips
$stmt = $pdo->prepare('SELECT COUNT(*) FROM trips WHERE vehicle_id = ?');
$stmt->execute([$id]);
$tripCount = $stmt->fetchColumn();
if ($tripCount > 0) {
errorResponse(
"Nelze smazat vozidlo s {$tripCount} záznamy jízd. Nejprve smažte záznamy jízd nebo deaktivujte vozidlo."
);
}
$stmt = $pdo->prepare('DELETE FROM vehicles WHERE id = ?');
$stmt->execute([$id]);
AuditLog::logDelete('vehicles', $id, $vehicle, 'Smazáno vozidlo');
successResponse(null, 'Vozidlo bylo smazáno');
}
// ============================================================================
// Print Handler
// ============================================================================
/**
* Format date range for display
*/
function formatPeriodName(string $startDate, string $endDate): string
{
$start = new DateTime($startDate);
$end = new DateTime($endDate);
// If same month
if ($start->format('Y-m') === $end->format('Y-m')) {
return getCzechMonthName((int)$start->format('n')) . ' ' . $start->format('Y');
}
// If same year
if ($start->format('Y') === $end->format('Y')) {
return $start->format('j.n.') . ' - ' . $end->format('j.n.Y');
}
// Different years
return $start->format('j.n.Y') . ' - ' . $end->format('j.n.Y');
}
/**
* GET - Print data for trips (admin)
*/
function handleGetPrint(PDO $pdo): void
{
$dateFrom = $_GET['date_from'] ?? null;
$dateTo = $_GET['date_to'] ?? null;
$vehicleId = isset($_GET['vehicle_id']) && $_GET['vehicle_id'] !== '' ? (int)$_GET['vehicle_id'] : null;
$filterUserId = isset($_GET['user_id']) && $_GET['user_id'] !== '' ? (int)$_GET['user_id'] : null;
// Default to current month if no dates provided
if (!$dateFrom || !$dateTo) {
$startDate = date('Y-m-01');
$endDate = date('Y-m-t');
} else {
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateFrom) || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateTo)) {
errorResponse('Neplatný formát data (očekáváno YYYY-MM-DD)');
}
$startDate = $dateFrom;
$endDate = $dateTo;
}
$sql = "
SELECT t.*, v.spz, v.name as vehicle_name, v.brand, v.model,
CONCAT(u.first_name, ' ', u.last_name) as driver_name
FROM trips t
JOIN vehicles v ON t.vehicle_id = v.id
JOIN users u ON t.user_id = u.id
WHERE t.trip_date BETWEEN ? AND ?
";
$params = [$startDate, $endDate];
if ($vehicleId) {
$sql .= ' AND t.vehicle_id = ?';
$params[] = $vehicleId;
}
if ($filterUserId) {
$sql .= ' AND t.user_id = ?';
$params[] = $filterUserId;
}
$sql .= ' ORDER BY t.trip_date ASC, t.start_km ASC';
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$trips = $stmt->fetchAll();
// Get vehicles for filter
$stmt = $pdo->query('SELECT id, spz, name FROM vehicles ORDER BY name');
$vehicles = $stmt->fetchAll();
// Get users for filter
$stmt = $pdo->query(
"SELECT id, CONCAT(first_name, ' ', last_name) as name FROM users WHERE is_active = 1 ORDER BY last_name"
);
$users = $stmt->fetchAll();
// Calculate totals
$totalDistance = 0;
$businessDistance = 0;
$privateDistance = 0;
foreach ($trips as $trip) {
$totalDistance += $trip['distance'];
if ($trip['is_business']) {
$businessDistance += $trip['distance'];
} else {
$privateDistance += $trip['distance'];
}
}
// Get selected vehicle/user names for header
$selectedVehicleName = '';
if ($vehicleId) {
$stmt = $pdo->prepare("SELECT CONCAT(spz, ' - ', name) as name FROM vehicles WHERE id = ?");
$stmt->execute([$vehicleId]);
$v = $stmt->fetch();
$selectedVehicleName = $v ? $v['name'] : '';
}
$selectedUserName = '';
if ($filterUserId) {
$stmt = $pdo->prepare("SELECT CONCAT(first_name, ' ', last_name) as name FROM users WHERE id = ?");
$stmt->execute([$filterUserId]);
$u = $stmt->fetch();
$selectedUserName = $u ? $u['name'] : '';
}
successResponse([
'trips' => $trips,
'vehicles' => $vehicles,
'users' => $users,
'date_from' => $startDate,
'date_to' => $endDate,
'period_name' => formatPeriodName($startDate, $endDate),
'selected_vehicle' => $vehicleId,
'selected_vehicle_name' => $selectedVehicleName,
'selected_user' => $filterUserId,
'selected_user_name' => $selectedUserName,
'totals' => [
'total' => $totalDistance,
'business' => $businessDistance,
'private' => $privateDistance,
'count' => count($trips),
],
]);
}