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
}
+62 -37
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,40 +657,64 @@ 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>";
}else if (($_POST["aktion"] ?? '') == "6") { }else if (($_POST["aktion"] ?? '') == "6") {
@@ -2023,10 +2048,10 @@ if(!check_worker()){
<script type="text/javascript"> <script type="text/javascript">
function AddneueTermine(){ function AddneueTermine(){
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;
+127 -97
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) {
@@ -1257,5 +1287,5 @@ try {
</div> </div>
<?php include __DIR__ . "/templates/footer.inc.php"; ?> <?php include __DIR__ . "/templates/footer.inc.php"; ?>
+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
{ {
+191 -197
View File
@@ -1,197 +1,191 @@
<?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');
ini_set('display_errors', '1');
// Enable full error reporting for API debugging ini_set('display_startup_errors', '1');
ini_set('display_errors', '1'); error_reporting(E_ALL);
ini_set('display_startup_errors', '1');
error_reporting(E_ALL); $user = check_user();
$isAdmin = is_admin_user();
$user = check_user();
$isAdmin = is_admin_user(); $start = $_GET['start'] ?? null;
$end = $_GET['end'] ?? null;
$start = $_GET['start'] ?? null; // expected ISO date $onlyApproved = isset($_GET['only_approved']) && ($_GET['only_approved'] == '1' || $_GET['only_approved'] === 'true');
$end = $_GET['end'] ?? null; $public = isset($_GET['public']) && ($_GET['public'] == '1' || $_GET['public'] === 'true');
$onlyApproved = isset($_GET['only_approved']) && ($_GET['only_approved'] == '1' || $_GET['only_approved'] === 'true'); $includeRejected = isset($_GET['include_rejected']) && ($_GET['include_rejected'] == '1' || $_GET['include_rejected'] === 'true');
// public allows non-admin users to request all *approved* vacations (team view) $onlyPersonal = isset($_GET['only_personal']) && ($_GET['only_personal'] == '1' || $_GET['only_personal'] === 'true');
$public = isset($_GET['public']) && ($_GET['public'] == '1' || $_GET['public'] === 'true'); $publicAll = isset($_GET['public_all']) && ($_GET['public_all'] == '1' || $_GET['public_all'] === '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'); if (!$start || !$end) {
// only_personal forces the API to return only the current user's vacations (useful even if the user is admin) http_response_code(400);
$onlyPersonal = isset($_GET['only_personal']) && ($_GET['only_personal'] == '1' || $_GET['only_personal'] === 'true'); echo json_encode(['error' => 'start and end required']);
// public_all when used with public=1 returns all non-rejected team vacations (approved + beantragt) exit;
$publicAll = isset($_GET['public_all']) && ($_GET['public_all'] == '1' || $_GET['public_all'] === 'true'); }
if (!$start || !$end) { $events = [];
http_response_code(400); try {
echo json_encode(['error' => 'start and end required']); $branch = 'unknown';
exit; $debugMode = isset($_GET['debug']) && ($_GET['debug'] == '1' || $_GET['debug'] === 'true');
} $showEmployeeNames = $isAdmin || $public;
$events = []; if ($onlyPersonal) {
try { $branch = 'onlyPersonal';
$branch = 'unknown'; if ($onlyApproved) {
$debugMode = isset($_GET['debug']) && ($_GET['debug'] == '1' || $_GET['debug'] === 'true'); $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");
// Vacations: support a personal-only mode, admin mode, and public/team mode $stmt->execute([$_SESSION['userid'], $end, $start]);
if ($onlyPersonal) { } else {
$branch = 'onlyPersonal'; if ($includeRejected) {
if ($onlyApproved) { $branch = 'onlyPersonal_includeRejected';
$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 >= ? 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 { $branch = 'onlyPersonal_excludeRejected';
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.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");
$branch = 'onlyPersonal_includeRejected'; $stmt->execute([$_SESSION['userid'], $end, $start]);
$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 { } elseif ($isAdmin) {
$branch = 'onlyPersonal_excludeRejected'; $branch = 'admin';
$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"); if ($onlyApproved) {
$stmt->execute([$_SESSION['userid'], $end, $start]); $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]);
} elseif ($isAdmin) { } else {
$branch = 'admin'; if ($includeRejected) {
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.start_date <= ? AND v.end_date >= ? ORDER BY v.start_date");
$branch = 'admin_onlyApproved'; $stmt->execute([$end, $start]);
$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"); } else {
$stmt->execute([$end, $start]); $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");
} else { $stmt->execute([$end, $start]);
// 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"); } else {
$stmt->execute([$end, $start]); $branch = 'public_or_regular';
} else { if ($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 (v.status IS NULL OR LOWER(TRIM(v.status)) IN ('genehmigt','beantragt')) ORDER BY v.start_date"); $branch = 'public_onlyApproved';
$stmt->execute([$end, $start]); $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) {
} else { $branch = 'public_publicAll';
$branch = 'public_or_regular'; $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");
if ($public && $onlyApproved) { $stmt->execute([$end, $start]);
$branch = 'public_onlyApproved'; } else {
// public team view: show all approved vacations (read-only) 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.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([$end, $start]); $stmt->execute([$_SESSION['userid'], $end, $start]);
} elseif ($public && $publicAll) { } else {
$branch = 'public_publicAll'; if ($includeRejected) {
// public team view: explicitly show only approved (genehmigt) and pending (beantragt) vacations $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.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 = $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([$end, $start]); $stmt->execute([$_SESSION['userid'], $end, $start]);
} else { } else {
if ($onlyApproved) { $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 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 (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]);
} else { }
// By default exclude rejected ('abgelehnt') for regular users; include if include_rejected=1 }
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]); try {
} else { $vacations = $stmt->fetchAll(PDO::FETCH_ASSOC);
$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"); if ($debugMode) {
$stmt->execute([$_SESSION['userid'], $end, $start]); $rawStatuses = array_map(function($r){ return $r['status'] ?? null; }, $vacations);
} $meta = [
} 'branch' => $branch,
} 'count' => count($vacations),
} 'raw_statuses' => $rawStatuses
];
try { }
$vacations = $stmt->fetchAll();
foreach ($vacations as $v) {
// If debug mode is enabled, prepare meta information if (isset($v['status'])) {
if ($debugMode) { $normalized = preg_replace('/\s+/u', ' ', $v['status']);
$rawStatuses = array_map(function($r){ return $r['status'] ?? null; }, $vacations); $status = mb_strtolower(trim($normalized));
$meta = [ } else {
'branch' => $branch, $status = '';
'count' => count($vacations), }
'raw_statuses' => $rawStatuses
]; if (!$isAdmin && !$includeRejected && mb_stripos($status, 'abgelehnt') !== false) {
} continue;
}
foreach ($vacations as $v) {
// Normalize status: collapse whitespace (including NBSP), trim, lowercase $isApproved = (mb_stripos($status, 'genehmigt') !== false);
if (isset($v['status'])) { $employeeName = trim(($v['vorname'] ?? '') . ' ' . ($v['nachname'] ?? ''));
$normalized = preg_replace('/\s+/u', ' ', $v['status']); if ($showEmployeeNames && $employeeName !== '') {
$status = mb_strtolower(trim($normalized)); $title = $employeeName;
} else { if ($v['status'] !== null && $v['status'] !== '') {
$status = ''; $title .= ' (' . $v['status'] . ')';
} }
// Defensive filter: do not expose rejected ('abgelehnt') entries to non-admins } elseif ($isApproved) {
if (!$isAdmin && !$includeRejected && mb_stripos($status, 'abgelehnt') !== false) { $title = 'Urlaub';
continue; } else {
} $title = 'Urlaubsantrag';
$isApproved = (mb_stripos($status, 'genehmigt') !== false); }
if ($isAdmin) {
$title = $v['vorname'] . ' ' . $v['nachname'] . ' (' . ($v['status'] ?? 'beantragt') . ')'; try {
} else { $endInclusive = (new DateTime($v['end_date']))->modify('+1 day')->format('Y-m-d');
$title = $isApproved ? 'Urlaub' : 'Urlaubsantrag'; } catch (Exception $e) {
} $endInclusive = $v['start_date'];
// Safely compute end date; fallback to start_date if invalid }
try {
$endInclusive = (new DateTime($v['end_date']))->modify('+1 day')->format('Y-m-d'); $events[] = [
} catch (Exception $e) { 'id' => 'vac_' . $v['id'],
$endInclusive = $v['start_date']; 'title' => $title,
} 'start' => $v['start_date'],
$events[] = [ 'end' => $endInclusive,
'id' => 'vac_' . $v['id'], 'allDay' => true,
'title' => $title, 'color' => ($isApproved) ? '#28a745' : '#ffc107',
'start' => $v['start_date'], 'extendedProps' => [
'end' => $endInclusive, 'type' => 'user',
'allDay' => true, 'user_id' => $v['user_id'],
'color' => ($isApproved) ? '#28a745' : '#ffc107', 'employee_name' => $employeeName,
'extendedProps' => [ 'status' => $v['status'],
'type' => 'user', 'comment' => $v['comment_user'] ?? ''
'user_id' => $v['user_id'], ]
'status' => $v['status'], ];
'comment' => $v['comment_user'] ?? '' }
] } catch (Exception $ex) {
]; header('Content-Type: application/json; charset=utf-8');
} $payload = ['error' => $ex->getMessage(), 'branch' => $branch, 'trace' => $ex->getTraceAsString()];
} catch (Exception $ex) { echo json_encode($payload);
header('Content-Type: application/json; charset=utf-8'); exit;
$payload = ['error' => $ex->getMessage(), 'branch' => $branch, 'trace' => $ex->getTraceAsString()]; }
echo json_encode($payload); } catch (Exception $ex) {
exit; header('Content-Type: application/json; charset=utf-8');
} $payload = ['error' => $ex->getMessage(), 'branch' => $branch, 'trace' => $ex->getTraceAsString()];
echo json_encode($payload);
} catch (Exception $ex) { exit;
header('Content-Type: application/json; charset=utf-8'); }
$payload = ['error' => $ex->getMessage(), 'branch' => $branch, 'trace' => $ex->getTraceAsString()];
echo json_encode($payload); $stmt = $pdo->prepare("SELECT * FROM company_holidays WHERE start_date <= ? AND end_date >= ? ORDER BY start_date");
exit; $stmt->execute([$end, $start]);
} $holidays = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Company holidays (visible to all) foreach ($holidays as $h) {
$stmt = $pdo->prepare("SELECT * FROM company_holidays WHERE start_date <= ? AND end_date >= ? ORDER BY start_date"); $endInclusive = (new DateTime($h['end_date']))->modify('+1 day')->format('Y-m-d');
$stmt->execute([$end, $start]); $events[] = [
$holidays = $stmt->fetchAll(); 'id' => 'com_' . $h['id'],
'title' => $h['description'] ?: 'Betriebsurlaub',
foreach ($holidays as $h) { 'start' => $h['start_date'],
$endInclusive = (new DateTime($h['end_date']))->modify('+1 day')->format('Y-m-d'); 'end' => $endInclusive,
$events[] = [ 'allDay' => true,
'id' => 'com_' . $h['id'], 'color' => '#007bff',
'title' => $h['description'] ?: 'Betriebsurlaub', 'extendedProps' => [
'start' => $h['start_date'], 'type' => 'company',
'end' => $endInclusive, 'description' => $h['description']
'allDay' => true, ]
'color' => '#007bff', ];
'extendedProps' => [ }
'type' => 'company',
'description' => $h['description'] header('Content-Type: application/json; charset=utf-8');
] if ($debugMode) {
]; echo json_encode(['events' => $events, 'meta' => $meta]);
} } else {
echo json_encode($events);
header('Content-Type: application/json; charset=utf-8'); }
if ($debugMode) { ?>
echo json_encode(['events' => $events, 'meta' => $meta]);
} else {
echo json_encode($events);
}
?>
+159 -90
View File
@@ -1,90 +1,159 @@
<?php <?php
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();
if (!is_admin_user()) { $user = check_user();
die('Zugriff verweigert. Nur Chefs dürfen Betriebsurlaub verwalten.'); if (!is_admin_user()) {
} die('Zugriff verweigert. Nur Chefs duerfen Betriebsurlaub verwalten.');
}
// Create table if not exists (optional helper)
// Administrators can also run the SQL directly in DB. This is just a convenience. $error = '';
$schemaError = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['start_date']) && isset($_POST['end_date'])) {
$start = $_POST['start_date']; try {
$end = $_POST['end_date']; vacationSyncEnsureSchema($pdo);
$desc = trim($_POST['description'] ?? 'Betriebsurlaub'); } catch (Throwable $e) {
$schemaError = 'Die Seite konnte das Urlaubsschema nicht automatisch aktualisieren: ' . $e->getMessage();
$stmt = $pdo->prepare("INSERT INTO company_holidays (start_date, end_date, description, created_by) VALUES (?, ?, ?, ?)"); }
$stmt->execute([$start, $end, $desc, $_SESSION['userid']]);
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['start_date']) && isset($_POST['end_date'])) {
header('Location: company_holidays.php'); $start = trim((string)($_POST['start_date'] ?? ''));
exit(); $end = trim((string)($_POST['end_date'] ?? ''));
} $desc = trim((string)($_POST['description'] ?? 'Betriebsurlaub'));
$vertretung = trim((string)($_POST['vertretung'] ?? ''));
include 'header.php'; $vertretertelefon = trim((string)($_POST['vertretertelefon'] ?? ''));
$vertreteradresse = trim((string)($_POST['vertreteradresse'] ?? ''));
$stmt = $pdo->prepare("SELECT * FROM company_holidays ORDER BY start_date DESC"); $vertreterurl = trim((string)($_POST['vertreterurl'] ?? ''));
$stmt->execute();
$holidays = $stmt->fetchAll(); if ($start === '' || $end === '') {
$error = 'Bitte Start- und Enddatum angeben.';
?> } elseif ($start > $end) {
$error = 'Das Enddatum darf nicht vor dem Startdatum liegen.';
<div class="container"> } elseif ($vertretung === '' || $vertretertelefon === '' || $vertreteradresse === '' || $vertreterurl === '') {
<h2>Betriebsurlaub verwalten</h2> $error = 'Bitte alle Vertreterinformationen vollstaendig ausfuellen.';
} elseif ($schemaError !== '') {
<form method="post" class="form-inline mb-3"> $error = $schemaError;
<div class="form-group mr-2"> } else {
<label>Von:</label> $stmt = $pdo->prepare("
<input type="date" name="start_date" class="form-control" required> INSERT INTO company_holidays (
</div> start_date, end_date, description, vertretung, vertretertelefon, vertreteradresse, vertreterurl, created_by
<div class="form-group mr-2"> ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
<label>Bis:</label> ");
<input type="date" name="end_date" class="form-control" required> $stmt->execute([
</div> $start,
<div class="form-group mr-2"> $end,
<label>Beschreibung:</label> $desc,
<input type="text" name="description" class="form-control" placeholder="z. B. Betriebsurlaub Weihnachten"> $vertretung,
</div> $vertretertelefon,
<button class="btn btn-primary">Hinzufügen</button> $vertreteradresse,
</form> $vertreterurl,
$_SESSION['userid']
<table class="table table-bordered"> ]);
<thead> vacationSyncUrlaubFromCompanyHoliday($pdo, (int)$pdo->lastInsertId());
<tr>
<th>Von</th> header('Location: company_holidays.php');
<th>Bis</th> exit();
<th>Beschreibung</th> }
<th>Erstellt von</th> }
<th>Aktion</th>
</tr> include 'header.php';
</thead>
<tbody> $stmt = $pdo->prepare("SELECT * FROM company_holidays ORDER BY start_date DESC");
<?php foreach ($holidays as $h): ?> $stmt->execute();
<tr> $holidays = $stmt->fetchAll(PDO::FETCH_ASSOC);
<td><?php echo $h['start_date']; ?></td> ?>
<td><?php echo $h['end_date']; ?></td>
<td><?php echo htmlspecialchars($h['description']); ?></td> <div class="container">
<td><?php <h2>Betriebsurlaub verwalten</h2>
$s = $pdo->prepare("SELECT vorname, nachname FROM users WHERE id = ?");
$s->execute([$h['created_by']]); <?php if ($error !== ''): ?>
$u = $s->fetch(); <div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
echo htmlspecialchars($u['vorname'] . ' ' . $u['nachname']); <?php endif; ?>
?></td> <?php if ($schemaError !== ''): ?>
<td> <div class="alert alert-warning"><?php echo htmlspecialchars($schemaError); ?></div>
<form method="post" action="deleteCompanyHoliday.php" onsubmit="return confirm('Betriebsurlaub wirklich löschen?');"> <?php endif; ?>
<input type="hidden" name="id" value="<?php echo intval($h['id']); ?>">
<button class="btn btn-sm btn-danger">Löschen</button> <form method="post" class="mb-3">
</form> <div class="form-row">
</td> <div class="form-group col-md-3">
</tr> <label>Von:</label>
<?php endforeach; ?> <input type="date" name="start_date" class="form-control" required>
</tbody> </div>
</table> <div class="form-group col-md-3">
<label>Bis:</label>
</div> <input type="date" name="end_date" class="form-control" required>
</div>
<?php include 'footer.php'; <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 class="form-row">
<div class="form-group col-md-6">
<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 class="form-row">
<div class="form-group col-md-8">
<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>
<button class="btn btn-primary">Hinzufuegen</button>
</form>
<table class="table table-bordered">
<thead>
<tr>
<th>Von</th>
<th>Bis</th>
<th>Beschreibung</th>
<th>Vertretung</th>
<th>Kontakt</th>
<th>Erstellt von</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
<?php foreach ($holidays as $h): ?>
<tr>
<td><?php echo htmlspecialchars((string)$h['start_date']); ?></td>
<td><?php echo htmlspecialchars((string)$h['end_date']); ?></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
$s = $pdo->prepare("SELECT vorname, nachname FROM users WHERE id = ?");
$s->execute([$h['created_by']]);
$u = $s->fetch(PDO::FETCH_ASSOC);
echo htmlspecialchars(trim(($u['vorname'] ?? '') . ' ' . ($u['nachname'] ?? '')));
?></td>
<td>
<form method="post" action="deleteCompanyHoliday.php" onsubmit="return confirm('Betriebsurlaub wirklich loeschen?');">
<input type="hidden" name="id" value="<?php echo (int)$h['id']; ?>">
<button class="btn btn-sm btn-danger">Loeschen</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php include 'footer.php'; ?>
+3 -1
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,9 +19,10 @@ 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]);
header('Location: company_holidays.php'); header('Location: company_holidays.php');
exit; exit;
?> ?>
+34 -38
View File
@@ -1,38 +1,34 @@
<?php <?php
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');
$user = check_user(); $user = check_user();
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['id'])) { if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['id'])) {
http_response_code(400); http_response_code(400);
die('Bad request'); die('Bad request');
} }
$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(PDO::FETCH_ASSOC);
$vac = $stmt->fetch();
if (!$vac) {
if (!$vac) { die('Urlaubseintrag nicht gefunden.');
die('Urlaubseintrag nicht gefunden.'); }
}
$canManageTeamVacations = can_manage_team_vacations();
$isAdmin = is_admin_user(); if (!$canManageTeamVacations && (int)$vac['user_id'] !== (int)$_SESSION['userid']) {
die('Zugriff verweigert.');
if (!$isAdmin && $vac['user_id'] != $_SESSION['userid']) { }
die('Zugriff verweigert.');
} $del = $pdo->prepare("DELETE FROM vacations WHERE id = ?");
$del->execute([$id]);
// Allow deletion for admins or owner
$del = $pdo->prepare("DELETE FROM vacations WHERE id = ?"); header('Location: ' . $referer);
$del->execute([$id]); exit();
?>
header('Location: ' . $referer);
exit();
?>
+10 -1
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
*/ */
@@ -187,4 +196,4 @@ function isValidSequence($sequence) {
} }
?> ?>
+241 -178
View File
@@ -1,178 +1,241 @@
<?php <?php
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");
$user = check_user(); $user = check_user();
if (!isset($_SESSION['userid'])) { if (!isset($_SESSION['userid'])) {
die("Kein Benutzer angemeldet."); die("Kein Benutzer angemeldet.");
} }
$user_id = $_SESSION['userid']; $user_id = (int)$_SESSION['userid'];
$message = ""; $canManageTeamVacations = can_manage_team_vacations();
$error = ""; $message = "";
$error = "";
function calculateWorkingDays($start, $end) { $selected_user_id = $user_id;
$start = new DateTime($start);
$end = new DateTime($end); $selectableUsers = [];
$end->modify('+1 day'); if ($canManageTeamVacations) {
$stmtUsers = $pdo->prepare("
$interval = new DateInterval('P1D'); SELECT id, vorname, nachname, email
$period = new DatePeriod($start, $interval, $end); FROM users
WHERE zeiterfassung = 1
$workingDays = 0; ORDER BY nachname, vorname
");
foreach ($period as $day) { $stmtUsers->execute();
if ($day->format('N') < 6) { // 1 (Mo) - 5 (Fr) $selectableUsers = $stmtUsers->fetchAll(PDO::FETCH_ASSOC);
$workingDays++; }
}
} function calculateWorkingDays($start, $end) {
$start = new DateTime($start);
return $workingDays; $end = new DateTime($end);
} $end->modify('+1 day');
if ($_SERVER["REQUEST_METHOD"] == "POST") { $interval = new DateInterval('P1D');
$period = new DatePeriod($start, $interval, $end);
$start_date = $_POST['start_date'];
$end_date = $_POST['end_date']; $workingDays = 0;
$comment = trim($_POST['comment']); foreach ($period as $day) {
if ($day->format('N') < 6) {
if (empty($start_date) || empty($end_date)) { $workingDays++;
$error = "Bitte beide Datumsfelder ausfüllen."; }
} elseif ($start_date > $end_date) { }
$error = "Enddatum liegt vor dem Startdatum.";
} elseif ($start_date < date("Y-m-d")) { return $workingDays;
$error = "Urlaub kann nicht in der Vergangenheit beantragt werden."; }
} else {
if ($_SERVER["REQUEST_METHOD"] === "POST") {
// Überschneidung prüfen $start_date = trim((string)($_POST['start_date'] ?? ''));
$stmt = $pdo->prepare(" $end_date = trim((string)($_POST['end_date'] ?? ''));
SELECT COUNT(*) FROM vacations $comment = trim((string)($_POST['comment'] ?? ''));
WHERE user_id = ? $selected_user_id = $canManageTeamVacations ? (int)($_POST['user_id'] ?? $user_id) : $user_id;
AND status != 'abgelehnt'
AND ( $selectedUser = null;
(start_date BETWEEN ? AND ?) if ($selected_user_id <= 0) {
OR (end_date BETWEEN ? AND ?) $error = "Bitte einen Mitarbeiter auswaehlen.";
OR (? BETWEEN start_date AND end_date) } else {
) $stmtSelectedUser = $pdo->prepare("
"); SELECT id, vorname, nachname
$stmt->execute([$user_id, $start_date, $end_date, $start_date, $end_date, $start_date]); FROM users
$exists = $stmt->fetchColumn(); WHERE id = ?
AND zeiterfassung = 1
if ($exists > 0) { LIMIT 1
$error = "Der Zeitraum überschneidet sich mit einem bestehenden Antrag."; ");
} else { $stmtSelectedUser->execute([$selected_user_id]);
$selectedUser = $stmtSelectedUser->fetch(PDO::FETCH_ASSOC);
$days = calculateWorkingDays($start_date, $end_date);
if (!$selectedUser) {
$insert = $pdo->prepare(" $error = "Der ausgewaehlte Mitarbeiter wurde nicht gefunden.";
INSERT INTO vacations (user_id, start_date, end_date, days, comment_user) }
VALUES (?, ?, ?, ?, ?) }
");
if ($error === "" && ($start_date === '' || $end_date === '')) {
$insert->execute([$user_id, $start_date, $end_date, $days, $comment]); $error = "Bitte beide Datumsfelder ausfuellen.";
} elseif ($error === "" && $start_date > $end_date) {
$message = "Urlaubsantrag erfolgreich eingereicht ($days Werktage)."; $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(*)
<?php include 'header.php'; ?> FROM vacations
WHERE user_id = ?
<div class="container"> AND status != 'abgelehnt'
<div class="row"> AND (
<div class="col-md-8 offset-md-2"> (start_date BETWEEN ? AND ?)
OR (end_date BETWEEN ? AND ?)
<h2>Urlaubsantrag</h2> OR (? BETWEEN start_date AND end_date)
)
<?php if ($error): ?> ");
<div class="alert alert-danger"><?php echo $error; ?></div> $stmt->execute([$selected_user_id, $start_date, $end_date, $start_date, $end_date, $start_date]);
<?php endif; ?> $exists = (int)$stmt->fetchColumn();
<?php if ($message): ?> if ($exists > 0) {
<div class="alert alert-success"><?php echo $message; ?></div> $error = "Der Zeitraum ueberschneidet sich mit einem bestehenden Antrag.";
<?php endif; ?> } else {
$days = calculateWorkingDays($start_date, $end_date);
<form method="post">
$insert = $pdo->prepare("
<div class="form-group"> INSERT INTO vacations (user_id, start_date, end_date, days, comment_user)
<label>Von:</label> VALUES (?, ?, ?, ?, ?)
<input type="date" name="start_date" class="form-control" required> ");
</div> $insert->execute([$selected_user_id, $start_date, $end_date, $days, $comment]);
<div class="form-group"> if ($selected_user_id !== $user_id && $selectedUser) {
<label>Bis:</label> $message = "Urlaub fuer " . $selectedUser['vorname'] . " " . $selectedUser['nachname'] . " erfolgreich eingereicht ($days Werktage).";
<input type="date" name="end_date" class="form-control" required> } else {
</div> $message = "Urlaubsantrag erfolgreich eingereicht ($days Werktage).";
}
<div class="form-group"> }
<label>Kommentar (optional):</label> }
<textarea name="comment" class="form-control"></textarea> }
</div> ?>
<br> <?php include 'header.php'; ?>
<button type="submit" class="btn btn-primary btn-block"> <div class="container">
Urlaub beantragen <div class="row">
</button> <div class="col-md-8 offset-md-2">
</form> <h2>Urlaubsantrag</h2>
<hr> <?php if ($error): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
<h4>Meine Anträge</h4> <?php endif; ?>
<?php <?php if ($message): ?>
$stmt = $pdo->prepare(" <div class="alert alert-success"><?php echo htmlspecialchars($message); ?></div>
SELECT * FROM vacations <?php endif; ?>
WHERE user_id = ?
ORDER BY created_at DESC <form method="post">
");
$stmt->execute([$user_id]); <?php if ($canManageTeamVacations): ?>
$antraege = $stmt->fetchAll(); <div class="form-group">
?> <label>Mitarbeiter:</label>
<select name="user_id" class="form-control" required>
<table class="table table-bordered"> <?php foreach ($selectableUsers as $employee): ?>
<tr> <?php $employeeId = (int)$employee['id']; ?>
<th>Von</th> <option value="<?php echo $employeeId; ?>" <?php echo ($selected_user_id === $employeeId) ? 'selected' : ''; ?>>
<th>Bis</th> <?php echo htmlspecialchars(trim($employee['nachname'] . ', ' . $employee['vorname'] . ' | ' . $employee['email'])); ?>
<th>Tage</th> </option>
<th>Status</th> <?php endforeach; ?>
<th>Aktion</th> </select>
</tr> </div>
<?php endif; ?>
<?php foreach ($antraege as $a): ?>
<tr> <div class="form-group">
<td><?php echo $a['start_date']; ?></td> <label>Von:</label>
<td><?php echo $a['end_date']; ?></td> <input type="date" name="start_date" class="form-control" required>
<td><?php echo $a['days']; ?></td> </div>
<td>
<?php <div class="form-group">
if ($a['status'] == 'beantragt') { <label>Bis:</label>
echo '<span class="badge badge-warning">Beantragt</span>'; <input type="date" name="end_date" class="form-control" required>
} elseif ($a['status'] == 'genehmigt') { </div>
echo '<span class="badge badge-success">Genehmigt</span>';
} else { <div class="form-group">
echo '<span class="badge badge-danger">Abgelehnt</span>'; <label>Kommentar (optional):</label>
} <textarea name="comment" class="form-control"></textarea>
?> </div>
</td>
<td> <br>
<form method="post" action="deleteVacation.php" onsubmit="return confirm('Wirklich löschen?');">
<input type="hidden" name="id" value="<?php echo $a['id']; ?>"> <button type="submit" class="btn btn-primary btn-block">
<input type="hidden" name="referer" value="urlaubsantrag.php"> <?php echo $canManageTeamVacations ? 'Urlaub eintragen' : 'Urlaub beantragen'; ?>
<button type="submit" class="btn btn-sm btn-danger">Löschen</button> </button>
</form>
</td> </form>
</tr>
<?php endforeach; ?> <hr>
</table> <h4><?php echo $canManageTeamVacations ? 'Urlaubseintraege' : 'Meine Antraege'; ?></h4>
</div> <?php
</div> $listSql = "
</div> SELECT v.*, u.vorname, u.nachname
FROM vacations v
<?php include 'footer.php'; ?> JOIN users u ON u.id = v.user_id
";
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">
<tr>
<?php if ($canManageTeamVacations): ?>
<th>Mitarbeiter</th>
<?php endif; ?>
<th>Von</th>
<th>Bis</th>
<th>Tage</th>
<th>Status</th>
<th>Aktion</th>
</tr>
<?php foreach ($antraege as $a): ?>
<tr>
<?php if ($canManageTeamVacations): ?>
<td><?php echo htmlspecialchars(trim($a['vorname'] . ' ' . $a['nachname'])); ?></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>
<?php
if ($a['status'] === 'beantragt' || $a['status'] === null || $a['status'] === '') {
echo '<span class="badge badge-warning">Beantragt</span>';
} elseif ($a['status'] === 'genehmigt') {
echo '<span class="badge badge-success">Genehmigt</span>';
} else {
echo '<span class="badge badge-danger">Abgelehnt</span>';
}
?>
</td>
<td>
<form method="post" action="deleteVacation.php" onsubmit="return confirm('Wirklich loeschen?');">
<input type="hidden" name="id" value="<?php echo (int)$a['id']; ?>">
<input type="hidden" name="referer" value="urlaubsantrag.php">
<button type="submit" class="btn btn-sm btn-danger">Loeschen</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</table>
</div>
</div>
</div>
<?php include 'footer.php'; ?>
+11 -10
View File
@@ -41,16 +41,17 @@ document.addEventListener('DOMContentLoaded', function() {
var url = 'api/vacations.php?start=' + info.startStr + '&end=' + info.endStr; var url = 'api/vacations.php?start=' + info.startStr + '&end=' + info.endStr;
fetch(url).then(function(res){ return res.json(); }).then(function(data){ successCallback(data); }).catch(function(err){ failureCallback(err); }); fetch(url).then(function(res){ return res.json(); }).then(function(data){ successCallback(data); }).catch(function(err){ failureCallback(err); });
}, },
eventClick: function(info) { eventClick: function(info) {
var ev = info.event; var ev = info.event;
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 += 'Status: ' + (props.status || '') + '<br>'; html += 'Mitarbeiter: ' + (props.employee_name || '') + '<br>';
html += 'Kommentar: ' + (props.comment || '') + '<br>'; html += 'Status: ' + (props.status || '') + '<br>';
} else if (props.type === 'company') { html += 'Kommentar: ' + (props.comment || '') + '<br>';
html += 'Beschreibung: ' + (props.description || '') + '<br>'; } else if (props.type === 'company') {
} html += 'Beschreibung: ' + (props.description || '') + '<br>';
}
document.getElementById('detailsContent').innerHTML = html; document.getElementById('detailsContent').innerHTML = html;
document.getElementById('eventDetails').style.display = 'block'; document.getElementById('eventDetails').style.display = 'block';
} }
+63 -59
View File
@@ -1,59 +1,63 @@
<?php <?php
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');
// allow any logged-in user to view the team calendar (read-only) $user = check_user();
$user = check_user();
include 'header.php';
include 'header.php'; ?>
?>
<div class="container">
<div class="container"> <h2>Team Urlaubskalender</h2>
<h2>Team Urlaubskalender</h2> <div id="calendar"></div>
<div id="calendar"></div> <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>
<div id="eventDetails" style="display:none;"> <div id="eventDetails" style="display:none;">
<h4>Details</h4> <h4>Details</h4>
<div id="detailsContent"></div> <div id="detailsContent"></div>
</div> </div>
</div> </div>
<link href='https://cdn.jsdelivr.net/npm/fullcalendar@5.11.3/main.min.css' rel='stylesheet' /> <link href='https://cdn.jsdelivr.net/npm/fullcalendar@5.11.3/main.min.css' rel='stylesheet' />
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@5.11.3/main.min.js'></script> <script src='https://cdn.jsdelivr.net/npm/fullcalendar@5.11.3/main.min.js'></script>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar'); var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, { var calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth', initialView: 'dayGridMonth',
firstDay: 1, firstDay: 1,
height: 650, height: 650,
events: function(info, successCallback, failureCallback) { events: function(info, successCallback, failureCallback) {
var url = 'api/vacations.php?start=' + info.startStr + '&end=' + info.endStr + '&public=1&public_all=1'; var url = 'api/vacations.php?start=' + info.startStr + '&end=' + info.endStr + '&public=1&public_all=1';
fetch(url).then(function(res){ return res.json(); }).then(function(data){ successCallback(data); }).catch(function(err){ failureCallback(err); }); fetch(url).then(function(res){ return res.json(); }).then(function(data){ successCallback(data); }).catch(function(err){ failureCallback(err); });
}, },
eventClick: function(info) { eventClick: function(info) {
var ev = info.event; var ev = info.event;
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>';
} else if (props.type === 'company') { html += 'Status: ' + (props.status || '') + '<br>';
html += 'Beschreibung: ' + (props.description || '') + '<br>'; if (props.comment) {
} html += 'Kommentar: ' + props.comment + '<br>';
document.getElementById('detailsContent').innerHTML = html; }
document.getElementById('eventDetails').style.display = 'block'; } else if (props.type === 'company') {
} html += 'Beschreibung: ' + (props.description || '') + '<br>';
}); }
document.getElementById('detailsContent').innerHTML = html;
calendar.render(); document.getElementById('eventDetails').style.display = 'block';
}); }
</script> });
<?php include 'footer.php'; ?> calendar.render();
});
</script>
<?php include 'footer.php'; ?>