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
/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
require_once __DIR__ . "/../inc/config.inc.php";
require_once __DIR__ . "/../inc/functions.inc.php";
require_once __DIR__ . "/../inc/company_holiday_sync.inc.php";
// Login prüfen
$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') . "'>
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') . "'>
<br>Vertretung Adresse: <input class='form-control' name='vertreteradresse[]' type='text' value='" . htmlspecialchars($vertreteradresse, 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' 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>";
}
@@ -656,40 +657,64 @@ if(!check_worker()){
}else if (($_POST["aktion"] ?? '') == "5") {
// Termine in DB speichern.
$i =0;
foreach ($_POST['Starttime'] as $Starttime) {
//echo $datum . "<br>";
if($_POST["Starttime"][$i] != "0000-00-00"){
//echo $_POST["urlaubid"][$i] . "<br>";
$stmt = $pdo->prepare("
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)
");
$pdo->beginTransaction();
try {
foreach ($_POST['Starttime'] as $Starttime) {
if($_POST["Starttime"][$i] != "0000-00-00"){
$vertretung = trim((string)($_POST['vertretung'][$i] ?? ''));
$vertretertelefon = trim((string)($_POST['vertretertelefon'][$i] ?? ''));
$vertreteradresse = trim((string)($_POST['vertreteradresse'][$i] ?? ''));
$vertreterurl = trim((string)($_POST['vertreterurl'][$i] ?? ''));
$ok = $stmt->execute([
':urlaubid' => (int)$_POST['urlaubid'][$i], // 0 = INSERT, >0 = UPDATE
':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 ($vertretung === '' || $vertretertelefon === '' || $vertreteradresse === '' || $vertreterurl === '') {
throw new RuntimeException("Bitte alle Vertreterinformationen fuer jeden Urlaubseintrag vollstaendig ausfuellen.");
}
if (!$ok) {
throw new RuntimeException("Fehler beim Eintragen in der Datenbank.");
$stmt = $pdo->prepare("
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>";
}else if (($_POST["aktion"] ?? '') == "6") {
@@ -2023,10 +2048,10 @@ if(!check_worker()){
<script type="text/javascript">
function AddneueTermine(){
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>
//document.getElementById('neueTermine').innerHTML = div;
+127 -97
View File
@@ -405,6 +405,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
SendMailMessageVorlage($pdo, '1', $tid, $mailTemplateId);
}
impfWorkflowNotificationProcess($pdo);
$message = count($terminIds) . " Terminanfragen wurden erstellt und versendet.";
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
@@ -436,7 +437,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
1
);
if ($ok) {
$notificationEvents = impfWorkflowNotificationProcess($pdo);
$message = $msg;
if (!empty($notificationEvents)) {
$message .= ' ' . count($notificationEvents) . " Impfworkflow-Benachrichtigung(en) wurden versendet.";
}
} else {
$error = $msg;
}
@@ -481,7 +486,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
1
);
if ($ok) {
$notificationEvents = impfWorkflowNotificationProcess($pdo);
$message = $msg;
if (!empty($notificationEvents)) {
$message .= ' ' . count($notificationEvents) . " Impfworkflow-Benachrichtigung(en) wurden versendet.";
}
} else {
$error = $msg;
}
@@ -595,6 +604,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$exists = (bool)$stDelete->fetch(PDO::FETCH_ASSOC);
workflowDeleteWaitlistEntry($pdo, $warteid);
if ($exists) {
impfWorkflowNotificationProcess($pdo);
$message = "Wartelisten-Eintrag wurde gelöscht.";
} else {
$error = "Wartelisten-Eintrag nicht gefunden.";
@@ -663,51 +673,67 @@ $eventOverview = [];
$planWaitCounts = [];
try {
$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);
$needsPlanData = in_array($view, ['teilnehmer', 'event-create'], true);
$needsEligibilityData = ($view === 'event-create');
$needsPersonSearch = ($view === 'teilnehmer' && $personSearch !== '');
$needsWaitRows = ($view === 'warteliste');
$needsUpcomingRows = ($view === 'event-teilnehmer');
$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 = [];
foreach ($plans as $p) {
foreach ($p['impfstoff_id_list'] as $impfstoffId) {
$planExistsForImpfstoff[(int)$impfstoffId] = true;
}
}
$plans = impfGetZeitraumRows($pdo, true);
foreach ($rules as $r) {
$iid = (int)$r['impfstoff_id'];
$dosen = (int)$r['dosen_pro_flasche'];
if ($dosen <= 0 || !isset($planExistsForImpfstoff[$iid])) {
continue;
$planExistsForImpfstoff = [];
foreach ($plans as $p) {
foreach ($p['impfstoff_id_list'] as $impfstoffId) {
$planExistsForImpfstoff[(int)$impfstoffId] = true;
}
}
$configuredImpfstoffe[] = $r;
$hasEligiblePlan = false;
foreach ($plans as $plan) {
if (!in_array($iid, $plan['impfstoff_id_list'] ?? [], true)) {
foreach ($rules as $r) {
$iid = (int)$r['impfstoff_id'];
if (!isset($planExistsForImpfstoff[$iid])) {
continue;
}
$planId = (int)$plan['zeitraum_id'];
$planWaitCounts[$iid][$planId] = workflowCountWaitersForPlan($pdo, $iid, $planId);
if ($planWaitCounts[$iid][$planId] >= $dosen) {
$hasEligiblePlan = true;
$configuredImpfstoffe[] = $r;
if (!$needsEligibilityData) {
continue;
}
}
if ($hasEligiblePlan) {
$eligible[] = $r;
$dosen = (int)$r['dosen_pro_flasche'];
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 . '%';
$searchExactId = ctype_digit($personSearch) ? (int)$personSearch : -1;
$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);
}
$stWait = $pdo->prepare("SELECT w.warteid, w.userid, w.checked, w.impfstoff, w.impfart, w.impfenzeitraum, w.zeitraum_id, w.letzteimpfung, w.date_created,
p.vorname, p.nachname, p.geburtstag, p.email, p.tele,
i.impfname
FROM warteliste w
INNER JOIN persons p ON p.person_id = w.userid
LEFT JOIN impfstoff i ON i.impfid = w.impfstoff
WHERE w.checked IN (0, 1)
ORDER BY w.checked DESC, w.date_created ASC
LIMIT 500");
$stWait->execute();
$waitRows = $stWait->fetchAll(PDO::FETCH_ASSOC);
if ($needsWaitRows) {
$stWait = $pdo->prepare("SELECT w.warteid, w.userid, w.checked, w.impfstoff, w.impfart, w.impfenzeitraum, w.zeitraum_id, w.letzteimpfung, w.date_created,
p.vorname, p.nachname, p.geburtstag, p.email, p.tele,
i.impfname
FROM warteliste w
INNER JOIN persons p ON p.person_id = w.userid
LEFT JOIN impfstoff i ON i.impfid = w.impfstoff
WHERE w.checked IN (0, 1)
ORDER BY w.checked DESC, w.date_created ASC
LIMIT 500");
$stWait->execute();
$waitRows = $stWait->fetchAll(PDO::FETCH_ASSOC);
foreach ($waitRows as &$waitRow) {
$waitRow['zeitraum_labels'] = impfGetWartelistenZeitraeumeLabels($pdo, (int)$waitRow['warteid'], false);
if (!empty($waitRow['zeitraum_labels'])) {
$waitRow['impfenzeitraum'] = implode(' | ', $waitRow['zeitraum_labels']);
$waitIds = array_map(static function (array $waitRow): int {
return (int)($waitRow['warteid'] ?? 0);
}, $waitRows);
$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);
$notificationEvents = impfWorkflowNotificationProcess($pdo);
if (!empty($notificationEvents)) {
$notificationText = count($notificationEvents) . " Impfworkflow-Benachrichtigung(en) wurden versendet.";
$message = ($message === '') ? $notificationText : ($message . ' ' . $notificationText);
unset($waitRow);
}
$stUpcoming = $pdo->prepare("SELECT ts.timeid, ts.date, ts.start, ts.ende, ts.impfdosen,
i.impfname, o.anzeigename, o.adresse,
it.terminid, it.checked, it.behandelt, it.impfart,
p.vorname, p.nachname, p.geburtstag, p.email, p.tele
FROM timeslots ts
INNER JOIN impfstoff i ON i.impfid = ts.impfstoff
LEFT JOIN impfort o ON o.ortid = ts.impfortid
LEFT JOIN impftermin it ON it.timeid = ts.timeid
LEFT JOIN persons p ON p.person_id = it.userid
WHERE ts.date >= :today
AND ts.aktiv = 1
ORDER BY ts.date, ts.start, ts.ende, i.impfname, p.nachname, p.vorname");
$stUpcoming->execute(['today' => date('Y-m-d')]);
$upcomingRows = $stUpcoming->fetchAll(PDO::FETCH_ASSOC);
if ($needsUpcomingRows) {
$stUpcoming = $pdo->prepare("SELECT ts.timeid, ts.date, ts.start, ts.ende, ts.impfdosen,
i.impfname, o.anzeigename, o.adresse,
it.terminid, it.checked, it.behandelt, it.impfart,
p.vorname, p.nachname, p.geburtstag, p.email, p.tele
FROM timeslots ts
INNER JOIN impfstoff i ON i.impfid = ts.impfstoff
LEFT JOIN impfort o ON o.ortid = ts.impfortid
LEFT JOIN impftermin it ON it.timeid = ts.timeid
LEFT JOIN persons p ON p.person_id = it.userid
WHERE ts.date >= :today
AND ts.aktiv = 1
ORDER BY ts.date, ts.start, ts.ende, i.impfname, p.nachname, p.vorname");
$stUpcoming->execute(['today' => date('Y-m-d')]);
$upcomingRows = $stUpcoming->fetchAll(PDO::FETCH_ASSOC);
foreach ($upcomingRows as $row) {
$timeid = (int)$row['timeid'];
if (!isset($eventOverview[$timeid])) {
$eventOverview[$timeid] = [
'timeid' => $timeid,
'date' => $row['date'],
'start' => $row['start'],
'ende' => $row['ende'],
'impfdosen' => (int)$row['impfdosen'],
'impfname' => $row['impfname'],
'anzeigename' => $row['anzeigename'],
'adresse' => $row['adresse'],
'teilnehmer' => [],
];
}
if (!empty($row['terminid'])) {
$eventOverview[$timeid]['teilnehmer'][] = [
'terminid' => (int)$row['terminid'],
'checked' => (int)($row['checked'] ?? 0),
'behandelt' => (int)($row['behandelt'] ?? 0),
'impfart' => (int)($row['impfart'] ?? 1),
'vorname' => (string)($row['vorname'] ?? ''),
'nachname' => (string)($row['nachname'] ?? ''),
'geburtstag' => (string)($row['geburtstag'] ?? ''),
'email' => (string)($row['email'] ?? ''),
'tele' => (string)($row['tele'] ?? ''),
];
foreach ($upcomingRows as $row) {
$timeid = (int)$row['timeid'];
if (!isset($eventOverview[$timeid])) {
$eventOverview[$timeid] = [
'timeid' => $timeid,
'date' => $row['date'],
'start' => $row['start'],
'ende' => $row['ende'],
'impfdosen' => (int)$row['impfdosen'],
'impfname' => $row['impfname'],
'anzeigename' => $row['anzeigename'],
'adresse' => $row['adresse'],
'teilnehmer' => [],
];
}
if (!empty($row['terminid'])) {
$eventOverview[$timeid]['teilnehmer'][] = [
'terminid' => (int)$row['terminid'],
'checked' => (int)($row['checked'] ?? 0),
'behandelt' => (int)($row['behandelt'] ?? 0),
'impfart' => (int)($row['impfart'] ?? 1),
'vorname' => (string)($row['vorname'] ?? ''),
'nachname' => (string)($row['nachname'] ?? ''),
'geburtstag' => (string)($row['geburtstag'] ?? ''),
'email' => (string)($row['email'] ?? ''),
'tele' => (string)($row['tele'] ?? ''),
];
}
}
}
} catch (Throwable $e) {
@@ -1257,5 +1287,5 @@ try {
</div>
<?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')) {
function impfSetWartelistenZeitraeume(PDO $pdo, int $warteid, $zeitraumIds): void
{
+191 -197
View File
@@ -1,197 +1,191 @@
<?php
// API: returns JSON events for FullCalendar
session_start();
require_once(__DIR__ . '/../inc/config.inc.php');
require_once(__DIR__ . '/../inc/functions.inc.php');
// Enable full error reporting for API debugging
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
$user = check_user();
$isAdmin = is_admin_user();
$start = $_GET['start'] ?? null; // expected ISO date
$end = $_GET['end'] ?? null;
$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');
// 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');
// 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');
// 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');
if (!$start || !$end) {
http_response_code(400);
echo json_encode(['error' => 'start and end required']);
exit;
}
$events = [];
try {
$branch = 'unknown';
$debugMode = isset($_GET['debug']) && ($_GET['debug'] == '1' || $_GET['debug'] === 'true');
// Vacations: support a personal-only mode, admin mode, and public/team mode
if ($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) {
$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 {
// 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]);
} 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]);
}
}
}
}
try {
$vacations = $stmt->fetchAll();
// If debug mode is enabled, prepare meta information
if ($debugMode) {
$rawStatuses = array_map(function($r){ return $r['status'] ?? null; }, $vacations);
$meta = [
'branch' => $branch,
'count' => count($vacations),
'raw_statuses' => $rawStatuses
];
}
foreach ($vacations as $v) {
// Normalize status: collapse whitespace (including NBSP), trim, lowercase
if (isset($v['status'])) {
$normalized = preg_replace('/\s+/u', ' ', $v['status']);
$status = mb_strtolower(trim($normalized));
} else {
$status = '';
}
// Defensive filter: do not expose rejected ('abgelehnt') entries to non-admins
if (!$isAdmin && !$includeRejected && mb_stripos($status, 'abgelehnt') !== false) {
continue;
}
$isApproved = (mb_stripos($status, 'genehmigt') !== false);
if ($isAdmin) {
$title = $v['vorname'] . ' ' . $v['nachname'] . ' (' . ($v['status'] ?? 'beantragt') . ')';
} else {
$title = $isApproved ? 'Urlaub' : 'Urlaubsantrag';
}
// 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) {
header('Content-Type: application/json; charset=utf-8');
$payload = ['error' => $ex->getMessage(), 'branch' => $branch, 'trace' => $ex->getTraceAsString()];
echo json_encode($payload);
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->execute([$end, $start]);
$holidays = $stmt->fetchAll();
foreach ($holidays as $h) {
$endInclusive = (new DateTime($h['end_date']))->modify('+1 day')->format('Y-m-d');
$events[] = [
'id' => 'com_' . $h['id'],
'title' => $h['description'] ?: 'Betriebsurlaub',
'start' => $h['start_date'],
'end' => $endInclusive,
'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);
}
?>
<?php
session_start();
require_once(__DIR__ . '/../inc/config.inc.php');
require_once(__DIR__ . '/../inc/functions.inc.php');
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
$user = check_user();
$isAdmin = is_admin_user();
$start = $_GET['start'] ?? null;
$end = $_GET['end'] ?? null;
$onlyApproved = isset($_GET['only_approved']) && ($_GET['only_approved'] == '1' || $_GET['only_approved'] === 'true');
$public = isset($_GET['public']) && ($_GET['public'] == '1' || $_GET['public'] === 'true');
$includeRejected = isset($_GET['include_rejected']) && ($_GET['include_rejected'] == '1' || $_GET['include_rejected'] === 'true');
$onlyPersonal = isset($_GET['only_personal']) && ($_GET['only_personal'] == '1' || $_GET['only_personal'] === 'true');
$publicAll = isset($_GET['public_all']) && ($_GET['public_all'] == '1' || $_GET['public_all'] === 'true');
if (!$start || !$end) {
http_response_code(400);
echo json_encode(['error' => 'start and end required']);
exit;
}
$events = [];
try {
$branch = 'unknown';
$debugMode = isset($_GET['debug']) && ($_GET['debug'] == '1' || $_GET['debug'] === 'true');
$showEmployeeNames = $isAdmin || $public;
if ($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 {
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';
$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]);
}
}
}
}
try {
$vacations = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($debugMode) {
$rawStatuses = array_map(function($r){ return $r['status'] ?? null; }, $vacations);
$meta = [
'branch' => $branch,
'count' => count($vacations),
'raw_statuses' => $rawStatuses
];
}
foreach ($vacations as $v) {
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'] ?? ''
]
];
}
} 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;
}
} 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;
}
$stmt = $pdo->prepare("SELECT * FROM company_holidays WHERE start_date <= ? AND end_date >= ? ORDER BY start_date");
$stmt->execute([$end, $start]);
$holidays = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($holidays as $h) {
$endInclusive = (new DateTime($h['end_date']))->modify('+1 day')->format('Y-m-d');
$events[] = [
'id' => 'com_' . $h['id'],
'title' => $h['description'] ?: 'Betriebsurlaub',
'start' => $h['start_date'],
'end' => $endInclusive,
'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);
}
?>
+159 -90
View File
@@ -1,90 +1,159 @@
<?php
session_start();
require_once('inc/config.inc.php');
require_once('inc/functions.inc.php');
$user = check_user();
if (!is_admin_user()) {
die('Zugriff verweigert. Nur Chefs dürfen Betriebsurlaub verwalten.');
}
// Create table if not exists (optional helper)
// Administrators can also run the SQL directly in DB. This is just a convenience.
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['start_date']) && isset($_POST['end_date'])) {
$start = $_POST['start_date'];
$end = $_POST['end_date'];
$desc = trim($_POST['description'] ?? 'Betriebsurlaub');
$stmt = $pdo->prepare("INSERT INTO company_holidays (start_date, end_date, description, created_by) VALUES (?, ?, ?, ?)");
$stmt->execute([$start, $end, $desc, $_SESSION['userid']]);
header('Location: company_holidays.php');
exit();
}
include 'header.php';
$stmt = $pdo->prepare("SELECT * FROM company_holidays ORDER BY start_date DESC");
$stmt->execute();
$holidays = $stmt->fetchAll();
?>
<div class="container">
<h2>Betriebsurlaub verwalten</h2>
<form method="post" class="form-inline mb-3">
<div class="form-group mr-2">
<label>Von:</label>
<input type="date" name="start_date" class="form-control" required>
</div>
<div class="form-group mr-2">
<label>Bis:</label>
<input type="date" name="end_date" class="form-control" required>
</div>
<div class="form-group mr-2">
<label>Beschreibung:</label>
<input type="text" name="description" class="form-control" placeholder="z. B. Betriebsurlaub Weihnachten">
</div>
<button class="btn btn-primary">Hinzufügen</button>
</form>
<table class="table table-bordered">
<thead>
<tr>
<th>Von</th>
<th>Bis</th>
<th>Beschreibung</th>
<th>Erstellt von</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
<?php foreach ($holidays as $h): ?>
<tr>
<td><?php echo $h['start_date']; ?></td>
<td><?php echo $h['end_date']; ?></td>
<td><?php echo htmlspecialchars($h['description']); ?></td>
<td><?php
$s = $pdo->prepare("SELECT vorname, nachname FROM users WHERE id = ?");
$s->execute([$h['created_by']]);
$u = $s->fetch();
echo htmlspecialchars($u['vorname'] . ' ' . $u['nachname']);
?></td>
<td>
<form method="post" action="deleteCompanyHoliday.php" onsubmit="return confirm('Betriebsurlaub wirklich löschen?');">
<input type="hidden" name="id" value="<?php echo intval($h['id']); ?>">
<button class="btn btn-sm btn-danger">Löschen</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php include 'footer.php';
?>
<?php
session_start();
require_once('inc/config.inc.php');
require_once('inc/functions.inc.php');
require_once(__DIR__ . '/../inc/company_holiday_sync.inc.php');
$user = check_user();
if (!is_admin_user()) {
die('Zugriff verweigert. Nur Chefs duerfen Betriebsurlaub verwalten.');
}
$error = '';
$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'])) {
$start = trim((string)($_POST['start_date'] ?? ''));
$end = trim((string)($_POST['end_date'] ?? ''));
$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'] ?? ''));
if ($start === '' || $end === '') {
$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');
exit();
}
}
include 'header.php';
$stmt = $pdo->prepare("SELECT * FROM company_holidays ORDER BY start_date DESC");
$stmt->execute();
$holidays = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<div class="container">
<h2>Betriebsurlaub verwalten</h2>
<?php if ($error !== ''): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
<?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 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();
require_once('inc/config.inc.php');
require_once('inc/functions.inc.php');
require_once(__DIR__ . '/../inc/company_holiday_sync.inc.php');
$user = check_user();
if (!is_admin_user()) {
@@ -18,9 +19,10 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['id'])) {
$id = intval($_POST['id']);
vacationSyncDeleteUrlaubByCompanyHoliday($pdo, $id);
$stmt = $pdo->prepare("DELETE FROM company_holidays WHERE id = ?");
$stmt->execute([$id]);
header('Location: company_holidays.php');
exit;
?>
?>
+34 -38
View File
@@ -1,38 +1,34 @@
<?php
session_start();
require_once('inc/config.inc.php');
require_once('inc/functions.inc.php');
$user = check_user();
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['id'])) {
http_response_code(400);
die('Bad request');
}
$id = (int)$_POST['id'];
$referer = $_POST['referer'] ?? 'urlaubsantrag.php';
// Fetch vacation to verify ownership
$stmt = $pdo->prepare("SELECT user_id, status FROM vacations WHERE id = ?");
$stmt->execute([$id]);
$vac = $stmt->fetch();
if (!$vac) {
die('Urlaubseintrag nicht gefunden.');
}
$isAdmin = is_admin_user();
if (!$isAdmin && $vac['user_id'] != $_SESSION['userid']) {
die('Zugriff verweigert.');
}
// Allow deletion for admins or owner
$del = $pdo->prepare("DELETE FROM vacations WHERE id = ?");
$del->execute([$id]);
header('Location: ' . $referer);
exit();
?>
<?php
session_start();
require_once('inc/config.inc.php');
require_once('inc/functions.inc.php');
$user = check_user();
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['id'])) {
http_response_code(400);
die('Bad request');
}
$id = (int)$_POST['id'];
$referer = $_POST['referer'] ?? 'urlaubsantrag.php';
$stmt = $pdo->prepare("SELECT user_id, status FROM vacations WHERE id = ?");
$stmt->execute([$id]);
$vac = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$vac) {
die('Urlaubseintrag nicht gefunden.');
}
$canManageTeamVacations = can_manage_team_vacations();
if (!$canManageTeamVacations && (int)$vac['user_id'] !== (int)$_SESSION['userid']) {
die('Zugriff verweigert.');
}
$del = $pdo->prepare("DELETE FROM vacations WHERE id = ?");
$del->execute([$id]);
header('Location: ' . $referer);
exit();
?>
+10 -1
View File
@@ -145,6 +145,15 @@ function is_admin_user() {
$statement->execute(array('id' => $_SESSION['userid']));
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
*/
@@ -187,4 +196,4 @@ function isValidSequence($sequence) {
}
?>
?>
+241 -178
View File
@@ -1,178 +1,241 @@
<?php
session_start();
require_once("inc/config.inc.php");
require_once("inc/functions.inc.php");
$user = check_user();
if (!isset($_SESSION['userid'])) {
die("Kein Benutzer angemeldet.");
}
$user_id = $_SESSION['userid'];
$message = "";
$error = "";
function calculateWorkingDays($start, $end) {
$start = new DateTime($start);
$end = new DateTime($end);
$end->modify('+1 day');
$interval = new DateInterval('P1D');
$period = new DatePeriod($start, $interval, $end);
$workingDays = 0;
foreach ($period as $day) {
if ($day->format('N') < 6) { // 1 (Mo) - 5 (Fr)
$workingDays++;
}
}
return $workingDays;
}
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$start_date = $_POST['start_date'];
$end_date = $_POST['end_date'];
$comment = trim($_POST['comment']);
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 {
// Überschneidung prüfen
$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([$user_id, $start_date, $end_date, $start_date, $end_date, $start_date]);
$exists = $stmt->fetchColumn();
if ($exists > 0) {
$error = "Der Zeitraum überschneidet sich mit einem bestehenden Antrag.";
} else {
$days = calculateWorkingDays($start_date, $end_date);
$insert = $pdo->prepare("
INSERT INTO vacations (user_id, start_date, end_date, days, comment_user)
VALUES (?, ?, ?, ?, ?)
");
$insert->execute([$user_id, $start_date, $end_date, $days, $comment]);
$message = "Urlaubsantrag erfolgreich eingereicht ($days Werktage).";
}
}
}
?>
<?php include 'header.php'; ?>
<div class="container">
<div class="row">
<div class="col-md-8 offset-md-2">
<h2>Urlaubsantrag</h2>
<?php if ($error): ?>
<div class="alert alert-danger"><?php echo $error; ?></div>
<?php endif; ?>
<?php if ($message): ?>
<div class="alert alert-success"><?php echo $message; ?></div>
<?php endif; ?>
<form method="post">
<div class="form-group">
<label>Von:</label>
<input type="date" name="start_date" class="form-control" required>
</div>
<div class="form-group">
<label>Bis:</label>
<input type="date" name="end_date" class="form-control" required>
</div>
<div class="form-group">
<label>Kommentar (optional):</label>
<textarea name="comment" class="form-control"></textarea>
</div>
<br>
<button type="submit" class="btn btn-primary btn-block">
Urlaub beantragen
</button>
</form>
<hr>
<h4>Meine Anträge</h4>
<?php
$stmt = $pdo->prepare("
SELECT * FROM vacations
WHERE user_id = ?
ORDER BY created_at DESC
");
$stmt->execute([$user_id]);
$antraege = $stmt->fetchAll();
?>
<table class="table table-bordered">
<tr>
<th>Von</th>
<th>Bis</th>
<th>Tage</th>
<th>Status</th>
<th>Aktion</th>
</tr>
<?php foreach ($antraege as $a): ?>
<tr>
<td><?php echo $a['start_date']; ?></td>
<td><?php echo $a['end_date']; ?></td>
<td><?php echo $a['days']; ?></td>
<td>
<?php
if ($a['status'] == 'beantragt') {
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 löschen?');">
<input type="hidden" name="id" value="<?php echo $a['id']; ?>">
<input type="hidden" name="referer" value="urlaubsantrag.php">
<button type="submit" class="btn btn-sm btn-danger">Löschen</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</table>
</div>
</div>
</div>
<?php include 'footer.php'; ?>
<?php
session_start();
require_once("inc/config.inc.php");
require_once("inc/functions.inc.php");
$user = check_user();
if (!isset($_SESSION['userid'])) {
die("Kein Benutzer angemeldet.");
}
$user_id = (int)$_SESSION['userid'];
$canManageTeamVacations = can_manage_team_vacations();
$message = "";
$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) {
$start = new DateTime($start);
$end = new DateTime($end);
$end->modify('+1 day');
$interval = new DateInterval('P1D');
$period = new DatePeriod($start, $interval, $end);
$workingDays = 0;
foreach ($period as $day) {
if ($day->format('N') < 6) {
$workingDays++;
}
}
return $workingDays;
}
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;
$selectedUser = null;
if ($selected_user_id <= 0) {
$error = "Bitte einen Mitarbeiter auswaehlen.";
} else {
$stmtSelectedUser = $pdo->prepare("
SELECT id, vorname, nachname
FROM users
WHERE id = ?
AND zeiterfassung = 1
LIMIT 1
");
$stmtSelectedUser->execute([$selected_user_id]);
$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) {
$error = "Der Zeitraum ueberschneidet sich mit einem bestehenden Antrag.";
} else {
$days = calculateWorkingDays($start_date, $end_date);
$insert = $pdo->prepare("
INSERT INTO vacations (user_id, start_date, end_date, days, comment_user)
VALUES (?, ?, ?, ?, ?)
");
$insert->execute([$selected_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).";
} else {
$message = "Urlaubsantrag erfolgreich eingereicht ($days Werktage).";
}
}
}
}
?>
<?php include 'header.php'; ?>
<div class="container">
<div class="row">
<div class="col-md-8 offset-md-2">
<h2>Urlaubsantrag</h2>
<?php if ($error): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
<?php if ($message): ?>
<div class="alert alert-success"><?php echo htmlspecialchars($message); ?></div>
<?php endif; ?>
<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">
<label>Von:</label>
<input type="date" name="start_date" class="form-control" required>
</div>
<div class="form-group">
<label>Bis:</label>
<input type="date" name="end_date" class="form-control" required>
</div>
<div class="form-group">
<label>Kommentar (optional):</label>
<textarea name="comment" class="form-control"></textarea>
</div>
<br>
<button type="submit" class="btn btn-primary btn-block">
<?php echo $canManageTeamVacations ? 'Urlaub eintragen' : 'Urlaub beantragen'; ?>
</button>
</form>
<hr>
<h4><?php echo $canManageTeamVacations ? 'Urlaubseintraege' : 'Meine Antraege'; ?></h4>
<?php
$listSql = "
SELECT v.*, u.vorname, u.nachname
FROM vacations v
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;
fetch(url).then(function(res){ return res.json(); }).then(function(data){ successCallback(data); }).catch(function(err){ failureCallback(err); });
},
eventClick: function(info) {
var ev = info.event;
var props = ev.extendedProps;
var html = '<strong>' + ev.title + '</strong><br>' + ev.start.toLocaleDateString() + ' - ' + (new Date(ev.end).toLocaleDateString()) + '<br>';
if (props.type === 'user') {
html += 'Status: ' + (props.status || '') + '<br>';
html += 'Kommentar: ' + (props.comment || '') + '<br>';
} else if (props.type === 'company') {
html += 'Beschreibung: ' + (props.description || '') + '<br>';
}
eventClick: function(info) {
var ev = info.event;
var props = ev.extendedProps;
var html = '<strong>' + ev.title + '</strong><br>' + ev.start.toLocaleDateString() + ' - ' + (new Date(ev.end).toLocaleDateString()) + '<br>';
if (props.type === 'user') {
html += 'Mitarbeiter: ' + (props.employee_name || '') + '<br>';
html += 'Status: ' + (props.status || '') + '<br>';
html += 'Kommentar: ' + (props.comment || '') + '<br>';
} else if (props.type === 'company') {
html += 'Beschreibung: ' + (props.description || '') + '<br>';
}
document.getElementById('detailsContent').innerHTML = html;
document.getElementById('eventDetails').style.display = 'block';
}
+63 -59
View File
@@ -1,59 +1,63 @@
<?php
session_start();
require_once('inc/config.inc.php');
require_once('inc/functions.inc.php');
// allow any logged-in user to view the team calendar (read-only)
$user = check_user();
include 'header.php';
?>
<div class="container">
<h2>Team Urlaubskalender</h2>
<div id="calendar"></div>
<br>
<div>
<span class="badge badge-success">genehmigt</span>
<span class="badge badge-primary">Betriebsurlaub</span>
</div>
<br>
<div id="eventDetails" style="display:none;">
<h4>Details</h4>
<div id="detailsContent"></div>
</div>
</div>
<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>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth',
firstDay: 1,
height: 650,
events: function(info, successCallback, failureCallback) {
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); });
},
eventClick: function(info) {
var ev = info.event;
var props = ev.extendedProps;
var html = '<strong>' + ev.title + '</strong><br>' + ev.start.toLocaleDateString() + ' - ' + (new Date(ev.end).toLocaleDateString()) + '<br>';
if (props.type === 'user') {
html += 'Mitarbeiter-ID: ' + (props.user_id || '') + '<br>';
} else if (props.type === 'company') {
html += 'Beschreibung: ' + (props.description || '') + '<br>';
}
document.getElementById('detailsContent').innerHTML = html;
document.getElementById('eventDetails').style.display = 'block';
}
});
calendar.render();
});
</script>
<?php include 'footer.php'; ?>
<?php
session_start();
require_once('inc/config.inc.php');
require_once('inc/functions.inc.php');
$user = check_user();
include 'header.php';
?>
<div class="container">
<h2>Team Urlaubskalender</h2>
<div id="calendar"></div>
<br>
<div>
<span class="badge badge-success">genehmigt</span>
<span class="badge badge-warning">beantragt</span>
<span class="badge badge-primary">Betriebsurlaub</span>
</div>
<br>
<div id="eventDetails" style="display:none;">
<h4>Details</h4>
<div id="detailsContent"></div>
</div>
</div>
<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>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth',
firstDay: 1,
height: 650,
events: function(info, successCallback, failureCallback) {
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); });
},
eventClick: function(info) {
var ev = info.event;
var props = ev.extendedProps;
var html = '<strong>' + ev.title + '</strong><br>' + ev.start.toLocaleDateString() + ' - ' + (new Date(ev.end).toLocaleDateString()) + '<br>';
if (props.type === 'user') {
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') {
html += 'Beschreibung: ' + (props.description || '') + '<br>';
}
document.getElementById('detailsContent').innerHTML = html;
document.getElementById('eventDetails').style.display = 'block';
}
});
calendar.render();
});
</script>
<?php include 'footer.php'; ?>