Anpassung Ladezeit Impfen + Urlaubsplaner

This commit is contained in:
2026-03-30 08:44:45 +02:00
parent 8470e90f56
commit e22dbc980c
12 changed files with 1368 additions and 708 deletions
+36 -11
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,10 +657,19 @@ 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;
$pdo->beginTransaction();
try {
foreach ($_POST['Starttime'] as $Starttime) { foreach ($_POST['Starttime'] as $Starttime) {
//echo $datum . "<br>";
if($_POST["Starttime"][$i] != "0000-00-00"){ if($_POST["Starttime"][$i] != "0000-00-00"){
//echo $_POST["urlaubid"][$i] . "<br>"; $vertretung = trim((string)($_POST['vertretung'][$i] ?? ''));
$vertretertelefon = trim((string)($_POST['vertretertelefon'][$i] ?? ''));
$vertreteradresse = trim((string)($_POST['vertreteradresse'][$i] ?? ''));
$vertreterurl = trim((string)($_POST['vertreterurl'][$i] ?? ''));
if ($vertretung === '' || $vertretertelefon === '' || $vertreteradresse === '' || $vertreterurl === '') {
throw new RuntimeException("Bitte alle Vertreterinformationen fuer jeden Urlaubseintrag vollstaendig ausfuellen.");
}
$stmt = $pdo->prepare(" $stmt = $pdo->prepare("
INSERT INTO urlaub INSERT INTO urlaub
(urlaubid, vertretung, start, ende, vertretertelefon, vertreteradresse, vertreterurl) (urlaubid, vertretung, start, ende, vertretertelefon, vertreteradresse, vertreterurl)
@@ -676,20 +686,35 @@ if(!check_worker()){
$ok = $stmt->execute([ $ok = $stmt->execute([
':urlaubid' => (int)$_POST['urlaubid'][$i], // 0 = INSERT, >0 = UPDATE ':urlaubid' => (int)$_POST['urlaubid'][$i], // 0 = INSERT, >0 = UPDATE
':vertretung' => $_POST['vertretung'][$i], ':vertretung' => $vertretung,
':start' => $_POST['Starttime'][$i], ':start' => $_POST['Starttime'][$i],
':ende' => $_POST['Endetime'][$i], ':ende' => $_POST['Endetime'][$i],
':telefon' => $_POST['vertretertelefon'][$i], ':telefon' => $vertretertelefon,
':adresse' => $_POST['vertreteradresse'][$i], ':adresse' => $vertreteradresse,
':url' => $_POST['vertreterurl'][$i], ':url' => $vertreterurl,
]); ]);
if (!$ok) { if (!$ok) {
throw new RuntimeException("Fehler beim Eintragen in der Datenbank."); 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") {
@@ -2026,7 +2051,7 @@ if(!check_worker()){
var div = document.getElementById('neuerUrlaub'); var div = document.getElementById('neuerUrlaub');
div.innerHTML += "Start: <input name=Starttime[] type=date class='form-control' > Ende: <input name=Endetime[] type=date class='form-control' >Vertretung: <input type=text name=vertretung[] weight=100 class='form-control'> Vertretung Telefon: <input type=text name=vertretertelefon[] weight=100 class='form-control'> Vertretung Adresse: <input type=text name=vertreteradresse[] weight=100 class='form-control'> Vertretung Webseite: <input type=text name=vertreterurl[] weight=100 class='form-control'> <input name=urlaubid[] type=hidden value='0'> <br>"; div.innerHTML += "Start: <input name=Starttime[] type=date class='form-control' > Ende: <input name=Endetime[] type=date class='form-control' >Vertretung: <input type=text name=vertretung[] weight=100 class='form-control' required> Vertretung Telefon: <input type=text name=vertretertelefon[] weight=100 class='form-control' required> Vertretung Adresse: <input type=text name=vertreteradresse[] weight=100 class='form-control' required> Vertretung Webseite: <input type=text name=vertreterurl[] weight=100 class='form-control' required> <input name=urlaubid[] type=hidden value='0'> <br>";
//Public: <select name=aktiv[] id='aktiv' required ><option value='1' >Ja</option> <option value='0'>Nein</option></select> //Public: <select name=aktiv[] id='aktiv' required ><option value='1' >Ja</option> <option value='0'>Nein</option></select>
//document.getElementById('neueTermine').innerHTML = div; //document.getElementById('neueTermine').innerHTML = div;
+39 -9
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,6 +673,13 @@ $eventOverview = [];
$planWaitCounts = []; $planWaitCounts = [];
try { try {
$needsPlanData = in_array($view, ['teilnehmer', 'event-create'], true);
$needsEligibilityData = ($view === 'event-create');
$needsPersonSearch = ($view === 'teilnehmer' && $personSearch !== '');
$needsWaitRows = ($view === 'warteliste');
$needsUpcomingRows = ($view === 'event-teilnehmer');
if ($needsPlanData) {
$stRules = $pdo->prepare("SELECT r.impfstoff_id, r.dosen_pro_flasche, i.impfname, $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 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 FROM impfstoff_workflow r
@@ -683,12 +700,20 @@ try {
foreach ($rules as $r) { foreach ($rules as $r) {
$iid = (int)$r['impfstoff_id']; $iid = (int)$r['impfstoff_id'];
$dosen = (int)$r['dosen_pro_flasche']; if (!isset($planExistsForImpfstoff[$iid])) {
if ($dosen <= 0 || !isset($planExistsForImpfstoff[$iid])) {
continue; continue;
} }
$configuredImpfstoffe[] = $r; $configuredImpfstoffe[] = $r;
if (!$needsEligibilityData) {
continue;
}
$dosen = (int)$r['dosen_pro_flasche'];
if ($dosen <= 0) {
continue;
}
$hasEligiblePlan = false; $hasEligiblePlan = false;
foreach ($plans as $plan) { foreach ($plans as $plan) {
if (!in_array($iid, $plan['impfstoff_id_list'] ?? [], true)) { if (!in_array($iid, $plan['impfstoff_id_list'] ?? [], true)) {
@@ -706,8 +731,9 @@ try {
$eligible[] = $r; $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,6 +752,7 @@ try {
$personResults = $stPersons->fetchAll(PDO::FETCH_ASSOC); $personResults = $stPersons->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, $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, p.vorname, p.nachname, p.geburtstag, p.email, p.tele,
i.impfname i.impfname
@@ -738,20 +765,22 @@ try {
$stWait->execute(); $stWait->execute();
$waitRows = $stWait->fetchAll(PDO::FETCH_ASSOC); $waitRows = $stWait->fetchAll(PDO::FETCH_ASSOC);
$waitIds = array_map(static function (array $waitRow): int {
return (int)($waitRow['warteid'] ?? 0);
}, $waitRows);
$waitLabelsById = impfGetWartelistenZeitraeumeLabelsMap($pdo, $waitIds, false);
foreach ($waitRows as &$waitRow) { foreach ($waitRows as &$waitRow) {
$waitRow['zeitraum_labels'] = impfGetWartelistenZeitraeumeLabels($pdo, (int)$waitRow['warteid'], false); $warteid = (int)($waitRow['warteid'] ?? 0);
$waitRow['zeitraum_labels'] = $waitLabelsById[$warteid] ?? [];
if (!empty($waitRow['zeitraum_labels'])) { if (!empty($waitRow['zeitraum_labels'])) {
$waitRow['impfenzeitraum'] = implode(' | ', $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);
} }
if ($needsUpcomingRows) {
$stUpcoming = $pdo->prepare("SELECT ts.timeid, ts.date, ts.start, ts.ende, ts.impfdosen, $stUpcoming = $pdo->prepare("SELECT ts.timeid, ts.date, ts.start, ts.ende, ts.impfdosen,
i.impfname, o.anzeigename, o.adresse, i.impfname, o.anzeigename, o.adresse,
it.terminid, it.checked, it.behandelt, it.impfart, it.terminid, it.checked, it.behandelt, it.impfart,
@@ -796,6 +825,7 @@ try {
]; ];
} }
} }
}
} catch (Throwable $e) { } catch (Throwable $e) {
$error = trim($error . ' Fehler beim Laden der Impfverwaltung: ' . $e->getMessage()); $error = trim($error . ' Fehler beim Laden der Impfverwaltung: ' . $e->getMessage());
} }
+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
{ {
+19 -25
View File
@@ -1,10 +1,8 @@
<?php <?php
// API: returns JSON events for FullCalendar
session_start(); session_start();
require_once(__DIR__ . '/../inc/config.inc.php'); require_once(__DIR__ . '/../inc/config.inc.php');
require_once(__DIR__ . '/../inc/functions.inc.php'); require_once(__DIR__ . '/../inc/functions.inc.php');
// Enable full error reporting for API debugging
ini_set('display_errors', '1'); ini_set('display_errors', '1');
ini_set('display_startup_errors', '1'); ini_set('display_startup_errors', '1');
error_reporting(E_ALL); error_reporting(E_ALL);
@@ -12,16 +10,12 @@ error_reporting(E_ALL);
$user = check_user(); $user = check_user();
$isAdmin = is_admin_user(); $isAdmin = is_admin_user();
$start = $_GET['start'] ?? null; // expected ISO date $start = $_GET['start'] ?? null;
$end = $_GET['end'] ?? null; $end = $_GET['end'] ?? null;
$onlyApproved = isset($_GET['only_approved']) && ($_GET['only_approved'] == '1' || $_GET['only_approved'] === 'true'); $onlyApproved = isset($_GET['only_approved']) && ($_GET['only_approved'] == '1' || $_GET['only_approved'] === 'true');
// public allows non-admin users to request all *approved* vacations (team view)
$public = isset($_GET['public']) && ($_GET['public'] == '1' || $_GET['public'] === 'true'); $public = isset($_GET['public']) && ($_GET['public'] == '1' || $_GET['public'] === 'true');
// include_rejected if set to 1 will return rejected entries; default behavior: do not return rejected for regular users
$includeRejected = isset($_GET['include_rejected']) && ($_GET['include_rejected'] == '1' || $_GET['include_rejected'] === 'true'); $includeRejected = isset($_GET['include_rejected']) && ($_GET['include_rejected'] == '1' || $_GET['include_rejected'] === 'true');
// only_personal forces the API to return only the current user's vacations (useful even if the user is admin)
$onlyPersonal = isset($_GET['only_personal']) && ($_GET['only_personal'] == '1' || $_GET['only_personal'] === 'true'); $onlyPersonal = isset($_GET['only_personal']) && ($_GET['only_personal'] == '1' || $_GET['only_personal'] === 'true');
// public_all when used with public=1 returns all non-rejected team vacations (approved + beantragt)
$publicAll = isset($_GET['public_all']) && ($_GET['public_all'] == '1' || $_GET['public_all'] === 'true'); $publicAll = isset($_GET['public_all']) && ($_GET['public_all'] == '1' || $_GET['public_all'] === 'true');
if (!$start || !$end) { if (!$start || !$end) {
@@ -34,8 +28,8 @@ $events = [];
try { try {
$branch = 'unknown'; $branch = 'unknown';
$debugMode = isset($_GET['debug']) && ($_GET['debug'] == '1' || $_GET['debug'] === 'true'); $debugMode = isset($_GET['debug']) && ($_GET['debug'] == '1' || $_GET['debug'] === 'true');
$showEmployeeNames = $isAdmin || $public;
// Vacations: support a personal-only mode, admin mode, and public/team mode
if ($onlyPersonal) { if ($onlyPersonal) {
$branch = 'onlyPersonal'; $branch = 'onlyPersonal';
if ($onlyApproved) { if ($onlyApproved) {
@@ -60,7 +54,6 @@ if ($onlyPersonal) {
$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.start_date <= ? AND v.end_date >= ? AND LOWER(TRIM(v.status)) = 'genehmigt' ORDER BY v.start_date");
$stmt->execute([$end, $start]); $stmt->execute([$end, $start]);
} else { } else {
// By default admins see genehmigt + beantragt; include_rejected=1 can override
if ($includeRejected) { 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 = $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]); $stmt->execute([$end, $start]);
@@ -73,12 +66,10 @@ if ($onlyPersonal) {
$branch = 'public_or_regular'; $branch = 'public_or_regular';
if ($public && $onlyApproved) { if ($public && $onlyApproved) {
$branch = '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 = $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]); $stmt->execute([$end, $start]);
} elseif ($public && $publicAll) { } elseif ($public && $publicAll) {
$branch = '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 = $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]); $stmt->execute([$end, $start]);
} else { } else {
@@ -86,7 +77,6 @@ if ($onlyPersonal) {
$stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.user_id = ? AND v.start_date <= ? AND v.end_date >= ? AND LOWER(TRIM(v.status)) = 'genehmigt' ORDER BY v.start_date"); $stmt = $pdo->prepare("SELECT v.*, u.vorname, u.nachname FROM vacations v JOIN users u ON v.user_id = u.id WHERE v.user_id = ? AND v.start_date <= ? AND v.end_date >= ? AND LOWER(TRIM(v.status)) = 'genehmigt' ORDER BY v.start_date");
$stmt->execute([$_SESSION['userid'], $end, $start]); $stmt->execute([$_SESSION['userid'], $end, $start]);
} else { } else {
// By default exclude rejected ('abgelehnt') for regular users; include if include_rejected=1
if ($includeRejected) { if ($includeRejected) {
$branch = 'regular_includeRejected'; $branch = '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 = $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");
@@ -101,9 +91,8 @@ if ($onlyPersonal) {
} }
try { try {
$vacations = $stmt->fetchAll(); $vacations = $stmt->fetchAll(PDO::FETCH_ASSOC);
// If debug mode is enabled, prepare meta information
if ($debugMode) { if ($debugMode) {
$rawStatuses = array_map(function($r){ return $r['status'] ?? null; }, $vacations); $rawStatuses = array_map(function($r){ return $r['status'] ?? null; }, $vacations);
$meta = [ $meta = [
@@ -114,29 +103,36 @@ try {
} }
foreach ($vacations as $v) { foreach ($vacations as $v) {
// Normalize status: collapse whitespace (including NBSP), trim, lowercase
if (isset($v['status'])) { if (isset($v['status'])) {
$normalized = preg_replace('/\s+/u', ' ', $v['status']); $normalized = preg_replace('/\s+/u', ' ', $v['status']);
$status = mb_strtolower(trim($normalized)); $status = mb_strtolower(trim($normalized));
} else { } else {
$status = ''; $status = '';
} }
// Defensive filter: do not expose rejected ('abgelehnt') entries to non-admins
if (!$isAdmin && !$includeRejected && mb_stripos($status, 'abgelehnt') !== false) { if (!$isAdmin && !$includeRejected && mb_stripos($status, 'abgelehnt') !== false) {
continue; continue;
} }
$isApproved = (mb_stripos($status, 'genehmigt') !== false); $isApproved = (mb_stripos($status, 'genehmigt') !== false);
if ($isAdmin) { $employeeName = trim(($v['vorname'] ?? '') . ' ' . ($v['nachname'] ?? ''));
$title = $v['vorname'] . ' ' . $v['nachname'] . ' (' . ($v['status'] ?? 'beantragt') . ')'; if ($showEmployeeNames && $employeeName !== '') {
} else { $title = $employeeName;
$title = $isApproved ? 'Urlaub' : 'Urlaubsantrag'; if ($v['status'] !== null && $v['status'] !== '') {
$title .= ' (' . $v['status'] . ')';
} }
// Safely compute end date; fallback to start_date if invalid } elseif ($isApproved) {
$title = 'Urlaub';
} else {
$title = 'Urlaubsantrag';
}
try { try {
$endInclusive = (new DateTime($v['end_date']))->modify('+1 day')->format('Y-m-d'); $endInclusive = (new DateTime($v['end_date']))->modify('+1 day')->format('Y-m-d');
} catch (Exception $e) { } catch (Exception $e) {
$endInclusive = $v['start_date']; $endInclusive = $v['start_date'];
} }
$events[] = [ $events[] = [
'id' => 'vac_' . $v['id'], 'id' => 'vac_' . $v['id'],
'title' => $title, 'title' => $title,
@@ -147,6 +143,7 @@ try {
'extendedProps' => [ 'extendedProps' => [
'type' => 'user', 'type' => 'user',
'user_id' => $v['user_id'], 'user_id' => $v['user_id'],
'employee_name' => $employeeName,
'status' => $v['status'], 'status' => $v['status'],
'comment' => $v['comment_user'] ?? '' 'comment' => $v['comment_user'] ?? ''
] ]
@@ -158,7 +155,6 @@ try {
echo json_encode($payload); echo json_encode($payload);
exit; exit;
} }
} catch (Exception $ex) { } catch (Exception $ex) {
header('Content-Type: application/json; charset=utf-8'); header('Content-Type: application/json; charset=utf-8');
$payload = ['error' => $ex->getMessage(), 'branch' => $branch, 'trace' => $ex->getTraceAsString()]; $payload = ['error' => $ex->getMessage(), 'branch' => $branch, 'trace' => $ex->getTraceAsString()];
@@ -166,10 +162,9 @@ try {
exit; exit;
} }
// Company holidays (visible to all)
$stmt = $pdo->prepare("SELECT * FROM company_holidays WHERE start_date <= ? AND end_date >= ? ORDER BY start_date"); $stmt = $pdo->prepare("SELECT * FROM company_holidays WHERE start_date <= ? AND end_date >= ? ORDER BY start_date");
$stmt->execute([$end, $start]); $stmt->execute([$end, $start]);
$holidays = $stmt->fetchAll(); $holidays = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($holidays as $h) { foreach ($holidays as $h) {
$endInclusive = (new DateTime($h['end_date']))->modify('+1 day')->format('Y-m-d'); $endInclusive = (new DateTime($h['end_date']))->modify('+1 day')->format('Y-m-d');
@@ -193,5 +188,4 @@ if ($debugMode) {
} else { } else {
echo json_encode($events); echo json_encode($events);
} }
?> ?>
+95 -26
View File
@@ -2,52 +2,115 @@
session_start(); session_start();
require_once('inc/config.inc.php'); require_once('inc/config.inc.php');
require_once('inc/functions.inc.php'); require_once('inc/functions.inc.php');
require_once(__DIR__ . '/../inc/company_holiday_sync.inc.php');
$user = check_user(); $user = check_user();
if (!is_admin_user()) { if (!is_admin_user()) {
die('Zugriff verweigert. Nur Chefs dürfen Betriebsurlaub verwalten.'); die('Zugriff verweigert. Nur Chefs duerfen Betriebsurlaub verwalten.');
} }
// Create table if not exists (optional helper) $error = '';
// Administrators can also run the SQL directly in DB. This is just a convenience. $schemaError = '';
try {
vacationSyncEnsureSchema($pdo);
} catch (Throwable $e) {
$schemaError = 'Die Seite konnte das Urlaubsschema nicht automatisch aktualisieren: ' . $e->getMessage();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['start_date']) && isset($_POST['end_date'])) { if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['start_date']) && isset($_POST['end_date'])) {
$start = $_POST['start_date']; $start = trim((string)($_POST['start_date'] ?? ''));
$end = $_POST['end_date']; $end = trim((string)($_POST['end_date'] ?? ''));
$desc = trim($_POST['description'] ?? 'Betriebsurlaub'); $desc = trim((string)($_POST['description'] ?? 'Betriebsurlaub'));
$vertretung = trim((string)($_POST['vertretung'] ?? ''));
$vertretertelefon = trim((string)($_POST['vertretertelefon'] ?? ''));
$vertreteradresse = trim((string)($_POST['vertreteradresse'] ?? ''));
$vertreterurl = trim((string)($_POST['vertreterurl'] ?? ''));
$stmt = $pdo->prepare("INSERT INTO company_holidays (start_date, end_date, description, created_by) VALUES (?, ?, ?, ?)"); if ($start === '' || $end === '') {
$stmt->execute([$start, $end, $desc, $_SESSION['userid']]); $error = 'Bitte Start- und Enddatum angeben.';
} elseif ($start > $end) {
$error = 'Das Enddatum darf nicht vor dem Startdatum liegen.';
} elseif ($vertretung === '' || $vertretertelefon === '' || $vertreteradresse === '' || $vertreterurl === '') {
$error = 'Bitte alle Vertreterinformationen vollstaendig ausfuellen.';
} elseif ($schemaError !== '') {
$error = $schemaError;
} else {
$stmt = $pdo->prepare("
INSERT INTO company_holidays (
start_date, end_date, description, vertretung, vertretertelefon, vertreteradresse, vertreterurl, created_by
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$start,
$end,
$desc,
$vertretung,
$vertretertelefon,
$vertreteradresse,
$vertreterurl,
$_SESSION['userid']
]);
vacationSyncUrlaubFromCompanyHoliday($pdo, (int)$pdo->lastInsertId());
header('Location: company_holidays.php'); header('Location: company_holidays.php');
exit(); exit();
} }
}
include 'header.php'; include 'header.php';
$stmt = $pdo->prepare("SELECT * FROM company_holidays ORDER BY start_date DESC"); $stmt = $pdo->prepare("SELECT * FROM company_holidays ORDER BY start_date DESC");
$stmt->execute(); $stmt->execute();
$holidays = $stmt->fetchAll(); $holidays = $stmt->fetchAll(PDO::FETCH_ASSOC);
?> ?>
<div class="container"> <div class="container">
<h2>Betriebsurlaub verwalten</h2> <h2>Betriebsurlaub verwalten</h2>
<form method="post" class="form-inline mb-3"> <?php if ($error !== ''): ?>
<div class="form-group mr-2"> <div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
<?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> <label>Von:</label>
<input type="date" name="start_date" class="form-control" required> <input type="date" name="start_date" class="form-control" required>
</div> </div>
<div class="form-group mr-2"> <div class="form-group col-md-3">
<label>Bis:</label> <label>Bis:</label>
<input type="date" name="end_date" class="form-control" required> <input type="date" name="end_date" class="form-control" required>
</div> </div>
<div class="form-group mr-2"> <div class="form-group col-md-6">
<label>Beschreibung:</label> <label>Beschreibung:</label>
<input type="text" name="description" class="form-control" placeholder="z. B. Betriebsurlaub Weihnachten"> <input type="text" name="description" class="form-control" placeholder="z. B. Betriebsurlaub Weihnachten">
</div> </div>
<button class="btn btn-primary">Hinzufügen</button> </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> </form>
<table class="table table-bordered"> <table class="table table-bordered">
@@ -56,6 +119,8 @@ $holidays = $stmt->fetchAll();
<th>Von</th> <th>Von</th>
<th>Bis</th> <th>Bis</th>
<th>Beschreibung</th> <th>Beschreibung</th>
<th>Vertretung</th>
<th>Kontakt</th>
<th>Erstellt von</th> <th>Erstellt von</th>
<th>Aktion</th> <th>Aktion</th>
</tr> </tr>
@@ -63,19 +128,25 @@ $holidays = $stmt->fetchAll();
<tbody> <tbody>
<?php foreach ($holidays as $h): ?> <?php foreach ($holidays as $h): ?>
<tr> <tr>
<td><?php echo $h['start_date']; ?></td> <td><?php echo htmlspecialchars((string)$h['start_date']); ?></td>
<td><?php echo $h['end_date']; ?></td> <td><?php echo htmlspecialchars((string)$h['end_date']); ?></td>
<td><?php echo htmlspecialchars($h['description']); ?></td> <td><?php echo htmlspecialchars((string)$h['description']); ?></td>
<td><?php echo htmlspecialchars((string)$h['vertretung']); ?></td>
<td>
<?php echo htmlspecialchars((string)$h['vertretertelefon']); ?><br>
<?php echo htmlspecialchars((string)$h['vertreteradresse']); ?><br>
<?php echo htmlspecialchars((string)$h['vertreterurl']); ?>
</td>
<td><?php <td><?php
$s = $pdo->prepare("SELECT vorname, nachname FROM users WHERE id = ?"); $s = $pdo->prepare("SELECT vorname, nachname FROM users WHERE id = ?");
$s->execute([$h['created_by']]); $s->execute([$h['created_by']]);
$u = $s->fetch(); $u = $s->fetch(PDO::FETCH_ASSOC);
echo htmlspecialchars($u['vorname'] . ' ' . $u['nachname']); echo htmlspecialchars(trim(($u['vorname'] ?? '') . ' ' . ($u['nachname'] ?? '')));
?></td> ?></td>
<td> <td>
<form method="post" action="deleteCompanyHoliday.php" onsubmit="return confirm('Betriebsurlaub wirklich löschen?');"> <form method="post" action="deleteCompanyHoliday.php" onsubmit="return confirm('Betriebsurlaub wirklich loeschen?');">
<input type="hidden" name="id" value="<?php echo intval($h['id']); ?>"> <input type="hidden" name="id" value="<?php echo (int)$h['id']; ?>">
<button class="btn btn-sm btn-danger">Löschen</button> <button class="btn btn-sm btn-danger">Loeschen</button>
</form> </form>
</td> </td>
</tr> </tr>
@@ -85,6 +156,4 @@ $holidays = $stmt->fetchAll();
</div> </div>
<?php include 'footer.php'; <?php include 'footer.php'; ?>
?>
+2
View File
@@ -2,6 +2,7 @@
session_start(); session_start();
require_once('inc/config.inc.php'); require_once('inc/config.inc.php');
require_once('inc/functions.inc.php'); require_once('inc/functions.inc.php');
require_once(__DIR__ . '/../inc/company_holiday_sync.inc.php');
$user = check_user(); $user = check_user();
if (!is_admin_user()) { if (!is_admin_user()) {
@@ -18,6 +19,7 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['id'])) {
$id = intval($_POST['id']); $id = intval($_POST['id']);
vacationSyncDeleteUrlaubByCompanyHoliday($pdo, $id);
$stmt = $pdo->prepare("DELETE FROM company_holidays WHERE id = ?"); $stmt = $pdo->prepare("DELETE FROM company_holidays WHERE id = ?");
$stmt->execute([$id]); $stmt->execute([$id]);
+3 -7
View File
@@ -13,26 +13,22 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['id'])) {
$id = (int)$_POST['id']; $id = (int)$_POST['id'];
$referer = $_POST['referer'] ?? 'urlaubsantrag.php'; $referer = $_POST['referer'] ?? 'urlaubsantrag.php';
// Fetch vacation to verify ownership
$stmt = $pdo->prepare("SELECT user_id, status FROM vacations WHERE id = ?"); $stmt = $pdo->prepare("SELECT user_id, status FROM vacations WHERE id = ?");
$stmt->execute([$id]); $stmt->execute([$id]);
$vac = $stmt->fetch(); $vac = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$vac) { if (!$vac) {
die('Urlaubseintrag nicht gefunden.'); die('Urlaubseintrag nicht gefunden.');
} }
$isAdmin = is_admin_user(); $canManageTeamVacations = can_manage_team_vacations();
if (!$canManageTeamVacations && (int)$vac['user_id'] !== (int)$_SESSION['userid']) {
if (!$isAdmin && $vac['user_id'] != $_SESSION['userid']) {
die('Zugriff verweigert.'); die('Zugriff verweigert.');
} }
// Allow deletion for admins or owner
$del = $pdo->prepare("DELETE FROM vacations WHERE id = ?"); $del = $pdo->prepare("DELETE FROM vacations WHERE id = ?");
$del->execute([$id]); $del->execute([$id]);
header('Location: ' . $referer); header('Location: ' . $referer);
exit(); exit();
?> ?>
+9
View File
@@ -145,6 +145,15 @@ function is_admin_user() {
$statement->execute(array('id' => $_SESSION['userid'])); $statement->execute(array('id' => $_SESSION['userid']));
return ($statement->rowCount() == 1); return ($statement->rowCount() == 1);
} }
/**
* Returns true when the user may manage vacations for the team.
* In the current Zeiterfassung, team leads are represented by the
* existing admin role.
*/
function can_manage_team_vacations() {
return is_admin_user();
}
/** /**
* Prüft, ob der Benutzer Bearbeiter ist * Prüft, ob der Benutzer Bearbeiter ist
*/ */
+103 -40
View File
@@ -9,9 +9,23 @@ if (!isset($_SESSION['userid'])) {
die("Kein Benutzer angemeldet."); die("Kein Benutzer angemeldet.");
} }
$user_id = $_SESSION['userid']; $user_id = (int)$_SESSION['userid'];
$canManageTeamVacations = can_manage_team_vacations();
$message = ""; $message = "";
$error = ""; $error = "";
$selected_user_id = $user_id;
$selectableUsers = [];
if ($canManageTeamVacations) {
$stmtUsers = $pdo->prepare("
SELECT id, vorname, nachname, email
FROM users
WHERE zeiterfassung = 1
ORDER BY nachname, vorname
");
$stmtUsers->execute();
$selectableUsers = $stmtUsers->fetchAll(PDO::FETCH_ASSOC);
}
function calculateWorkingDays($start, $end) { function calculateWorkingDays($start, $end) {
$start = new DateTime($start); $start = new DateTime($start);
@@ -22,9 +36,8 @@ function calculateWorkingDays($start, $end) {
$period = new DatePeriod($start, $interval, $end); $period = new DatePeriod($start, $interval, $end);
$workingDays = 0; $workingDays = 0;
foreach ($period as $day) { foreach ($period as $day) {
if ($day->format('N') < 6) { // 1 (Mo) - 5 (Fr) if ($day->format('N') < 6) {
$workingDays++; $workingDays++;
} }
} }
@@ -32,23 +45,41 @@ function calculateWorkingDays($start, $end) {
return $workingDays; return $workingDays;
} }
if ($_SERVER["REQUEST_METHOD"] == "POST") { if ($_SERVER["REQUEST_METHOD"] === "POST") {
$start_date = trim((string)($_POST['start_date'] ?? ''));
$end_date = trim((string)($_POST['end_date'] ?? ''));
$comment = trim((string)($_POST['comment'] ?? ''));
$selected_user_id = $canManageTeamVacations ? (int)($_POST['user_id'] ?? $user_id) : $user_id;
$start_date = $_POST['start_date']; $selectedUser = null;
$end_date = $_POST['end_date']; if ($selected_user_id <= 0) {
$comment = trim($_POST['comment']); $error = "Bitte einen Mitarbeiter auswaehlen.";
if (empty($start_date) || empty($end_date)) {
$error = "Bitte beide Datumsfelder ausfüllen.";
} elseif ($start_date > $end_date) {
$error = "Enddatum liegt vor dem Startdatum.";
} elseif ($start_date < date("Y-m-d")) {
$error = "Urlaub kann nicht in der Vergangenheit beantragt werden.";
} else { } else {
$stmtSelectedUser = $pdo->prepare("
SELECT id, vorname, nachname
FROM users
WHERE id = ?
AND zeiterfassung = 1
LIMIT 1
");
$stmtSelectedUser->execute([$selected_user_id]);
$selectedUser = $stmtSelectedUser->fetch(PDO::FETCH_ASSOC);
// Überschneidung prüfen 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(" $stmt = $pdo->prepare("
SELECT COUNT(*) FROM vacations SELECT COUNT(*)
FROM vacations
WHERE user_id = ? WHERE user_id = ?
AND status != 'abgelehnt' AND status != 'abgelehnt'
AND ( AND (
@@ -57,26 +88,28 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
OR (? BETWEEN start_date AND end_date) OR (? BETWEEN start_date AND end_date)
) )
"); ");
$stmt->execute([$user_id, $start_date, $end_date, $start_date, $end_date, $start_date]); $stmt->execute([$selected_user_id, $start_date, $end_date, $start_date, $end_date, $start_date]);
$exists = $stmt->fetchColumn(); $exists = (int)$stmt->fetchColumn();
if ($exists > 0) { if ($exists > 0) {
$error = "Der Zeitraum überschneidet sich mit einem bestehenden Antrag."; $error = "Der Zeitraum ueberschneidet sich mit einem bestehenden Antrag.";
} else { } else {
$days = calculateWorkingDays($start_date, $end_date); $days = calculateWorkingDays($start_date, $end_date);
$insert = $pdo->prepare(" $insert = $pdo->prepare("
INSERT INTO vacations (user_id, start_date, end_date, days, comment_user) INSERT INTO vacations (user_id, start_date, end_date, days, comment_user)
VALUES (?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?)
"); ");
$insert->execute([$selected_user_id, $start_date, $end_date, $days, $comment]);
$insert->execute([$user_id, $start_date, $end_date, $days, $comment]); if ($selected_user_id !== $user_id && $selectedUser) {
$message = "Urlaub fuer " . $selectedUser['vorname'] . " " . $selectedUser['nachname'] . " erfolgreich eingereicht ($days Werktage).";
} else {
$message = "Urlaubsantrag erfolgreich eingereicht ($days Werktage)."; $message = "Urlaubsantrag erfolgreich eingereicht ($days Werktage).";
} }
} }
} }
}
?> ?>
<?php include 'header.php'; ?> <?php include 'header.php'; ?>
@@ -88,15 +121,29 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
<h2>Urlaubsantrag</h2> <h2>Urlaubsantrag</h2>
<?php if ($error): ?> <?php if ($error): ?>
<div class="alert alert-danger"><?php echo $error; ?></div> <div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?> <?php endif; ?>
<?php if ($message): ?> <?php if ($message): ?>
<div class="alert alert-success"><?php echo $message; ?></div> <div class="alert alert-success"><?php echo htmlspecialchars($message); ?></div>
<?php endif; ?> <?php endif; ?>
<form method="post"> <form method="post">
<?php if ($canManageTeamVacations): ?>
<div class="form-group">
<label>Mitarbeiter:</label>
<select name="user_id" class="form-control" required>
<?php foreach ($selectableUsers as $employee): ?>
<?php $employeeId = (int)$employee['id']; ?>
<option value="<?php echo $employeeId; ?>" <?php echo ($selected_user_id === $employeeId) ? 'selected' : ''; ?>>
<?php echo htmlspecialchars(trim($employee['nachname'] . ', ' . $employee['vorname'] . ' | ' . $employee['email'])); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
<div class="form-group"> <div class="form-group">
<label>Von:</label> <label>Von:</label>
<input type="date" name="start_date" class="form-control" required> <input type="date" name="start_date" class="form-control" required>
@@ -115,27 +162,40 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
<br> <br>
<button type="submit" class="btn btn-primary btn-block"> <button type="submit" class="btn btn-primary btn-block">
Urlaub beantragen <?php echo $canManageTeamVacations ? 'Urlaub eintragen' : 'Urlaub beantragen'; ?>
</button> </button>
</form> </form>
<hr> <hr>
<h4>Meine Anträge</h4> <h4><?php echo $canManageTeamVacations ? 'Urlaubseintraege' : 'Meine Antraege'; ?></h4>
<?php <?php
$stmt = $pdo->prepare(" $listSql = "
SELECT * FROM vacations SELECT v.*, u.vorname, u.nachname
WHERE user_id = ? FROM vacations v
ORDER BY created_at DESC JOIN users u ON u.id = v.user_id
"); ";
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]); $stmt->execute([$user_id]);
$antraege = $stmt->fetchAll(); }
$antraege = $stmt->fetchAll(PDO::FETCH_ASSOC);
?> ?>
<table class="table table-bordered"> <table class="table table-bordered">
<tr> <tr>
<?php if ($canManageTeamVacations): ?>
<th>Mitarbeiter</th>
<?php endif; ?>
<th>Von</th> <th>Von</th>
<th>Bis</th> <th>Bis</th>
<th>Tage</th> <th>Tage</th>
@@ -145,14 +205,17 @@ $antraege = $stmt->fetchAll();
<?php foreach ($antraege as $a): ?> <?php foreach ($antraege as $a): ?>
<tr> <tr>
<td><?php echo $a['start_date']; ?></td> <?php if ($canManageTeamVacations): ?>
<td><?php echo $a['end_date']; ?></td> <td><?php echo htmlspecialchars(trim($a['vorname'] . ' ' . $a['nachname'])); ?></td>
<td><?php echo $a['days']; ?></td> <?php endif; ?>
<td><?php echo htmlspecialchars((string)$a['start_date']); ?></td>
<td><?php echo htmlspecialchars((string)$a['end_date']); ?></td>
<td><?php echo (int)$a['days']; ?></td>
<td> <td>
<?php <?php
if ($a['status'] == 'beantragt') { if ($a['status'] === 'beantragt' || $a['status'] === null || $a['status'] === '') {
echo '<span class="badge badge-warning">Beantragt</span>'; echo '<span class="badge badge-warning">Beantragt</span>';
} elseif ($a['status'] == 'genehmigt') { } elseif ($a['status'] === 'genehmigt') {
echo '<span class="badge badge-success">Genehmigt</span>'; echo '<span class="badge badge-success">Genehmigt</span>';
} else { } else {
echo '<span class="badge badge-danger">Abgelehnt</span>'; echo '<span class="badge badge-danger">Abgelehnt</span>';
@@ -160,10 +223,10 @@ $antraege = $stmt->fetchAll();
?> ?>
</td> </td>
<td> <td>
<form method="post" action="deleteVacation.php" onsubmit="return confirm('Wirklich löschen?');"> <form method="post" action="deleteVacation.php" onsubmit="return confirm('Wirklich loeschen?');">
<input type="hidden" name="id" value="<?php echo $a['id']; ?>"> <input type="hidden" name="id" value="<?php echo (int)$a['id']; ?>">
<input type="hidden" name="referer" value="urlaubsantrag.php"> <input type="hidden" name="referer" value="urlaubsantrag.php">
<button type="submit" class="btn btn-sm btn-danger">Löschen</button> <button type="submit" class="btn btn-sm btn-danger">Loeschen</button>
</form> </form>
</td> </td>
</tr> </tr>
+1
View File
@@ -46,6 +46,7 @@ document.addEventListener('DOMContentLoaded', function() {
var props = ev.extendedProps; var props = ev.extendedProps;
var html = '<strong>' + ev.title + '</strong><br>' + ev.start.toLocaleDateString() + ' - ' + (new Date(ev.end).toLocaleDateString()) + '<br>'; var html = '<strong>' + ev.title + '</strong><br>' + ev.start.toLocaleDateString() + ' - ' + (new Date(ev.end).toLocaleDateString()) + '<br>';
if (props.type === 'user') { if (props.type === 'user') {
html += 'Mitarbeiter: ' + (props.employee_name || '') + '<br>';
html += 'Status: ' + (props.status || '') + '<br>'; html += 'Status: ' + (props.status || '') + '<br>';
html += 'Kommentar: ' + (props.comment || '') + '<br>'; html += 'Kommentar: ' + (props.comment || '') + '<br>';
} else if (props.type === 'company') { } else if (props.type === 'company') {
+6 -2
View File
@@ -3,7 +3,6 @@ session_start();
require_once('inc/config.inc.php'); require_once('inc/config.inc.php');
require_once('inc/functions.inc.php'); require_once('inc/functions.inc.php');
// allow any logged-in user to view the team calendar (read-only)
$user = check_user(); $user = check_user();
include 'header.php'; include 'header.php';
@@ -15,6 +14,7 @@ include 'header.php';
<br> <br>
<div> <div>
<span class="badge badge-success">genehmigt</span> <span class="badge badge-success">genehmigt</span>
<span class="badge badge-warning">beantragt</span>
<span class="badge badge-primary">Betriebsurlaub</span> <span class="badge badge-primary">Betriebsurlaub</span>
</div> </div>
<br> <br>
@@ -43,7 +43,11 @@ document.addEventListener('DOMContentLoaded', function() {
var props = ev.extendedProps; var props = ev.extendedProps;
var html = '<strong>' + ev.title + '</strong><br>' + ev.start.toLocaleDateString() + ' - ' + (new Date(ev.end).toLocaleDateString()) + '<br>'; var html = '<strong>' + ev.title + '</strong><br>' + ev.start.toLocaleDateString() + ' - ' + (new Date(ev.end).toLocaleDateString()) + '<br>';
if (props.type === 'user') { if (props.type === 'user') {
html += 'Mitarbeiter-ID: ' + (props.user_id || '') + '<br>'; html += 'Mitarbeiter: ' + (props.employee_name || ev.title || '') + '<br>';
html += 'Status: ' + (props.status || '') + '<br>';
if (props.comment) {
html += 'Kommentar: ' + props.comment + '<br>';
}
} else if (props.type === 'company') { } else if (props.type === 'company') {
html += 'Beschreibung: ' + (props.description || '') + '<br>'; html += 'Beschreibung: ' + (props.description || '') + '<br>';
} }