Initial commit

This commit is contained in:
2026-03-12 12:43:56 +01:00
commit f733dee856
137 changed files with 51192 additions and 0 deletions

737
api/admin/attendance.php Normal file
View File

@@ -0,0 +1,737 @@
<?php
/**
* BOHA Automation - Attendance API
*
* Endpoints:
* GET /api/admin/attendance.php - Get current shift status and today's shifts
* GET /api/admin/attendance.php?action=history - Get attendance history for month
* GET /api/admin/attendance.php?action=admin - Get all attendance records (admin)
* GET /api/admin/attendance.php?action=balances - Get leave balances (admin)
* GET /api/admin/attendance.php?action=workfund&year=YYYY - Get work fund overview (admin)
* GET /api/admin/attendance.php?action=location&id=X - Get location for record (admin)
* GET /api/admin/attendance.php?action=print - Get print data for attendance (admin)
* GET /api/admin/attendance.php?action=projects - Get active projects list
* GET /api/admin/attendance.php?action=project_report&month=YYYY-MM - Get project hours report (admin)
* GET /api/admin/attendance.php?action=project_logs&attendance_id=X - Get project logs for a shift
* POST /api/admin/attendance.php - Clock in/out/break actions
* POST /api/admin/attendance.php?action=update_address - Doplnit adresu k poslednimu zaznamu
* POST /api/admin/attendance.php?action=notes - Save notes for current shift
* POST /api/admin/attendance.php?action=switch_project - Switch active project on current shift
* POST /api/admin/attendance.php?action=leave - Add leave record
* POST /api/admin/attendance.php?action=save_project_logs - Save project logs for record (admin)
* POST /api/admin/attendance.php?action=create - Create attendance record (admin)
* POST /api/admin/attendance.php?action=bulk_attendance - Bulk add attendance for month (admin)
* POST /api/admin/attendance.php?action=balances - Update leave balance (admin)
* PUT /api/admin/attendance.php?id=X - Update attendance record (admin)
* DELETE /api/admin/attendance.php?id=X - Delete attendance record (admin)
*/
declare(strict_types=1);
require_once dirname(__DIR__) . '/config.php';
require_once dirname(__DIR__) . '/includes/JWTAuth.php';
require_once dirname(__DIR__) . '/includes/AuditLog.php';
require_once dirname(__DIR__) . '/includes/CzechHolidays.php';
require_once dirname(__DIR__) . '/includes/AttendanceHelpers.php';
require_once dirname(__DIR__) . '/includes/AttendanceAdmin.php';
setCorsHeaders();
setSecurityHeaders();
setNoCacheHeaders();
header('Content-Type: application/json; charset=utf-8');
$authData = JWTAuth::requireAuth();
AuditLog::setUser($authData['user_id'], $authData['user']['username'] ?? 'unknown');
$method = $_SERVER['REQUEST_METHOD'];
$action = $_GET['action'] ?? '';
$recordId = isset($_GET['id']) ? (int) $_GET['id'] : null;
try {
$pdo = db();
$userId = $authData['user_id'];
$isAdmin = $authData['user']['is_admin'] ?? false;
switch ($method) {
case 'GET':
if ($action === 'history') {
requirePermission($authData, 'attendance.history');
handleGetHistory($pdo, $userId);
} elseif ($action === 'admin') {
requirePermission($authData, 'attendance.admin');
handleGetAdmin($pdo);
} elseif ($action === 'balances') {
requirePermission($authData, 'attendance.balances');
handleGetBalances($pdo);
} elseif ($action === 'location' && $recordId) {
requirePermission($authData, 'attendance.admin');
handleGetLocation($pdo, $recordId);
} elseif ($action === 'users') {
requirePermission($authData, 'attendance.admin');
handleGetUsers($pdo);
} elseif ($action === 'print') {
requirePermission($authData, 'attendance.admin');
handleGetPrint($pdo);
} elseif ($action === 'workfund') {
requirePermission($authData, 'attendance.balances');
handleGetWorkFund($pdo);
} elseif ($action === 'projects') {
handleGetProjects();
} elseif ($action === 'project_report') {
if (!hasPermission($authData, 'attendance.admin') && !hasPermission($authData, 'attendance.balances')) {
requirePermission($authData, 'attendance.admin');
}
handleGetProjectReport($pdo);
} elseif ($action === 'project_logs') {
handleGetProjectLogs($pdo, $userId, $authData);
} else {
requirePermission($authData, 'attendance.record');
handleGetCurrent($pdo, $userId);
}
break;
case 'POST':
if ($action === 'leave') {
requirePermission($authData, 'attendance.record');
handleAddLeave($pdo, $userId);
} elseif ($action === 'notes') {
requirePermission($authData, 'attendance.record');
handleSaveNotes($pdo, $userId);
} elseif ($action === 'switch_project') {
requirePermission($authData, 'attendance.record');
handleSwitchProject($pdo, $userId);
} elseif ($action === 'save_project_logs') {
requirePermission($authData, 'attendance.admin');
handleSaveProjectLogs($pdo);
} elseif ($action === 'create') {
requirePermission($authData, 'attendance.admin');
handleCreateAttendance($pdo);
} elseif ($action === 'bulk_attendance') {
requirePermission($authData, 'attendance.admin');
handleBulkAttendance($pdo);
} elseif ($action === 'balances') {
requirePermission($authData, 'attendance.balances');
handleUpdateBalance($pdo);
} elseif ($action === 'update_address') {
requirePermission($authData, 'attendance.record');
handleUpdateAddress($pdo, $userId);
} else {
requirePermission($authData, 'attendance.record');
handlePunch($pdo, $userId);
}
break;
case 'PUT':
requirePermission($authData, 'attendance.admin');
if (!$recordId) {
errorResponse('ID záznamu je povinné');
}
handleUpdateAttendance($pdo, $recordId);
break;
case 'DELETE':
requirePermission($authData, 'attendance.admin');
if (!$recordId) {
errorResponse('ID záznamu je povinné');
}
handleDeleteAttendance($pdo, $recordId);
break;
default:
errorResponse('Metoda není povolena', 405);
}
} catch (PDOException $e) {
error_log('Attendance API error: ' . $e->getMessage());
errorResponse('Chyba databáze', 500);
}
// ============================================================================
// User-facing handlers
// ============================================================================
function handleGetCurrent(PDO $pdo, int $userId): void
{
$today = date('Y-m-d');
$stmt = $pdo->prepare("
SELECT * FROM attendance
WHERE user_id = ? AND departure_time IS NULL AND (leave_type IS NULL OR leave_type = 'work')
ORDER BY created_at DESC LIMIT 1
");
$stmt->execute([$userId]);
$ongoingShift = $stmt->fetch();
$projectLogs = [];
$activeProjectId = null;
if ($ongoingShift) {
$stmt = $pdo->prepare('SELECT * FROM attendance_project_logs WHERE attendance_id = ? ORDER BY started_at ASC');
$stmt->execute([$ongoingShift['id']]);
$projectLogs = $stmt->fetchAll();
foreach ($projectLogs as $log) {
if ($log['ended_at'] === null) {
$activeProjectId = (int)$log['project_id'];
break;
}
}
}
$stmt = $pdo->prepare("
SELECT * FROM attendance
WHERE user_id = ? AND shift_date = ?
AND departure_time IS NOT NULL
AND (leave_type IS NULL OR leave_type = 'work')
ORDER BY arrival_time DESC
");
$stmt->execute([$userId, $today]);
$todayShifts = $stmt->fetchAll();
$completedShiftIds = array_column($todayShifts, 'id');
$completedProjectLogs = [];
if (!empty($completedShiftIds)) {
$placeholders = implode(',', array_fill(0, count($completedShiftIds), '?'));
$stmt = $pdo->prepare(
"SELECT * FROM attendance_project_logs
WHERE attendance_id IN ($placeholders)
ORDER BY started_at ASC"
);
$stmt->execute($completedShiftIds);
$allLogs = $stmt->fetchAll();
foreach ($allLogs as $log) {
$completedProjectLogs[$log['attendance_id']][] = $log;
}
}
$leaveBalance = getLeaveBalance($pdo, $userId);
$currentYear = (int)date('Y');
$currentMonth = (int)date('m');
$fund = CzechHolidays::getMonthlyWorkFund($currentYear, $currentMonth);
$businessDays = CzechHolidays::getBusinessDaysInMonth($currentYear, $currentMonth);
$startDate = date('Y-m-01');
$endDate = date('Y-m-t');
$stmt = $pdo->prepare('
SELECT * FROM attendance
WHERE user_id = ? AND shift_date BETWEEN ? AND ?
');
$stmt->execute([$userId, $startDate, $endDate]);
$monthRecords = $stmt->fetchAll();
$workedMinutes = 0;
$leaveHoursMonth = 0;
$vacationHours = 0;
$sickHours = 0;
$holidayHours = 0;
$unpaidHours = 0;
foreach ($monthRecords as $rec) {
$lt = $rec['leave_type'] ?? 'work';
$lh = (float)($rec['leave_hours'] ?? 0);
if ($lt === 'work') {
if ($rec['departure_time']) {
$workedMinutes += calculateWorkMinutes($rec);
}
} elseif ($lt === 'vacation') {
$vacationHours += $lh;
$leaveHoursMonth += $lh;
} elseif ($lt === 'sick') {
$sickHours += $lh;
$leaveHoursMonth += $lh;
} elseif ($lt === 'holiday') {
$holidayHours += $lh;
} elseif ($lt === 'unpaid') {
$unpaidHours += $lh;
}
}
$workedHours = round($workedMinutes / 60, 1);
$covered = $workedHours + $leaveHoursMonth;
$remaining = max(0, $fund - $covered);
$overtime = max(0, round($covered - $fund, 1));
$monthlyFund = [
'fund' => $fund,
'business_days' => $businessDays,
'worked' => $workedHours,
'leave_hours' => $leaveHoursMonth,
'vacation_hours' => $vacationHours,
'sick_hours' => $sickHours,
'holiday_hours' => $holidayHours,
'unpaid_hours' => $unpaidHours,
'covered' => $covered,
'remaining' => $remaining,
'overtime' => $overtime,
'month_name' => getCzechMonthName($currentMonth) . ' ' . $currentYear,
];
// Enrich project logs with names
$allLogProjectIds = [];
foreach ($projectLogs as $l) {
$allLogProjectIds[$l['project_id']] = $l['project_id'];
}
foreach ($completedProjectLogs as $logs) {
foreach ($logs as $l) {
$allLogProjectIds[$l['project_id']] = $l['project_id'];
}
}
$projNameMap = fetchProjectNames($allLogProjectIds);
foreach ($projectLogs as &$l) {
$l['project_name'] = $projNameMap[$l['project_id']] ?? null;
}
unset($l);
foreach ($completedProjectLogs as &$logs) {
foreach ($logs as &$l) {
$l['project_name'] = $projNameMap[$l['project_id']] ?? null;
}
unset($l);
}
unset($logs);
foreach ($todayShifts as &$shift) {
$shift['project_logs'] = $completedProjectLogs[$shift['id']] ?? [];
}
unset($shift);
successResponse([
'ongoing_shift' => $ongoingShift,
'today_shifts' => $todayShifts,
'date' => $today,
'leave_balance' => $leaveBalance,
'monthly_fund' => $monthlyFund,
'project_logs' => $projectLogs,
'active_project_id' => $activeProjectId,
]);
}
function handleGetHistory(PDO $pdo, int $userId): void
{
$month = validateMonth();
$year = (int)substr($month, 0, 4);
$monthNum = (int)substr($month, 5, 2);
$startDate = "{$month}-01";
$endDate = date('Y-m-t', strtotime($startDate));
$stmt = $pdo->prepare('
SELECT * FROM attendance
WHERE user_id = ? AND shift_date BETWEEN ? AND ?
ORDER BY shift_date DESC
');
$stmt->execute([$userId, $startDate, $endDate]);
$records = $stmt->fetchAll();
enrichRecordsWithProjectLogs($pdo, $records);
$totalMinutes = 0;
$vacationHours = 0;
$sickHours = 0;
$holidayHours = 0;
$unpaidHours = 0;
foreach ($records as $record) {
$leaveType = $record['leave_type'] ?? 'work';
$leaveHours = (float)($record['leave_hours'] ?? 0);
if ($leaveType === 'vacation') {
$vacationHours += $leaveHours;
} elseif ($leaveType === 'sick') {
$sickHours += $leaveHours;
} elseif ($leaveType === 'holiday') {
$holidayHours += $leaveHours;
} elseif ($leaveType === 'unpaid') {
$unpaidHours += $leaveHours;
} else {
$totalMinutes += calculateWorkMinutes($record);
}
}
$fund = CzechHolidays::getMonthlyWorkFund($year, $monthNum);
$businessDays = CzechHolidays::getBusinessDaysInMonth($year, $monthNum);
$workedHours = round($totalMinutes / 60, 1);
$leaveHoursCovered = $vacationHours + $sickHours;
$covered = $workedHours + $leaveHoursCovered;
$remaining = max(0, round($fund - $covered, 1));
$overtime = max(0, round($covered - $fund, 1));
$leaveBalance = getLeaveBalance($pdo, $userId, $year);
successResponse([
'records' => $records,
'month' => $month,
'year' => $year,
'month_name' => getCzechMonthName($monthNum) . ' ' . $year,
'total_minutes' => $totalMinutes,
'vacation_hours' => $vacationHours,
'sick_hours' => $sickHours,
'holiday_hours' => $holidayHours,
'unpaid_hours' => $unpaidHours,
'leave_balance' => $leaveBalance,
'monthly_fund' => [
'fund' => $fund,
'business_days' => $businessDays,
'worked' => $workedHours,
'leave_hours' => $leaveHoursCovered,
'covered' => $covered,
'remaining' => $remaining,
'overtime' => $overtime,
],
]);
}
function handlePunch(PDO $pdo, int $userId): void
{
$input = getJsonInput();
$action = $input['punch_action'] ?? '';
$today = date('Y-m-d');
$rawNow = date('Y-m-d H:i:s');
$lat = isset($input['latitude']) && $input['latitude'] !== '' ? (float)$input['latitude'] : null;
$lng = isset($input['longitude']) && $input['longitude'] !== '' ? (float)$input['longitude'] : null;
$accuracy = isset($input['accuracy']) && $input['accuracy'] !== '' ? (float)$input['accuracy'] : null;
$address = !empty($input['address']) ? $input['address'] : null;
$stmt = $pdo->prepare("
SELECT * FROM attendance
WHERE user_id = ? AND departure_time IS NULL AND (leave_type IS NULL OR leave_type = 'work')
ORDER BY created_at DESC LIMIT 1
");
$stmt->execute([$userId]);
$ongoingShift = $stmt->fetch();
if ($action === 'arrival' && !$ongoingShift) {
$now = roundUpTo15Minutes($rawNow);
$stmt = $pdo->prepare('
INSERT INTO attendance
(user_id, shift_date, arrival_time, arrival_lat, arrival_lng, arrival_accuracy, arrival_address)
VALUES (?, ?, ?, ?, ?, ?, ?)
');
$stmt->execute([$userId, $today, $now, $lat, $lng, $accuracy, $address]);
AuditLog::logCreate('attendance', (int)$pdo->lastInsertId(), [
'arrival_time' => $now,
'location' => $address,
], 'Příchod zaznamenán');
successResponse(null, 'Příchod zaznamenán');
} elseif ($ongoingShift) {
switch ($action) {
case 'break_start':
if ($ongoingShift['arrival_time'] && !$ongoingShift['break_start']) {
$breakStart = roundToNearest10Minutes($rawNow);
$breakEnd = date('Y-m-d H:i:s', strtotime($breakStart) + (30 * 60));
$stmt = $pdo->prepare('UPDATE attendance SET break_start = ?, break_end = ? WHERE id = ?');
$stmt->execute([$breakStart, $breakEnd, $ongoingShift['id']]);
successResponse(null, 'Pauza zaznamenána');
} else {
errorResponse('Nelze zadat pauzu');
}
break;
case 'departure':
if ($ongoingShift['arrival_time'] && !$ongoingShift['departure_time']) {
$now = roundDownTo15Minutes($rawNow);
// Auto-add break if shift is longer than 6h and no break
if (!$ongoingShift['break_start'] && !$ongoingShift['break_end']) {
$arrivalTime = strtotime($ongoingShift['arrival_time']);
$departureTime = strtotime($now);
$hoursWorked = ($departureTime - $arrivalTime) / 3600;
if ($hoursWorked > 12) {
$midPoint = $arrivalTime + (($departureTime - $arrivalTime) / 2);
$breakStart = roundToNearest10Minutes(date('Y-m-d H:i:s', $midPoint - (30 * 60)));
$breakEnd = roundToNearest10Minutes(date('Y-m-d H:i:s', $midPoint + (30 * 60)));
$stmt = $pdo->prepare('UPDATE attendance SET break_start = ?, break_end = ? WHERE id = ?');
$stmt->execute([$breakStart, $breakEnd, $ongoingShift['id']]);
} elseif ($hoursWorked > 6) {
$midPoint = $arrivalTime + (($departureTime - $arrivalTime) / 2);
$breakStart = roundToNearest10Minutes(date('Y-m-d H:i:s', $midPoint - (15 * 60)));
$breakEnd = roundToNearest10Minutes(date('Y-m-d H:i:s', $midPoint + (15 * 60)));
$stmt = $pdo->prepare('UPDATE attendance SET break_start = ?, break_end = ? WHERE id = ?');
$stmt->execute([$breakStart, $breakEnd, $ongoingShift['id']]);
}
}
$stmt = $pdo->prepare('
UPDATE attendance
SET departure_time = ?, departure_lat = ?, departure_lng = ?,
departure_accuracy = ?, departure_address = ?
WHERE id = ?
');
$stmt->execute([$now, $lat, $lng, $accuracy, $address, $ongoingShift['id']]);
// Close any open project log
$stmt = $pdo->prepare('
UPDATE attendance_project_logs SET ended_at = ? WHERE attendance_id = ? AND ended_at IS NULL
');
$stmt->execute([$now, $ongoingShift['id']]);
AuditLog::logUpdate('attendance', $ongoingShift['id'], [], [
'departure_time' => $now,
'location' => $address,
], 'Odchod zaznamenán');
successResponse(null, 'Odchod zaznamenán');
} else {
errorResponse('Nelze zadat odchod');
}
break;
default:
errorResponse('Neplatná akce');
}
} else {
errorResponse('Neplatná akce - nemáte aktivní směnu');
}
}
function handleUpdateAddress(PDO $pdo, int $userId): void
{
$input = getJsonInput();
$address = trim($input['address'] ?? '');
$punchAction = $input['punch_action'] ?? '';
if (!$address) {
successResponse(null);
return;
}
if ($punchAction === 'arrival') {
$stmt = $pdo->prepare("
UPDATE attendance SET arrival_address = ?
WHERE id = (
SELECT id FROM (
SELECT id FROM attendance
WHERE user_id = ? AND (arrival_address IS NULL OR arrival_address = '')
ORDER BY created_at DESC LIMIT 1
) t
)
");
} else {
$stmt = $pdo->prepare("
UPDATE attendance SET departure_address = ?
WHERE id = (
SELECT id FROM (
SELECT id FROM attendance
WHERE user_id = ? AND (departure_address IS NULL OR departure_address = '')
AND departure_time IS NOT NULL
ORDER BY created_at DESC LIMIT 1
) t
)
");
}
$stmt->execute([$address, $userId]);
successResponse(null);
}
function handleAddLeave(PDO $pdo, int $userId): void
{
$input = getJsonInput();
$leaveType = $input['leave_type'] ?? '';
$leaveDate = $input['leave_date'] ?? '';
$leaveHours = (float)($input['leave_hours'] ?? 8);
$notes = trim($input['notes'] ?? '');
if (!$leaveType || !$leaveDate || $leaveHours <= 0) {
errorResponse('Vyplňte všechna povinná pole');
}
if (!in_array($leaveType, ['vacation', 'sick', 'unpaid'])) {
errorResponse('Neplatný typ nepřítomnosti');
}
if ($leaveType === 'vacation') {
$year = (int)date('Y', strtotime($leaveDate));
$balance = getLeaveBalance($pdo, $userId, $year);
if ($balance['vacation_remaining'] < $leaveHours) {
errorResponse(
"Nemáte dostatek hodin dovolené. Zbývá vám "
. "{$balance['vacation_remaining']} hodin, požadujete {$leaveHours} hodin."
);
}
}
$stmt = $pdo->prepare('
INSERT INTO attendance (user_id, shift_date, leave_type, leave_hours, notes)
VALUES (?, ?, ?, ?, ?)
');
$stmt->execute([$userId, $leaveDate, $leaveType, $leaveHours, $notes ?: null]);
updateLeaveBalance($pdo, $userId, $leaveDate, $leaveType, $leaveHours);
AuditLog::logCreate('attendance', (int)$pdo->lastInsertId(), [
'leave_type' => $leaveType,
'leave_hours' => $leaveHours,
], "Zaznamenána nepřítomnost: $leaveType");
successResponse(null, 'Nepřítomnost byla zaznamenána');
}
function handleSaveNotes(PDO $pdo, int $userId): void
{
$input = getJsonInput();
$notes = trim($input['notes'] ?? '');
$stmt = $pdo->prepare('
SELECT id FROM attendance
WHERE user_id = ? AND departure_time IS NULL
ORDER BY created_at DESC LIMIT 1
');
$stmt->execute([$userId]);
$currentShift = $stmt->fetch();
if (!$currentShift) {
errorResponse('Nemáte aktivní směnu');
}
$stmt = $pdo->prepare('UPDATE attendance SET notes = ? WHERE id = ?');
$stmt->execute([$notes, $currentShift['id']]);
successResponse(null, 'Poznámka byla uložena');
}
function handleGetProjects(): void
{
try {
$pdo = db();
$stmt = $pdo->query(
"SELECT id, project_number, name FROM projects
WHERE status = 'aktivni' ORDER BY project_number ASC"
);
$projects = $stmt->fetchAll();
successResponse(['projects' => $projects]);
} catch (\Exception $e) {
error_log('Failed to fetch projects: ' . $e->getMessage());
successResponse(['projects' => []]);
}
}
function handleSwitchProject(PDO $pdo, int $userId): void
{
$input = getJsonInput();
/** @var mixed $rawProjectId */
$rawProjectId = $input['project_id'] ?? null;
$projectId = isset($input['project_id']) && $rawProjectId !== '' && $rawProjectId !== null
? (int)$rawProjectId
: null;
$stmt = $pdo->prepare("
SELECT id FROM attendance
WHERE user_id = ? AND departure_time IS NULL AND (leave_type IS NULL OR leave_type = 'work')
ORDER BY created_at DESC LIMIT 1
");
$stmt->execute([$userId]);
$currentShift = $stmt->fetch();
if (!$currentShift) {
errorResponse('Nemáte aktivní směnu');
}
$attendanceId = $currentShift['id'];
$now = date('Y-m-d H:i:s');
$stmt = $pdo->prepare(
'UPDATE attendance_project_logs SET ended_at = ?
WHERE attendance_id = ? AND ended_at IS NULL'
);
$stmt->execute([$now, $attendanceId]);
if ($projectId) {
$stmt = $pdo->prepare(
'INSERT INTO attendance_project_logs
(attendance_id, project_id, started_at) VALUES (?, ?, ?)'
);
$stmt->execute([$attendanceId, $projectId, $now]);
}
$stmt = $pdo->prepare('UPDATE attendance SET project_id = ? WHERE id = ?');
$stmt->execute([$projectId, $attendanceId]);
successResponse(null, $projectId ? 'Projekt přepnut' : 'Projekt zastaven');
}
/** @param array<string, mixed> $authData */
function handleGetProjectLogs(PDO $pdo, int $currentUserId, array $authData): void
{
$attendanceId = (int)($_GET['attendance_id'] ?? 0);
if (!$attendanceId) {
errorResponse('attendance_id je povinné');
}
// Ověření vlastnictví záznamu nebo admin oprávnění
if (!hasPermission($authData, 'attendance.admin')) {
$ownerStmt = $pdo->prepare('SELECT user_id FROM attendance WHERE id = ?');
$ownerStmt->execute([$attendanceId]);
$owner = $ownerStmt->fetch();
if (!$owner || (int)$owner['user_id'] !== $currentUserId) {
errorResponse('Nemáte oprávnění zobrazit tyto záznamy', 403);
}
}
$stmt = $pdo->prepare('SELECT * FROM attendance_project_logs WHERE attendance_id = ? ORDER BY started_at ASC');
$stmt->execute([$attendanceId]);
$logs = $stmt->fetchAll();
$projectIds = [];
foreach ($logs as $l) {
$projectIds[$l['project_id']] = $l['project_id'];
}
$projNameMap = fetchProjectNames($projectIds);
foreach ($logs as &$l) {
$l['project_name'] = $projNameMap[$l['project_id']] ?? null;
}
unset($l);
successResponse(['logs' => $logs]);
}
function handleSaveProjectLogs(PDO $pdo): void
{
$input = getJsonInput();
$attendanceId = (int)($input['attendance_id'] ?? 0);
$logs = $input['project_logs'] ?? [];
if (!$attendanceId) {
errorResponse('attendance_id je povinné');
}
$stmt = $pdo->prepare('SELECT * FROM attendance WHERE id = ?');
$stmt->execute([$attendanceId]);
$record = $stmt->fetch();
if (!$record) {
errorResponse('Záznam nebyl nalezen', 404);
}
$stmt = $pdo->prepare('DELETE FROM attendance_project_logs WHERE attendance_id = ?');
$stmt->execute([$attendanceId]);
if (!empty($logs)) {
$stmt = $pdo->prepare(
'INSERT INTO attendance_project_logs
(attendance_id, project_id, hours, minutes) VALUES (?, ?, ?, ?)'
);
foreach ($logs as $log) {
$projectId = (int)($log['project_id'] ?? 0);
if (!$projectId) {
continue;
}
$h = (int)($log['hours'] ?? 0);
$m = (int)($log['minutes'] ?? 0);
if ($h === 0 && $m === 0) {
continue;
}
$stmt->execute([$attendanceId, $projectId, $h, $m]);
}
}
successResponse(null, 'Projektové záznamy byly uloženy');
}