This commit is contained in:
2026-03-30 20:37:17 +02:00
16 changed files with 1419 additions and 708 deletions
+5
View File
@@ -0,0 +1,5 @@
*.woff binary
*.woff2 binary
*.ttf binary
*.eot binary
*.otf binary
+6
View File
@@ -25,3 +25,9 @@
/app/Config/database.php /app/Config/database.php
/vendors/* /vendors/*
# Local editor/deploy configuration
/.vscode/ftp-sync.json
/.vscode/sftp.json
.vscode/ftp-sync.json
.vscode/sftp.json
+27
View File
@@ -0,0 +1,27 @@
{
"remotePath": "./",
"host": "your-host",
"username": "your-username",
"password": "your-password",
"port": 21,
"secure": true,
"protocol": "ftp",
"uploadOnSave": false,
"passive": false,
"debug": false,
"privateKeyPath": null,
"passphrase": null,
"agent": null,
"allow": [],
"ignore": [
"\\.vscode",
"\\.git",
"\\.DS_Store"
],
"generatedFiles": {
"extensionsToInclude": [
""
],
"path": ""
}
}
+13
View File
@@ -0,0 +1,13 @@
{
"name": "Project Deployment",
"host": "your-host",
"protocol": "ftp",
"port": 21,
"username": "your-username",
"password": "your-password",
"remotePath": "/",
"secure": true,
"uploadOnSave": false,
"useTempFile": false,
"openSsh": false
}
+59 -34
View File
@@ -4,6 +4,7 @@ session_start();
// WICHTIG: Pfade aus /admin heraus korrekt auflösen // WICHTIG: Pfade aus /admin heraus korrekt auflösen
require_once __DIR__ . "/../inc/config.inc.php"; require_once __DIR__ . "/../inc/config.inc.php";
require_once __DIR__ . "/../inc/functions.inc.php"; require_once __DIR__ . "/../inc/functions.inc.php";
require_once __DIR__ . "/../inc/company_holiday_sync.inc.php";
// Login prüfen // Login prüfen
$user = check_admin_user(); $user = check_admin_user();
@@ -624,12 +625,12 @@ if(!check_worker()){
echo "Start: <input class='form-control' name='Starttime[]' type='date' value='" . htmlspecialchars($start, ENT_QUOTES, 'UTF-8') . "'> echo "Start: <input class='form-control' name='Starttime[]' type='date' value='" . htmlspecialchars($start, ENT_QUOTES, 'UTF-8') . "'>
Ende: <input class='form-control' name='Endetime[]' type='date' value='" . htmlspecialchars($ende, ENT_QUOTES, 'UTF-8') . "'><br> Ende: <input class='form-control' name='Endetime[]' type='date' value='" . htmlspecialchars($ende, ENT_QUOTES, 'UTF-8') . "'><br>
Vertretung: <input class='form-control' name='vertretung[]' type='text' value='" . htmlspecialchars($vertretung, ENT_QUOTES, 'UTF-8') . "'>"; Vertretung: <input class='form-control' name='vertretung[]' type='text' required value='" . htmlspecialchars($vertretung, ENT_QUOTES, 'UTF-8') . "'>";
echo "<br>Vertretung Telefon: <input class='form-control' name='vertretertelefon[]' type='text' value='" . htmlspecialchars($vertretertelefon, ENT_QUOTES, 'UTF-8') . "'> echo "<br>Vertretung Telefon: <input class='form-control' name='vertretertelefon[]' type='text' required value='" . htmlspecialchars($vertretertelefon, ENT_QUOTES, 'UTF-8') . "'>
<br>Vertretung Adresse: <input class='form-control' name='vertreteradresse[]' type='text' value='" . htmlspecialchars($vertreteradresse, ENT_QUOTES, 'UTF-8') . "'>"; <br>Vertretung Adresse: <input class='form-control' name='vertreteradresse[]' type='text' required value='" . htmlspecialchars($vertreteradresse, ENT_QUOTES, 'UTF-8') . "'>";
echo "<br>Vertretung Webseite: <input class='form-control' name='vertreterurl[]' type='text' value='" . htmlspecialchars($vertreterurl, ENT_QUOTES, 'UTF-8') . "'>"; echo "<br>Vertretung Webseite: <input class='form-control' name='vertreterurl[]' type='text' required value='" . htmlspecialchars($vertreterurl, ENT_QUOTES, 'UTF-8') . "'>";
echo "<input name='urlaubid[]' type='hidden' value='" . $urlaubid . "'><br>"; echo "<input name='urlaubid[]' type='hidden' value='" . $urlaubid . "'><br>";
} }
@@ -656,39 +657,63 @@ if(!check_worker()){
}else if (($_POST["aktion"] ?? '') == "5") { }else if (($_POST["aktion"] ?? '') == "5") {
// Termine in DB speichern. // Termine in DB speichern.
$i =0; $i =0;
foreach ($_POST['Starttime'] as $Starttime) { $pdo->beginTransaction();
//echo $datum . "<br>"; try {
if($_POST["Starttime"][$i] != "0000-00-00"){ foreach ($_POST['Starttime'] as $Starttime) {
//echo $_POST["urlaubid"][$i] . "<br>"; if($_POST["Starttime"][$i] != "0000-00-00"){
$stmt = $pdo->prepare(" $vertretung = trim((string)($_POST['vertretung'][$i] ?? ''));
INSERT INTO urlaub $vertretertelefon = trim((string)($_POST['vertretertelefon'][$i] ?? ''));
(urlaubid, vertretung, start, ende, vertretertelefon, vertreteradresse, vertreterurl) $vertreteradresse = trim((string)($_POST['vertreteradresse'][$i] ?? ''));
VALUES $vertreterurl = trim((string)($_POST['vertreterurl'][$i] ?? ''));
(:urlaubid, :vertretung, :start, :ende, :telefon, :adresse, :url)
ON DUPLICATE KEY UPDATE
vertretung = VALUES(vertretung),
start = VALUES(start),
ende = VALUES(ende),
vertretertelefon = VALUES(vertretertelefon),
vertreteradresse = VALUES(vertreteradresse),
vertreterurl = VALUES(vertreterurl)
");
$ok = $stmt->execute([ if ($vertretung === '' || $vertretertelefon === '' || $vertreteradresse === '' || $vertreterurl === '') {
':urlaubid' => (int)$_POST['urlaubid'][$i], // 0 = INSERT, >0 = UPDATE throw new RuntimeException("Bitte alle Vertreterinformationen fuer jeden Urlaubseintrag vollstaendig ausfuellen.");
':vertretung' => $_POST['vertretung'][$i], }
':start' => $_POST['Starttime'][$i],
':ende' => $_POST['Endetime'][$i],
':telefon' => $_POST['vertretertelefon'][$i],
':adresse' => $_POST['vertreteradresse'][$i],
':url' => $_POST['vertreterurl'][$i],
]);
if (!$ok) { $stmt = $pdo->prepare("
throw new RuntimeException("Fehler beim Eintragen in der Datenbank."); INSERT INTO urlaub
(urlaubid, vertretung, start, ende, vertretertelefon, vertreteradresse, vertreterurl)
VALUES
(:urlaubid, :vertretung, :start, :ende, :telefon, :adresse, :url)
ON DUPLICATE KEY UPDATE
vertretung = VALUES(vertretung),
start = VALUES(start),
ende = VALUES(ende),
vertretertelefon = VALUES(vertretertelefon),
vertreteradresse = VALUES(vertreteradresse),
vertreterurl = VALUES(vertreterurl)
");
$ok = $stmt->execute([
':urlaubid' => (int)$_POST['urlaubid'][$i], // 0 = INSERT, >0 = UPDATE
':vertretung' => $vertretung,
':start' => $_POST['Starttime'][$i],
':ende' => $_POST['Endetime'][$i],
':telefon' => $vertretertelefon,
':adresse' => $vertreteradresse,
':url' => $vertreterurl,
]);
if (!$ok) {
throw new RuntimeException("Fehler beim Eintragen in der Datenbank.");
}
$urlaubId = (int)$_POST['urlaubid'][$i];
if ($urlaubId <= 0) {
$urlaubId = (int)$pdo->lastInsertId();
}
if ($urlaubId > 0) {
vacationSyncCompanyHolidayFromUrlaub($pdo, $urlaubId, $internUserId);
}
} }
$i++;
} }
$i++; $pdo->commit();
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
throw $e;
} }
echo "Einträge wurden in der Datenbank gespeichert!<br><br>"; echo "Einträge wurden in der Datenbank gespeichert!<br><br>";
@@ -2026,7 +2051,7 @@ if(!check_worker()){
var div = document.getElementById('neuerUrlaub'); var div = document.getElementById('neuerUrlaub');
div.innerHTML += "Start: <input name=Starttime[] type=date class='form-control' > Ende: <input name=Endetime[] type=date class='form-control' >Vertretung: <input type=text name=vertretung[] weight=100 class='form-control'> Vertretung Telefon: <input type=text name=vertretertelefon[] weight=100 class='form-control'> Vertretung Adresse: <input type=text name=vertreteradresse[] weight=100 class='form-control'> Vertretung Webseite: <input type=text name=vertreterurl[] weight=100 class='form-control'> <input name=urlaubid[] type=hidden value='0'> <br>"; div.innerHTML += "Start: <input name=Starttime[] type=date class='form-control' > Ende: <input name=Endetime[] type=date class='form-control' >Vertretung: <input type=text name=vertretung[] weight=100 class='form-control' required> Vertretung Telefon: <input type=text name=vertretertelefon[] weight=100 class='form-control' required> Vertretung Adresse: <input type=text name=vertreteradresse[] weight=100 class='form-control' required> Vertretung Webseite: <input type=text name=vertreterurl[] weight=100 class='form-control' required> <input name=urlaubid[] type=hidden value='0'> <br>";
//Public: <select name=aktiv[] id='aktiv' required ><option value='1' >Ja</option> <option value='0'>Nein</option></select> //Public: <select name=aktiv[] id='aktiv' required ><option value='1' >Ja</option> <option value='0'>Nein</option></select>
//document.getElementById('neueTermine').innerHTML = div; //document.getElementById('neueTermine').innerHTML = div;
+125 -95
View File
@@ -405,6 +405,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
SendMailMessageVorlage($pdo, '1', $tid, $mailTemplateId); SendMailMessageVorlage($pdo, '1', $tid, $mailTemplateId);
} }
impfWorkflowNotificationProcess($pdo);
$message = count($terminIds) . " Terminanfragen wurden erstellt und versendet."; $message = count($terminIds) . " Terminanfragen wurden erstellt und versendet.";
} catch (Throwable $e) { } catch (Throwable $e) {
if ($pdo->inTransaction()) { if ($pdo->inTransaction()) {
@@ -436,7 +437,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
1 1
); );
if ($ok) { if ($ok) {
$notificationEvents = impfWorkflowNotificationProcess($pdo);
$message = $msg; $message = $msg;
if (!empty($notificationEvents)) {
$message .= ' ' . count($notificationEvents) . " Impfworkflow-Benachrichtigung(en) wurden versendet.";
}
} else { } else {
$error = $msg; $error = $msg;
} }
@@ -481,7 +486,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
1 1
); );
if ($ok) { if ($ok) {
$notificationEvents = impfWorkflowNotificationProcess($pdo);
$message = $msg; $message = $msg;
if (!empty($notificationEvents)) {
$message .= ' ' . count($notificationEvents) . " Impfworkflow-Benachrichtigung(en) wurden versendet.";
}
} else { } else {
$error = $msg; $error = $msg;
} }
@@ -595,6 +604,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$exists = (bool)$stDelete->fetch(PDO::FETCH_ASSOC); $exists = (bool)$stDelete->fetch(PDO::FETCH_ASSOC);
workflowDeleteWaitlistEntry($pdo, $warteid); workflowDeleteWaitlistEntry($pdo, $warteid);
if ($exists) { if ($exists) {
impfWorkflowNotificationProcess($pdo);
$message = "Wartelisten-Eintrag wurde gelöscht."; $message = "Wartelisten-Eintrag wurde gelöscht.";
} else { } else {
$error = "Wartelisten-Eintrag nicht gefunden."; $error = "Wartelisten-Eintrag nicht gefunden.";
@@ -663,51 +673,67 @@ $eventOverview = [];
$planWaitCounts = []; $planWaitCounts = [];
try { try {
$stRules = $pdo->prepare("SELECT r.impfstoff_id, r.dosen_pro_flasche, i.impfname, $needsPlanData = in_array($view, ['teilnehmer', 'event-create'], true);
COALESCE((SELECT COUNT(DISTINCT w.userid) FROM warteliste w WHERE w.checked = 1 AND (w.impfstoff = r.impfstoff_id OR w.impfstoff = 0)),0) AS wartende $needsEligibilityData = ($view === 'event-create');
FROM impfstoff_workflow r $needsPersonSearch = ($view === 'teilnehmer' && $personSearch !== '');
INNER JOIN impfstoff i ON i.impfid = r.impfstoff_id $needsWaitRows = ($view === 'warteliste');
WHERE (i.aktiv = 1 OR i.aktivwarteliste = 1 OR i.aktivtermin = 1 OR i.aktivgrippe = 1) $needsUpcomingRows = ($view === 'event-teilnehmer');
ORDER BY i.impfname");
$stRules->execute();
$rules = $stRules->fetchAll(PDO::FETCH_ASSOC);
$plans = impfGetZeitraumRows($pdo, true); if ($needsPlanData) {
$stRules = $pdo->prepare("SELECT r.impfstoff_id, r.dosen_pro_flasche, i.impfname,
COALESCE((SELECT COUNT(DISTINCT w.userid) FROM warteliste w WHERE w.checked = 1 AND (w.impfstoff = r.impfstoff_id OR w.impfstoff = 0)),0) AS wartende
FROM impfstoff_workflow r
INNER JOIN impfstoff i ON i.impfid = r.impfstoff_id
WHERE (i.aktiv = 1 OR i.aktivwarteliste = 1 OR i.aktivtermin = 1 OR i.aktivgrippe = 1)
ORDER BY i.impfname");
$stRules->execute();
$rules = $stRules->fetchAll(PDO::FETCH_ASSOC);
$planExistsForImpfstoff = []; $plans = impfGetZeitraumRows($pdo, true);
foreach ($plans as $p) {
foreach ($p['impfstoff_id_list'] as $impfstoffId) {
$planExistsForImpfstoff[(int)$impfstoffId] = true;
}
}
foreach ($rules as $r) { $planExistsForImpfstoff = [];
$iid = (int)$r['impfstoff_id']; foreach ($plans as $p) {
$dosen = (int)$r['dosen_pro_flasche']; foreach ($p['impfstoff_id_list'] as $impfstoffId) {
if ($dosen <= 0 || !isset($planExistsForImpfstoff[$iid])) { $planExistsForImpfstoff[(int)$impfstoffId] = true;
continue; }
} }
$configuredImpfstoffe[] = $r; foreach ($rules as $r) {
$hasEligiblePlan = false; $iid = (int)$r['impfstoff_id'];
foreach ($plans as $plan) { if (!isset($planExistsForImpfstoff[$iid])) {
if (!in_array($iid, $plan['impfstoff_id_list'] ?? [], true)) {
continue; continue;
} }
$planId = (int)$plan['zeitraum_id']; $configuredImpfstoffe[] = $r;
$planWaitCounts[$iid][$planId] = workflowCountWaitersForPlan($pdo, $iid, $planId); if (!$needsEligibilityData) {
if ($planWaitCounts[$iid][$planId] >= $dosen) { continue;
$hasEligiblePlan = true;
} }
}
if ($hasEligiblePlan) { $dosen = (int)$r['dosen_pro_flasche'];
$eligible[] = $r; if ($dosen <= 0) {
continue;
}
$hasEligiblePlan = false;
foreach ($plans as $plan) {
if (!in_array($iid, $plan['impfstoff_id_list'] ?? [], true)) {
continue;
}
$planId = (int)$plan['zeitraum_id'];
$planWaitCounts[$iid][$planId] = workflowCountWaitersForPlan($pdo, $iid, $planId);
if ($planWaitCounts[$iid][$planId] >= $dosen) {
$hasEligiblePlan = true;
}
}
if ($hasEligiblePlan) {
$eligible[] = $r;
}
} }
} }
if ($personSearch !== '') { if ($needsPersonSearch) {
$searchLike = '%' . $personSearch . '%'; $searchLike = '%' . $personSearch . '%';
$searchExactId = ctype_digit($personSearch) ? (int)$personSearch : -1; $searchExactId = ctype_digit($personSearch) ? (int)$personSearch : -1;
$stPersons = $pdo->prepare("SELECT person_id, vorname, nachname, geburtstag, email, tele, ort, plz, strasse $stPersons = $pdo->prepare("SELECT person_id, vorname, nachname, geburtstag, email, tele, ort, plz, strasse
@@ -726,74 +752,78 @@ try {
$personResults = $stPersons->fetchAll(PDO::FETCH_ASSOC); $personResults = $stPersons->fetchAll(PDO::FETCH_ASSOC);
} }
$stWait = $pdo->prepare("SELECT w.warteid, w.userid, w.checked, w.impfstoff, w.impfart, w.impfenzeitraum, w.zeitraum_id, w.letzteimpfung, w.date_created, if ($needsWaitRows) {
p.vorname, p.nachname, p.geburtstag, p.email, p.tele, $stWait = $pdo->prepare("SELECT w.warteid, w.userid, w.checked, w.impfstoff, w.impfart, w.impfenzeitraum, w.zeitraum_id, w.letzteimpfung, w.date_created,
i.impfname p.vorname, p.nachname, p.geburtstag, p.email, p.tele,
FROM warteliste w i.impfname
INNER JOIN persons p ON p.person_id = w.userid FROM warteliste w
LEFT JOIN impfstoff i ON i.impfid = w.impfstoff INNER JOIN persons p ON p.person_id = w.userid
WHERE w.checked IN (0, 1) LEFT JOIN impfstoff i ON i.impfid = w.impfstoff
ORDER BY w.checked DESC, w.date_created ASC WHERE w.checked IN (0, 1)
LIMIT 500"); ORDER BY w.checked DESC, w.date_created ASC
$stWait->execute(); LIMIT 500");
$waitRows = $stWait->fetchAll(PDO::FETCH_ASSOC); $stWait->execute();
$waitRows = $stWait->fetchAll(PDO::FETCH_ASSOC);
foreach ($waitRows as &$waitRow) { $waitIds = array_map(static function (array $waitRow): int {
$waitRow['zeitraum_labels'] = impfGetWartelistenZeitraeumeLabels($pdo, (int)$waitRow['warteid'], false); return (int)($waitRow['warteid'] ?? 0);
if (!empty($waitRow['zeitraum_labels'])) { }, $waitRows);
$waitRow['impfenzeitraum'] = implode(' | ', $waitRow['zeitraum_labels']); $waitLabelsById = impfGetWartelistenZeitraeumeLabelsMap($pdo, $waitIds, false);
foreach ($waitRows as &$waitRow) {
$warteid = (int)($waitRow['warteid'] ?? 0);
$waitRow['zeitraum_labels'] = $waitLabelsById[$warteid] ?? [];
if (!empty($waitRow['zeitraum_labels'])) {
$waitRow['impfenzeitraum'] = implode(' | ', $waitRow['zeitraum_labels']);
}
} }
} unset($waitRow);
unset($waitRow);
$notificationEvents = impfWorkflowNotificationProcess($pdo);
if (!empty($notificationEvents)) {
$notificationText = count($notificationEvents) . " Impfworkflow-Benachrichtigung(en) wurden versendet.";
$message = ($message === '') ? $notificationText : ($message . ' ' . $notificationText);
} }
$stUpcoming = $pdo->prepare("SELECT ts.timeid, ts.date, ts.start, ts.ende, ts.impfdosen, if ($needsUpcomingRows) {
i.impfname, o.anzeigename, o.adresse, $stUpcoming = $pdo->prepare("SELECT ts.timeid, ts.date, ts.start, ts.ende, ts.impfdosen,
it.terminid, it.checked, it.behandelt, it.impfart, i.impfname, o.anzeigename, o.adresse,
p.vorname, p.nachname, p.geburtstag, p.email, p.tele it.terminid, it.checked, it.behandelt, it.impfart,
FROM timeslots ts p.vorname, p.nachname, p.geburtstag, p.email, p.tele
INNER JOIN impfstoff i ON i.impfid = ts.impfstoff FROM timeslots ts
LEFT JOIN impfort o ON o.ortid = ts.impfortid INNER JOIN impfstoff i ON i.impfid = ts.impfstoff
LEFT JOIN impftermin it ON it.timeid = ts.timeid LEFT JOIN impfort o ON o.ortid = ts.impfortid
LEFT JOIN persons p ON p.person_id = it.userid LEFT JOIN impftermin it ON it.timeid = ts.timeid
WHERE ts.date >= :today LEFT JOIN persons p ON p.person_id = it.userid
AND ts.aktiv = 1 WHERE ts.date >= :today
ORDER BY ts.date, ts.start, ts.ende, i.impfname, p.nachname, p.vorname"); AND ts.aktiv = 1
$stUpcoming->execute(['today' => date('Y-m-d')]); ORDER BY ts.date, ts.start, ts.ende, i.impfname, p.nachname, p.vorname");
$upcomingRows = $stUpcoming->fetchAll(PDO::FETCH_ASSOC); $stUpcoming->execute(['today' => date('Y-m-d')]);
$upcomingRows = $stUpcoming->fetchAll(PDO::FETCH_ASSOC);
foreach ($upcomingRows as $row) { foreach ($upcomingRows as $row) {
$timeid = (int)$row['timeid']; $timeid = (int)$row['timeid'];
if (!isset($eventOverview[$timeid])) { if (!isset($eventOverview[$timeid])) {
$eventOverview[$timeid] = [ $eventOverview[$timeid] = [
'timeid' => $timeid, 'timeid' => $timeid,
'date' => $row['date'], 'date' => $row['date'],
'start' => $row['start'], 'start' => $row['start'],
'ende' => $row['ende'], 'ende' => $row['ende'],
'impfdosen' => (int)$row['impfdosen'], 'impfdosen' => (int)$row['impfdosen'],
'impfname' => $row['impfname'], 'impfname' => $row['impfname'],
'anzeigename' => $row['anzeigename'], 'anzeigename' => $row['anzeigename'],
'adresse' => $row['adresse'], 'adresse' => $row['adresse'],
'teilnehmer' => [], 'teilnehmer' => [],
]; ];
} }
if (!empty($row['terminid'])) { if (!empty($row['terminid'])) {
$eventOverview[$timeid]['teilnehmer'][] = [ $eventOverview[$timeid]['teilnehmer'][] = [
'terminid' => (int)$row['terminid'], 'terminid' => (int)$row['terminid'],
'checked' => (int)($row['checked'] ?? 0), 'checked' => (int)($row['checked'] ?? 0),
'behandelt' => (int)($row['behandelt'] ?? 0), 'behandelt' => (int)($row['behandelt'] ?? 0),
'impfart' => (int)($row['impfart'] ?? 1), 'impfart' => (int)($row['impfart'] ?? 1),
'vorname' => (string)($row['vorname'] ?? ''), 'vorname' => (string)($row['vorname'] ?? ''),
'nachname' => (string)($row['nachname'] ?? ''), 'nachname' => (string)($row['nachname'] ?? ''),
'geburtstag' => (string)($row['geburtstag'] ?? ''), 'geburtstag' => (string)($row['geburtstag'] ?? ''),
'email' => (string)($row['email'] ?? ''), 'email' => (string)($row['email'] ?? ''),
'tele' => (string)($row['tele'] ?? ''), 'tele' => (string)($row['tele'] ?? ''),
]; ];
}
} }
} }
} catch (Throwable $e) { } catch (Throwable $e) {
+373
View File
@@ -0,0 +1,373 @@
<?php
if (!function_exists('vacationSyncTableExists')) {
function vacationSyncTableExists(PDO $pdo, string $table): bool
{
$stmt = $pdo->prepare("SHOW TABLES LIKE :table_name");
$stmt->execute(['table_name' => $table]);
return (bool)$stmt->fetchColumn();
}
}
if (!function_exists('vacationSyncTableHasColumn')) {
function vacationSyncTableHasColumn(PDO $pdo, string $table, string $column): bool
{
$stmt = $pdo->prepare("SHOW COLUMNS FROM `" . $table . "` LIKE :column_name");
$stmt->execute(['column_name' => $column]);
return (bool)$stmt->fetch(PDO::FETCH_ASSOC);
}
}
if (!function_exists('vacationSyncEnsureSchema')) {
function vacationSyncEnsureSchema(PDO $pdo): void
{
$urlaubExists = vacationSyncTableExists($pdo, 'urlaub');
$companyHolidaysExists = vacationSyncTableExists($pdo, 'company_holidays');
if (!$urlaubExists && !$companyHolidaysExists) {
return;
}
if ($urlaubExists && !vacationSyncTableHasColumn($pdo, 'urlaub', 'company_holiday_id')) {
$pdo->exec("ALTER TABLE urlaub ADD COLUMN company_holiday_id INT NULL AFTER vertreterurl");
}
if (!$companyHolidaysExists) {
return;
}
if (!vacationSyncTableHasColumn($pdo, 'company_holidays', 'urlaub_id')) {
$pdo->exec("ALTER TABLE company_holidays ADD COLUMN urlaub_id INT NULL AFTER created_by");
}
if (!vacationSyncTableHasColumn($pdo, 'company_holidays', 'vertretung')) {
$pdo->exec("ALTER TABLE company_holidays ADD COLUMN vertretung VARCHAR(255) NOT NULL DEFAULT '' AFTER description");
}
if (!vacationSyncTableHasColumn($pdo, 'company_holidays', 'vertretertelefon')) {
$pdo->exec("ALTER TABLE company_holidays ADD COLUMN vertretertelefon VARCHAR(255) NOT NULL DEFAULT '' AFTER vertretung");
}
if (!vacationSyncTableHasColumn($pdo, 'company_holidays', 'vertreteradresse')) {
$pdo->exec("ALTER TABLE company_holidays ADD COLUMN vertreteradresse VARCHAR(1000) NOT NULL DEFAULT '' AFTER vertretertelefon");
}
if (!vacationSyncTableHasColumn($pdo, 'company_holidays', 'vertreterurl')) {
$pdo->exec("ALTER TABLE company_holidays ADD COLUMN vertreterurl VARCHAR(255) NOT NULL DEFAULT '' AFTER vertreteradresse");
}
}
}
if (!function_exists('vacationSyncFindCompanyHolidayIdForUrlaub')) {
function vacationSyncFindCompanyHolidayIdForUrlaub(PDO $pdo, int $urlaubId): int
{
if ($urlaubId <= 0) {
return 0;
}
if (!vacationSyncTableExists($pdo, 'urlaub') || !vacationSyncTableExists($pdo, 'company_holidays')) {
return 0;
}
vacationSyncEnsureSchema($pdo);
$stmt = $pdo->prepare("SELECT company_holiday_id FROM urlaub WHERE urlaubid = :urlaub_id LIMIT 1");
$stmt->execute(['urlaub_id' => $urlaubId]);
$linkedId = (int)($stmt->fetchColumn() ?: 0);
if ($linkedId > 0) {
return $linkedId;
}
$stmt = $pdo->prepare("SELECT id FROM company_holidays WHERE urlaub_id = :urlaub_id LIMIT 1");
$stmt->execute(['urlaub_id' => $urlaubId]);
$linkedId = (int)($stmt->fetchColumn() ?: 0);
if ($linkedId > 0) {
return $linkedId;
}
$stmt = $pdo->prepare("SELECT start, ende FROM urlaub WHERE urlaubid = :urlaub_id LIMIT 1");
$stmt->execute(['urlaub_id' => $urlaubId]);
$urlaub = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$urlaub) {
return 0;
}
$stmt = $pdo->prepare("
SELECT id
FROM company_holidays
WHERE start_date = :start_date
AND end_date = :end_date
ORDER BY id ASC
LIMIT 1
");
$stmt->execute([
'start_date' => $urlaub['start'],
'end_date' => $urlaub['ende'],
]);
return (int)($stmt->fetchColumn() ?: 0);
}
}
if (!function_exists('vacationSyncFindUrlaubIdForCompanyHoliday')) {
function vacationSyncFindUrlaubIdForCompanyHoliday(PDO $pdo, int $companyHolidayId): int
{
if ($companyHolidayId <= 0) {
return 0;
}
if (!vacationSyncTableExists($pdo, 'urlaub') || !vacationSyncTableExists($pdo, 'company_holidays')) {
return 0;
}
vacationSyncEnsureSchema($pdo);
$stmt = $pdo->prepare("SELECT urlaub_id FROM company_holidays WHERE id = :company_holiday_id LIMIT 1");
$stmt->execute(['company_holiday_id' => $companyHolidayId]);
$linkedId = (int)($stmt->fetchColumn() ?: 0);
if ($linkedId > 0) {
return $linkedId;
}
$stmt = $pdo->prepare("SELECT urlaubid FROM urlaub WHERE company_holiday_id = :company_holiday_id LIMIT 1");
$stmt->execute(['company_holiday_id' => $companyHolidayId]);
$linkedId = (int)($stmt->fetchColumn() ?: 0);
if ($linkedId > 0) {
return $linkedId;
}
$stmt = $pdo->prepare("SELECT start_date, end_date FROM company_holidays WHERE id = :company_holiday_id LIMIT 1");
$stmt->execute(['company_holiday_id' => $companyHolidayId]);
$holiday = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$holiday) {
return 0;
}
$stmt = $pdo->prepare("
SELECT urlaubid
FROM urlaub
WHERE start = :start_date
AND ende = :end_date
ORDER BY urlaubid ASC
LIMIT 1
");
$stmt->execute([
'start_date' => $holiday['start_date'],
'end_date' => $holiday['end_date'],
]);
return (int)($stmt->fetchColumn() ?: 0);
}
}
if (!function_exists('vacationSyncCompanyHolidayFromUrlaub')) {
function vacationSyncCompanyHolidayFromUrlaub(PDO $pdo, int $urlaubId, ?int $createdBy = null): int
{
if ($urlaubId <= 0) {
return 0;
}
if (!vacationSyncTableExists($pdo, 'urlaub') || !vacationSyncTableExists($pdo, 'company_holidays')) {
return 0;
}
vacationSyncEnsureSchema($pdo);
$stmtUrlaub = $pdo->prepare("
SELECT urlaubid, start, ende, vertretung, vertretertelefon, vertreteradresse, vertreterurl, company_holiday_id
FROM urlaub
WHERE urlaubid = :urlaub_id
LIMIT 1
");
$stmtUrlaub->execute(['urlaub_id' => $urlaubId]);
$urlaub = $stmtUrlaub->fetch(PDO::FETCH_ASSOC);
if (!$urlaub) {
return 0;
}
$companyHolidayId = (int)($urlaub['company_holiday_id'] ?? 0);
if ($companyHolidayId <= 0) {
$companyHolidayId = vacationSyncFindCompanyHolidayIdForUrlaub($pdo, $urlaubId);
}
$description = 'Betriebsurlaub';
if ($companyHolidayId > 0) {
$stmtExisting = $pdo->prepare("SELECT description FROM company_holidays WHERE id = :company_holiday_id LIMIT 1");
$stmtExisting->execute(['company_holiday_id' => $companyHolidayId]);
$existingDescription = $stmtExisting->fetchColumn();
if ($existingDescription !== false && trim((string)$existingDescription) !== '') {
$description = (string)$existingDescription;
}
}
if ($companyHolidayId > 0) {
$stmtUpdate = $pdo->prepare("
UPDATE company_holidays
SET start_date = :start_date,
end_date = :end_date,
vertretung = :vertretung,
vertretertelefon = :vertretertelefon,
vertreteradresse = :vertreteradresse,
vertreterurl = :vertreterurl,
urlaub_id = :urlaub_id
WHERE id = :company_holiday_id
");
$stmtUpdate->execute([
'start_date' => $urlaub['start'],
'end_date' => $urlaub['ende'],
'vertretung' => (string)$urlaub['vertretung'],
'vertretertelefon' => (string)$urlaub['vertretertelefon'],
'vertreteradresse' => (string)$urlaub['vertreteradresse'],
'vertreterurl' => (string)$urlaub['vertreterurl'],
'urlaub_id' => $urlaubId,
'company_holiday_id' => $companyHolidayId,
]);
} else {
$stmtInsert = $pdo->prepare("
INSERT INTO company_holidays (
start_date, end_date, description, vertretung, vertretertelefon, vertreteradresse, vertreterurl, created_by, urlaub_id
)
VALUES (
:start_date, :end_date, :description, :vertretung, :vertretertelefon, :vertreteradresse, :vertreterurl, :created_by, :urlaub_id
)
");
$stmtInsert->execute([
'start_date' => $urlaub['start'],
'end_date' => $urlaub['ende'],
'description' => $description,
'vertretung' => (string)$urlaub['vertretung'],
'vertretertelefon' => (string)$urlaub['vertretertelefon'],
'vertreteradresse' => (string)$urlaub['vertreteradresse'],
'vertreterurl' => (string)$urlaub['vertreterurl'],
'created_by' => $createdBy,
'urlaub_id' => $urlaubId,
]);
$companyHolidayId = (int)$pdo->lastInsertId();
}
if ($companyHolidayId > 0) {
$stmtLink = $pdo->prepare("UPDATE urlaub SET company_holiday_id = :company_holiday_id WHERE urlaubid = :urlaub_id");
$stmtLink->execute([
'company_holiday_id' => $companyHolidayId,
'urlaub_id' => $urlaubId,
]);
}
return $companyHolidayId;
}
}
if (!function_exists('vacationSyncUrlaubFromCompanyHoliday')) {
function vacationSyncUrlaubFromCompanyHoliday(PDO $pdo, int $companyHolidayId): int
{
if ($companyHolidayId <= 0) {
return 0;
}
if (!vacationSyncTableExists($pdo, 'company_holidays') || !vacationSyncTableExists($pdo, 'urlaub')) {
return 0;
}
vacationSyncEnsureSchema($pdo);
$stmtHoliday = $pdo->prepare("
SELECT id, start_date, end_date, vertretung, vertretertelefon, vertreteradresse, vertreterurl, urlaub_id
FROM company_holidays
WHERE id = :company_holiday_id
LIMIT 1
");
$stmtHoliday->execute(['company_holiday_id' => $companyHolidayId]);
$holiday = $stmtHoliday->fetch(PDO::FETCH_ASSOC);
if (!$holiday) {
return 0;
}
$urlaubId = (int)($holiday['urlaub_id'] ?? 0);
if ($urlaubId <= 0) {
$urlaubId = vacationSyncFindUrlaubIdForCompanyHoliday($pdo, $companyHolidayId);
}
if ($urlaubId > 0) {
$stmtUpdate = $pdo->prepare("
UPDATE urlaub
SET start = :start_date,
ende = :end_date,
vertretung = :vertretung,
vertretertelefon = :vertretertelefon,
vertreteradresse = :vertreteradresse,
vertreterurl = :vertreterurl,
company_holiday_id = :company_holiday_id
WHERE urlaubid = :urlaub_id
");
$stmtUpdate->execute([
'start_date' => $holiday['start_date'],
'end_date' => $holiday['end_date'],
'vertretung' => (string)$holiday['vertretung'],
'vertretertelefon' => (string)$holiday['vertretertelefon'],
'vertreteradresse' => (string)$holiday['vertreteradresse'],
'vertreterurl' => (string)$holiday['vertreterurl'],
'company_holiday_id' => $companyHolidayId,
'urlaub_id' => $urlaubId,
]);
} else {
$stmtInsert = $pdo->prepare("
INSERT INTO urlaub
(vertretung, start, ende, vertretertelefon, vertreteradresse, vertreterurl, company_holiday_id)
VALUES
(:vertretung, :start_date, :end_date, :vertretertelefon, :vertreteradresse, :vertreterurl, :company_holiday_id)
");
$stmtInsert->execute([
'vertretung' => (string)$holiday['vertretung'],
'start_date' => $holiday['start_date'],
'end_date' => $holiday['end_date'],
'vertretertelefon' => (string)$holiday['vertretertelefon'],
'vertreteradresse' => (string)$holiday['vertreteradresse'],
'vertreterurl' => (string)$holiday['vertreterurl'],
'company_holiday_id' => $companyHolidayId,
]);
$urlaubId = (int)$pdo->lastInsertId();
}
if ($urlaubId > 0) {
$stmtLink = $pdo->prepare("UPDATE company_holidays SET urlaub_id = :urlaub_id WHERE id = :company_holiday_id");
$stmtLink->execute([
'urlaub_id' => $urlaubId,
'company_holiday_id' => $companyHolidayId,
]);
}
return $urlaubId;
}
}
if (!function_exists('vacationSyncDeleteCompanyHolidayByUrlaub')) {
function vacationSyncDeleteCompanyHolidayByUrlaub(PDO $pdo, int $urlaubId): void
{
if (!vacationSyncTableExists($pdo, 'urlaub') || !vacationSyncTableExists($pdo, 'company_holidays')) {
return;
}
$companyHolidayId = vacationSyncFindCompanyHolidayIdForUrlaub($pdo, $urlaubId);
if ($companyHolidayId <= 0) {
return;
}
$stmt = $pdo->prepare("DELETE FROM company_holidays WHERE id = :company_holiday_id");
$stmt->execute(['company_holiday_id' => $companyHolidayId]);
}
}
if (!function_exists('vacationSyncDeleteUrlaubByCompanyHoliday')) {
function vacationSyncDeleteUrlaubByCompanyHoliday(PDO $pdo, int $companyHolidayId): void
{
if (!vacationSyncTableExists($pdo, 'urlaub') || !vacationSyncTableExists($pdo, 'company_holidays')) {
return;
}
$urlaubId = vacationSyncFindUrlaubIdForCompanyHoliday($pdo, $companyHolidayId);
if ($urlaubId <= 0) {
return;
}
$stmt = $pdo->prepare("DELETE FROM urlaub WHERE urlaubid = :urlaub_id");
$stmt->execute(['urlaub_id' => $urlaubId]);
}
}
+94
View File
@@ -550,6 +550,100 @@ if (!function_exists('impfGetWartelistenZeitraeumeLabels')) {
} }
} }
if (!function_exists('impfGetWartelistenZeitraeumeLabelsMap')) {
function impfGetWartelistenZeitraeumeLabelsMap(PDO $pdo, array $warteids, bool $onlyActive = false): array
{
$warteids = array_values(array_unique(array_filter(array_map('intval', $warteids), static function (int $warteid): bool {
return $warteid > 0;
})));
if (empty($warteids) || !impfTableExists($pdo, 'warteliste_zeitraum')) {
return [];
}
$result = [];
foreach ($warteids as $warteid) {
$result[$warteid] = [];
}
$placeholders = [];
$params = [];
foreach ($warteids as $index => $warteid) {
$key = 'wid' . $index;
$placeholders[] = ':' . $key;
$params[$key] = $warteid;
}
$inList = implode(', ', $placeholders);
$sql = "SELECT wz.warteid, z.zeitraum_id, z.bezeichnung, z.wochentag, z.start, z.ende, z.impfortid, z.aktiv, z.created_at,
o.anzeigename, o.adresse
FROM warteliste_zeitraum wz
INNER JOIN impf_zeitraum z ON z.zeitraum_id = wz.zeitraum_id
LEFT JOIN impfort o ON o.ortid = z.impfortid
WHERE wz.warteid IN (" . $inList . ")";
if ($onlyActive) {
$sql .= " AND z.aktiv = 1";
}
$sql .= " ORDER BY wz.warteid, z.wochentag, z.start, z.ende, z.bezeichnung, z.zeitraum_id";
$st = $pdo->prepare($sql);
$st->execute($params);
$rows = $st->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
$warteid = (int)($row['warteid'] ?? 0);
if ($warteid <= 0) {
continue;
}
$row['label'] = impfZeitraumLabel($row);
$result[$warteid][] = (string)$row['label'];
}
$missing = array_values(array_filter($warteids, static function (int $warteid) use ($result): bool {
return empty($result[$warteid]);
}));
if (empty($missing)) {
return $result;
}
$fallbackPlaceholders = [];
$fallbackParams = [];
foreach ($missing as $index => $warteid) {
$key = 'f_wid' . $index;
$fallbackPlaceholders[] = ':' . $key;
$fallbackParams[$key] = $warteid;
}
$fallbackInList = implode(', ', $fallbackPlaceholders);
$fallbackSql = "SELECT w.warteid, w.zeitraum_id, z.bezeichnung, z.wochentag, z.start, z.ende, z.impfortid, z.aktiv, z.created_at,
o.anzeigename, o.adresse
FROM warteliste w
LEFT JOIN impf_zeitraum z ON z.zeitraum_id = w.zeitraum_id
LEFT JOIN impfort o ON o.ortid = z.impfortid
WHERE w.warteid IN (" . $fallbackInList . ")
AND w.zeitraum_id IS NOT NULL";
if ($onlyActive) {
$fallbackSql .= " AND z.aktiv = 1";
}
$stFallback = $pdo->prepare($fallbackSql);
$stFallback->execute($fallbackParams);
$fallbackRows = $stFallback->fetchAll(PDO::FETCH_ASSOC);
foreach ($fallbackRows as $row) {
$warteid = (int)($row['warteid'] ?? 0);
if ($warteid <= 0) {
continue;
}
$row['label'] = impfZeitraumLabel($row);
$result[$warteid] = [(string)$row['label']];
}
return $result;
}
}
if (!function_exists('impfSetWartelistenZeitraeume')) { if (!function_exists('impfSetWartelistenZeitraeume')) {
function impfSetWartelistenZeitraeume(PDO $pdo, int $warteid, $zeitraumIds): void function impfSetWartelistenZeitraeume(PDO $pdo, int $warteid, $zeitraumIds): void
{ {
+113 -119
View File
@@ -1,10 +1,8 @@
<?php <?php
// API: returns JSON events for FullCalendar
session_start(); session_start();
require_once(__DIR__ . '/../inc/config.inc.php'); require_once(__DIR__ . '/../inc/config.inc.php');
require_once(__DIR__ . '/../inc/functions.inc.php'); require_once(__DIR__ . '/../inc/functions.inc.php');
// Enable full error reporting for API debugging
ini_set('display_errors', '1'); ini_set('display_errors', '1');
ini_set('display_startup_errors', '1'); ini_set('display_startup_errors', '1');
error_reporting(E_ALL); error_reporting(E_ALL);
@@ -12,16 +10,12 @@ error_reporting(E_ALL);
$user = check_user(); $user = check_user();
$isAdmin = is_admin_user(); $isAdmin = is_admin_user();
$start = $_GET['start'] ?? null; // expected ISO date $start = $_GET['start'] ?? null;
$end = $_GET['end'] ?? null; $end = $_GET['end'] ?? null;
$onlyApproved = isset($_GET['only_approved']) && ($_GET['only_approved'] == '1' || $_GET['only_approved'] === 'true'); $onlyApproved = isset($_GET['only_approved']) && ($_GET['only_approved'] == '1' || $_GET['only_approved'] === 'true');
// public allows non-admin users to request all *approved* vacations (team view)
$public = isset($_GET['public']) && ($_GET['public'] == '1' || $_GET['public'] === 'true'); $public = isset($_GET['public']) && ($_GET['public'] == '1' || $_GET['public'] === 'true');
// include_rejected if set to 1 will return rejected entries; default behavior: do not return rejected for regular users
$includeRejected = isset($_GET['include_rejected']) && ($_GET['include_rejected'] == '1' || $_GET['include_rejected'] === 'true'); $includeRejected = isset($_GET['include_rejected']) && ($_GET['include_rejected'] == '1' || $_GET['include_rejected'] === 'true');
// only_personal forces the API to return only the current user's vacations (useful even if the user is admin)
$onlyPersonal = isset($_GET['only_personal']) && ($_GET['only_personal'] == '1' || $_GET['only_personal'] === 'true'); $onlyPersonal = isset($_GET['only_personal']) && ($_GET['only_personal'] == '1' || $_GET['only_personal'] === 'true');
// public_all when used with public=1 returns all non-rejected team vacations (approved + beantragt)
$publicAll = isset($_GET['public_all']) && ($_GET['public_all'] == '1' || $_GET['public_all'] === 'true'); $publicAll = isset($_GET['public_all']) && ($_GET['public_all'] == '1' || $_GET['public_all'] === 'true');
if (!$start || !$end) { if (!$start || !$end) {
@@ -32,125 +26,134 @@ if (!$start || !$end) {
$events = []; $events = [];
try { try {
$branch = 'unknown'; $branch = 'unknown';
$debugMode = isset($_GET['debug']) && ($_GET['debug'] == '1' || $_GET['debug'] === 'true'); $debugMode = isset($_GET['debug']) && ($_GET['debug'] == '1' || $_GET['debug'] === 'true');
$showEmployeeNames = $isAdmin || $public;
// Vacations: support a personal-only mode, admin mode, and public/team mode if ($onlyPersonal) {
if ($onlyPersonal) { $branch = 'onlyPersonal';
$branch = 'onlyPersonal';
if ($onlyApproved) {
$branch = 'onlyPersonal_onlyApproved';
$stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.user_id = ? AND v.start_date <= ? AND v.end_date >= ? AND LOWER(TRIM(v.status)) = 'genehmigt' ORDER BY v.start_date");
$stmt->execute([$_SESSION['userid'], $end, $start]);
} else {
if ($includeRejected) {
$branch = 'onlyPersonal_includeRejected';
$stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.user_id = ? AND v.start_date <= ? AND v.end_date >= ? ORDER BY v.start_date");
$stmt->execute([$_SESSION['userid'], $end, $start]);
} else {
$branch = 'onlyPersonal_excludeRejected';
$stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.user_id = ? AND v.start_date <= ? AND v.end_date >= ? AND (v.status IS NULL OR LOWER(TRIM(v.status)) != 'abgelehnt') ORDER BY v.start_date");
$stmt->execute([$_SESSION['userid'], $end, $start]);
}
}
} elseif ($isAdmin) {
$branch = 'admin';
if ($onlyApproved) {
$branch = 'admin_onlyApproved';
$stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.start_date <= ? AND v.end_date >= ? AND LOWER(TRIM(v.status)) = 'genehmigt' ORDER BY v.start_date");
$stmt->execute([$end, $start]);
} else {
// By default admins see genehmigt + beantragt; include_rejected=1 can override
if ($includeRejected) {
$stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.start_date <= ? AND v.end_date >= ? ORDER BY v.start_date");
$stmt->execute([$end, $start]);
} else {
$stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.start_date <= ? AND v.end_date >= ? AND (v.status IS NULL OR LOWER(TRIM(v.status)) IN ('genehmigt','beantragt')) ORDER BY v.start_date");
$stmt->execute([$end, $start]);
}
}
} else {
$branch = 'public_or_regular';
if ($public && $onlyApproved) {
$branch = 'public_onlyApproved';
// public team view: show all approved vacations (read-only)
$stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.start_date <= ? AND v.end_date >= ? AND LOWER(TRIM(v.status)) = 'genehmigt' ORDER BY v.start_date");
$stmt->execute([$end, $start]);
} elseif ($public && $publicAll) {
$branch = 'public_publicAll';
// public team view: explicitly show only approved (genehmigt) and pending (beantragt) vacations
$stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.start_date <= ? AND v.end_date >= ? AND (v.status IS NULL OR LOWER(TRIM(v.status)) IN ('genehmigt','beantragt')) ORDER BY v.start_date");
$stmt->execute([$end, $start]);
} else {
if ($onlyApproved) { if ($onlyApproved) {
$branch = 'onlyPersonal_onlyApproved';
$stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.user_id = ? AND v.start_date <= ? AND v.end_date >= ? AND LOWER(TRIM(v.status)) = 'genehmigt' ORDER BY v.start_date"); $stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.user_id = ? AND v.start_date <= ? AND v.end_date >= ? AND LOWER(TRIM(v.status)) = 'genehmigt' ORDER BY v.start_date");
$stmt->execute([$_SESSION['userid'], $end, $start]); $stmt->execute([$_SESSION['userid'], $end, $start]);
} else { } else {
// By default exclude rejected ('abgelehnt') for regular users; include if include_rejected=1
if ($includeRejected) { if ($includeRejected) {
$branch = 'regular_includeRejected'; $branch = 'onlyPersonal_includeRejected';
$stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.user_id = ? AND v.start_date <= ? AND v.end_date >= ? ORDER BY v.start_date"); $stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.user_id = ? AND v.start_date <= ? AND v.end_date >= ? ORDER BY v.start_date");
$stmt->execute([$_SESSION['userid'], $end, $start]); $stmt->execute([$_SESSION['userid'], $end, $start]);
} else { } else {
$branch = 'regular_excludeRejected'; $branch = 'onlyPersonal_excludeRejected';
$stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.user_id = ? AND v.start_date <= ? AND v.end_date >= ? AND (v.status IS NULL OR LOWER(TRIM(v.status)) != 'abgelehnt') ORDER BY v.start_date"); $stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.user_id = ? AND v.start_date <= ? AND v.end_date >= ? AND (v.status IS NULL OR LOWER(TRIM(v.status)) != 'abgelehnt') ORDER BY v.start_date");
$stmt->execute([$_SESSION['userid'], $end, $start]); $stmt->execute([$_SESSION['userid'], $end, $start]);
} }
} }
} } elseif ($isAdmin) {
} $branch = 'admin';
if ($onlyApproved) {
try { $branch = 'admin_onlyApproved';
$vacations = $stmt->fetchAll(); $stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.start_date <= ? AND v.end_date >= ? AND LOWER(TRIM(v.status)) = 'genehmigt' ORDER BY v.start_date");
$stmt->execute([$end, $start]);
// If debug mode is enabled, prepare meta information } else {
if ($debugMode) { if ($includeRejected) {
$rawStatuses = array_map(function($r){ return $r['status'] ?? null; }, $vacations); $stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.start_date <= ? AND v.end_date >= ? ORDER BY v.start_date");
$meta = [ $stmt->execute([$end, $start]);
'branch' => $branch, } else {
'count' => count($vacations), $stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.start_date <= ? AND v.end_date >= ? AND (v.status IS NULL OR LOWER(TRIM(v.status)) IN ('genehmigt','beantragt')) ORDER BY v.start_date");
'raw_statuses' => $rawStatuses $stmt->execute([$end, $start]);
]; }
}
} else {
$branch = 'public_or_regular';
if ($public && $onlyApproved) {
$branch = 'public_onlyApproved';
$stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.start_date <= ? AND v.end_date >= ? AND LOWER(TRIM(v.status)) = 'genehmigt' ORDER BY v.start_date");
$stmt->execute([$end, $start]);
} elseif ($public && $publicAll) {
$branch = 'public_publicAll';
$stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.start_date <= ? AND v.end_date >= ? AND (v.status IS NULL OR LOWER(TRIM(v.status)) IN ('genehmigt','beantragt')) ORDER BY v.start_date");
$stmt->execute([$end, $start]);
} else {
if ($onlyApproved) {
$stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.user_id = ? AND v.start_date <= ? AND v.end_date >= ? AND LOWER(TRIM(v.status)) = 'genehmigt' ORDER BY v.start_date");
$stmt->execute([$_SESSION['userid'], $end, $start]);
} else {
if ($includeRejected) {
$branch = 'regular_includeRejected';
$stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.user_id = ? AND v.start_date <= ? AND v.end_date >= ? ORDER BY v.start_date");
$stmt->execute([$_SESSION['userid'], $end, $start]);
} else {
$branch = 'regular_excludeRejected';
$stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.user_id = ? AND v.start_date <= ? AND v.end_date >= ? AND (v.status IS NULL OR LOWER(TRIM(v.status)) != 'abgelehnt') ORDER BY v.start_date");
$stmt->execute([$_SESSION['userid'], $end, $start]);
}
}
}
} }
foreach ($vacations as $v) { try {
// Normalize status: collapse whitespace (including NBSP), trim, lowercase $vacations = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (isset($v['status'])) {
$normalized = preg_replace('/\s+/u', ' ', $v['status']); if ($debugMode) {
$status = mb_strtolower(trim($normalized)); $rawStatuses = array_map(function($r){ return $r['status'] ?? null; }, $vacations);
} else { $meta = [
$status = ''; 'branch' => $branch,
'count' => count($vacations),
'raw_statuses' => $rawStatuses
];
} }
// Defensive filter: do not expose rejected ('abgelehnt') entries to non-admins
if (!$isAdmin && !$includeRejected && mb_stripos($status, 'abgelehnt') !== false) { foreach ($vacations as $v) {
continue; if (isset($v['status'])) {
$normalized = preg_replace('/\s+/u', ' ', $v['status']);
$status = mb_strtolower(trim($normalized));
} else {
$status = '';
}
if (!$isAdmin && !$includeRejected && mb_stripos($status, 'abgelehnt') !== false) {
continue;
}
$isApproved = (mb_stripos($status, 'genehmigt') !== false);
$employeeName = trim(($v['vorname'] ?? '') . ' ' . ($v['nachname'] ?? ''));
if ($showEmployeeNames && $employeeName !== '') {
$title = $employeeName;
if ($v['status'] !== null && $v['status'] !== '') {
$title .= ' (' . $v['status'] . ')';
}
} elseif ($isApproved) {
$title = 'Urlaub';
} else {
$title = 'Urlaubsantrag';
}
try {
$endInclusive = (new DateTime($v['end_date']))->modify('+1 day')->format('Y-m-d');
} catch (Exception $e) {
$endInclusive = $v['start_date'];
}
$events[] = [
'id' => 'vac_' . $v['id'],
'title' => $title,
'start' => $v['start_date'],
'end' => $endInclusive,
'allDay' => true,
'color' => ($isApproved) ? '#28a745' : '#ffc107',
'extendedProps' => [
'type' => 'user',
'user_id' => $v['user_id'],
'employee_name' => $employeeName,
'status' => $v['status'],
'comment' => $v['comment_user'] ?? ''
]
];
} }
$isApproved = (mb_stripos($status, 'genehmigt') !== false); } catch (Exception $ex) {
if ($isAdmin) { header('Content-Type: application/json; charset=utf-8');
$title = $v['vorname'] . ' ' . $v['nachname'] . ' (' . ($v['status'] ?? 'beantragt') . ')'; $payload = ['error' => $ex->getMessage(), 'branch' => $branch, 'trace' => $ex->getTraceAsString()];
} else { echo json_encode($payload);
$title = $isApproved ? 'Urlaub' : 'Urlaubsantrag'; exit;
}
// Safely compute end date; fallback to start_date if invalid
try {
$endInclusive = (new DateTime($v['end_date']))->modify('+1 day')->format('Y-m-d');
} catch (Exception $e) {
$endInclusive = $v['start_date'];
}
$events[] = [
'id' => 'vac_' . $v['id'],
'title' => $title,
'start' => $v['start_date'],
'end' => $endInclusive,
'allDay' => true,
'color' => ($isApproved) ? '#28a745' : '#ffc107',
'extendedProps' => [
'type' => 'user',
'user_id' => $v['user_id'],
'status' => $v['status'],
'comment' => $v['comment_user'] ?? ''
]
];
} }
} catch (Exception $ex) { } catch (Exception $ex) {
header('Content-Type: application/json; charset=utf-8'); header('Content-Type: application/json; charset=utf-8');
@@ -159,17 +162,9 @@ try {
exit; exit;
} }
} catch (Exception $ex) {
header('Content-Type: application/json; charset=utf-8');
$payload = ['error' => $ex->getMessage(), 'branch' => $branch, 'trace' => $ex->getTraceAsString()];
echo json_encode($payload);
exit;
}
// Company holidays (visible to all)
$stmt = $pdo->prepare("SELECT * FROM company_holidays WHERE start_date <= ? AND end_date >= ? ORDER BY start_date"); $stmt = $pdo->prepare("SELECT * FROM company_holidays WHERE start_date <= ? AND end_date >= ? ORDER BY start_date");
$stmt->execute([$end, $start]); $stmt->execute([$end, $start]);
$holidays = $stmt->fetchAll(); $holidays = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($holidays as $h) { foreach ($holidays as $h) {
$endInclusive = (new DateTime($h['end_date']))->modify('+1 day')->format('Y-m-d'); $endInclusive = (new DateTime($h['end_date']))->modify('+1 day')->format('Y-m-d');
@@ -193,5 +188,4 @@ if ($debugMode) {
} else { } else {
echo json_encode($events); echo json_encode($events);
} }
?> ?>
+103 -34
View File
@@ -2,52 +2,115 @@
session_start(); session_start();
require_once('inc/config.inc.php'); require_once('inc/config.inc.php');
require_once('inc/functions.inc.php'); require_once('inc/functions.inc.php');
require_once(__DIR__ . '/../inc/company_holiday_sync.inc.php');
$user = check_user(); $user = check_user();
if (!is_admin_user()) { if (!is_admin_user()) {
die('Zugriff verweigert. Nur Chefs dürfen Betriebsurlaub verwalten.'); die('Zugriff verweigert. Nur Chefs duerfen Betriebsurlaub verwalten.');
} }
// Create table if not exists (optional helper) $error = '';
// Administrators can also run the SQL directly in DB. This is just a convenience. $schemaError = '';
try {
vacationSyncEnsureSchema($pdo);
} catch (Throwable $e) {
$schemaError = 'Die Seite konnte das Urlaubsschema nicht automatisch aktualisieren: ' . $e->getMessage();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['start_date']) && isset($_POST['end_date'])) { if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['start_date']) && isset($_POST['end_date'])) {
$start = $_POST['start_date']; $start = trim((string)($_POST['start_date'] ?? ''));
$end = $_POST['end_date']; $end = trim((string)($_POST['end_date'] ?? ''));
$desc = trim($_POST['description'] ?? 'Betriebsurlaub'); $desc = trim((string)($_POST['description'] ?? 'Betriebsurlaub'));
$vertretung = trim((string)($_POST['vertretung'] ?? ''));
$vertretertelefon = trim((string)($_POST['vertretertelefon'] ?? ''));
$vertreteradresse = trim((string)($_POST['vertreteradresse'] ?? ''));
$vertreterurl = trim((string)($_POST['vertreterurl'] ?? ''));
$stmt = $pdo->prepare("INSERT INTO company_holidays (start_date, end_date, description, created_by) VALUES (?, ?, ?, ?)"); if ($start === '' || $end === '') {
$stmt->execute([$start, $end, $desc, $_SESSION['userid']]); $error = 'Bitte Start- und Enddatum angeben.';
} elseif ($start > $end) {
$error = 'Das Enddatum darf nicht vor dem Startdatum liegen.';
} elseif ($vertretung === '' || $vertretertelefon === '' || $vertreteradresse === '' || $vertreterurl === '') {
$error = 'Bitte alle Vertreterinformationen vollstaendig ausfuellen.';
} elseif ($schemaError !== '') {
$error = $schemaError;
} else {
$stmt = $pdo->prepare("
INSERT INTO company_holidays (
start_date, end_date, description, vertretung, vertretertelefon, vertreteradresse, vertreterurl, created_by
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$start,
$end,
$desc,
$vertretung,
$vertretertelefon,
$vertreteradresse,
$vertreterurl,
$_SESSION['userid']
]);
vacationSyncUrlaubFromCompanyHoliday($pdo, (int)$pdo->lastInsertId());
header('Location: company_holidays.php'); header('Location: company_holidays.php');
exit(); exit();
}
} }
include 'header.php'; include 'header.php';
$stmt = $pdo->prepare("SELECT * FROM company_holidays ORDER BY start_date DESC"); $stmt = $pdo->prepare("SELECT * FROM company_holidays ORDER BY start_date DESC");
$stmt->execute(); $stmt->execute();
$holidays = $stmt->fetchAll(); $holidays = $stmt->fetchAll(PDO::FETCH_ASSOC);
?> ?>
<div class="container"> <div class="container">
<h2>Betriebsurlaub verwalten</h2> <h2>Betriebsurlaub verwalten</h2>
<form method="post" class="form-inline mb-3"> <?php if ($error !== ''): ?>
<div class="form-group mr-2"> <div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
<label>Von:</label> <?php endif; ?>
<input type="date" name="start_date" class="form-control" required> <?php if ($schemaError !== ''): ?>
<div class="alert alert-warning"><?php echo htmlspecialchars($schemaError); ?></div>
<?php endif; ?>
<form method="post" class="mb-3">
<div class="form-row">
<div class="form-group col-md-3">
<label>Von:</label>
<input type="date" name="start_date" class="form-control" required>
</div>
<div class="form-group col-md-3">
<label>Bis:</label>
<input type="date" name="end_date" class="form-control" required>
</div>
<div class="form-group col-md-6">
<label>Beschreibung:</label>
<input type="text" name="description" class="form-control" placeholder="z. B. Betriebsurlaub Weihnachten">
</div>
</div> </div>
<div class="form-group mr-2"> <div class="form-row">
<label>Bis:</label> <div class="form-group col-md-6">
<input type="date" name="end_date" class="form-control" required> <label>Vertretung:</label>
<input type="text" name="vertretung" class="form-control" required>
</div>
<div class="form-group col-md-6">
<label>Vertretung Telefon:</label>
<input type="text" name="vertretertelefon" class="form-control" required>
</div>
</div> </div>
<div class="form-group mr-2"> <div class="form-row">
<label>Beschreibung:</label> <div class="form-group col-md-8">
<input type="text" name="description" class="form-control" placeholder="z. B. Betriebsurlaub Weihnachten"> <label>Vertretung Adresse:</label>
<input type="text" name="vertreteradresse" class="form-control" required>
</div>
<div class="form-group col-md-4">
<label>Vertretung Webseite:</label>
<input type="text" name="vertreterurl" class="form-control" required>
</div>
</div> </div>
<button class="btn btn-primary">Hinzufügen</button> <button class="btn btn-primary">Hinzufuegen</button>
</form> </form>
<table class="table table-bordered"> <table class="table table-bordered">
@@ -56,6 +119,8 @@ $holidays = $stmt->fetchAll();
<th>Von</th> <th>Von</th>
<th>Bis</th> <th>Bis</th>
<th>Beschreibung</th> <th>Beschreibung</th>
<th>Vertretung</th>
<th>Kontakt</th>
<th>Erstellt von</th> <th>Erstellt von</th>
<th>Aktion</th> <th>Aktion</th>
</tr> </tr>
@@ -63,19 +128,25 @@ $holidays = $stmt->fetchAll();
<tbody> <tbody>
<?php foreach ($holidays as $h): ?> <?php foreach ($holidays as $h): ?>
<tr> <tr>
<td><?php echo $h['start_date']; ?></td> <td><?php echo htmlspecialchars((string)$h['start_date']); ?></td>
<td><?php echo $h['end_date']; ?></td> <td><?php echo htmlspecialchars((string)$h['end_date']); ?></td>
<td><?php echo htmlspecialchars($h['description']); ?></td> <td><?php echo htmlspecialchars((string)$h['description']); ?></td>
<td><?php echo htmlspecialchars((string)$h['vertretung']); ?></td>
<td>
<?php echo htmlspecialchars((string)$h['vertretertelefon']); ?><br>
<?php echo htmlspecialchars((string)$h['vertreteradresse']); ?><br>
<?php echo htmlspecialchars((string)$h['vertreterurl']); ?>
</td>
<td><?php <td><?php
$s = $pdo->prepare("SELECT vorname, nachname FROM users WHERE id = ?"); $s = $pdo->prepare("SELECT vorname, nachname FROM users WHERE id = ?");
$s->execute([$h['created_by']]); $s->execute([$h['created_by']]);
$u = $s->fetch(); $u = $s->fetch(PDO::FETCH_ASSOC);
echo htmlspecialchars($u['vorname'] . ' ' . $u['nachname']); echo htmlspecialchars(trim(($u['vorname'] ?? '') . ' ' . ($u['nachname'] ?? '')));
?></td> ?></td>
<td> <td>
<form method="post" action="deleteCompanyHoliday.php" onsubmit="return confirm('Betriebsurlaub wirklich löschen?');"> <form method="post" action="deleteCompanyHoliday.php" onsubmit="return confirm('Betriebsurlaub wirklich loeschen?');">
<input type="hidden" name="id" value="<?php echo intval($h['id']); ?>"> <input type="hidden" name="id" value="<?php echo (int)$h['id']; ?>">
<button class="btn btn-sm btn-danger">Löschen</button> <button class="btn btn-sm btn-danger">Loeschen</button>
</form> </form>
</td> </td>
</tr> </tr>
@@ -85,6 +156,4 @@ $holidays = $stmt->fetchAll();
</div> </div>
<?php include 'footer.php'; <?php include 'footer.php'; ?>
?>
+2
View File
@@ -2,6 +2,7 @@
session_start(); session_start();
require_once('inc/config.inc.php'); require_once('inc/config.inc.php');
require_once('inc/functions.inc.php'); require_once('inc/functions.inc.php');
require_once(__DIR__ . '/../inc/company_holiday_sync.inc.php');
$user = check_user(); $user = check_user();
if (!is_admin_user()) { if (!is_admin_user()) {
@@ -18,6 +19,7 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['id'])) {
$id = intval($_POST['id']); $id = intval($_POST['id']);
vacationSyncDeleteUrlaubByCompanyHoliday($pdo, $id);
$stmt = $pdo->prepare("DELETE FROM company_holidays WHERE id = ?"); $stmt = $pdo->prepare("DELETE FROM company_holidays WHERE id = ?");
$stmt->execute([$id]); $stmt->execute([$id]);
+3 -7
View File
@@ -13,26 +13,22 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['id'])) {
$id = (int)$_POST['id']; $id = (int)$_POST['id'];
$referer = $_POST['referer'] ?? 'urlaubsantrag.php'; $referer = $_POST['referer'] ?? 'urlaubsantrag.php';
// Fetch vacation to verify ownership
$stmt = $pdo->prepare("SELECT user_id, status FROM vacations WHERE id = ?"); $stmt = $pdo->prepare("SELECT user_id, status FROM vacations WHERE id = ?");
$stmt->execute([$id]); $stmt->execute([$id]);
$vac = $stmt->fetch(); $vac = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$vac) { if (!$vac) {
die('Urlaubseintrag nicht gefunden.'); die('Urlaubseintrag nicht gefunden.');
} }
$isAdmin = is_admin_user(); $canManageTeamVacations = can_manage_team_vacations();
if (!$canManageTeamVacations && (int)$vac['user_id'] !== (int)$_SESSION['userid']) {
if (!$isAdmin && $vac['user_id'] != $_SESSION['userid']) {
die('Zugriff verweigert.'); die('Zugriff verweigert.');
} }
// Allow deletion for admins or owner
$del = $pdo->prepare("DELETE FROM vacations WHERE id = ?"); $del = $pdo->prepare("DELETE FROM vacations WHERE id = ?");
$del->execute([$id]); $del->execute([$id]);
header('Location: ' . $referer); header('Location: ' . $referer);
exit(); exit();
?> ?>
+9
View File
@@ -145,6 +145,15 @@ function is_admin_user() {
$statement->execute(array('id' => $_SESSION['userid'])); $statement->execute(array('id' => $_SESSION['userid']));
return ($statement->rowCount() == 1); return ($statement->rowCount() == 1);
} }
/**
* Returns true when the user may manage vacations for the team.
* In the current Zeiterfassung, team leads are represented by the
* existing admin role.
*/
function can_manage_team_vacations() {
return is_admin_user();
}
/** /**
* Prüft, ob der Benutzer Bearbeiter ist * Prüft, ob der Benutzer Bearbeiter ist
*/ */
+114 -51
View File
@@ -9,9 +9,23 @@ if (!isset($_SESSION['userid'])) {
die("Kein Benutzer angemeldet."); die("Kein Benutzer angemeldet.");
} }
$user_id = $_SESSION['userid']; $user_id = (int)$_SESSION['userid'];
$canManageTeamVacations = can_manage_team_vacations();
$message = ""; $message = "";
$error = ""; $error = "";
$selected_user_id = $user_id;
$selectableUsers = [];
if ($canManageTeamVacations) {
$stmtUsers = $pdo->prepare("
SELECT id, vorname, nachname, email
FROM users
WHERE zeiterfassung = 1
ORDER BY nachname, vorname
");
$stmtUsers->execute();
$selectableUsers = $stmtUsers->fetchAll(PDO::FETCH_ASSOC);
}
function calculateWorkingDays($start, $end) { function calculateWorkingDays($start, $end) {
$start = new DateTime($start); $start = new DateTime($start);
@@ -22,9 +36,8 @@ function calculateWorkingDays($start, $end) {
$period = new DatePeriod($start, $interval, $end); $period = new DatePeriod($start, $interval, $end);
$workingDays = 0; $workingDays = 0;
foreach ($period as $day) { foreach ($period as $day) {
if ($day->format('N') < 6) { // 1 (Mo) - 5 (Fr) if ($day->format('N') < 6) {
$workingDays++; $workingDays++;
} }
} }
@@ -32,48 +45,68 @@ function calculateWorkingDays($start, $end) {
return $workingDays; return $workingDays;
} }
if ($_SERVER["REQUEST_METHOD"] == "POST") { if ($_SERVER["REQUEST_METHOD"] === "POST") {
$start_date = trim((string)($_POST['start_date'] ?? ''));
$end_date = trim((string)($_POST['end_date'] ?? ''));
$comment = trim((string)($_POST['comment'] ?? ''));
$selected_user_id = $canManageTeamVacations ? (int)($_POST['user_id'] ?? $user_id) : $user_id;
$start_date = $_POST['start_date']; $selectedUser = null;
$end_date = $_POST['end_date']; if ($selected_user_id <= 0) {
$comment = trim($_POST['comment']); $error = "Bitte einen Mitarbeiter auswaehlen.";
if (empty($start_date) || empty($end_date)) {
$error = "Bitte beide Datumsfelder ausfüllen.";
} elseif ($start_date > $end_date) {
$error = "Enddatum liegt vor dem Startdatum.";
} elseif ($start_date < date("Y-m-d")) {
$error = "Urlaub kann nicht in der Vergangenheit beantragt werden.";
} else { } else {
$stmtSelectedUser = $pdo->prepare("
// Überschneidung prüfen SELECT id, vorname, nachname
$stmt = $pdo->prepare(" FROM users
SELECT COUNT(*) FROM vacations WHERE id = ?
WHERE user_id = ? AND zeiterfassung = 1
AND status != 'abgelehnt' LIMIT 1
AND (
(start_date BETWEEN ? AND ?)
OR (end_date BETWEEN ? AND ?)
OR (? BETWEEN start_date AND end_date)
)
"); ");
$stmt->execute([$user_id, $start_date, $end_date, $start_date, $end_date, $start_date]); $stmtSelectedUser->execute([$selected_user_id]);
$exists = $stmt->fetchColumn(); $selectedUser = $stmtSelectedUser->fetch(PDO::FETCH_ASSOC);
if (!$selectedUser) {
$error = "Der ausgewaehlte Mitarbeiter wurde nicht gefunden.";
}
}
if ($error === "" && ($start_date === '' || $end_date === '')) {
$error = "Bitte beide Datumsfelder ausfuellen.";
} elseif ($error === "" && $start_date > $end_date) {
$error = "Enddatum liegt vor dem Startdatum.";
} elseif ($error === "" && $start_date < date("Y-m-d")) {
$error = "Urlaub kann nicht in der Vergangenheit beantragt werden.";
} elseif ($error === "") {
$stmt = $pdo->prepare("
SELECT COUNT(*)
FROM vacations
WHERE user_id = ?
AND status != 'abgelehnt'
AND (
(start_date BETWEEN ? AND ?)
OR (end_date BETWEEN ? AND ?)
OR (? BETWEEN start_date AND end_date)
)
");
$stmt->execute([$selected_user_id, $start_date, $end_date, $start_date, $end_date, $start_date]);
$exists = (int)$stmt->fetchColumn();
if ($exists > 0) { if ($exists > 0) {
$error = "Der Zeitraum überschneidet sich mit einem bestehenden Antrag."; $error = "Der Zeitraum ueberschneidet sich mit einem bestehenden Antrag.";
} else { } else {
$days = calculateWorkingDays($start_date, $end_date); $days = calculateWorkingDays($start_date, $end_date);
$insert = $pdo->prepare(" $insert = $pdo->prepare("
INSERT INTO vacations (user_id, start_date, end_date, days, comment_user) INSERT INTO vacations (user_id, start_date, end_date, days, comment_user)
VALUES (?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?)
"); ");
$insert->execute([$selected_user_id, $start_date, $end_date, $days, $comment]);
$insert->execute([$user_id, $start_date, $end_date, $days, $comment]); if ($selected_user_id !== $user_id && $selectedUser) {
$message = "Urlaub fuer " . $selectedUser['vorname'] . " " . $selectedUser['nachname'] . " erfolgreich eingereicht ($days Werktage).";
$message = "Urlaubsantrag erfolgreich eingereicht ($days Werktage)."; } else {
$message = "Urlaubsantrag erfolgreich eingereicht ($days Werktage).";
}
} }
} }
} }
@@ -88,15 +121,29 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
<h2>Urlaubsantrag</h2> <h2>Urlaubsantrag</h2>
<?php if ($error): ?> <?php if ($error): ?>
<div class="alert alert-danger"><?php echo $error; ?></div> <div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?> <?php endif; ?>
<?php if ($message): ?> <?php if ($message): ?>
<div class="alert alert-success"><?php echo $message; ?></div> <div class="alert alert-success"><?php echo htmlspecialchars($message); ?></div>
<?php endif; ?> <?php endif; ?>
<form method="post"> <form method="post">
<?php if ($canManageTeamVacations): ?>
<div class="form-group">
<label>Mitarbeiter:</label>
<select name="user_id" class="form-control" required>
<?php foreach ($selectableUsers as $employee): ?>
<?php $employeeId = (int)$employee['id']; ?>
<option value="<?php echo $employeeId; ?>" <?php echo ($selected_user_id === $employeeId) ? 'selected' : ''; ?>>
<?php echo htmlspecialchars(trim($employee['nachname'] . ', ' . $employee['vorname'] . ' | ' . $employee['email'])); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
<div class="form-group"> <div class="form-group">
<label>Von:</label> <label>Von:</label>
<input type="date" name="start_date" class="form-control" required> <input type="date" name="start_date" class="form-control" required>
@@ -115,27 +162,40 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
<br> <br>
<button type="submit" class="btn btn-primary btn-block"> <button type="submit" class="btn btn-primary btn-block">
Urlaub beantragen <?php echo $canManageTeamVacations ? 'Urlaub eintragen' : 'Urlaub beantragen'; ?>
</button> </button>
</form> </form>
<hr> <hr>
<h4>Meine Anträge</h4> <h4><?php echo $canManageTeamVacations ? 'Urlaubseintraege' : 'Meine Antraege'; ?></h4>
<?php <?php
$stmt = $pdo->prepare(" $listSql = "
SELECT * FROM vacations SELECT v.*, u.vorname, u.nachname
WHERE user_id = ? FROM vacations v
ORDER BY created_at DESC JOIN users u ON u.id = v.user_id
"); ";
$stmt->execute([$user_id]);
$antraege = $stmt->fetchAll(); if ($canManageTeamVacations) {
$listSql .= " ORDER BY v.created_at DESC";
$stmt = $pdo->prepare($listSql);
$stmt->execute();
} else {
$listSql .= " WHERE v.user_id = ? ORDER BY v.created_at DESC";
$stmt = $pdo->prepare($listSql);
$stmt->execute([$user_id]);
}
$antraege = $stmt->fetchAll(PDO::FETCH_ASSOC);
?> ?>
<table class="table table-bordered"> <table class="table table-bordered">
<tr> <tr>
<?php if ($canManageTeamVacations): ?>
<th>Mitarbeiter</th>
<?php endif; ?>
<th>Von</th> <th>Von</th>
<th>Bis</th> <th>Bis</th>
<th>Tage</th> <th>Tage</th>
@@ -145,14 +205,17 @@ $antraege = $stmt->fetchAll();
<?php foreach ($antraege as $a): ?> <?php foreach ($antraege as $a): ?>
<tr> <tr>
<td><?php echo $a['start_date']; ?></td> <?php if ($canManageTeamVacations): ?>
<td><?php echo $a['end_date']; ?></td> <td><?php echo htmlspecialchars(trim($a['vorname'] . ' ' . $a['nachname'])); ?></td>
<td><?php echo $a['days']; ?></td> <?php endif; ?>
<td><?php echo htmlspecialchars((string)$a['start_date']); ?></td>
<td><?php echo htmlspecialchars((string)$a['end_date']); ?></td>
<td><?php echo (int)$a['days']; ?></td>
<td> <td>
<?php <?php
if ($a['status'] == 'beantragt') { if ($a['status'] === 'beantragt' || $a['status'] === null || $a['status'] === '') {
echo '<span class="badge badge-warning">Beantragt</span>'; echo '<span class="badge badge-warning">Beantragt</span>';
} elseif ($a['status'] == 'genehmigt') { } elseif ($a['status'] === 'genehmigt') {
echo '<span class="badge badge-success">Genehmigt</span>'; echo '<span class="badge badge-success">Genehmigt</span>';
} else { } else {
echo '<span class="badge badge-danger">Abgelehnt</span>'; echo '<span class="badge badge-danger">Abgelehnt</span>';
@@ -160,10 +223,10 @@ $antraege = $stmt->fetchAll();
?> ?>
</td> </td>
<td> <td>
<form method="post" action="deleteVacation.php" onsubmit="return confirm('Wirklich löschen?');"> <form method="post" action="deleteVacation.php" onsubmit="return confirm('Wirklich loeschen?');">
<input type="hidden" name="id" value="<?php echo $a['id']; ?>"> <input type="hidden" name="id" value="<?php echo (int)$a['id']; ?>">
<input type="hidden" name="referer" value="urlaubsantrag.php"> <input type="hidden" name="referer" value="urlaubsantrag.php">
<button type="submit" class="btn btn-sm btn-danger">Löschen</button> <button type="submit" class="btn btn-sm btn-danger">Loeschen</button>
</form> </form>
</td> </td>
</tr> </tr>
+1
View File
@@ -46,6 +46,7 @@ document.addEventListener('DOMContentLoaded', function() {
var props = ev.extendedProps; var props = ev.extendedProps;
var html = '<strong>' + ev.title + '</strong><br>' + ev.start.toLocaleDateString() + ' - ' + (new Date(ev.end).toLocaleDateString()) + '<br>'; var html = '<strong>' + ev.title + '</strong><br>' + ev.start.toLocaleDateString() + ' - ' + (new Date(ev.end).toLocaleDateString()) + '<br>';
if (props.type === 'user') { if (props.type === 'user') {
html += 'Mitarbeiter: ' + (props.employee_name || '') + '<br>';
html += 'Status: ' + (props.status || '') + '<br>'; html += 'Status: ' + (props.status || '') + '<br>';
html += 'Kommentar: ' + (props.comment || '') + '<br>'; html += 'Kommentar: ' + (props.comment || '') + '<br>';
} else if (props.type === 'company') { } else if (props.type === 'company') {
+6 -2
View File
@@ -3,7 +3,6 @@ session_start();
require_once('inc/config.inc.php'); require_once('inc/config.inc.php');
require_once('inc/functions.inc.php'); require_once('inc/functions.inc.php');
// allow any logged-in user to view the team calendar (read-only)
$user = check_user(); $user = check_user();
include 'header.php'; include 'header.php';
@@ -15,6 +14,7 @@ include 'header.php';
<br> <br>
<div> <div>
<span class="badge badge-success">genehmigt</span> <span class="badge badge-success">genehmigt</span>
<span class="badge badge-warning">beantragt</span>
<span class="badge badge-primary">Betriebsurlaub</span> <span class="badge badge-primary">Betriebsurlaub</span>
</div> </div>
<br> <br>
@@ -43,7 +43,11 @@ document.addEventListener('DOMContentLoaded', function() {
var props = ev.extendedProps; var props = ev.extendedProps;
var html = '<strong>' + ev.title + '</strong><br>' + ev.start.toLocaleDateString() + ' - ' + (new Date(ev.end).toLocaleDateString()) + '<br>'; var html = '<strong>' + ev.title + '</strong><br>' + ev.start.toLocaleDateString() + ' - ' + (new Date(ev.end).toLocaleDateString()) + '<br>';
if (props.type === 'user') { if (props.type === 'user') {
html += 'Mitarbeiter-ID: ' + (props.user_id || '') + '<br>'; html += 'Mitarbeiter: ' + (props.employee_name || ev.title || '') + '<br>';
html += 'Status: ' + (props.status || '') + '<br>';
if (props.comment) {
html += 'Kommentar: ' + props.comment + '<br>';
}
} else if (props.type === 'company') { } else if (props.type === 'company') {
html += 'Beschreibung: ' + (props.description || '') + '<br>'; html += 'Beschreibung: ' + (props.description || '') + '<br>';
} }