Compare commits
20 Commits
7ef1bbb2e9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| fd320ba0c6 | |||
| aae89a45a8 | |||
| 6360af272a | |||
| 091702c2a2 | |||
| c9b0026f52 | |||
| bb422005d0 | |||
| 098c2d4275 | |||
| 7388b5b379 | |||
| 016753293c | |||
| 874e8a04c0 | |||
| 0084516414 | |||
| e22dbc980c | |||
| 8470e90f56 | |||
| 26666aef30 | |||
| 3fee4eefe2 | |||
| 6dd0ac86b2 | |||
| 211ce11e06 | |||
| 00077aa09a | |||
| 4b4c1f74df | |||
| f5ffaf297d |
@@ -0,0 +1,5 @@
|
||||
*.woff binary
|
||||
*.woff2 binary
|
||||
*.ttf binary
|
||||
*.eot binary
|
||||
*.otf binary
|
||||
@@ -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
|
||||
|
||||
Vendored
+27
@@ -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": ""
|
||||
}
|
||||
}
|
||||
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"sqltools.connections": [
|
||||
{
|
||||
"mysqlOptions": {
|
||||
"authProtocol": "default",
|
||||
"enableSsl": "Disabled"
|
||||
},
|
||||
"ssh": "Disabled",
|
||||
"previewLimit": 50,
|
||||
"server": "mysql2fda.netcup.net",
|
||||
"port": 3306,
|
||||
"driver": "MySQL",
|
||||
"name": "Praxis Creutzburg",
|
||||
"database": "k25330_pracreutz",
|
||||
"username": "k25330_pracreutz"
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+13
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
CREATE TABLE IF NOT EXISTS time_error_notification_state (
|
||||
employee_id INT NOT NULL,
|
||||
cycle_started_on DATE NOT NULL,
|
||||
first_error_date DATE NOT NULL,
|
||||
last_notification_stage VARCHAR(50) DEFAULT NULL,
|
||||
last_notification_sent_at DATETIME DEFAULT NULL,
|
||||
employee_day_1_sent_at DATETIME DEFAULT NULL,
|
||||
employee_day_3_sent_at DATETIME DEFAULT NULL,
|
||||
admin_day_7_sent_at DATETIME DEFAULT NULL,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (employee_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS time_error_notifications (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
employee_id INT NOT NULL,
|
||||
cycle_started_on DATE NOT NULL,
|
||||
notification_stage VARCHAR(50) NOT NULL,
|
||||
recipient_email VARCHAR(255) NOT NULL,
|
||||
sent_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uniq_time_error_notification (employee_id, cycle_started_on, notification_stage, recipient_email)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
+97
-60
@@ -1,16 +1,19 @@
|
||||
<?php
|
||||
// admin/bootstrap.php
|
||||
ob_start(); // fängt zufälligen Output ab, verhindert "headers already sent" Folgeschäden
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
session_start();
|
||||
}
|
||||
session_start();
|
||||
|
||||
require_once(__DIR__ . "/../inc/config.inc.php");
|
||||
require_once(__DIR__ . "/../inc/functions.inc.php");
|
||||
// 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();
|
||||
|
||||
include __DIR__ . "/templates/header.inc.php";
|
||||
|
||||
$user = check_admin_user();
|
||||
$internUserId = (int)$_SESSION['auth']['id'];
|
||||
include("templates/header.inc.php");
|
||||
|
||||
if (!$user) { echo "<div class='container main-container'><h3>Erst anmelden: <a href=login.php>Login</a></h3><br>";
|
||||
|
||||
include("templates/footer.inc.php");
|
||||
@@ -18,22 +21,21 @@ include("templates/footer.inc.php");
|
||||
?>
|
||||
<script src="/admin/js/tinymce/tinymce.min.js" referrerpolicy="origin"></script>
|
||||
<div class="container main-container">
|
||||
<?php
|
||||
##test2
|
||||
echo '<div style="float: right; width: 200px; ">';
|
||||
echo "<form action='". $_SERVER['PHP_SELF'] . "' id='formbenutzersuche' method=POST>";
|
||||
echo '<input type="hidden" name="aktion" value="benutzersuche" />';
|
||||
echo '<input type="hidden" name="userid_input" id="userid_input" />';
|
||||
echo '<label>Benutzersuche Anfragen:</label>
|
||||
<input type="text" id="user_input" name="skill_input" width="48"/>';
|
||||
//echo '<input type="submit" class="btn btn-primary" id="submitbox" value="" />';
|
||||
echo "</form>";
|
||||
echo '</div>';
|
||||
?>
|
||||
<h2>Administration - Anfragen</h2>
|
||||
|
||||
Hallo <?php echo htmlentities($user['vorname']); ?>,<br>
|
||||
Herzlich Willkommen im internen Bereich!<br><br>
|
||||
<div style="display:flex; justify-content:space-between; align-items:flex-start; gap:20px; flex-wrap:wrap;">
|
||||
<div>
|
||||
<h2>Administration - Anfragen</h2>
|
||||
Hallo <?php echo htmlentities($user['vorname']); ?>,<br>
|
||||
Herzlich Willkommen im internen Bereich!<br><br>
|
||||
</div>
|
||||
<div style="width:200px;">
|
||||
<form action="<?php echo htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES, 'UTF-8'); ?>" id="formbenutzersuche" method="POST">
|
||||
<input type="hidden" name="aktion" value="benutzersuche" />
|
||||
<input type="hidden" name="userid_input" id="userid_input" />
|
||||
<label>Benutzersuche Anfragen:</label>
|
||||
<input type="text" id="user_input" name="skill_input" width="48" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -41,13 +43,24 @@ Herzlich Willkommen im internen Bereich!<br><br>
|
||||
|
||||
<?php
|
||||
|
||||
$aktion = $_POST["aktion"] ?? $_GET["aktion"] ?? '';
|
||||
$artRequest = $_POST["art"] ?? $_GET["art"] ?? "1";
|
||||
|
||||
if ($aktion !== '') {
|
||||
$_POST["aktion"] = $aktion;
|
||||
}
|
||||
|
||||
if ($artRequest !== '') {
|
||||
$_POST["art"] = $artRequest;
|
||||
}
|
||||
|
||||
if(!check_worker()){
|
||||
echo "<div class='container main-container'><h3>Erst anmelden: <a href=login.php>Login</a></h3><br>";
|
||||
echo $_SESSION['userid'];
|
||||
|
||||
}else{
|
||||
|
||||
if (($_POST["aktion"] ?? '') == "1") {
|
||||
if ($aktion == "1") {
|
||||
|
||||
|
||||
echo "<header><h2>Anfragen bearbeiten</h2></header>";
|
||||
@@ -93,7 +106,7 @@ if(!check_worker()){
|
||||
|
||||
|
||||
|
||||
$art = $_POST["art"] ?? "1";
|
||||
$art = $artRequest;
|
||||
|
||||
// Default
|
||||
$sql = "
|
||||
@@ -623,12 +636,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>";
|
||||
}
|
||||
@@ -655,39 +668,63 @@ 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>";
|
||||
|
||||
@@ -2025,7 +2062,7 @@ if(!check_worker()){
|
||||
|
||||
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;
|
||||
|
||||
+403
-199
@@ -4,6 +4,7 @@ session_start();
|
||||
require_once __DIR__ . "/../inc/config.inc.php";
|
||||
require_once __DIR__ . "/../inc/functions.inc.php";
|
||||
require_once __DIR__ . "/../inc/functions.impfen.inc.php";
|
||||
require_once __DIR__ . "/../inc/impfworkflow_notifications.inc.php";
|
||||
|
||||
$user = check_admin_user();
|
||||
include __DIR__ . "/templates/header.inc.php";
|
||||
@@ -78,11 +79,87 @@ function ensureWorkflowTables(PDO $pdo): void
|
||||
impfWorkflowEnsureTables($pdo);
|
||||
}
|
||||
|
||||
function workflowDeleteWaitlistEntry(PDO $pdo, int $warteid): void
|
||||
{
|
||||
if ($warteid <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$stDeleteMap = $pdo->prepare("DELETE FROM warteliste_zeitraum WHERE warteid = :wid");
|
||||
$stDeleteMap->execute(['wid' => $warteid]);
|
||||
|
||||
$stDelete = $pdo->prepare("DELETE FROM warteliste WHERE warteid = :wid");
|
||||
$stDelete->execute(['wid' => $warteid]);
|
||||
}
|
||||
|
||||
function workflowLoadWaitlistEntry(PDO $pdo, int $warteid): ?array
|
||||
{
|
||||
if ($warteid <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$stWait = $pdo->prepare("SELECT w.warteid, w.userid, w.checked, p.vorname, p.nachname
|
||||
FROM warteliste w
|
||||
LEFT JOIN persons p ON p.person_id = w.userid
|
||||
WHERE w.warteid = :wid
|
||||
LIMIT 1");
|
||||
$stWait->execute(['wid' => $warteid]);
|
||||
$row = $stWait->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
return $row ?: null;
|
||||
}
|
||||
|
||||
function workflowCountWaitersForPlan(PDO $pdo, int $impfstoffId, int $planId): int
|
||||
{
|
||||
return impfWorkflowNotificationCountWaitersForPlan($pdo, $impfstoffId, $planId);
|
||||
}
|
||||
|
||||
function workflowLoadWaitRowsForPlan(PDO $pdo, int $impfstoffId, int $planId): array
|
||||
{
|
||||
$stW = $pdo->prepare("SELECT w.warteid, w.userid, w.hash, w.impfart, w.Impfaufklaerung, w.WeitereFragen, w.letzteimpfung, w.date_created
|
||||
FROM warteliste w
|
||||
WHERE w.checked = 1
|
||||
AND (w.impfstoff = :iid OR w.impfstoff = 0)
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM warteliste_zeitraum wz
|
||||
WHERE wz.warteid = w.warteid
|
||||
AND wz.zeitraum_id = :zid
|
||||
)
|
||||
ORDER BY w.date_created ASC, w.warteid ASC");
|
||||
$stW->execute([
|
||||
'iid' => $impfstoffId,
|
||||
'zid' => $planId,
|
||||
]);
|
||||
|
||||
return $stW->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
function workflowCountStrictWaitersForPlan(PDO $pdo, int $impfstoffId, int $planId): int
|
||||
{
|
||||
$stW = $pdo->prepare("SELECT COUNT(DISTINCT w.userid)
|
||||
FROM warteliste w
|
||||
WHERE w.checked = 1
|
||||
AND (w.impfstoff = :iid OR w.impfstoff = 0)
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM warteliste_zeitraum wz
|
||||
WHERE wz.warteid = w.warteid
|
||||
AND wz.zeitraum_id = :zid
|
||||
)");
|
||||
$stW->execute([
|
||||
'iid' => $impfstoffId,
|
||||
'zid' => $planId,
|
||||
]);
|
||||
|
||||
return (int)$stW->fetchColumn();
|
||||
}
|
||||
|
||||
function workflowAddWartelisteEntry(
|
||||
PDO $pdo,
|
||||
int $personId,
|
||||
int $impfstoffId,
|
||||
int $planId,
|
||||
$planIds,
|
||||
int $impfart,
|
||||
?string $letzteImpfung,
|
||||
int $checked
|
||||
@@ -92,7 +169,7 @@ function workflowAddWartelisteEntry(
|
||||
}
|
||||
|
||||
$impfstoffId = max(0, $impfstoffId);
|
||||
$planId = max(0, $planId);
|
||||
$planIds = impfNormalizeZeitraumIds($planIds);
|
||||
$impfart = ($impfart >= 1 && $impfart <= 4) ? $impfart : 1;
|
||||
if ($impfart > 1 && !$letzteImpfung) {
|
||||
$impfart = 1;
|
||||
@@ -112,38 +189,39 @@ function workflowAddWartelisteEntry(
|
||||
$impfstoffName = 'ohne Vorgabe';
|
||||
$zeitraum = 'Flexibel';
|
||||
|
||||
if ($planId > 0) {
|
||||
$plan = impfLoadZeitraumById($pdo, $planId, true);
|
||||
if (!$plan) {
|
||||
return [false, "Das ausgewaehlte Zeitfenster ist nicht mehr verfuegbar."];
|
||||
}
|
||||
if (!empty($planIds)) {
|
||||
$zeitraumLabels = [];
|
||||
foreach ($planIds as $planId) {
|
||||
$plan = impfLoadZeitraumById($pdo, $planId, true);
|
||||
if (!$plan) {
|
||||
return [false, "Mindestens ein ausgewaehltes Zeitfenster ist nicht mehr verfuegbar."];
|
||||
}
|
||||
|
||||
$zugeordneteImpfstoffe = $plan['impfstoff_id_list'] ?? [];
|
||||
if ($impfstoffId > 0 && !in_array($impfstoffId, $zugeordneteImpfstoffe, true)) {
|
||||
return [false, "Impfstoff und Zeitfenster passen nicht zusammen."];
|
||||
$zugeordneteImpfstoffe = $plan['impfstoff_id_list'] ?? [];
|
||||
if ($impfstoffId > 0 && !in_array($impfstoffId, $zugeordneteImpfstoffe, true)) {
|
||||
return [false, "Impfstoff und Zeitfenster passen nicht zusammen."];
|
||||
}
|
||||
|
||||
if ($impfstoffId <= 0) {
|
||||
if (count($zugeordneteImpfstoffe) !== 1) {
|
||||
return [false, "Bitte einen Impfstoff auswaehlen, der allen Zeitfenstern eindeutig zugeordnet ist."];
|
||||
}
|
||||
$currentImpfstoffId = (int)$zugeordneteImpfstoffe[0];
|
||||
if ($impfstoffValue > 0 && $impfstoffValue !== $currentImpfstoffId) {
|
||||
return [false, "Die ausgewaehlten Zeitfenster gehoeren zu unterschiedlichen Impfstoffen."];
|
||||
}
|
||||
$impfstoffValue = $currentImpfstoffId;
|
||||
}
|
||||
|
||||
$zeitraumLabels[] = workflowPlanLabel($plan);
|
||||
}
|
||||
|
||||
if ($impfstoffId <= 0) {
|
||||
if (count($zugeordneteImpfstoffe) !== 1) {
|
||||
return [false, "Bitte einen Impfstoff auswaehlen, der dem Zeitfenster zugeordnet ist."];
|
||||
}
|
||||
$impfstoffId = (int)$zugeordneteImpfstoffe[0];
|
||||
$impfstoffId = $impfstoffValue;
|
||||
}
|
||||
}
|
||||
|
||||
$stImpfstoff = $pdo->prepare("SELECT impfid, impfname
|
||||
FROM impfstoff
|
||||
WHERE impfid = :iid
|
||||
LIMIT 1");
|
||||
$stImpfstoff->execute(['iid' => $impfstoffId]);
|
||||
$impfstoff = $stImpfstoff->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$impfstoff) {
|
||||
return [false, "Der ausgewaehlte Impfstoff wurde nicht gefunden."];
|
||||
}
|
||||
|
||||
$impfstoffValue = $impfstoffId;
|
||||
$impfstoffName = (string)$impfstoff['impfname'];
|
||||
$zeitraum = workflowPlanLabel($plan);
|
||||
} elseif ($impfstoffId > 0) {
|
||||
if ($impfstoffId > 0) {
|
||||
$stImpfstoff = $pdo->prepare("SELECT impfid, impfname
|
||||
FROM impfstoff
|
||||
WHERE impfid = :iid
|
||||
@@ -158,6 +236,10 @@ function workflowAddWartelisteEntry(
|
||||
$impfstoffValue = 0;
|
||||
}
|
||||
|
||||
if (!empty($planIds)) {
|
||||
$zeitraum = implode(' | ', $zeitraumLabels);
|
||||
}
|
||||
|
||||
$stDup = $pdo->prepare("SELECT warteid
|
||||
FROM warteliste
|
||||
WHERE userid = :uid
|
||||
@@ -172,7 +254,7 @@ function workflowAddWartelisteEntry(
|
||||
$hash = md5('admin-warte-' . $personId . '-' . microtime(true) . '-' . random_int(1000, 9999));
|
||||
$checkedValue = ($checked === 0) ? 0 : 1;
|
||||
$letzteValue = ($impfart === 1) ? null : ($letzteImpfung ?: null);
|
||||
$zeitraumIdValue = ($planId > 0) ? $planId : null;
|
||||
$zeitraumIdValue = !empty($planIds) ? (int)$planIds[0] : null;
|
||||
|
||||
$stInsert = $pdo->prepare("INSERT INTO warteliste
|
||||
(userid, checked, hash, impfenangebot, impfstoff, Patientenart, Impfaufklaerung, WeitereFragen, impfart, impfenmit, letzteimpfung, impfenzeitraum, zeitraum_id, date_created)
|
||||
@@ -190,6 +272,11 @@ function workflowAddWartelisteEntry(
|
||||
'zeitraum_id' => $zeitraumIdValue,
|
||||
]);
|
||||
|
||||
$warteid = (int)$pdo->lastInsertId();
|
||||
if (!empty($planIds)) {
|
||||
impfSetWartelistenZeitraeume($pdo, $warteid, $planIds);
|
||||
}
|
||||
|
||||
$personName = trim((string)$person['vorname'] . ' ' . (string)$person['nachname']);
|
||||
return [true, "Wartelistenplatz fuer {$personName} ({$impfstoffName}) gespeichert."];
|
||||
}
|
||||
@@ -235,16 +322,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
} else {
|
||||
$dosen = (int)$rule['dosen_pro_flasche'];
|
||||
|
||||
$stCount = $pdo->prepare("SELECT COUNT(DISTINCT userid)
|
||||
FROM warteliste
|
||||
WHERE checked = 1
|
||||
AND (impfstoff = :iid OR impfstoff = 0)
|
||||
AND (zeitraum_id = :zid OR zeitraum_id IS NULL)");
|
||||
$stCount->execute([
|
||||
'iid' => $impfstoffId,
|
||||
'zid' => $planId,
|
||||
]);
|
||||
$wartende = (int)$stCount->fetchColumn();
|
||||
$wartende = workflowCountWaitersForPlan($pdo, $impfstoffId, $planId);
|
||||
|
||||
if ($wartende < $dosen) {
|
||||
$error = "Nicht genug bestätigte Warteteilnehmer: {$wartende} von {$dosen}.";
|
||||
@@ -286,16 +364,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
]);
|
||||
$timeid = (int)$pdo->lastInsertId();
|
||||
|
||||
$stW = $pdo->prepare("SELECT warteid, userid, hash, impfart, Impfaufklaerung, WeitereFragen, letzteimpfung
|
||||
FROM warteliste
|
||||
WHERE checked = 1
|
||||
AND (impfstoff = :iid OR impfstoff = 0)
|
||||
AND (zeitraum_id = :zid OR zeitraum_id IS NULL)
|
||||
ORDER BY date_created ASC, warteid ASC");
|
||||
$stW->bindValue(':iid', $impfstoffId, PDO::PARAM_INT);
|
||||
$stW->bindValue(':zid', $planId, PDO::PARAM_INT);
|
||||
$stW->execute();
|
||||
$warteRowsRaw = $stW->fetchAll(PDO::FETCH_ASSOC);
|
||||
$warteRowsRaw = workflowLoadWaitRowsForPlan($pdo, $impfstoffId, $planId);
|
||||
$warteRows = [];
|
||||
$seenUserIds = [];
|
||||
foreach ($warteRowsRaw as $warteRow) {
|
||||
@@ -333,6 +402,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
]);
|
||||
$terminIds[] = (int)$pdo->lastInsertId();
|
||||
$stDelW->execute(['wid' => (int)$w['warteid']]);
|
||||
$pdo->prepare("DELETE FROM warteliste_zeitraum WHERE warteid = :wid")
|
||||
->execute(['wid' => (int)$w['warteid']]);
|
||||
}
|
||||
|
||||
$stReduce = $pdo->prepare("UPDATE timeslots SET impfdosen = GREATEST(impfdosen - :cnt, 0) WHERE timeid = :timeid");
|
||||
@@ -344,6 +415,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()) {
|
||||
@@ -363,19 +435,23 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
} elseif ($aktion === 'add_waitlist_existing') {
|
||||
$personId = (int)($_POST['wl_person_id'] ?? 0);
|
||||
$impfstoffId = (int)($_POST['wl_impfstoff_id'] ?? 0);
|
||||
$planId = (int)($_POST['wl_plan_id'] ?? 0);
|
||||
$planIds = impfNormalizeZeitraumIds($_POST['wl_plan_ids'] ?? ($_POST['wl_plan_id'] ?? []));
|
||||
|
||||
[$ok, $msg] = workflowAddWartelisteEntry(
|
||||
$pdo,
|
||||
$personId,
|
||||
$impfstoffId,
|
||||
$planId,
|
||||
$planIds,
|
||||
1,
|
||||
null,
|
||||
1
|
||||
);
|
||||
if ($ok) {
|
||||
$notificationEvents = impfWorkflowNotificationProcess($pdo);
|
||||
$message = $msg;
|
||||
if (!empty($notificationEvents)) {
|
||||
$message .= ' ' . count($notificationEvents) . " Impfworkflow-Benachrichtigung(en) wurden versendet.";
|
||||
}
|
||||
} else {
|
||||
$error = $msg;
|
||||
}
|
||||
@@ -391,7 +467,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$patientenart = ((int)($_POST['new_patientenart'] ?? 0) === 1) ? 1 : 0;
|
||||
|
||||
$impfstoffId = (int)($_POST['new_impfstoff_id'] ?? 0);
|
||||
$planId = (int)($_POST['new_plan_id'] ?? 0);
|
||||
$planIds = impfNormalizeZeitraumIds($_POST['new_plan_ids'] ?? ($_POST['new_plan_id'] ?? []));
|
||||
|
||||
if ($vorname === '' || $nachname === '' || $geburtstag === '') {
|
||||
$error = "Für neue Patienten sind Vorname, Nachname und Geburtstag erforderlich.";
|
||||
@@ -414,13 +490,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$pdo,
|
||||
$personId,
|
||||
$impfstoffId,
|
||||
$planId,
|
||||
$planIds,
|
||||
1,
|
||||
null,
|
||||
1
|
||||
);
|
||||
if ($ok) {
|
||||
$notificationEvents = impfWorkflowNotificationProcess($pdo);
|
||||
$message = $msg;
|
||||
if (!empty($notificationEvents)) {
|
||||
$message .= ' ' . count($notificationEvents) . " Impfworkflow-Benachrichtigung(en) wurden versendet.";
|
||||
}
|
||||
} else {
|
||||
$error = $msg;
|
||||
}
|
||||
@@ -529,57 +609,143 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if ($warteid <= 0) {
|
||||
$error = "Ungültiger Wartelisten-Eintrag.";
|
||||
} else {
|
||||
$stDelete = $pdo->prepare("DELETE FROM warteliste WHERE warteid = :wid");
|
||||
$stDelete = $pdo->prepare("SELECT warteid FROM warteliste WHERE warteid = :wid");
|
||||
$stDelete->execute(['wid' => $warteid]);
|
||||
if ($stDelete->rowCount() > 0) {
|
||||
$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.";
|
||||
}
|
||||
}
|
||||
} elseif ($aktion === 'confirm_waitlist') {
|
||||
$warteid = (int)($_POST['warteid'] ?? 0);
|
||||
if ($warteid <= 0) {
|
||||
$error = "Ungültiger Wartelisten-Eintrag.";
|
||||
} else {
|
||||
$waitRow = workflowLoadWaitlistEntry($pdo, $warteid);
|
||||
if (!$waitRow) {
|
||||
$error = "Wartelisten-Eintrag nicht gefunden.";
|
||||
} elseif ((int)$waitRow['checked'] >= 1) {
|
||||
$error = "Der Wartelisten-Eintrag ist bereits bestätigt.";
|
||||
} else {
|
||||
$stUpdate = $pdo->prepare("UPDATE warteliste
|
||||
SET checked = 1
|
||||
WHERE warteid = :wid
|
||||
AND checked < 1");
|
||||
$stUpdate->execute(['wid' => $warteid]);
|
||||
|
||||
if ($stUpdate->rowCount() < 1) {
|
||||
$error = "Wartelisten-Eintrag konnte nicht bestätigt werden.";
|
||||
} else {
|
||||
SendMailMessageVorlage($pdo, '2', $warteid, '9');
|
||||
$notificationEvents = impfWorkflowNotificationProcess($pdo);
|
||||
$personName = trim((string)($waitRow['vorname'] ?? '') . ' ' . (string)($waitRow['nachname'] ?? ''));
|
||||
$message = "Wartelisten-Eintrag für " . trim($personName) . " wurde bestätigt.";
|
||||
if (!empty($notificationEvents)) {
|
||||
$message .= ' ' . count($notificationEvents) . " Impfworkflow-Benachrichtigung(en) wurden versendet.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif ($aktion === 'cancel_waitlist') {
|
||||
$warteid = (int)($_POST['warteid'] ?? 0);
|
||||
if ($warteid <= 0) {
|
||||
$error = "Ungültiger Wartelisten-Eintrag.";
|
||||
} else {
|
||||
$waitRow = workflowLoadWaitlistEntry($pdo, $warteid);
|
||||
if (!$waitRow) {
|
||||
$error = "Wartelisten-Eintrag nicht gefunden.";
|
||||
} else {
|
||||
SendMailMessageVorlage($pdo, '2', $warteid, '10');
|
||||
workflowDeleteWaitlistEntry($pdo, $warteid);
|
||||
$notificationEvents = impfWorkflowNotificationProcess($pdo);
|
||||
$personName = trim((string)($waitRow['vorname'] ?? '') . ' ' . (string)($waitRow['nachname'] ?? ''));
|
||||
$message = "Wartelisten-Eintrag für " . trim($personName) . " wurde abgesagt.";
|
||||
if (!empty($notificationEvents)) {
|
||||
$message .= ' ' . count($notificationEvents) . " Impfworkflow-Benachrichtigung(en) wurden versendet.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rules = [];
|
||||
$plans = [];
|
||||
$configuredImpfstoffe = [];
|
||||
$configuredImpfstoffNames = [];
|
||||
$eligible = [];
|
||||
$personResults = [];
|
||||
$waitRows = [];
|
||||
$upcomingRows = [];
|
||||
$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);
|
||||
|
||||
$planExistsForImpfstoff = [];
|
||||
foreach ($plans as $p) {
|
||||
foreach ($p['impfstoff_id_list'] as $impfstoffId) {
|
||||
$planExistsForImpfstoff[(int)$impfstoffId] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($rules as $r) {
|
||||
$iid = (int)$r['impfstoff_id'];
|
||||
$dosen = (int)$r['dosen_pro_flasche'];
|
||||
if ($dosen > 0 && isset($planExistsForImpfstoff[$iid])) {
|
||||
foreach ($rules as $r) {
|
||||
$iid = (int)$r['impfstoff_id'];
|
||||
$configuredImpfstoffNames[$iid] = (string)$r['impfname'];
|
||||
if (!isset($planExistsForImpfstoff[$iid])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$configuredImpfstoffe[] = $r;
|
||||
if ((int)$r['wartende'] >= $dosen) {
|
||||
if (!$needsEligibilityData) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$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] = workflowCountStrictWaitersForPlan($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
|
||||
@@ -598,71 +764,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) {
|
||||
$zeitraumId = (int)($waitRow['zeitraum_id'] ?? 0);
|
||||
if ($zeitraumId > 0) {
|
||||
$zeitraum = impfLoadZeitraumById($pdo, $zeitraumId, true);
|
||||
if ($zeitraum) {
|
||||
$waitRow['impfenzeitraum'] = $zeitraum['label'];
|
||||
$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);
|
||||
}
|
||||
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) {
|
||||
@@ -728,7 +901,13 @@ try {
|
||||
<?php echo esc((string)$w['tele']); ?>
|
||||
</td>
|
||||
<td><?php echo esc((string)($w['impfname'] ?: 'Unbekannt')); ?></td>
|
||||
<td><?php echo esc((string)$w['impfenzeitraum']); ?></td>
|
||||
<td>
|
||||
<?php if (!empty($w['zeitraum_labels'])): ?>
|
||||
<?php echo implode('<br>', array_map('esc', $w['zeitraum_labels'])); ?>
|
||||
<?php else: ?>
|
||||
<?php echo esc(impfLimitLabelLength((string)$w['impfenzeitraum'], 50)); ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php echo esc(workflowImpfartName((int)$w['impfart'])); ?>
|
||||
<?php if (!empty($w['letzteimpfung'])): ?>
|
||||
@@ -738,7 +917,19 @@ try {
|
||||
<td><?php echo esc(workflowWarteStatus((int)$w['checked'])); ?></td>
|
||||
<td><?php echo esc((string)$w['date_created']); ?></td>
|
||||
<td>
|
||||
<form method="post" onsubmit="return confirm('Eintrag wirklich löschen?');">
|
||||
<?php if ((int)$w['checked'] === 0): ?>
|
||||
<form method="post" style="display:inline-block; margin-right:6px;" onsubmit="return confirm('Eintrag wirklich als bestätigt markieren?');">
|
||||
<input type="hidden" name="aktion" value="confirm_waitlist">
|
||||
<input type="hidden" name="warteid" value="<?php echo (int)$w['warteid']; ?>">
|
||||
<button class="btn btn-success btn-xs" type="submit">Bestätigen</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<form method="post" style="display:inline-block; margin-right:6px;" onsubmit="return confirm('Wartelisten-Eintrag wirklich absagen?');">
|
||||
<input type="hidden" name="aktion" value="cancel_waitlist">
|
||||
<input type="hidden" name="warteid" value="<?php echo (int)$w['warteid']; ?>">
|
||||
<button class="btn btn-warning btn-xs" type="submit">Absagen</button>
|
||||
</form>
|
||||
<form method="post" style="display:inline-block;" onsubmit="return confirm('Eintrag wirklich löschen?');">
|
||||
<input type="hidden" name="aktion" value="delete_waitlist">
|
||||
<input type="hidden" name="warteid" value="<?php echo (int)$w['warteid']; ?>">
|
||||
<button class="btn btn-danger btn-xs" type="submit">Löschen</button>
|
||||
@@ -774,52 +965,58 @@ try {
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" class="form-inline">
|
||||
<form method="post">
|
||||
<input type="hidden" name="aktion" value="add_waitlist_existing">
|
||||
<input type="hidden" name="person_search" value="<?php echo esc($personSearch); ?>">
|
||||
|
||||
<label>Patient</label>
|
||||
<select class="form-control" name="wl_person_id" required <?php echo empty($personResults) ? 'disabled' : ''; ?>>
|
||||
<option value="">Bitte wählen</option>
|
||||
<?php foreach ($personResults as $p): ?>
|
||||
<?php
|
||||
$pid = (int)$p['person_id'];
|
||||
$selected = ((int)($_POST['wl_person_id'] ?? 0) === $pid) ? 'selected' : '';
|
||||
$personLabel = trim((string)$p['nachname'] . ', ' . (string)$p['vorname']) . ' | ' . (string)$p['geburtstag'] . ' | ' . (string)$p['email'];
|
||||
?>
|
||||
<option value="<?php echo $pid; ?>" <?php echo $selected; ?>><?php echo esc($personLabel); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div class="form-group" style="display:block; margin-bottom:10px;">
|
||||
<label>Patient</label>
|
||||
<select class="form-control" name="wl_person_id" required <?php echo empty($personResults) ? 'disabled' : ''; ?>>
|
||||
<option value="">Bitte wählen</option>
|
||||
<?php foreach ($personResults as $p): ?>
|
||||
<?php
|
||||
$pid = (int)$p['person_id'];
|
||||
$selected = ((int)($_POST['wl_person_id'] ?? 0) === $pid) ? 'selected' : '';
|
||||
$personLabel = trim((string)$p['nachname'] . ', ' . (string)$p['vorname']) . ' | ' . (string)$p['geburtstag'] . ' | ' . (string)$p['email'];
|
||||
?>
|
||||
<option value="<?php echo $pid; ?>" <?php echo $selected; ?>><?php echo esc($personLabel); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<label style="margin-left:10px;">Impfstoff</label>
|
||||
<select class="form-control" name="wl_impfstoff_id" id="existing_impfstoff">
|
||||
<option value="0" <?php echo ((int)($_POST['wl_impfstoff_id'] ?? 0) === 0) ? 'selected' : ''; ?>>Keine Vorgabe</option>
|
||||
<?php foreach ($configuredImpfstoffe as $r): ?>
|
||||
<?php
|
||||
$iid = (int)$r['impfstoff_id'];
|
||||
$selected = ((int)($_POST['wl_impfstoff_id'] ?? 0) === $iid) ? 'selected' : '';
|
||||
?>
|
||||
<option value="<?php echo $iid; ?>" <?php echo $selected; ?>>
|
||||
<?php echo esc($r['impfname'] . ' (Dosen: ' . $r['dosen_pro_flasche'] . ')'); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div class="form-group" style="display:block; margin-bottom:10px;">
|
||||
<label>Impfstoff</label>
|
||||
<select class="form-control" name="wl_impfstoff_id" id="existing_impfstoff">
|
||||
<option value="0" <?php echo ((int)($_POST['wl_impfstoff_id'] ?? 0) === 0) ? 'selected' : ''; ?>>Keine Vorgabe</option>
|
||||
<?php foreach ($configuredImpfstoffe as $r): ?>
|
||||
<?php
|
||||
$iid = (int)$r['impfstoff_id'];
|
||||
$selected = ((int)($_POST['wl_impfstoff_id'] ?? 0) === $iid) ? 'selected' : '';
|
||||
?>
|
||||
<option value="<?php echo $iid; ?>" <?php echo $selected; ?>>
|
||||
<?php echo esc($r['impfname'] . ' (Dosen: ' . $r['dosen_pro_flasche'] . ')'); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<label style="margin-left:10px;">Zeitfenster</label>
|
||||
<select class="form-control" name="wl_plan_id" id="existing_plan">
|
||||
<option value="0" <?php echo ((int)($_POST['wl_plan_id'] ?? 0) === 0) ? 'selected' : ''; ?>>Ohne Zeitfenster</option>
|
||||
<?php foreach ($plans as $p): ?>
|
||||
<?php
|
||||
$planId = (int)$p['zeitraum_id'];
|
||||
$selected = ((int)($_POST['wl_plan_id'] ?? 0) === $planId) ? 'selected' : '';
|
||||
$impfstoffeCsv = implode(',', $p['impfstoff_id_list']);
|
||||
$impfstoffeText = empty($p['impfstoff_name_list']) ? 'ohne Impfstoff' : implode(', ', $p['impfstoff_name_list']);
|
||||
?>
|
||||
<option value="<?php echo $planId; ?>" data-impfstoffe="<?php echo esc($impfstoffeCsv); ?>" <?php echo $selected; ?>>
|
||||
<?php echo esc(workflowPlanLabel($p) . ' | Impfstoffe: ' . $impfstoffeText); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div class="form-group" style="display:block;">
|
||||
<label>Zeitfenster</label>
|
||||
<?php $selectedExistingPlanIds = impfNormalizeZeitraumIds($_POST['wl_plan_ids'] ?? ($_POST['wl_plan_id'] ?? [])); ?>
|
||||
<select class="form-control" name="wl_plan_ids[]" id="existing_plan" multiple size="6">
|
||||
<?php foreach ($plans as $p): ?>
|
||||
<?php
|
||||
$planId = (int)$p['zeitraum_id'];
|
||||
$selected = in_array($planId, $selectedExistingPlanIds, true) ? 'selected' : '';
|
||||
$impfstoffeCsv = implode(',', $p['impfstoff_id_list']);
|
||||
$optionLabel = workflowPlanLabel($p);
|
||||
?>
|
||||
<option value="<?php echo $planId; ?>" data-impfstoffe="<?php echo esc($impfstoffeCsv); ?>" <?php echo $selected; ?>>
|
||||
<?php echo esc($optionLabel); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="workflow-action-row">
|
||||
<button class="btn btn-primary" type="submit" <?php echo empty($personResults) ? 'disabled' : ''; ?>>
|
||||
Zur Warteliste hinzufügen
|
||||
@@ -897,17 +1094,17 @@ try {
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<label>Zeitfenster</label>
|
||||
<select class="form-control" name="new_plan_id" id="new_plan">
|
||||
<option value="0" <?php echo ((int)($_POST['new_plan_id'] ?? 0) === 0) ? 'selected' : ''; ?>>Ohne Zeitfenster</option>
|
||||
<?php $selectedNewPlanIds = impfNormalizeZeitraumIds($_POST['new_plan_ids'] ?? ($_POST['new_plan_id'] ?? [])); ?>
|
||||
<select class="form-control" name="new_plan_ids[]" id="new_plan" multiple size="6">
|
||||
<?php foreach ($plans as $p): ?>
|
||||
<?php
|
||||
$planId = (int)$p['zeitraum_id'];
|
||||
$selected = ((int)($_POST['new_plan_id'] ?? 0) === $planId) ? 'selected' : '';
|
||||
$selected = in_array($planId, $selectedNewPlanIds, true) ? 'selected' : '';
|
||||
$impfstoffeCsv = implode(',', $p['impfstoff_id_list']);
|
||||
$impfstoffeText = empty($p['impfstoff_name_list']) ? 'ohne Impfstoff' : implode(', ', $p['impfstoff_name_list']);
|
||||
$optionLabel = workflowPlanLabel($p);
|
||||
?>
|
||||
<option value="<?php echo $planId; ?>" data-impfstoffe="<?php echo esc($impfstoffeCsv); ?>" <?php echo $selected; ?>>
|
||||
<?php echo esc(workflowPlanLabel($p) . ' | Impfstoffe: ' . $impfstoffeText); ?>
|
||||
<?php echo esc($optionLabel); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
@@ -935,7 +1132,7 @@ try {
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>Konkretes Impfevent erstellen</strong></div>
|
||||
<div class="panel-body">
|
||||
<p>Es werden nur Impfstoffe angeboten, bei denen die bestätigte Warteliste mindestens eine Flasche füllt.</p>
|
||||
<p>Es werden nur Impfstoffe und Zeiträume angeboten, bei denen die bestätigte Warteliste mindestens eine Flasche füllt und die Wartenden diesen Zeitraum auch ausdrücklich gewählt haben.</p>
|
||||
|
||||
<form method="post" class="form-inline" style="margin-bottom:14px;">
|
||||
<input type="hidden" name="aktion" value="create_event">
|
||||
@@ -951,13 +1148,17 @@ try {
|
||||
<select class="form-control" name="plan_id" id="event_plan" required>
|
||||
<option value="">Bitte wählen</option>
|
||||
<?php foreach ($plans as $p): ?>
|
||||
<?php
|
||||
$impfstoffeCsv = implode(',', $p['impfstoff_id_list']);
|
||||
$impfstoffeText = empty($p['impfstoff_name_list']) ? 'ohne Impfstoff' : implode(', ', $p['impfstoff_name_list']);
|
||||
?>
|
||||
<option value="<?php echo (int)$p['zeitraum_id']; ?>" data-impfstoffe="<?php echo esc($impfstoffeCsv); ?>">
|
||||
<?php echo esc(workflowPlanLabel($p) . ' | Impfstoffe: ' . $impfstoffeText); ?>
|
||||
</option>
|
||||
<?php foreach (($p['impfstoff_id_list'] ?? []) as $planImpfstoffId): ?>
|
||||
<?php
|
||||
$waiterCount = (int)($planWaitCounts[(int)$planImpfstoffId][(int)$p['zeitraum_id']] ?? 0);
|
||||
if ($waiterCount <= 0) {
|
||||
continue;
|
||||
}
|
||||
?>
|
||||
<option value="<?php echo (int)$p['zeitraum_id']; ?>" data-impfstoffe="<?php echo esc((string)$planImpfstoffId); ?>">
|
||||
<?php echo esc(workflowPlanLabel($p) . ' | Impfstoff: ' . ($configuredImpfstoffNames[(int)$planImpfstoffId] ?? 'Unbekannt') . ' | Wartende: ' . $waiterCount); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
@@ -1061,15 +1262,18 @@ try {
|
||||
function filterPlans() {
|
||||
var val = impfstoff.value;
|
||||
var needsFilter = (val !== "" && val !== "0");
|
||||
var selectedValid = false;
|
||||
var selectedValues = [];
|
||||
for (var s = 0; s < plan.options.length; s++) {
|
||||
if (plan.options[s].selected && plan.options[s].value !== '') {
|
||||
selectedValues.push(plan.options[s].value);
|
||||
}
|
||||
}
|
||||
var selectedValid = 0;
|
||||
for (var i = 0; i < plan.options.length; i++) {
|
||||
var opt = plan.options[i];
|
||||
var optionImpfstoffe = opt.getAttribute('data-impfstoffe');
|
||||
if (!optionImpfstoffe) {
|
||||
opt.hidden = false;
|
||||
if (opt.value === plan.value) {
|
||||
selectedValid = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
var ids = optionImpfstoffe.split(',');
|
||||
@@ -1083,11 +1287,13 @@ try {
|
||||
}
|
||||
}
|
||||
opt.hidden = !visible;
|
||||
if (visible && opt.value === plan.value) {
|
||||
selectedValid = true;
|
||||
if (!visible) {
|
||||
opt.selected = false;
|
||||
} else if (selectedValues.indexOf(opt.value) !== -1) {
|
||||
selectedValid++;
|
||||
}
|
||||
}
|
||||
if (!selectedValid) {
|
||||
if (selectedValues.length > 0 && selectedValid === 0) {
|
||||
plan.value = '';
|
||||
}
|
||||
}
|
||||
@@ -1103,5 +1309,3 @@ try {
|
||||
</div>
|
||||
|
||||
<?php include __DIR__ . "/templates/footer.inc.php"; ?>
|
||||
|
||||
|
||||
|
||||
@@ -289,6 +289,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
if ($aktion === 'add_zeitraum') {
|
||||
$bezeichnung = trim((string)($_POST['bezeichnung'] ?? ''));
|
||||
if (function_exists('mb_substr')) {
|
||||
$bezeichnung = mb_substr($bezeichnung, 0, 50, 'UTF-8');
|
||||
} else {
|
||||
$bezeichnung = substr($bezeichnung, 0, 50);
|
||||
}
|
||||
$wochentag = (int)($_POST['wochentag'] ?? 0);
|
||||
$start = trim((string)($_POST['start'] ?? ''));
|
||||
$ende = trim((string)($_POST['ende'] ?? ''));
|
||||
@@ -608,7 +613,7 @@ $rules = $stRules->fetchAll(PDO::FETCH_ASSOC);
|
||||
<form method="post" class="form-inline" style="margin-bottom:12px;">
|
||||
<input type="hidden" name="aktion" value="add_zeitraum">
|
||||
<label>Bezeichnung</label>
|
||||
<input class="form-control" type="text" name="bezeichnung" placeholder="z. B. Mittwoch Vormittag">
|
||||
<input class="form-control" type="text" name="bezeichnung" maxlength="50" placeholder="z. B. Mittwoch Vormittag">
|
||||
<label style="margin-left:10px;">Wochentag</label>
|
||||
<select class="form-control" name="wochentag" required>
|
||||
<option value="1">Montag</option>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
$(function() {
|
||||
$("#user_input").autocomplete({
|
||||
source: "inc/suchepatient.php",
|
||||
source: "../inc/suchepatient.php",
|
||||
minLength: 3,
|
||||
select: function( event, ui ) {
|
||||
event.preventDefault();
|
||||
|
||||
+61
-17
@@ -1,17 +1,19 @@
|
||||
<?php
|
||||
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/functions.inc.php";
|
||||
require_once __DIR__ . "/../inc/impfworkflow_notifications.inc.php";
|
||||
|
||||
// Login prüfen
|
||||
// Login prüfen
|
||||
$user = check_admin_user();
|
||||
|
||||
include __DIR__ . "/templates/header.inc.php";
|
||||
|
||||
$user = check_admin_user();
|
||||
$internUserId = (int)$_SESSION['auth']['id'];
|
||||
$activeTab = 'data';
|
||||
|
||||
if (!$user) { echo "<div class='container main-container'><h3>Erst anmelden: <a href=login.php>Login</a></h3><br>";
|
||||
|
||||
@@ -22,11 +24,12 @@ if(isset($_GET['save'])) {
|
||||
$save = $_GET['save'];
|
||||
|
||||
if($save == 'personal_data') {
|
||||
$activeTab = 'data';
|
||||
$vorname = trim($_POST['vorname']);
|
||||
$nachname = trim($_POST['nachname']);
|
||||
|
||||
if($vorname == "" || $nachname == "") {
|
||||
$error_msg = "Bitte Vor- und Nachname ausfüllen.";
|
||||
$error_msg = "Bitte Vor- und Nachname ausfüllen.";
|
||||
} else {
|
||||
$statement = $pdo->prepare("UPDATE users SET vorname = :vorname, nachname = :nachname, updated_at=NOW() WHERE id = :userid");
|
||||
$result = $statement->execute(array('vorname' => $vorname, 'nachname'=> $nachname, 'userid' => $user['id'] ));
|
||||
@@ -34,14 +37,15 @@ if(isset($_GET['save'])) {
|
||||
$success_msg = "Daten erfolgreich gespeichert.";
|
||||
}
|
||||
} else if($save == 'email') {
|
||||
$activeTab = 'email';
|
||||
$passwort = $_POST['passwort'];
|
||||
$email = trim($_POST['email']);
|
||||
$email2 = trim($_POST['email2']);
|
||||
|
||||
if($email != $email2) {
|
||||
$error_msg = "Die eingegebenen E-Mail-Adressen stimmten nicht überein.";
|
||||
$error_msg = "Die eingegebenen E-Mail-Adressen stimmten nicht überein.";
|
||||
} else if(!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$error_msg = "Bitte eine gültige E-Mail-Adresse eingeben.";
|
||||
$error_msg = "Bitte eine gültige E-Mail-Adresse eingeben.";
|
||||
} else if(!password_verify($passwort, $user['passwort'])) {
|
||||
$error_msg = "Bitte korrektes Passwort eingeben.";
|
||||
} else {
|
||||
@@ -51,13 +55,31 @@ if(isset($_GET['save'])) {
|
||||
$success_msg = "E-Mail-Adresse erfolgreich gespeichert.";
|
||||
}
|
||||
|
||||
} else if($save == 'impfworkflow_notification') {
|
||||
$activeTab = 'impfworkflow';
|
||||
$benachrichtigungEmail = trim((string)($_POST['benachrichtigung_email'] ?? ''));
|
||||
|
||||
if ($benachrichtigungEmail !== '' && !filter_var($benachrichtigungEmail, FILTER_VALIDATE_EMAIL)) {
|
||||
$error_msg = "Bitte eine gueltige E-Mail-Adresse fuer die Impfworkflow-Benachrichtigung eingeben.";
|
||||
} else {
|
||||
try {
|
||||
impfWorkflowNotificationSetEmail($pdo, $benachrichtigungEmail);
|
||||
$success_msg = ($benachrichtigungEmail !== '')
|
||||
? "Impfworkflow-Benachrichtigungsadresse gespeichert."
|
||||
: "Impfworkflow-Benachrichtigungsadresse geloescht.";
|
||||
} catch (Throwable $e) {
|
||||
$error_msg = "Die Impfworkflow-Benachrichtigungsadresse konnte nicht gespeichert werden: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
} else if($save == 'passwort') {
|
||||
$activeTab = 'passwort';
|
||||
$passwortAlt = $_POST['passwortAlt'];
|
||||
$passwortNeu = trim($_POST['passwortNeu']);
|
||||
$passwortNeu2 = trim($_POST['passwortNeu2']);
|
||||
|
||||
if($passwortNeu != $passwortNeu2) {
|
||||
$error_msg = "Die eingegebenen Passwörter stimmten nicht überein.";
|
||||
$error_msg = "Die eingegebenen Passwörter stimmten nicht überein.";
|
||||
} else if($passwortNeu == "") {
|
||||
$error_msg = "Das Passwort darf nicht leer sein.";
|
||||
} else if(!password_verify($passwortAlt, $user['passwort'])) {
|
||||
@@ -107,14 +129,15 @@ endif;
|
||||
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="active"><a href="#data" aria-controls="home" role="tab" data-toggle="tab">Persönliche Daten</a></li>
|
||||
<li role="presentation"><a href="#email" aria-controls="profile" role="tab" data-toggle="tab">E-Mail</a></li>
|
||||
<li role="presentation"><a href="#passwort" aria-controls="messages" role="tab" data-toggle="tab">Passwort</a></li>
|
||||
<li role="presentation" class="<?php echo ($activeTab === 'data') ? 'active' : ''; ?>"><a href="#data" aria-controls="home" role="tab" data-toggle="tab">Persönliche Daten</a></li>
|
||||
<li role="presentation" class="<?php echo ($activeTab === 'email') ? 'active' : ''; ?>"><a href="#email" aria-controls="profile" role="tab" data-toggle="tab">E-Mail</a></li>
|
||||
<li role="presentation" class="<?php echo ($activeTab === 'impfworkflow') ? 'active' : ''; ?>"><a href="#impfworkflow" aria-controls="impfworkflow" role="tab" data-toggle="tab">Impfworkflow</a></li>
|
||||
<li role="presentation" class="<?php echo ($activeTab === 'passwort') ? 'active' : ''; ?>"><a href="#passwort" aria-controls="messages" role="tab" data-toggle="tab">Passwort</a></li>
|
||||
</ul>
|
||||
|
||||
<!-- Persönliche Daten-->
|
||||
<!-- Persönliche Daten-->
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="data">
|
||||
<div role="tabpanel" class="tab-pane <?php echo ($activeTab === 'data') ? 'active' : ''; ?>" id="data">
|
||||
<br>
|
||||
<form action="?save=personal_data" method="post" class="form-horizontal">
|
||||
<div class="form-group">
|
||||
@@ -139,10 +162,10 @@ endif;
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Änderung der E-Mail-Adresse -->
|
||||
<div role="tabpanel" class="tab-pane" id="email">
|
||||
<!-- Änderung der E-Mail-Adresse -->
|
||||
<div role="tabpanel" class="tab-pane <?php echo ($activeTab === 'email') ? 'active' : ''; ?>" id="email">
|
||||
<br>
|
||||
<p>Zum Änderen deiner E-Mail-Adresse gib bitte dein aktuelles Passwort sowie die neue E-Mail-Adresse ein.</p>
|
||||
<p>Zum Änderen deiner E-Mail-Adresse gib bitte dein aktuelles Passwort sowie die neue E-Mail-Adresse ein.</p>
|
||||
<form action="?save=email" method="post" class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label for="inputPasswort" class="col-sm-2 control-label">Passwort</label>
|
||||
@@ -174,10 +197,31 @@ endif;
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Änderung des Passworts -->
|
||||
<div role="tabpanel" class="tab-pane" id="passwort">
|
||||
<div role="tabpanel" class="tab-pane <?php echo ($activeTab === 'impfworkflow') ? 'active' : ''; ?>" id="impfworkflow">
|
||||
<br>
|
||||
<p>Zum Änderen deines Passworts gib bitte dein aktuelles Passwort sowie das neue Passwort ein.</p>
|
||||
<p>Hier hinterlegst du die E-Mail-Adresse, an die spaeter Impfworkflow-Benachrichtigungen gesendet werden sollen.</p>
|
||||
<?php $currentNotificationEmail = impfWorkflowNotificationGetEmail($pdo); ?>
|
||||
<form action="?save=impfworkflow_notification" method="post" class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label for="inputImpfworkflowMail" class="col-sm-2 control-label">Benachrichtigungs-E-Mail</label>
|
||||
<div class="col-sm-10">
|
||||
<input class="form-control" id="inputImpfworkflowMail" name="benachrichtigung_email" type="email" value="<?php echo htmlentities($currentNotificationEmail); ?>" placeholder="benachrichtigung@praxis.de">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<p class="help-block">Leer lassen, um Benachrichtigungen zu deaktivieren.</p>
|
||||
<button type="submit" class="btn btn-primary">Speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Änderung des Passworts -->
|
||||
<div role="tabpanel" class="tab-pane <?php echo ($activeTab === 'passwort') ? 'active' : ''; ?>" id="passwort">
|
||||
<br>
|
||||
<p>Zum Änderen deines Passworts gib bitte dein aktuelles Passwort sowie das neue Passwort ein.</p>
|
||||
<form action="?save=passwort" method="post" class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label for="inputPasswort" class="col-sm-2 control-label">Altes Passwort</label>
|
||||
|
||||
@@ -17,6 +17,7 @@ FROM (
|
||||
UNION ALL SELECT 'impfstoff_wochenplan'
|
||||
UNION ALL SELECT 'impf_zeitraum'
|
||||
UNION ALL SELECT 'impf_zeitraum_impfstoff'
|
||||
UNION ALL SELECT 'warteliste_zeitraum'
|
||||
UNION ALL SELECT 'warteliste'
|
||||
) t
|
||||
LEFT JOIN information_schema.tables it
|
||||
@@ -51,6 +52,9 @@ FROM (
|
||||
UNION ALL SELECT 'impf_zeitraum', 'impfortid'
|
||||
UNION ALL SELECT 'impf_zeitraum_impfstoff', 'zeitraum_id'
|
||||
UNION ALL SELECT 'impf_zeitraum_impfstoff', 'impfstoff_id'
|
||||
UNION ALL SELECT 'warteliste_zeitraum', 'warteid'
|
||||
UNION ALL SELECT 'warteliste_zeitraum', 'zeitraum_id'
|
||||
UNION ALL SELECT 'warteliste_zeitraum', 'created_at'
|
||||
UNION ALL SELECT 'warteliste', 'warteid'
|
||||
UNION ALL SELECT 'warteliste', 'userid'
|
||||
UNION ALL SELECT 'warteliste', 'impfenzeitraum'
|
||||
@@ -71,6 +75,7 @@ SELECT
|
||||
END AS status
|
||||
FROM (
|
||||
SELECT 'warteliste' AS table_name, 'idx_warteliste_zeitraum' AS index_name
|
||||
UNION ALL SELECT 'warteliste_zeitraum', 'idx_warteliste_zeitraum_zeitraum'
|
||||
UNION ALL SELECT 'impfstoff_wochenplan', 'idx_impfstoff_wochenplan_impfstoff'
|
||||
UNION ALL SELECT 'impfstoff_wochenplan', 'idx_impfstoff_wochenplan_wochentag'
|
||||
UNION ALL SELECT 'impf_zeitraum', 'idx_impf_zeitraum_wochentag'
|
||||
@@ -102,6 +107,24 @@ SELECT
|
||||
ELSE 'TABLE_MISSING'
|
||||
END AS status;
|
||||
|
||||
SELECT
|
||||
'meta' AS check_type,
|
||||
'impf_workflow_meta.legacy_warteliste_zeitraeume_migrated' AS object_name,
|
||||
CASE
|
||||
WHEN EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'impf_workflow_meta'
|
||||
) THEN COALESCE((
|
||||
SELECT CONCAT('VALUE=', meta_value)
|
||||
FROM impf_workflow_meta
|
||||
WHERE meta_key = 'legacy_warteliste_zeitraeume_migrated'
|
||||
LIMIT 1
|
||||
), 'MISSING')
|
||||
ELSE 'TABLE_MISSING'
|
||||
END AS status;
|
||||
|
||||
SELECT
|
||||
'data' AS check_type,
|
||||
'impfstoff_wochenplan rows' AS object_name,
|
||||
@@ -154,3 +177,16 @@ SELECT
|
||||
) THEN CAST((SELECT COUNT(*) FROM warteliste WHERE zeitraum_id IS NOT NULL) AS CHAR)
|
||||
ELSE 'COLUMN_MISSING'
|
||||
END AS status;
|
||||
|
||||
SELECT
|
||||
'data' AS check_type,
|
||||
'warteliste_zeitraum rows' AS object_name,
|
||||
CASE
|
||||
WHEN EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'warteliste_zeitraum'
|
||||
) THEN CAST((SELECT COUNT(*) FROM warteliste_zeitraum) AS CHAR)
|
||||
ELSE 'TABLE_MISSING'
|
||||
END AS status;
|
||||
|
||||
@@ -51,6 +51,14 @@ CREATE TABLE IF NOT EXISTS `impf_zeitraum_impfstoff` (
|
||||
INDEX `idx_impf_zeitraum_impfstoff_impfstoff` (`impfstoff_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `warteliste_zeitraum` (
|
||||
`warteid` INT NOT NULL,
|
||||
`zeitraum_id` INT NOT NULL,
|
||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`warteid`, `zeitraum_id`),
|
||||
INDEX `idx_warteliste_zeitraum_zeitraum` (`zeitraum_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
DROP PROCEDURE IF EXISTS `migrate_praxis_schema_20260320`;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE `migrate_praxis_schema_20260320`()
|
||||
@@ -60,6 +68,8 @@ BEGIN
|
||||
DECLARE v_warteliste_exists INT DEFAULT 0;
|
||||
DECLARE v_zeitraum_id_exists INT DEFAULT 0;
|
||||
DECLARE v_warteliste_index_exists INT DEFAULT 0;
|
||||
DECLARE v_warteliste_zeitraum_exists INT DEFAULT 0;
|
||||
DECLARE v_warteliste_zeitraum_index_exists INT DEFAULT 0;
|
||||
DECLARE v_legacy_plan_exists INT DEFAULT 0;
|
||||
|
||||
SELECT COUNT(*)
|
||||
@@ -114,6 +124,35 @@ BEGIN
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
SELECT COUNT(*)
|
||||
INTO v_warteliste_zeitraum_exists
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'warteliste_zeitraum';
|
||||
|
||||
IF v_warteliste_zeitraum_exists = 0 THEN
|
||||
CREATE TABLE `warteliste_zeitraum` (
|
||||
`warteid` INT NOT NULL,
|
||||
`zeitraum_id` INT NOT NULL,
|
||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`warteid`, `zeitraum_id`),
|
||||
INDEX `idx_warteliste_zeitraum_zeitraum` (`zeitraum_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
SET v_warteliste_zeitraum_exists = 1;
|
||||
ELSE
|
||||
SELECT COUNT(*)
|
||||
INTO v_warteliste_zeitraum_index_exists
|
||||
FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'warteliste_zeitraum'
|
||||
AND index_name = 'idx_warteliste_zeitraum_zeitraum';
|
||||
|
||||
IF v_warteliste_zeitraum_index_exists = 0 THEN
|
||||
ALTER TABLE `warteliste_zeitraum`
|
||||
ADD INDEX `idx_warteliste_zeitraum_zeitraum` (`zeitraum_id`);
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
SELECT COUNT(*)
|
||||
INTO v_legacy_plan_exists
|
||||
FROM information_schema.tables
|
||||
@@ -159,6 +198,18 @@ BEGIN
|
||||
VALUES ('legacy_wochenplan_migrated', '1') AS `incoming`
|
||||
ON DUPLICATE KEY UPDATE `meta_value` = `incoming`.`meta_value`;
|
||||
END IF;
|
||||
|
||||
IF v_warteliste_exists > 0 AND v_warteliste_zeitraum_exists > 0 THEN
|
||||
INSERT IGNORE INTO `warteliste_zeitraum` (`warteid`, `zeitraum_id`)
|
||||
SELECT `warteid`, `zeitraum_id`
|
||||
FROM `warteliste`
|
||||
WHERE `zeitraum_id` IS NOT NULL
|
||||
AND `zeitraum_id` > 0;
|
||||
|
||||
INSERT INTO `impf_workflow_meta` (`meta_key`, `meta_value`)
|
||||
VALUES ('legacy_warteliste_zeitraeume_migrated', '1') AS `incoming`
|
||||
ON DUPLICATE KEY UPDATE `meta_value` = `incoming`.`meta_value`;
|
||||
END IF;
|
||||
END $$
|
||||
DELIMITER ;
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
-- Migration fuer Mehrfach-Zeitfenster in der Impfwarteliste.
|
||||
-- Idempotent: kann mehrfach ausgefuehrt werden.
|
||||
-- Bestehende Tabellen werden nicht neu aufgebaut, sondern nur erweitert.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `impf_workflow_meta` (
|
||||
`meta_key` VARCHAR(100) NOT NULL,
|
||||
`meta_value` VARCHAR(255) NOT NULL DEFAULT '',
|
||||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`meta_key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
DROP PROCEDURE IF EXISTS `migrate_warteliste_multi_zeitfenster_20260322`;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE `migrate_warteliste_multi_zeitfenster_20260322`()
|
||||
BEGIN
|
||||
DECLARE v_warteliste_exists INT DEFAULT 0;
|
||||
DECLARE v_zeitraum_id_exists INT DEFAULT 0;
|
||||
DECLARE v_warteliste_index_exists INT DEFAULT 0;
|
||||
DECLARE v_warteliste_zeitraum_exists INT DEFAULT 0;
|
||||
DECLARE v_warteliste_zeitraum_index_exists INT DEFAULT 0;
|
||||
|
||||
SELECT COUNT(*)
|
||||
INTO v_warteliste_exists
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'warteliste';
|
||||
|
||||
IF v_warteliste_exists = 0 THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = 'Tabelle warteliste wurde nicht gefunden.';
|
||||
END IF;
|
||||
|
||||
SELECT COUNT(*)
|
||||
INTO v_zeitraum_id_exists
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'warteliste'
|
||||
AND column_name = 'zeitraum_id';
|
||||
|
||||
IF v_zeitraum_id_exists = 0 THEN
|
||||
ALTER TABLE `warteliste`
|
||||
ADD COLUMN `zeitraum_id` INT NULL AFTER `impfenzeitraum`;
|
||||
END IF;
|
||||
|
||||
SELECT COUNT(*)
|
||||
INTO v_warteliste_index_exists
|
||||
FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'warteliste'
|
||||
AND index_name = 'idx_warteliste_zeitraum';
|
||||
|
||||
IF v_warteliste_index_exists = 0 THEN
|
||||
ALTER TABLE `warteliste`
|
||||
ADD INDEX `idx_warteliste_zeitraum` (`zeitraum_id`);
|
||||
END IF;
|
||||
|
||||
SELECT COUNT(*)
|
||||
INTO v_warteliste_zeitraum_exists
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'warteliste_zeitraum';
|
||||
|
||||
IF v_warteliste_zeitraum_exists = 0 THEN
|
||||
CREATE TABLE `warteliste_zeitraum` (
|
||||
`warteid` INT NOT NULL,
|
||||
`zeitraum_id` INT NOT NULL,
|
||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`warteid`, `zeitraum_id`),
|
||||
INDEX `idx_warteliste_zeitraum_zeitraum` (`zeitraum_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
SET v_warteliste_zeitraum_exists = 1;
|
||||
END IF;
|
||||
|
||||
SELECT COUNT(*)
|
||||
INTO v_warteliste_zeitraum_index_exists
|
||||
FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'warteliste_zeitraum'
|
||||
AND index_name = 'idx_warteliste_zeitraum_zeitraum';
|
||||
|
||||
IF v_warteliste_zeitraum_index_exists = 0 THEN
|
||||
ALTER TABLE `warteliste_zeitraum`
|
||||
ADD INDEX `idx_warteliste_zeitraum_zeitraum` (`zeitraum_id`);
|
||||
END IF;
|
||||
|
||||
INSERT IGNORE INTO `warteliste_zeitraum` (`warteid`, `zeitraum_id`)
|
||||
SELECT `warteid`, `zeitraum_id`
|
||||
FROM `warteliste`
|
||||
WHERE `zeitraum_id` IS NOT NULL
|
||||
AND `zeitraum_id` > 0;
|
||||
|
||||
INSERT INTO `impf_workflow_meta` (`meta_key`, `meta_value`)
|
||||
VALUES ('legacy_warteliste_zeitraeume_migrated', '1') AS `incoming`
|
||||
ON DUPLICATE KEY UPDATE `meta_value` = `incoming`.`meta_value`;
|
||||
END $$
|
||||
DELIMITER ;
|
||||
|
||||
CALL `migrate_warteliste_multi_zeitfenster_20260322`();
|
||||
DROP PROCEDURE IF EXISTS `migrate_warteliste_multi_zeitfenster_20260322`;
|
||||
@@ -0,0 +1,24 @@
|
||||
CREATE TABLE IF NOT EXISTS time_error_notification_state (
|
||||
employee_id INT NOT NULL,
|
||||
cycle_started_on DATE NOT NULL,
|
||||
first_error_date DATE NOT NULL,
|
||||
last_notification_stage VARCHAR(50) DEFAULT NULL,
|
||||
last_notification_sent_at DATETIME DEFAULT NULL,
|
||||
employee_day_1_sent_at DATETIME DEFAULT NULL,
|
||||
employee_day_3_sent_at DATETIME DEFAULT NULL,
|
||||
admin_day_7_sent_at DATETIME DEFAULT NULL,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (employee_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS time_error_notifications (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
employee_id INT NOT NULL,
|
||||
cycle_started_on DATE NOT NULL,
|
||||
notification_stage VARCHAR(50) NOT NULL,
|
||||
recipient_email VARCHAR(255) NOT NULL,
|
||||
sent_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uniq_time_error_notification (employee_id, cycle_started_on, notification_stage, recipient_email)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
@@ -63,7 +63,21 @@
|
||||
<?php else: ?>
|
||||
<div id="navbar" class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li><a href="anfragen.php">Anfragen</a></li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Anfragen <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="anfragen.php">Übersicht Anfragen</a></li>
|
||||
<li><a href="anfragen.php?aktion=1">Anfragen bearbeiten</a></li>
|
||||
<li><a href="anfragen.php?aktion=12">Formular-Auswertung</a></li>
|
||||
<li><a href="anfragen.php?aktion=18">Mailvorlagen anlegen</a></li>
|
||||
<li><a href="anfragen.php?aktion=16">Mailvorlagen anpassen</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="anfragen.php?aktion=4">Urlaub eintragen</a></li>
|
||||
<li><a href="anfragen.php?aktion=6">Notfallsprechstunde eintragen</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="http://ts03.fritz.box:8080/" target="_blank">Anrufbeantworter</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Impfungen <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
@@ -74,17 +88,18 @@
|
||||
<li><a href="impfworkflow_stammdaten.php">Stammdaten</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<!--<li><a href="togoadmin.php">togo-Impfung</a></li>-->
|
||||
<li><a href="http://ts03.fritz.box:8080/" target="_blank">Anrufbeantworter</a></li>
|
||||
|
||||
|
||||
<li><a href="../zeiterfassung">Zeiterfassung</a></li>
|
||||
<li><a href="webseitenadmin.php">Webseiteninhalt ändern</a></li>
|
||||
<li><a href="settings.php">Einstellungen</a></li>
|
||||
<li><a href="logout.php">Logout</a></li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Verwaltung <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="../zeiterfassung">Zeiterfassung</a></li>
|
||||
<li><a href="webseitenadmin.php">Webseiteninhalt ändern</a></li>
|
||||
<li><a href="settings.php">Einstellungen</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="zeiterfassung_hilfe.php">Hilfe</a></li>
|
||||
<li><a href="logout.php">Logout</a></li>
|
||||
</ul>
|
||||
</div><!--/.navbar-collapse -->
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
@@ -58,17 +58,23 @@ if (!check_worker()) {
|
||||
$inhaltid = (int)($_POST["inhaltid"] ?? 0);
|
||||
$inhalt = $_POST["inhalt"] ?? "";
|
||||
$webseitentitel = $_POST["webseitentitel"] ?? "";
|
||||
$beschreibung = $_POST["beschreibung"] ?? "";
|
||||
$url = $_POST["url"] ?? "";
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE webseiteninhalt
|
||||
SET inhalt = :inhalt,
|
||||
webseitentitel = :webseitentitel
|
||||
webseitentitel = :webseitentitel,
|
||||
beschreibung = :beschreibung,
|
||||
url = :url
|
||||
WHERE inhaltid = :inhaltid
|
||||
");
|
||||
$stmt->execute([
|
||||
':inhalt' => $inhalt,
|
||||
':webseitentitel' => $webseitentitel,
|
||||
':beschreibung' => $beschreibung,
|
||||
':url' => $url,
|
||||
':inhaltid' => $inhaltid,
|
||||
]);
|
||||
|
||||
@@ -85,7 +91,7 @@ if (!check_worker()) {
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT webseitentitel, inhalt
|
||||
SELECT webseitentitel, inhalt, beschreibung, url
|
||||
FROM webseiteninhalt
|
||||
WHERE inhaltid = ?
|
||||
LIMIT 1
|
||||
@@ -98,13 +104,20 @@ if (!check_worker()) {
|
||||
} else {
|
||||
$webseitentitel = $rowconfig["webseitentitel"] ?? "";
|
||||
$inhalt = $rowconfig["inhalt"] ?? "";
|
||||
$beschreibung = $rowconfig["beschreibung"] ?? "";
|
||||
$url = $rowconfig["url"] ?? "";
|
||||
|
||||
echo "<h1>Webseiteninhalt bearbeiten</h1><br>";
|
||||
echo "<h4>Vorlage: " . htmlspecialchars($webseitentitel, ENT_QUOTES, 'UTF-8') . "</h4>";
|
||||
echo "<br><br>";
|
||||
|
||||
echo "<form action='" . htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES, 'UTF-8') . "' method='POST'>";
|
||||
echo "<input name='webseitentitel' type='hidden' value='" . htmlspecialchars($webseitentitel, ENT_QUOTES, 'UTF-8') . "'>";
|
||||
echo "<label>Titel</label><br>";
|
||||
echo "<input name='webseitentitel' type='text' class='form-control' value='" . htmlspecialchars($webseitentitel, ENT_QUOTES, 'UTF-8') . "'><br><br>";
|
||||
echo "<label>Beschreibung</label><br>";
|
||||
echo "<input name='beschreibung' type='text' class='form-control' value='" . htmlspecialchars($beschreibung, ENT_QUOTES, 'UTF-8') . "'><br><br>";
|
||||
echo "<label>URL / Hinweis</label><br>";
|
||||
echo "<input name='url' type='text' class='form-control' value='" . htmlspecialchars($url, ENT_QUOTES, 'UTF-8') . "'><br><br>";
|
||||
echo "<div id='my-editor'></div>";
|
||||
// Inhalt ist HTML -> bewusst NICHT escapen, sonst zerstörst du HTML im Editor
|
||||
echo "<textarea height='200' name='inhalt' id='trumbowyg-demo'>" . $inhalt . "</textarea>";
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
require_once __DIR__ . "/../inc/config.inc.php";
|
||||
require_once __DIR__ . "/../inc/functions.inc.php";
|
||||
|
||||
$user = check_admin_user();
|
||||
|
||||
include __DIR__ . "/templates/header.inc.php";
|
||||
|
||||
if (!$user) {
|
||||
echo "<div class='container main-container'><h3>Erst anmelden: <a href='login.php'>Login</a></h3><br>";
|
||||
include __DIR__ . "/templates/footer.inc.php";
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="container main-container">
|
||||
<div class="page-header">
|
||||
<h2>FAQ und Anleitung: Admin + Zeiterfassung</h2>
|
||||
<p>Diese Seite dient als Nachschlagewerk für die tägliche Arbeit in der Administration und in der Zeiterfassung.</p>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Kurzüberblick:</strong> Die Administration steuert Anfragen, Inhalte, Einstellungen und Sonderbereiche. Die Zeiterfassung steuert Stempelungen, Korrekturen, Abwesenheiten, Fehlbuchungen, PDF-Ausgaben und Benachrichtigungen.
|
||||
</div>
|
||||
|
||||
<h3>1. Administration</h3>
|
||||
|
||||
<h4>Anfragen</h4>
|
||||
<p>Im Bereich <strong>Anfragen</strong> werden eingehende Formularanfragen bearbeitet, gefiltert und beantwortet. Dort lassen sich offene, letzte oder alle Anfragen anzeigen und in den Bearbeitungsstatus überführen.</p>
|
||||
|
||||
<h4>Mailvorlagen</h4>
|
||||
<p>Mailvorlagen werden genutzt, um wiederkehrende Antworten und Abläufe einheitlich zu versenden. Änderungen an Vorlagen wirken sich auf spätere Nachrichten aus, daher sollten Texte dort zentral gepflegt werden.</p>
|
||||
|
||||
<h4>Formular-Auswertung</h4>
|
||||
<p>Die Formular-Auswertung dient dazu, Anfragen strukturiert auszuwerten. Das ist vor allem hilfreich, wenn Mengen, Anfragearten oder Bearbeitungsstände geprüft werden sollen.</p>
|
||||
|
||||
<h4>Impfverwaltung</h4>
|
||||
<p>Unter <strong>Impfungen</strong> werden Wartelisten, Teilnehmer, Impfevents und Stammdaten verwaltet. Dieser Bereich ist unabhängig von der Zeiterfassung, gehört aber zur Admin-Oberfläche.</p>
|
||||
|
||||
<h4>Webseiteninhalte ändern</h4>
|
||||
<p>Über <strong>Webseiteninhalt ändern</strong> werden Texte und Inhalte der öffentlichen Webseite gepflegt. Änderungen dort betreffen direkt die Darstellung der Praxis-Webseite.</p>
|
||||
|
||||
<h4>Einstellungen</h4>
|
||||
<p>Im Bereich <strong>Einstellungen</strong> werden zentrale Konfigurationen gepflegt, zum Beispiel Benachrichtigungsadressen und technische Grundeinstellungen. Änderungen dort sollten bewusst vorgenommen werden.</p>
|
||||
|
||||
<h4>Zeiterfassung aus der Admin-Oberfläche</h4>
|
||||
<p>Die Zeiterfassung ist aus dem Admin-Menü direkt erreichbar. Dort wechseln Administratoren in den operativen Bereich für Zeiten, Fehlbuchungen, Abwesenheiten und Mitarbeiterverwaltung.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>2. Zeiterfassung: Funktionen für Mitarbeiter</h3>
|
||||
|
||||
<h4>Startseite / Stempeln</h4>
|
||||
<p>Auf der Startseite der Zeiterfassung wird gestempelt. Das System setzt automatisch abwechselnd <strong>KOMMEN</strong> und <strong>GEHEN</strong>. Zusätzlich wird dort angezeigt, ob aktuell ein Buchungsproblem vorhanden ist.</p>
|
||||
|
||||
<h4>Zeitübersicht</h4>
|
||||
<p>In der <strong>Zeitübersicht</strong> können die eigenen Buchungen für einen Monat kontrolliert werden. Dort lassen sich auch Tagesansichten aufrufen und bei Bedarf manuell anpassen.</p>
|
||||
|
||||
<h4>Fehlbuchungen</h4>
|
||||
<p>Der Bereich <strong>Fehlbuchungen</strong> zeigt unvollständige oder fehlerhafte KOMMEN/GEHEN-Folgen an. Mitarbeiter sehen dort ihre eigenen problematischen Tage und können diese korrigieren.</p>
|
||||
|
||||
<h4>Abwesenheitsantrag</h4>
|
||||
<p>Über <strong>Abwesenheitsantrag</strong> werden Urlaube und weitere Abwesenheitsgründe eingereicht. Der Antrag wird anschließend über die Genehmigungsfunktionen der Admins geprüft.</p>
|
||||
|
||||
<h4>Mein Abwesenheitskalender</h4>
|
||||
<p>Im eigenen Kalender sind die persönlichen Abwesenheiten sichtbar. So kann jeder Mitarbeiter seine eigenen Anträge und genehmigten Zeiten prüfen.</p>
|
||||
|
||||
<h4>Team-Urlaubskalender</h4>
|
||||
<p>Der Team-Kalender zeigt genehmigte Urlaubseinträge des Teams sowie Betriebsurlaub. Damit lassen sich Überschneidungen und Abwesenheiten leichter erkennen.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>3. Zeiterfassung: Funktionen für Admins</h3>
|
||||
|
||||
<h4>Alle Zeitbuchungen</h4>
|
||||
<p>Unter <strong>Alle Zeitbuchungen</strong> kann für jeden Mitarbeiter ein Monat ausgewählt und angezeigt werden. Zusätzlich lassen sich Einzel-PDFs und eine Sammel-PDF für alle Mitarbeiter eines Monats erzeugen.</p>
|
||||
|
||||
<h4>PDF-Ausgaben</h4>
|
||||
<p>Die Einzel-PDF erstellt die Monatsübersicht eines einzelnen Mitarbeiters. Die Sammel-PDF enthält alle Mitarbeiter mit Buchungen im gewählten Monat. Mitarbeiter ohne Buchung im Monat werden dabei nicht ausgegeben.</p>
|
||||
|
||||
<h4>Alle Fehlbuchungen</h4>
|
||||
<p>Unter <strong>Alle Fehlbuchungen</strong> werden fehlerhafte Tage aller Mitarbeiter angezeigt. Von dort aus können Admins einzelne Tage manuell bearbeiten oder automatische Schließungen für einen Mitarbeiter durchführen.</p>
|
||||
|
||||
<h4>Fehlbuchungen automatisch schließen</h4>
|
||||
<p>Admins können für einen Mitarbeiter alle automatisch schließbaren Fehlbuchungen mit einer Stundenanzahl ergänzen. Beispiel: Fehlt das <strong>GEHEN</strong>, wird es um die angegebene Anzahl Stunden nach dem letzten <strong>KOMMEN</strong> eingetragen.</p>
|
||||
|
||||
<h4>Mitarbeiterverwaltung</h4>
|
||||
<p>In der Mitarbeiterverwaltung werden Mitarbeiter angelegt und gepflegt. Dort werden unter anderem E-Mail, Rollen, Zeiterfassungsberechtigung, Admin-Status und Kartenzuordnungen verwaltet.</p>
|
||||
|
||||
<h4>Abwesenheitsübersicht</h4>
|
||||
<p>Die Abwesenheitsübersicht dient zur Kontrolle aller Abwesenheitseinträge. Dort werden pro Mitarbeiter die Urlaubstage für den Anspruch sowie die übrigen Abwesenheitsgründe je Jahr zusammengefasst.</p>
|
||||
|
||||
<h4>Abwesenheiten genehmigen</h4>
|
||||
<p>Im Bereich <strong>Abwesenheiten genehmigen</strong> prüfen Admins eingereichte Abwesenheiten und können diese annehmen oder ablehnen.</p>
|
||||
|
||||
<h4>Leitungskalender</h4>
|
||||
<p>Der Leitungskalender zeigt alle Abwesenheitstermine über alle Personen hinweg. Damit lassen sich Urlaub, Krankheit, Weiterbildung und weitere Gründe zentral koordinieren.</p>
|
||||
|
||||
<h4>Betriebsurlaub</h4>
|
||||
<p>Unter <strong>Betriebsurlaub</strong> werden zentrale Schließzeiten der Praxis gepflegt. Diese Einträge erscheinen im Urlaubskontext und können mit Vertreterinformationen hinterlegt werden.</p>
|
||||
|
||||
<h4>Benachrichtigungen bei Zeitfehlern</h4>
|
||||
<p>Für offene Zeitfehler existiert eine gestaffelte Benachrichtigungslogik. Mitarbeiter werden erinnert, und bei länger offenen Fehlern erfolgt später eine Eskalation an Admins. Die E-Mails enthalten einen Direktlink zur Zeiterfassung.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4. Typische Fragen von Mitarbeitern</h3>
|
||||
|
||||
<h4>Ich habe vergessen zu stempeln. Was soll ich tun?</h4>
|
||||
<p>Bitte in der Zeiterfassung den Bereich <strong>Fehlbuchungen</strong> oder die <strong>Zeitübersicht</strong> öffnen und den betroffenen Tag korrigieren. Falls das nicht möglich ist, bitte einen Admin informieren.</p>
|
||||
|
||||
<h4>Warum wird mir ein Buchungsfehler angezeigt?</h4>
|
||||
<p>Ein Fehler entsteht, wenn die Reihenfolge der Buchungen nicht stimmt, zum Beispiel zwei <strong>KOMMEN</strong> hintereinander oder wenn ein <strong>GEHEN</strong> fehlt.</p>
|
||||
|
||||
<h4>Bekomme ich Erinnerungen bei offenen Fehlern?</h4>
|
||||
<p>Ja. Offene Zeitfehler können Erinnerungen auslösen. Bleiben Fehler bestehen, werden sie nach dem vorgesehenen Ablauf weiter eskaliert.</p>
|
||||
|
||||
<h4>Kann ich meine Zeiten selbst ändern?</h4>
|
||||
<p>Eigene fehlerhafte Tage können in der Zeiterfassung angepasst werden. Größere Korrekturen oder Sammelkorrekturen werden durch Admins vorgenommen.</p>
|
||||
|
||||
<h4>Wo sehe ich meinen Urlaub?</h4>
|
||||
<p>Im Bereich <strong>Mein Abwesenheitskalender</strong>. Dort sind die eigenen Abwesenheitszeiträume sichtbar.</p>
|
||||
|
||||
<h4>Wo sehe ich, wann Kollegen im Urlaub sind?</h4>
|
||||
<p>Im <strong>Team-Urlaubskalender</strong>. Dort werden freigegebene Urlaube und Betriebsurlaub angezeigt.</p>
|
||||
|
||||
<h4>Was bedeutet Betriebsurlaub?</h4>
|
||||
<p>Betriebsurlaub sind zentrale Schließzeiten der Praxis. Diese werden administrativ gepflegt und im Abwesenheitskalender sichtbar gemacht.</p>
|
||||
|
||||
<h4>An wen wende ich mich bei falschen Zeiten, wenn ich sie nicht selbst korrigieren kann?</h4>
|
||||
<p>Dann sollte ein Admin oder Vorgesetzter informiert werden. Admins können einzelne Tage bearbeiten oder automatisch fehlende Ausstempelungen ergänzen.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>5. Typische Fragen von Admins</h3>
|
||||
|
||||
<h4>Wie finde ich schnell offene Zeitprobleme?</h4>
|
||||
<p>Über <strong>Alle Fehlbuchungen</strong> in der Zeiterfassung. Dort sind alle problematischen Tage je Mitarbeiter sichtbar.</p>
|
||||
|
||||
<h4>Wie schließe ich mehrere ähnliche Fehlbuchungen auf einmal?</h4>
|
||||
<p>In <strong>Alle Fehlbuchungen</strong> kann für einen Mitarbeiter eine Stundenanzahl eingetragen werden, um automatisch fehlende <strong>GEHEN</strong>-Buchungen zu ergänzen, wenn der letzte Eintrag des Tages ein <strong>KOMMEN</strong> ist.</p>
|
||||
|
||||
<h4>Wie erstelle ich Monatsnachweise für mehrere Mitarbeiter?</h4>
|
||||
<p>Über <strong>Alle Zeitbuchungen</strong> und dann die Sammel-PDF für den gewünschten Monat. So werden alle Mitarbeiter mit Buchungen in einem Dokument zusammengefasst.</p>
|
||||
|
||||
<h4>Warum erscheint ein Mitarbeiter nicht in der Sammel-PDF?</h4>
|
||||
<p>Mitarbeiter ohne Buchung im ausgewählten Monat werden in der Gesamt-PDF nicht aufgenommen.</p>
|
||||
|
||||
<h4>Was tun, wenn die PDF für einen Mitarbeiter nicht erzeugt wird?</h4>
|
||||
<p>Dann liegen im gewählten Monat meist noch offene Zeitfehler vor. Diese müssen zuerst bereinigt werden.</p>
|
||||
|
||||
<h4>Wo pflege ich Vertreterdaten beim Betriebsurlaub?</h4>
|
||||
<p>Im Bereich <strong>Betriebsurlaub</strong>. Dort werden Beschreibung, Vertretung, Telefonnummer, Adresse und URL gepflegt.</p>
|
||||
|
||||
<h4>Welche Abwesenheiten zählen auf den Urlaubsanspruch?</h4>
|
||||
<p>Nur <strong>Urlaub</strong> zählt auf den Urlaubsanspruch. Krankheit, Berufsschule, Weiterbildung, persönliche Gründe und Sonstiges werden separat ausgewertet.</p>
|
||||
|
||||
<h4>Wo finde ich den schnellsten Rückweg zwischen Admin und Zeiterfassung?</h4>
|
||||
<p>Es gibt direkte Menüeinträge zwischen beiden Bereichen. In der Zeiterfassung führt <strong>Zur Admin-Oberfläche</strong> zurück in die Verwaltung.</p>
|
||||
|
||||
<div class="alert alert-success" style="margin-top:30px;">
|
||||
<strong>Hinweis:</strong> Diese Seite ist als lebendes Handbuch gedacht. Wenn neue Funktionen in Admin oder Zeiterfassung hinzukommen, sollte diese Hilfeseite mit aktualisiert werden.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include __DIR__ . "/templates/footer.inc.php"; ?>
|
||||
@@ -5,11 +5,6 @@ CALSCALE:GREGORIAN
|
||||
METHOD:PUBLISH
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:Urlaub
|
||||
DTSTART:20250912
|
||||
DTEND:20250921
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:Urlaub
|
||||
DTSTART:20251002
|
||||
DTEND:20251005
|
||||
END:VEVENT
|
||||
|
||||
@@ -5,11 +5,6 @@ CALSCALE:GREGORIAN
|
||||
METHOD:PUBLISH
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:Urlaub
|
||||
DTSTART:20250912
|
||||
DTEND:20250921
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:Urlaub
|
||||
DTSTART:20251002
|
||||
DTEND:20251005
|
||||
END:VEVENT
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 126 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 229 KiB |
@@ -0,0 +1,6 @@
|
||||
<svg width="720" height="720" viewBox="0 0 720 720" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="720" height="720" rx="36" fill="#F3F4F6"/>
|
||||
<circle cx="360" cy="235" r="112" fill="#9CA3AF"/>
|
||||
<path d="M152 608C152 488.706 248.706 392 368 392H352C471.294 392 568 488.706 568 608V624H152V608Z" fill="#9CA3AF"/>
|
||||
<path d="M228 608C228 530.68 290.68 468 368 468H352C429.32 468 492 530.68 492 608V624H228V608Z" fill="#6B7280"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 452 B |
+174
-27
@@ -8,6 +8,32 @@
|
||||
<head>
|
||||
<?php
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '1');
|
||||
register_shutdown_function(static function (): void {
|
||||
$error = error_get_last();
|
||||
if ($error === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fatalTypes = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR];
|
||||
if (!in_array((int)$error['type'], $fatalTypes, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!headers_sent()) {
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
}
|
||||
|
||||
echo '<div style="max-width:900px;margin:20px auto;padding:16px;border:3px solid #b30000;background:#fff7f7;color:#111;font-family:Arial,sans-serif;">'
|
||||
. '<h3 style="margin-top:0;">Fehler in impfwarteliste.php</h3>'
|
||||
. '<p>Die Seite ist wegen eines PHP-Fehlers abgebrochen.</p>'
|
||||
. '<p><strong>Meldung:</strong> ' . htmlspecialchars((string)$error['message'], ENT_QUOTES, 'UTF-8') . '<br>'
|
||||
. '<strong>Datei:</strong> ' . htmlspecialchars((string)$error['file'], ENT_QUOTES, 'UTF-8') . '<br>'
|
||||
. '<strong>Zeile:</strong> ' . (int)$error['line'] . '</p>'
|
||||
. '</div>';
|
||||
});
|
||||
|
||||
include('header.php');
|
||||
|
||||
?>
|
||||
@@ -26,11 +52,17 @@
|
||||
include_once("inc/config.inc.php");
|
||||
include_once("inc/functions.inc.php");
|
||||
include_once('inc/functions.impfen.inc.php');
|
||||
include_once('inc/impfworkflow_notifications.inc.php');
|
||||
$workflowSetupError = '';
|
||||
if ($con instanceof mysqli) {
|
||||
mysqli_set_charset($con, "utf8mb4");
|
||||
}
|
||||
if (isset($pdo) && $pdo instanceof PDO) {
|
||||
impfWorkflowEnsureTables($pdo);
|
||||
try {
|
||||
impfWorkflowEnsureTables($pdo);
|
||||
} catch (Throwable $e) {
|
||||
$workflowSetupError = $e->getMessage();
|
||||
}
|
||||
}
|
||||
$zeitOptionenJson = "{}";
|
||||
?>
|
||||
@@ -57,11 +89,20 @@ $mailbetreff = "Ihr Wartelistenplatz für eine Impfung bei Praxis Creutzburg";
|
||||
|
||||
<section class="box special">
|
||||
<h2>Impfwarteliste</h2>
|
||||
<?php if ($workflowSetupError !== ''): ?>
|
||||
<div style="border:3px solid red; margin: 10px 0; padding: 10px; text-align:left;">
|
||||
Die Impfworkflow-Tabellen konnten nicht automatisch geprueft werden: <?php echo htmlspecialchars($workflowSetupError, ENT_QUOTES, 'UTF-8'); ?><br>
|
||||
Bitte fuehren Sie zuerst das Migrationsskript aus.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
|
||||
|
||||
if(isset($_POST["id"]) || isset($_GET["id"])){
|
||||
if($workflowSetupError !== ''){
|
||||
// Hinweis wurde bereits oberhalb ausgegeben.
|
||||
|
||||
}else if(isset($_POST["id"]) || isset($_GET["id"])){
|
||||
|
||||
if(isset($_POST["id"])){
|
||||
$id = mysqli_real_escape_string($con, $_POST["id"]);
|
||||
@@ -83,6 +124,17 @@ if(isset($_POST["id"]) || isset($_GET["id"])){
|
||||
$impfenzeitraum = $zeitraumAktuell['label'];
|
||||
}
|
||||
}
|
||||
$ausgewaehlteZeitraeume = [];
|
||||
if (isset($pdo) && $pdo instanceof PDO) {
|
||||
$ausgewaehlteZeitraeume = impfGetWartelistenZeitraeumeLabels($pdo, (int)$warteid, false);
|
||||
}
|
||||
if (!empty($ausgewaehlteZeitraeume)) {
|
||||
$impfenzeitraum = implode('<br>', array_map(static function ($label) {
|
||||
return e((string)$label);
|
||||
}, $ausgewaehlteZeitraeume));
|
||||
} else {
|
||||
$impfenzeitraum = htmlspecialchars((string)$impfenzeitraum, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
//echo $userid;
|
||||
$queryuser = mysqli_query($con, "SELECT * FROM persons WHERE person_id='" . $userid . "'");
|
||||
$rowuser = $queryuser->fetch_assoc();
|
||||
@@ -133,7 +185,7 @@ if(isset($_POST["id"]) || isset($_GET["id"])){
|
||||
echo "<h4>Art: $Patientenart</h4>";
|
||||
echo "<h4>Telefon: $tel</h4>";
|
||||
echo "<h4>Impfstoff: $impfstofftext</h4>";
|
||||
echo "<h4>Zeitraum: $impfenzeitraum </h4><br>";
|
||||
echo "<h4>Zeitraum:<br>$impfenzeitraum</h4><br>";
|
||||
|
||||
echo "<form action='". $_SERVER['PHP_SELF'] . "' method=POST>";
|
||||
echo '<input type="hidden" name="warteid" id="warteid" value="'. $warteid .'" />';
|
||||
@@ -151,6 +203,13 @@ if(isset($_POST["id"]) || isset($_GET["id"])){
|
||||
echo "Sie haben die folgenden Angaben:<br><br>";
|
||||
echo "<h4>Name: $userausgabe</h4>";
|
||||
echo "<h4>Impfstoff: $impfstofftext</h4><br>";
|
||||
if (!empty($ausgewaehlteZeitraeume)) {
|
||||
echo "<h4>Ausgewählte Zeitfenster:<br>" . implode('<br>', array_map(static function ($label) {
|
||||
return e((string)$label);
|
||||
}, $ausgewaehlteZeitraeume)) . "</h4>";
|
||||
} else {
|
||||
echo "<h4>Zeitraum: $impfenzeitraum</h4>";
|
||||
}
|
||||
echo "<h4>Wir informieren Sie, sobald ein konkreter Impftermin für Ihren Impfstoff festgelegt wurde.</h4>";
|
||||
echo "Die Terminvergabe erfolgt durch das Praxisteam, sobald eine komplette Impfflasche mit passenden Wartelistenplätzen gefüllt ist.<br><br>";
|
||||
echo "Können Sie Ihren Warteplatz nicht wahrnehmen oder benötigen diesen nicht mehr, dann tragen Sie sich bitte aus:<br>";
|
||||
@@ -198,6 +257,24 @@ if(isset($_POST["id"]) || isset($_GET["id"])){
|
||||
echo "<input type=hidden name='".$key."' value='".$value. "'>\n";
|
||||
continue;
|
||||
}
|
||||
if($key === "impfenzeitraeume" && is_array($value)){
|
||||
$zeitraumIds = impfNormalizeZeitraumIds($value);
|
||||
$zeitraumLabels = [];
|
||||
foreach ($zeitraumIds as $zeitraumId) {
|
||||
$zeitraumRow = null;
|
||||
if (isset($pdo) && $pdo instanceof PDO) {
|
||||
$zeitraumRow = impfLoadZeitraumById($pdo, (int)$zeitraumId, true);
|
||||
}
|
||||
if ($zeitraumRow) {
|
||||
$zeitraumLabels[] = (string)$zeitraumRow['label'];
|
||||
echo '<input type="hidden" name="impfenzeitraeume[]" value="'.(int)$zeitraumId.'">' . "\n";
|
||||
}
|
||||
}
|
||||
echo "<tr><td width=100 valign=top class=fett>$key:</td><td>" . implode("<br>", array_map(static function ($label) {
|
||||
return e((string)$label);
|
||||
}, $zeitraumLabels)) . "</td></tr>\n";
|
||||
continue;
|
||||
}
|
||||
if($value !== ""){
|
||||
if($key == "Impfstoff"){
|
||||
$sqlimpfstoffstring = "SELECT * FROM impfstoff WHERE impfid ='" . $value . "' order by sortierung";
|
||||
@@ -215,6 +292,7 @@ if(isset($_POST["id"]) || isset($_GET["id"])){
|
||||
}
|
||||
$zeitraumText = $zeitraumRow ? $zeitraumRow['label'] : 'Unbekannter Zeitraum';
|
||||
echo "<tr><td width=100 valign=top class=fett>$key:</td><td>$zeitraumText</td></tr>\n";
|
||||
echo"<input type=hidden name='impfenzeitraeume[]' value='".(int)$value."'>\n";
|
||||
echo"<input type=hidden name='".$key."' value='".$value. "'>\n";
|
||||
}else{
|
||||
echo "<tr><td width=100 valign=top class=fett>$key:</td><td>$value</td></tr>\n";
|
||||
@@ -249,12 +327,12 @@ if(isset($_POST["id"]) || isset($_GET["id"])){
|
||||
$Impfaufklaerung = mysqli_real_escape_string($con, $_POST["Impfaufklaerung"] ?? "Nein");
|
||||
$WeitereFragen = mysqli_real_escape_string($con, $_POST["WeitereFragen"] ?? "Nein");
|
||||
$impfenmit = mysqli_real_escape_string($con, $_POST["zusammenmit"] ?? "");
|
||||
$impfenzeitraumId = (int)($_POST["impfenzeitraum"] ?? 0);
|
||||
$impfenzeitraumIds = impfNormalizeZeitraumIds($_POST["impfenzeitraeume"] ?? ($_POST["impfenzeitraum"] ?? []));
|
||||
$impfart = (int)($_POST["impfart"] ?? 0);
|
||||
$letzteimpfung = trim($_POST["letzteimpfung"] ?? "");
|
||||
|
||||
if ($impfstoff <= 0 || $impfart <= 0 || $impfenzeitraumId <= 0) {
|
||||
echo "<h3>Pflichtfelder fehlen</h3><br>Bitte wählen Sie Impfstoff, Zeitraum und Impfungsart aus.<br><br>";
|
||||
if ($impfstoff <= 0 || $impfart <= 0 || empty($impfenzeitraumIds)) {
|
||||
echo "<h3>Pflichtfelder fehlen</h3><br>Bitte wählen Sie Impfstoff, mindestens einen Zeitraum und die Impfungsart aus.<br><br>";
|
||||
goto end_aktion_1;
|
||||
}
|
||||
|
||||
@@ -267,12 +345,19 @@ if(isset($_POST["id"]) || isset($_GET["id"])){
|
||||
$letzteimpfung = "";
|
||||
}
|
||||
|
||||
$zeitraumRow = (isset($pdo) && $pdo instanceof PDO) ? impfLoadZeitraumById($pdo, $impfenzeitraumId, true) : null;
|
||||
if (!$zeitraumRow || !in_array($impfstoff, $zeitraumRow['impfstoff_id_list'] ?? [], true)) {
|
||||
echo "<h3>Ungültiger Zeitraum</h3><br>Bitte wählen Sie einen gültigen Zeitraum für den ausgewählten Impfstoff.<br><br>";
|
||||
goto end_aktion_1;
|
||||
$zeitraumLabels = [];
|
||||
if (isset($pdo) && $pdo instanceof PDO) {
|
||||
foreach ($impfenzeitraumIds as $impfenzeitraumId) {
|
||||
$zeitraumRow = impfLoadZeitraumById($pdo, (int)$impfenzeitraumId, true);
|
||||
if (!$zeitraumRow || !in_array($impfstoff, $zeitraumRow['impfstoff_id_list'] ?? [], true)) {
|
||||
echo "<h3>Ungültiger Zeitraum</h3><br>Bitte wählen Sie einen gültigen Zeitraum für den ausgewählten Impfstoff aus.<br><br>";
|
||||
goto end_aktion_1;
|
||||
}
|
||||
$zeitraumLabels[] = (string)$zeitraumRow['label'];
|
||||
}
|
||||
}
|
||||
$impfenzeitraum = mysqli_real_escape_string($con, $zeitraumRow['label']);
|
||||
$impfenzeitraum = mysqli_real_escape_string($con, implode(' | ', $zeitraumLabels));
|
||||
$impfenzeitraumId = (int)($impfenzeitraumIds[0] ?? 0);
|
||||
|
||||
|
||||
//echo $impfenmit;
|
||||
@@ -333,12 +418,43 @@ if(isset($_POST["id"]) || isset($_GET["id"])){
|
||||
$letzteimpfungSql = ($letzteimpfung !== "") ? ("'" . mysqli_real_escape_string($con, $letzteimpfung) . "'") : "NULL";
|
||||
$query = mysqli_query($con, "SELECT * FROM warteliste WHERE userid='" . (int)$userid . "'");
|
||||
if($query && $query->num_rows == 0){
|
||||
$query = mysqli_query($con, "INSERT INTO warteliste (userid, hash, impfenangebot, impfstoff, Patientenart,Impfaufklaerung, WeitereFragen, date_created, impfenmit, impfenzeitraum, zeitraum_id, impfart, letzteimpfung, checked) VALUES ('". (int)$userid ."', '".$hash."', '".$impfenangebot."', '".$impfstoff."', '".$Patientenart."', '".$Impfaufklaerung."', '".$WeitereFragen."', now(), '".$impfenmit."', '".$impfenzeitraum."', '". (int)$impfenzeitraumId ."', '".$impfart."', ".$letzteimpfungSql.", '0')");
|
||||
if($query){
|
||||
$warteid = mysqli_insert_id($con);
|
||||
SendMailMessageVorlage($pdo, "2", $warteid , "8" );
|
||||
$warteid = 0;
|
||||
$saveOk = false;
|
||||
$saveErrorShown = false;
|
||||
try {
|
||||
if (isset($pdo) && $pdo instanceof PDO) {
|
||||
[$ok, $msg, $newWarteid] = impfCreateWaitlistEntryForPerson(
|
||||
$pdo,
|
||||
(int)$userid,
|
||||
$impfstoff,
|
||||
$impfenzeitraumIds,
|
||||
$impfart,
|
||||
($letzteimpfung !== "") ? $letzteimpfung : null,
|
||||
0
|
||||
);
|
||||
if (!$ok) {
|
||||
throw new RuntimeException($msg);
|
||||
}
|
||||
$warteid = (int)$newWarteid;
|
||||
$pdo->prepare("UPDATE warteliste SET impfenangebot = :impfenangebot, impfenmit = :impfenmit, Impfaufklaerung = :aufklaerung, WeitereFragen = :fragen WHERE warteid = :warteid")
|
||||
->execute([
|
||||
'impfenangebot' => $impfenangebot,
|
||||
'impfenmit' => $impfenmit,
|
||||
'aufklaerung' => $Impfaufklaerung,
|
||||
'fragen' => $WeitereFragen,
|
||||
'warteid' => $warteid,
|
||||
]);
|
||||
$saveOk = true;
|
||||
} else {
|
||||
throw new RuntimeException("PDO Verbindungsobjekt fehlt.");
|
||||
}
|
||||
SendMailMessageVorlage($pdo, "2", (int)$warteid, "8" );
|
||||
echo "<h3>Nachricht abgeschickt!</h3><br>Sie müssen die Eintragung in der Warteliste noch bestätigen!<br>Überprüfen Sie auch Ihren Spam-Filter!<br><br>";
|
||||
}else{
|
||||
} catch (Throwable $e) {
|
||||
echo "<h3>Speicherung nicht erfolgreich</h3><br>" . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8') . "<br><br>";
|
||||
$saveErrorShown = true;
|
||||
}
|
||||
if(!$saveOk && !$saveErrorShown){
|
||||
echo "<h3>Speicherung nicht erfolgreich</h3><br>Ihre Anfrage konnte nicht gespeichert werden.<br>Nutzen Sie das Formular erneut<br><br>";
|
||||
}
|
||||
}else{
|
||||
@@ -381,6 +497,13 @@ if(isset($_POST["id"]) || isset($_GET["id"])){
|
||||
$userausgabe = $vorname . " " . $nachname;
|
||||
|
||||
SendMailMessageVorlage($pdo, "2", $warteid , "9" );
|
||||
if (isset($pdo) && $pdo instanceof PDO) {
|
||||
try {
|
||||
impfWorkflowNotificationProcess($pdo);
|
||||
} catch (Throwable $e) {
|
||||
error_log('impfWorkflowNotificationProcess failed in impfwarteliste confirm: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -398,11 +521,22 @@ if(isset($_POST["id"]) || isset($_GET["id"])){
|
||||
$queryimpf = mysqli_query($con, "SELECT * FROM warteliste WHERE warteid='" . $_POST["warteid"] . "'");
|
||||
$rowimpf = $queryimpf->fetch_assoc() ;
|
||||
SendMailMessageVorlage($pdo, "2", $_POST["warteid"], "10" );
|
||||
if (isset($pdo) && $pdo instanceof PDO) {
|
||||
$pdo->prepare("DELETE FROM warteliste_zeitraum WHERE warteid = :warteid")->execute([
|
||||
'warteid' => (int)$_POST["warteid"],
|
||||
]);
|
||||
}
|
||||
$query = mysqli_query($con, "DELETE FROM warteliste WHERE warteid ='".$_POST["warteid"]."'");
|
||||
if($query){
|
||||
echo "<h4>Ihr Warteplatz wurde erfolgreich gelöscht!<h4><br>";
|
||||
echo "Sie erhalten gleiche eine schriftliche Bestätigung per E-Mail<br>";
|
||||
|
||||
if (isset($pdo) && $pdo instanceof PDO) {
|
||||
try {
|
||||
impfWorkflowNotificationProcess($pdo);
|
||||
} catch (Throwable $e) {
|
||||
error_log('impfWorkflowNotificationProcess failed in impfwarteliste delete: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -443,9 +577,12 @@ if(isset($_POST["id"]) || isset($_GET["id"])){
|
||||
echo "<h4>Mail wird gleich versendet!</h4>";
|
||||
echo "<br>Überprüfen Sie auch Ihren SPAM Ordner!<br>";
|
||||
SendMailMessageVorlage($pdo, "1", (int)$terminid, "1" );
|
||||
|
||||
|
||||
$query = mysqli_query($con, "DELETE FROM warteliste WHERE warteid ='".$warteid."'");
|
||||
if (isset($pdo) && $pdo instanceof PDO) {
|
||||
$pdo->prepare("DELETE FROM warteliste_zeitraum WHERE warteid = :warteid")->execute([
|
||||
'warteid' => (int)$warteid,
|
||||
]);
|
||||
}
|
||||
$query = mysqli_query($con, "DELETE FROM warteliste WHERE warteid ='".$warteid."'");
|
||||
}else{
|
||||
echo "<h4>Fehler bei Speichern der Anfragen!</h4>";
|
||||
echo "<br>Versuchen Sie es später erneut!<br>";
|
||||
@@ -512,7 +649,7 @@ if(isset($_POST["id"]) || isset($_GET["id"])){
|
||||
}
|
||||
$zeitOptionenByImpfstoff[$iid][] = [
|
||||
'id' => (int)$zeitraum['zeitraum_id'],
|
||||
'label' => (string)$zeitraum['label'],
|
||||
'label' => impfZeitraumLabel($zeitraum, false),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -602,9 +739,9 @@ if(isset($_POST["id"]) || isset($_GET["id"])){
|
||||
</div>
|
||||
<br>
|
||||
<div class="12u">
|
||||
<label for="impfenzeitraum">Wählen Sie den möglichen Zeitbereich für den gewählten Impfstoff:</label>
|
||||
<label for="impfenzeitraeume">Wählen Sie die möglichen Zeitbereiche für den gewählten Impfstoff:</label>
|
||||
<div class="select-wrapper">
|
||||
<select name="impfenzeitraum" id="impfenzeitraum" required disabled onchange="checkZeitraum()">
|
||||
<select name="impfenzeitraeume[]" id="impfenzeitraeume" multiple size="6" required disabled onchange="checkZeitraum()">
|
||||
<option value="">- Bitte zuerst Impfstoff auswählen -</option>
|
||||
</select>
|
||||
<div id="Zeitrauminfo"></div>
|
||||
@@ -680,6 +817,7 @@ if(isset($_POST["id"]) || isset($_GET["id"])){
|
||||
|
||||
<script>
|
||||
const zeitfensterByImpfstoff = <?php echo $zeitOptionenJson ?: '{}'; ?>;
|
||||
const initialZeitraumIds = <?php echo json_encode(impfNormalizeZeitraumIds($_POST["impfenzeitraeume"] ?? ($_POST["impfenzeitraum"] ?? [])), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
|
||||
|
||||
function isIE() {
|
||||
return /Trident\/|MSIE/.test(window.navigator.userAgent);
|
||||
@@ -687,7 +825,7 @@ if(isset($_POST["id"]) || isset($_GET["id"])){
|
||||
|
||||
function updateZeitfenster() {
|
||||
const impfstoff = document.getElementById('AstraImpfung');
|
||||
const zeitraum = document.getElementById('impfenzeitraum');
|
||||
const zeitraum = document.getElementById('impfenzeitraeume');
|
||||
const zeitraumInfo = document.getElementById('Zeitrauminfo');
|
||||
const submit = document.getElementById('submitbox');
|
||||
const impfstoffId = impfstoff.value;
|
||||
@@ -697,7 +835,7 @@ if(isset($_POST["id"]) || isset($_GET["id"])){
|
||||
if (!impfstoffId || optionen.length === 0) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = '';
|
||||
opt.textContent = '- Kein Zeitbereich verfügbar -';
|
||||
opt.textContent = '- Keine Zeitbereiche verfügbar -';
|
||||
zeitraum.appendChild(opt);
|
||||
zeitraum.disabled = true;
|
||||
submit.disabled = true;
|
||||
@@ -713,11 +851,15 @@ if(isset($_POST["id"]) || isset($_GET["id"])){
|
||||
const opt = document.createElement('option');
|
||||
opt.value = String(eintrag.id || '');
|
||||
opt.textContent = eintrag.label || '';
|
||||
if (initialZeitraumIds.includes(Number(eintrag.id))) {
|
||||
opt.selected = true;
|
||||
}
|
||||
zeitraum.appendChild(opt);
|
||||
});
|
||||
zeitraum.disabled = false;
|
||||
submit.disabled = false;
|
||||
zeitraumInfo.innerHTML = '';
|
||||
checkZeitraum();
|
||||
}
|
||||
|
||||
function checklastImpf() {
|
||||
@@ -736,10 +878,14 @@ if(isset($_POST["id"]) || isset($_GET["id"])){
|
||||
|
||||
function checkZeitraum() {
|
||||
const info = document.getElementById('Zeitrauminfo');
|
||||
if (document.getElementById('impfenzeitraum').value === "") {
|
||||
const submit = document.getElementById('submitbox');
|
||||
const selected = Array.from(document.getElementById('impfenzeitraeume').selectedOptions || []).filter((opt) => opt.value !== '');
|
||||
if (selected.length === 0) {
|
||||
info.innerHTML = "";
|
||||
submit.disabled = true;
|
||||
} else {
|
||||
info.innerHTML = '<div style="border:5px solid red; margin: 5px; padding: 5px;">Bitte halten Sie sich den gewählten Zeitraum frei. Die konkrete Terminanfrage erhalten Sie später per E-Mail.</div>';
|
||||
info.innerHTML = '<div style="border:5px solid red; margin: 5px; padding: 5px;">Bitte halten Sie sich die gewählten Zeitbereiche frei. Die konkrete Terminanfrage erhalten Sie später per E-Mail.</div>';
|
||||
submit.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -750,6 +896,7 @@ if(isset($_POST["id"]) || isset($_GET["id"])){
|
||||
}
|
||||
checklastImpf();
|
||||
updateZeitfenster();
|
||||
checkZeitraum();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -0,0 +1,387 @@
|
||||
<?php
|
||||
|
||||
if (!function_exists('vacationSyncTableExists')) {
|
||||
function vacationSyncTableExists(PDO $pdo, string $table): bool
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT COUNT(*)
|
||||
FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = :table_name"
|
||||
);
|
||||
$stmt->execute(['table_name' => $table]);
|
||||
return (int)$stmt->fetchColumn() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('vacationSyncTableHasColumn')) {
|
||||
function vacationSyncTableHasColumn(PDO $pdo, string $table, string $column): bool
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = :table_name
|
||||
AND COLUMN_NAME = :column_name"
|
||||
);
|
||||
$stmt->execute([
|
||||
'table_name' => $table,
|
||||
'column_name' => $column,
|
||||
]);
|
||||
return (int)$stmt->fetchColumn() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
+369
-49
@@ -35,6 +35,41 @@ if (!function_exists('impfTableHasIndex')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('impfEnsureTable')) {
|
||||
function impfEnsureTable(PDO $pdo, string $table, string $createSql): void
|
||||
{
|
||||
if (impfTableExists($pdo, $table)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pdo->exec($createSql);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('impfNormalizeZeitraumIds')) {
|
||||
function impfNormalizeZeitraumIds($zeitraumIds): array
|
||||
{
|
||||
if ($zeitraumIds === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!is_array($zeitraumIds)) {
|
||||
$zeitraumIds = [$zeitraumIds];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($zeitraumIds as $zeitraumId) {
|
||||
$zeitraumId = (int)$zeitraumId;
|
||||
if ($zeitraumId <= 0 || isset($result[$zeitraumId])) {
|
||||
continue;
|
||||
}
|
||||
$result[$zeitraumId] = $zeitraumId;
|
||||
}
|
||||
|
||||
return array_values($result);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('impfWeekdayName')) {
|
||||
function impfWeekdayName(int $day): string
|
||||
{
|
||||
@@ -52,24 +87,44 @@ if (!function_exists('impfWeekdayName')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('impfLimitLabelLength')) {
|
||||
function impfLimitLabelLength(string $text, int $maxLength = 50): string
|
||||
{
|
||||
$text = trim($text);
|
||||
if ($text === '') {
|
||||
return $text;
|
||||
}
|
||||
|
||||
if (function_exists('mb_strimwidth')) {
|
||||
return rtrim(mb_strimwidth($text, 0, $maxLength, '...', 'UTF-8'));
|
||||
}
|
||||
|
||||
if (strlen($text) <= $maxLength) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
return rtrim(substr($text, 0, max(0, $maxLength - 3))) . '...';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('impfWorkflowEnsureTables')) {
|
||||
function impfWorkflowEnsureTables(PDO $pdo): void
|
||||
{
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS impf_workflow_meta (
|
||||
impfEnsureTable($pdo, 'impf_workflow_meta', "CREATE TABLE impf_workflow_meta (
|
||||
meta_key VARCHAR(100) NOT NULL,
|
||||
meta_value VARCHAR(255) NOT NULL DEFAULT '',
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (meta_key)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3");
|
||||
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS impfstoff_workflow (
|
||||
impfEnsureTable($pdo, 'impfstoff_workflow', "CREATE TABLE impfstoff_workflow (
|
||||
impfstoff_id INT NOT NULL,
|
||||
dosen_pro_flasche INT NOT NULL,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (impfstoff_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3");
|
||||
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS impfstoff_wochenplan (
|
||||
impfEnsureTable($pdo, 'impfstoff_wochenplan', "CREATE TABLE impfstoff_wochenplan (
|
||||
plan_id INT NOT NULL AUTO_INCREMENT,
|
||||
impfstoff_id INT NOT NULL,
|
||||
wochentag TINYINT NOT NULL,
|
||||
@@ -83,7 +138,7 @@ if (!function_exists('impfWorkflowEnsureTables')) {
|
||||
INDEX idx_impfstoff_wochenplan_wochentag (wochentag)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3");
|
||||
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS impf_zeitraum (
|
||||
impfEnsureTable($pdo, 'impf_zeitraum', "CREATE TABLE impf_zeitraum (
|
||||
zeitraum_id INT NOT NULL AUTO_INCREMENT,
|
||||
bezeichnung VARCHAR(120) NOT NULL DEFAULT '',
|
||||
wochentag TINYINT NOT NULL,
|
||||
@@ -101,7 +156,7 @@ if (!function_exists('impfWorkflowEnsureTables')) {
|
||||
$pdo->exec("ALTER TABLE impf_zeitraum ADD COLUMN bezeichnung VARCHAR(120) NOT NULL DEFAULT '' AFTER zeitraum_id");
|
||||
}
|
||||
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS impf_zeitraum_impfstoff (
|
||||
impfEnsureTable($pdo, 'impf_zeitraum_impfstoff', "CREATE TABLE impf_zeitraum_impfstoff (
|
||||
zeitraum_id INT NOT NULL,
|
||||
impfstoff_id INT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
@@ -109,6 +164,18 @@ if (!function_exists('impfWorkflowEnsureTables')) {
|
||||
INDEX idx_impf_zeitraum_impfstoff_impfstoff (impfstoff_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3");
|
||||
|
||||
impfEnsureTable($pdo, 'warteliste_zeitraum', "CREATE TABLE warteliste_zeitraum (
|
||||
warteid INT NOT NULL,
|
||||
zeitraum_id INT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (warteid, zeitraum_id),
|
||||
INDEX idx_warteliste_zeitraum_zeitraum (zeitraum_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3");
|
||||
|
||||
if (impfTableExists($pdo, 'warteliste_zeitraum') && !impfTableHasIndex($pdo, 'warteliste_zeitraum', 'idx_warteliste_zeitraum_zeitraum')) {
|
||||
$pdo->exec("ALTER TABLE warteliste_zeitraum ADD INDEX idx_warteliste_zeitraum_zeitraum (zeitraum_id)");
|
||||
}
|
||||
|
||||
if (impfTableExists($pdo, 'warteliste') && !impfTableHasColumn($pdo, 'warteliste', 'zeitraum_id')) {
|
||||
$pdo->exec("ALTER TABLE warteliste ADD COLUMN zeitraum_id INT NULL AFTER impfenzeitraum");
|
||||
}
|
||||
@@ -117,6 +184,7 @@ if (!function_exists('impfWorkflowEnsureTables')) {
|
||||
}
|
||||
|
||||
impfWorkflowMigrateLegacyPlans($pdo);
|
||||
impfWorkflowMigrateLegacyWartelisteZeitraeume($pdo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,6 +295,42 @@ if (!function_exists('impfWorkflowMigrateLegacyPlans')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('impfWorkflowMigrateLegacyWartelisteZeitraeume')) {
|
||||
function impfWorkflowMigrateLegacyWartelisteZeitraeume(PDO $pdo): void
|
||||
{
|
||||
if (!impfTableExists($pdo, 'warteliste') || !impfTableExists($pdo, 'warteliste_zeitraum')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (impfWorkflowGetMeta($pdo, 'legacy_warteliste_zeitraeume_migrated') === '1') {
|
||||
return;
|
||||
}
|
||||
|
||||
$manageTransaction = !$pdo->inTransaction();
|
||||
if ($manageTransaction) {
|
||||
$pdo->beginTransaction();
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo->exec("INSERT IGNORE INTO warteliste_zeitraum (warteid, zeitraum_id)
|
||||
SELECT warteid, zeitraum_id
|
||||
FROM warteliste
|
||||
WHERE zeitraum_id IS NOT NULL
|
||||
AND zeitraum_id > 0");
|
||||
impfWorkflowSetMeta($pdo, 'legacy_warteliste_zeitraeume_migrated', '1');
|
||||
|
||||
if ($manageTransaction) {
|
||||
$pdo->commit();
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
if ($manageTransaction && $pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('impfCsvToIntList')) {
|
||||
function impfCsvToIntList(?string $csv): array
|
||||
{
|
||||
@@ -243,7 +347,7 @@ if (!function_exists('impfCsvToIntList')) {
|
||||
}
|
||||
|
||||
if (!function_exists('impfZeitraumLabel')) {
|
||||
function impfZeitraumLabel(array $zeitraum): string
|
||||
function impfZeitraumLabel(array $zeitraum, bool $includeName = true): string
|
||||
{
|
||||
$zeitText = impfWeekdayName((int)$zeitraum['wochentag']) . ' ' . substr((string)$zeitraum['start'], 0, 5) . '-' . substr((string)$zeitraum['ende'], 0, 5);
|
||||
$ort = trim((string)($zeitraum['anzeigename'] ?? '') . ' - ' . (string)($zeitraum['adresse'] ?? ''));
|
||||
@@ -252,12 +356,7 @@ if (!function_exists('impfZeitraumLabel')) {
|
||||
$zeitText .= ' (' . $ortText . ')';
|
||||
}
|
||||
|
||||
$bezeichnung = trim((string)($zeitraum['bezeichnung'] ?? ''));
|
||||
if ($bezeichnung !== '') {
|
||||
return $bezeichnung . ': ' . $zeitText;
|
||||
}
|
||||
|
||||
return $zeitText;
|
||||
return impfLimitLabelLength($zeitText, 50);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,7 +487,7 @@ if (!function_exists('impfGetWartelistenFormOptions')) {
|
||||
}
|
||||
$zeitfenster[$impfstoffId][] = [
|
||||
'id' => (int)$zeitraum['zeitraum_id'],
|
||||
'label' => (string)$zeitraum['label'],
|
||||
'label' => impfZeitraumLabel($zeitraum, false),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -406,12 +505,206 @@ if (!function_exists('impfGetWartelistenFormOptions')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('impfGetWartelistenZeitraeume')) {
|
||||
function impfGetWartelistenZeitraeume(PDO $pdo, int $warteid, bool $onlyActive = false): array
|
||||
{
|
||||
if ($warteid <= 0 || !impfTableExists($pdo, 'warteliste_zeitraum')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$sql = "SELECT 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 = :warteid";
|
||||
if ($onlyActive) {
|
||||
$sql .= " AND z.aktiv = 1";
|
||||
}
|
||||
$sql .= " ORDER BY z.wochentag, z.start, z.ende, z.bezeichnung, z.zeitraum_id";
|
||||
|
||||
$st = $pdo->prepare($sql);
|
||||
$st->execute(['warteid' => $warteid]);
|
||||
$rows = $st->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
foreach ($rows as &$row) {
|
||||
$row['label'] = impfZeitraumLabel($row);
|
||||
}
|
||||
unset($row);
|
||||
|
||||
if (!empty($rows)) {
|
||||
return $rows;
|
||||
}
|
||||
|
||||
$stFallback = $pdo->prepare("SELECT 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 = :warteid
|
||||
AND w.zeitraum_id IS NOT NULL
|
||||
LIMIT 1");
|
||||
$stFallback->execute(['warteid' => $warteid]);
|
||||
$row = $stFallback->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$row) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$row['label'] = impfZeitraumLabel($row);
|
||||
return [$row];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('impfGetWartelistenZeitraeumeLabels')) {
|
||||
function impfGetWartelistenZeitraeumeLabels(PDO $pdo, int $warteid, bool $onlyActive = false): array
|
||||
{
|
||||
$rows = impfGetWartelistenZeitraeume($pdo, $warteid, $onlyActive);
|
||||
return array_values(array_map(static function (array $row): string {
|
||||
return (string)($row['label'] ?? '');
|
||||
}, $rows));
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
$zeitraumIds = impfNormalizeZeitraumIds($zeitraumIds);
|
||||
if ($warteid <= 0) {
|
||||
throw new InvalidArgumentException('Unguelige Wartelisten-ID.');
|
||||
}
|
||||
|
||||
$manageTransaction = !$pdo->inTransaction();
|
||||
if ($manageTransaction) {
|
||||
$pdo->beginTransaction();
|
||||
}
|
||||
|
||||
try {
|
||||
$stDelete = $pdo->prepare("DELETE FROM warteliste_zeitraum WHERE warteid = :warteid");
|
||||
$stDelete->execute(['warteid' => $warteid]);
|
||||
|
||||
if (!empty($zeitraumIds)) {
|
||||
$stInsert = $pdo->prepare("INSERT INTO warteliste_zeitraum (warteid, zeitraum_id)
|
||||
VALUES (:warteid, :zeitraum_id)");
|
||||
foreach ($zeitraumIds as $zeitraumId) {
|
||||
$stInsert->execute([
|
||||
'warteid' => $warteid,
|
||||
'zeitraum_id' => $zeitraumId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($manageTransaction) {
|
||||
$pdo->commit();
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
if ($manageTransaction && $pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('impfCreateWaitlistEntryForPerson')) {
|
||||
function impfCreateWaitlistEntryForPerson(
|
||||
PDO $pdo,
|
||||
int $personId,
|
||||
int $impfstoffId,
|
||||
int $zeitraumId,
|
||||
$zeitraumIds,
|
||||
int $impfart,
|
||||
?string $letzteImpfung = null,
|
||||
int $checked = 1
|
||||
@@ -422,13 +715,15 @@ if (!function_exists('impfCreateWaitlistEntryForPerson')) {
|
||||
if ($impfstoffId <= 0) {
|
||||
return [false, 'Bitte einen Impfstoff auswaehlen.', null];
|
||||
}
|
||||
if ($zeitraumId <= 0) {
|
||||
return [false, 'Bitte ein Zeitfenster auswaehlen.', null];
|
||||
}
|
||||
if ($impfart < 1 || $impfart > 4) {
|
||||
return [false, 'Bitte eine gueltige Impfungsart auswaehlen.', null];
|
||||
}
|
||||
|
||||
$zeitraumIds = impfNormalizeZeitraumIds($zeitraumIds);
|
||||
if (empty($zeitraumIds)) {
|
||||
return [false, 'Bitte mindestens ein Zeitfenster auswaehlen.', null];
|
||||
}
|
||||
|
||||
$letzteImpfung = $letzteImpfung !== null ? trim($letzteImpfung) : null;
|
||||
if ($impfart === 1) {
|
||||
$letzteImpfung = null;
|
||||
@@ -448,26 +743,32 @@ if (!function_exists('impfCreateWaitlistEntryForPerson')) {
|
||||
return [false, 'Die Person wurde nicht gefunden.', null];
|
||||
}
|
||||
|
||||
$zeitraum = impfLoadZeitraumById($pdo, $zeitraumId, true);
|
||||
if (!$zeitraum) {
|
||||
return [false, 'Das ausgewaehlte Zeitfenster ist nicht mehr verfuegbar.', null];
|
||||
}
|
||||
if (!in_array($impfstoffId, $zeitraum['impfstoff_id_list'] ?? [], true)) {
|
||||
return [false, 'Impfstoff und Zeitfenster passen nicht zusammen.', null];
|
||||
$zeitraumRows = [];
|
||||
$zeitraumLabels = [];
|
||||
foreach ($zeitraumIds as $zeitraumId) {
|
||||
$row = impfLoadZeitraumById($pdo, $zeitraumId, true);
|
||||
if (!$row) {
|
||||
return [false, 'Mindestens ein ausgewaehltes Zeitfenster ist nicht mehr verfuegbar.', null];
|
||||
}
|
||||
if (!in_array($impfstoffId, $row['impfstoff_id_list'] ?? [], true)) {
|
||||
return [false, 'Impfstoff und Zeitfenster passen nicht zusammen.', null];
|
||||
}
|
||||
$zeitraumRows[$zeitraumId] = $row;
|
||||
$zeitraumLabels[] = (string)$row['label'];
|
||||
}
|
||||
|
||||
$stDup = $pdo->prepare("SELECT warteid
|
||||
FROM warteliste
|
||||
WHERE userid = :uid
|
||||
AND checked IN (0, 1)
|
||||
AND impfstoff = :impfstoff
|
||||
AND COALESCE(zeitraum_id, 0) = :zeitraum_id
|
||||
AND impfart = :impfart
|
||||
$stDup = $pdo->prepare("SELECT w.warteid
|
||||
FROM warteliste w
|
||||
LEFT JOIN warteliste_zeitraum wz ON wz.warteid = w.warteid
|
||||
WHERE w.userid = :uid
|
||||
AND w.checked IN (0, 1)
|
||||
AND w.impfstoff = :impfstoff
|
||||
AND w.impfart = :impfart
|
||||
GROUP BY w.warteid
|
||||
LIMIT 1");
|
||||
$stDup->execute([
|
||||
'uid' => $personId,
|
||||
'impfstoff' => $impfstoffId,
|
||||
'zeitraum_id' => $zeitraumId,
|
||||
'impfart' => $impfart,
|
||||
]);
|
||||
if ($stDup->fetchColumn()) {
|
||||
@@ -477,26 +778,45 @@ if (!function_exists('impfCreateWaitlistEntryForPerson')) {
|
||||
$patientenart = ((int)($person['patientenart'] ?? 0) === 1) ? 1 : 0;
|
||||
$hash = bin2hex(random_bytes(16));
|
||||
$checkedValue = ($checked === 0) ? 0 : 1;
|
||||
$primaerZeitraumId = (int)$zeitraumIds[0];
|
||||
$impfenzeitraum = implode(' | ', $zeitraumLabels);
|
||||
|
||||
$stInsert = $pdo->prepare("INSERT INTO warteliste
|
||||
(userid, checked, hash, impfenangebot, impfstoff, Patientenart, Impfaufklaerung, WeitereFragen, impfart, impfenmit, letzteimpfung, impfenzeitraum, zeitraum_id, date_created)
|
||||
VALUES
|
||||
(:userid, :checked, :hash, 1, :impfstoff, :patientenart, 0, 0, :impfart, '', :letzteimpfung, :impfenzeitraum, :zeitraum_id, NOW())");
|
||||
$stInsert->execute([
|
||||
'userid' => $personId,
|
||||
'checked' => $checkedValue,
|
||||
'hash' => $hash,
|
||||
'impfstoff' => $impfstoffId,
|
||||
'patientenart' => $patientenart,
|
||||
'impfart' => $impfart,
|
||||
'letzteimpfung' => $letzteImpfung,
|
||||
'impfenzeitraum' => (string)$zeitraum['label'],
|
||||
'zeitraum_id' => $zeitraumId,
|
||||
]);
|
||||
$manageTransaction = !$pdo->inTransaction();
|
||||
if ($manageTransaction) {
|
||||
$pdo->beginTransaction();
|
||||
}
|
||||
|
||||
$warteid = (int)$pdo->lastInsertId();
|
||||
$personName = trim((string)$person['vorname'] . ' ' . (string)$person['nachname']);
|
||||
try {
|
||||
$stInsert = $pdo->prepare("INSERT INTO warteliste
|
||||
(userid, checked, hash, impfenangebot, impfstoff, Patientenart, Impfaufklaerung, WeitereFragen, impfart, impfenmit, letzteimpfung, impfenzeitraum, zeitraum_id, date_created)
|
||||
VALUES
|
||||
(:userid, :checked, :hash, 1, :impfstoff, :patientenart, 0, 0, :impfart, '', :letzteimpfung, :impfenzeitraum, :zeitraum_id, NOW())");
|
||||
$stInsert->execute([
|
||||
'userid' => $personId,
|
||||
'checked' => $checkedValue,
|
||||
'hash' => $hash,
|
||||
'impfstoff' => $impfstoffId,
|
||||
'patientenart' => $patientenart,
|
||||
'impfart' => $impfart,
|
||||
'letzteimpfung' => $letzteImpfung,
|
||||
'impfenzeitraum' => $impfenzeitraum,
|
||||
'zeitraum_id' => $primaerZeitraumId,
|
||||
]);
|
||||
|
||||
return [true, 'Wartelistenplatz fuer ' . $personName . ' wurde gespeichert.', $warteid];
|
||||
$warteid = (int)$pdo->lastInsertId();
|
||||
impfSetWartelistenZeitraeume($pdo, $warteid, $zeitraumIds);
|
||||
|
||||
if ($manageTransaction) {
|
||||
$pdo->commit();
|
||||
}
|
||||
|
||||
$personName = trim((string)$person['vorname'] . ' ' . (string)$person['nachname']);
|
||||
return [true, 'Wartelistenplatz fuer ' . $personName . ' wurde gespeichert.', $warteid];
|
||||
} catch (Throwable $e) {
|
||||
if ($manageTransaction && $pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1262,6 +1262,8 @@ function Userspeichern($vorname, $nachname, $geburtstag, $mail, $tele, $ort, $pl
|
||||
UPDATE persons
|
||||
SET vorname=:vorname,
|
||||
nachname=:nachname,
|
||||
geburtstag=:geburtstag,
|
||||
email=:email,
|
||||
tele=:tele,
|
||||
ort=:ort,
|
||||
plz=:plz,
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
|
||||
if (!function_exists('impfWorkflowNotificationEnsureMetaTable')) {
|
||||
function impfWorkflowNotificationEnsureMetaTable(PDO $pdo): void
|
||||
{
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS impf_workflow_meta (
|
||||
meta_key VARCHAR(100) NOT NULL,
|
||||
meta_value VARCHAR(255) NOT NULL DEFAULT '',
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (meta_key)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('impfWorkflowNotificationGetMeta')) {
|
||||
function impfWorkflowNotificationGetMeta(PDO $pdo, string $key): ?string
|
||||
{
|
||||
impfWorkflowNotificationEnsureMetaTable($pdo);
|
||||
|
||||
$st = $pdo->prepare("SELECT meta_value
|
||||
FROM impf_workflow_meta
|
||||
WHERE meta_key = :meta_key
|
||||
LIMIT 1");
|
||||
$st->execute(['meta_key' => $key]);
|
||||
$value = $st->fetchColumn();
|
||||
|
||||
return ($value === false) ? null : (string)$value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('impfWorkflowNotificationSetMeta')) {
|
||||
function impfWorkflowNotificationSetMeta(PDO $pdo, string $key, string $value): void
|
||||
{
|
||||
impfWorkflowNotificationEnsureMetaTable($pdo);
|
||||
|
||||
$st = $pdo->prepare("INSERT INTO impf_workflow_meta (meta_key, meta_value)
|
||||
VALUES (:meta_key, :meta_value)
|
||||
ON DUPLICATE KEY UPDATE meta_value = VALUES(meta_value)");
|
||||
$st->execute([
|
||||
'meta_key' => $key,
|
||||
'meta_value' => $value,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('impfWorkflowNotificationGetEmail')) {
|
||||
function impfWorkflowNotificationGetEmail(PDO $pdo): string
|
||||
{
|
||||
return trim((string)(impfWorkflowNotificationGetMeta($pdo, 'benachrichtigung_email') ?? ''));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('impfWorkflowNotificationSetEmail')) {
|
||||
function impfWorkflowNotificationSetEmail(PDO $pdo, string $email): void
|
||||
{
|
||||
$email = trim($email);
|
||||
impfWorkflowNotificationSetMeta($pdo, 'benachrichtigung_email', $email);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('impfWorkflowNotificationIsReady')) {
|
||||
function impfWorkflowNotificationIsReady(PDO $pdo): bool
|
||||
{
|
||||
return impfWorkflowNotificationGetEmail($pdo) !== '';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('impfWorkflowNotificationShouldTrigger')) {
|
||||
function impfWorkflowNotificationShouldTrigger(int $wartende, int $dosen): bool
|
||||
{
|
||||
if ($wartende < 5) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($dosen > 5) {
|
||||
return $wartende >= $dosen;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('impfWorkflowNotificationStateKey')) {
|
||||
function impfWorkflowNotificationStateKey(int $impfstoffId, int $zeitraumId): string
|
||||
{
|
||||
return 'notification_sent_' . $impfstoffId . '_' . $zeitraumId;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('impfWorkflowNotificationCountWaitersForPlan')) {
|
||||
function impfWorkflowNotificationCountWaitersForPlan(PDO $pdo, int $impfstoffId, int $zeitraumId): int
|
||||
{
|
||||
if ($impfstoffId <= 0 || $zeitraumId <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$st = $pdo->prepare("SELECT COUNT(DISTINCT w.userid)
|
||||
FROM warteliste w
|
||||
WHERE w.checked = 1
|
||||
AND (w.impfstoff = :iid OR w.impfstoff = 0)
|
||||
AND (
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM warteliste_zeitraum wz
|
||||
WHERE wz.warteid = w.warteid
|
||||
AND wz.zeitraum_id = :zid
|
||||
)
|
||||
OR (
|
||||
NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM warteliste_zeitraum wz_none
|
||||
WHERE wz_none.warteid = w.warteid
|
||||
)
|
||||
AND (w.zeitraum_id = :zid OR w.zeitraum_id IS NULL)
|
||||
)
|
||||
)");
|
||||
$st->execute([
|
||||
'iid' => $impfstoffId,
|
||||
'zid' => $zeitraumId,
|
||||
]);
|
||||
|
||||
return (int)$st->fetchColumn();
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('impfWorkflowNotificationSendForPlan')) {
|
||||
function impfWorkflowNotificationSendForPlan(
|
||||
PDO $pdo,
|
||||
int $impfstoffId,
|
||||
string $impfstoffName,
|
||||
int $zeitraumId,
|
||||
string $zeitraumLabel,
|
||||
int $wartende,
|
||||
int $dosen
|
||||
): array {
|
||||
if ($impfstoffId <= 0 || $zeitraumId <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$stateKey = impfWorkflowNotificationStateKey($impfstoffId, $zeitraumId);
|
||||
$alreadySent = impfWorkflowNotificationGetMeta($pdo, $stateKey) === '1';
|
||||
$shouldTrigger = impfWorkflowNotificationShouldTrigger($wartende, $dosen);
|
||||
|
||||
if (!$shouldTrigger) {
|
||||
if ($alreadySent) {
|
||||
impfWorkflowNotificationSetMeta($pdo, $stateKey, '0');
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($alreadySent) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$email = impfWorkflowNotificationGetEmail($pdo);
|
||||
if ($email === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$thresholdText = ($dosen > 5)
|
||||
? 'Die Flasche hat mehr als 5 Dosen, daher wird erst bei einer vollen Flasche benachrichtigt.'
|
||||
: 'Es sind mindestens 5 Interessenten fuer dieses Zeitfenster vorhanden.';
|
||||
|
||||
$subject = 'Impfworkflow: Warteliste ist bereit fuer ' . $impfstoffName;
|
||||
$body = '<p>Fuer den Impfworkflow ist ein Zeitfenster benachrichtigungsreif.</p>'
|
||||
. '<p><strong>Impfstoff:</strong> ' . htmlspecialchars($impfstoffName, ENT_QUOTES, 'UTF-8') . '<br>'
|
||||
. '<strong>Zeitfenster:</strong> ' . htmlspecialchars($zeitraumLabel, ENT_QUOTES, 'UTF-8') . '<br>'
|
||||
. '<strong>Interessenten:</strong> ' . $wartende . '<br>'
|
||||
. '<strong>Dosen pro Flasche:</strong> ' . $dosen . '</p>'
|
||||
. '<p>' . htmlspecialchars($thresholdText, ENT_QUOTES, 'UTF-8') . '</p>';
|
||||
|
||||
if (!SendMailMessage($pdo, $email, $subject, $body)) {
|
||||
throw new RuntimeException('Benachrichtigungs-E-Mail konnte nicht versendet werden.');
|
||||
}
|
||||
|
||||
impfWorkflowNotificationSetMeta($pdo, $stateKey, '1');
|
||||
|
||||
return [[
|
||||
'impfstoff_id' => $impfstoffId,
|
||||
'zeitraum_id' => $zeitraumId,
|
||||
'email' => $email,
|
||||
'impfstoff' => $impfstoffName,
|
||||
'zeitraum' => $zeitraumLabel,
|
||||
'wartende' => $wartende,
|
||||
'dosen' => $dosen,
|
||||
]];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('impfWorkflowNotificationProcess')) {
|
||||
function impfWorkflowNotificationProcess(PDO $pdo, int $impfstoffId = 0, array $zeitraumIds = []): array
|
||||
{
|
||||
if (!function_exists('impfGetZeitraeumeByImpfstoff') || !function_exists('impfLoadZeitraumById')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$zeitraumIds = array_values(array_unique(array_filter(array_map('intval', $zeitraumIds), static function (int $zeitraumId): bool {
|
||||
return $zeitraumId > 0;
|
||||
})));
|
||||
|
||||
$sql = "SELECT r.impfstoff_id, r.dosen_pro_flasche, i.impfname
|
||||
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)";
|
||||
$params = [];
|
||||
if ($impfstoffId > 0) {
|
||||
$sql .= " AND r.impfstoff_id = :iid";
|
||||
$params['iid'] = $impfstoffId;
|
||||
}
|
||||
$sql .= " ORDER BY i.impfname";
|
||||
|
||||
$stRules = $pdo->prepare($sql);
|
||||
$stRules->execute($params);
|
||||
$rules = $stRules->fetchAll(PDO::FETCH_ASSOC);
|
||||
if (empty($rules)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$zeitraeumeByImpfstoff = impfGetZeitraeumeByImpfstoff($pdo, true);
|
||||
$sent = [];
|
||||
|
||||
foreach ($rules as $rule) {
|
||||
$currentImpfstoffId = (int)$rule['impfstoff_id'];
|
||||
$dosen = (int)$rule['dosen_pro_flasche'];
|
||||
if ($currentImpfstoffId <= 0 || $dosen <= 0 || empty($zeitraeumeByImpfstoff[$currentImpfstoffId])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($zeitraeumeByImpfstoff[$currentImpfstoffId] as $zeitraum) {
|
||||
$currentZeitraumId = (int)($zeitraum['zeitraum_id'] ?? 0);
|
||||
if ($currentZeitraumId <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (!empty($zeitraumIds) && !in_array($currentZeitraumId, $zeitraumIds, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$wartende = impfWorkflowNotificationCountWaitersForPlan($pdo, $currentImpfstoffId, $currentZeitraumId);
|
||||
$sent = array_merge(
|
||||
$sent,
|
||||
impfWorkflowNotificationSendForPlan(
|
||||
$pdo,
|
||||
$currentImpfstoffId,
|
||||
(string)$rule['impfname'],
|
||||
$currentZeitraumId,
|
||||
(string)($zeitraum['label'] ?? ''),
|
||||
$wartende,
|
||||
$dosen
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $sent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
if (!function_exists('websiteContentEnsureEntry')) {
|
||||
function websiteContentEnsureEntry(PDO $pdo, array $entry): int
|
||||
{
|
||||
$title = trim((string)($entry['webseitentitel'] ?? ''));
|
||||
if ($title === '') {
|
||||
throw new InvalidArgumentException('webseitentitel is required');
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT inhaltid
|
||||
FROM webseiteninhalt
|
||||
WHERE webseitentitel = :webseitentitel
|
||||
ORDER BY inhaltid DESC
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute(['webseitentitel' => $title]);
|
||||
$existingId = (int)($stmt->fetchColumn() ?: 0);
|
||||
|
||||
if ($existingId > 0) {
|
||||
return $existingId;
|
||||
}
|
||||
|
||||
$insert = $pdo->prepare("
|
||||
INSERT INTO webseiteninhalt (webseitentitel, inhalt, beschreibung, url)
|
||||
VALUES (:webseitentitel, :inhalt, :beschreibung, :url)
|
||||
");
|
||||
$insert->execute([
|
||||
'webseitentitel' => $title,
|
||||
'inhalt' => (string)($entry['inhalt'] ?? ''),
|
||||
'beschreibung' => (string)($entry['beschreibung'] ?? ''),
|
||||
'url' => (string)($entry['url'] ?? ''),
|
||||
]);
|
||||
|
||||
return (int)$pdo->lastInsertId();
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('websiteContentEnsureEntries')) {
|
||||
function websiteContentEnsureEntries(PDO $pdo, array $entries): void
|
||||
{
|
||||
foreach ($entries as $entry) {
|
||||
websiteContentEnsureEntry($pdo, $entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('websiteContentGetByTitle')) {
|
||||
function websiteContentGetByTitle(PDO $pdo, string $title, string $fallback = ''): array
|
||||
{
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT inhaltid, webseitentitel, inhalt, beschreibung, url
|
||||
FROM webseiteninhalt
|
||||
WHERE webseitentitel = :webseitentitel
|
||||
ORDER BY inhaltid DESC
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute(['webseitentitel' => $title]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($row) {
|
||||
return $row;
|
||||
}
|
||||
|
||||
return [
|
||||
'inhaltid' => 0,
|
||||
'webseitentitel' => $title,
|
||||
'inhalt' => $fallback,
|
||||
'beschreibung' => '',
|
||||
'url' => '',
|
||||
];
|
||||
}
|
||||
}
|
||||
+39
-10
@@ -3,6 +3,7 @@
|
||||
require_once(__DIR__ . "/../inc/config.inc.php");
|
||||
require_once(__DIR__ . "/../inc/functions.inc.php");
|
||||
require_once(__DIR__ . "/../inc/functions.impfen.inc.php");
|
||||
require_once(__DIR__ . "/../inc/impfworkflow_notifications.inc.php");
|
||||
|
||||
ini_set('display_errors', '1');
|
||||
error_reporting(E_ALL);
|
||||
@@ -66,7 +67,7 @@ $zeitOptionenJson = json_encode($zeitOptionenByImpfstoff, JSON_UNESCAPED_UNICODE
|
||||
|
||||
$form = [
|
||||
'impfstoff_id' => (int)($_POST['impfstoff_id'] ?? 0),
|
||||
'zeitraum_id' => (int)($_POST['zeitraum_id'] ?? 0),
|
||||
'zeitraum_ids' => impfNormalizeZeitraumIds($_POST['zeitraum_ids'] ?? ($_POST['zeitraum_id'] ?? [])),
|
||||
'impfart' => (int)($_POST['impfart'] ?? 0),
|
||||
'letzteimpfung' => trim((string)($_POST['letzteimpfung'] ?? '')),
|
||||
];
|
||||
@@ -82,6 +83,10 @@ $stActive = $pdo->prepare("SELECT w.warteid, w.checked, w.impfart, w.letzteimpfu
|
||||
ORDER BY w.date_created DESC, w.warteid DESC");
|
||||
$stActive->execute(['pid' => $personId]);
|
||||
$activeWaitRows = $stActive->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($activeWaitRows as &$activeWaitRow) {
|
||||
$activeWaitRow['zeitfenster_labels'] = impfGetWartelistenZeitraeumeLabels($pdo, (int)$activeWaitRow['warteid'], false);
|
||||
}
|
||||
unset($activeWaitRow);
|
||||
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST' && (string)($_POST['aktion'] ?? '') === 'create_waitlist') {
|
||||
try {
|
||||
@@ -89,7 +94,7 @@ if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST' && (string)($_POST['aktion'
|
||||
$pdo,
|
||||
$personId,
|
||||
(int)$form['impfstoff_id'],
|
||||
(int)$form['zeitraum_id'],
|
||||
$form['zeitraum_ids'],
|
||||
(int)$form['impfart'],
|
||||
$form['letzteimpfung'] !== '' ? $form['letzteimpfung'] : null,
|
||||
1
|
||||
@@ -106,6 +111,13 @@ if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST' && (string)($_POST['aktion'
|
||||
|
||||
$stActive->execute(['pid' => $personId]);
|
||||
$activeWaitRows = $stActive->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($activeWaitRows as &$activeWaitRow) {
|
||||
$activeWaitRow['zeitfenster_labels'] = impfGetWartelistenZeitraeumeLabels($pdo, (int)$activeWaitRow['warteid'], false);
|
||||
}
|
||||
unset($activeWaitRow);
|
||||
if (isset($pdo) && $pdo instanceof PDO) {
|
||||
impfWorkflowNotificationProcess($pdo);
|
||||
}
|
||||
} else {
|
||||
$errorMessage = (string)$message;
|
||||
}
|
||||
@@ -141,12 +153,17 @@ if (!empty($activeWaitRows)) {
|
||||
echo "Sie koennen im internen Bereich mehrere verschiedene Wartelistenanfragen anlegen. Exakte Duplikate werden weiterhin geblockt.";
|
||||
echo "</div>";
|
||||
echo "<table class='table table-bordered table-striped'>";
|
||||
echo "<thead><tr><th>Impfstoff</th><th>Zeitraum</th><th>Impfungsart</th><th>Status</th><th>Letzte Impfung</th></tr></thead><tbody>";
|
||||
echo "<thead><tr><th>Impfstoff</th><th>Zeitraeume</th><th>Impfungsart</th><th>Status</th><th>Letzte Impfung</th></tr></thead><tbody>";
|
||||
foreach ($activeWaitRows as $activeWaitRow) {
|
||||
$statusText = ((int)$activeWaitRow['checked'] === 1) ? 'Bestaetigt' : 'Unbestaetigt';
|
||||
$zeitfensterText = !empty($activeWaitRow['zeitfenster_labels'])
|
||||
? implode('<br>', array_map(static function (string $label): string {
|
||||
return e($label);
|
||||
}, $activeWaitRow['zeitfenster_labels']))
|
||||
: e((string)($activeWaitRow['impfenzeitraum'] ?? ''));
|
||||
echo "<tr>";
|
||||
echo "<td>" . e((string)($activeWaitRow['impfname'] ?? 'Unbekannt')) . "</td>";
|
||||
echo "<td>" . e((string)($activeWaitRow['impfenzeitraum'] ?? '')) . "</td>";
|
||||
echo "<td>" . $zeitfensterText . "</td>";
|
||||
echo "<td>" . e((string)($impfartLabels[(int)$activeWaitRow['impfart']] ?? ('Status ' . (int)$activeWaitRow['impfart']))) . "</td>";
|
||||
echo "<td>" . e($statusText) . "</td>";
|
||||
echo "<td>" . e((string)($activeWaitRow['letzteimpfung'] ?? '')) . "</td>";
|
||||
@@ -178,8 +195,8 @@ if (empty($verfuegbareImpfstoffe)) {
|
||||
|
||||
<div class="row" style="margin-top:12px;">
|
||||
<div class="col-sm-10">
|
||||
<label for="zeitraum_id">Welcher Zeitbereich passt fuer Sie?</label>
|
||||
<select class="form-control" name="zeitraum_id" id="zeitraum_id" required disabled>
|
||||
<label for="zeitraum_ids">Welche Zeitbereiche passen fuer Sie?</label>
|
||||
<select class="form-control" name="zeitraum_ids[]" id="zeitraum_ids" required multiple size="6" disabled onchange="checkLetzteImpfung()">
|
||||
<option value="">- Bitte zuerst Impfstoff auswaehlen -</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -215,11 +232,11 @@ if (empty($verfuegbareImpfstoffe)) {
|
||||
|
||||
<script>
|
||||
const zeitfensterByImpfstoff = <?php echo $zeitOptionenJson ?: '{}'; ?>;
|
||||
const initialZeitraumId = <?php echo (int)$form['zeitraum_id']; ?>;
|
||||
const initialZeitraumIds = <?php echo json_encode($form['zeitraum_ids'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
|
||||
|
||||
function updateZeitfenster() {
|
||||
const impfstoff = document.getElementById('impfstoff_id');
|
||||
const zeitraum = document.getElementById('zeitraum_id');
|
||||
const zeitraum = document.getElementById('zeitraum_ids');
|
||||
const submit = document.getElementById('submit_waitlist');
|
||||
const impfstoffId = impfstoff.value;
|
||||
const optionen = zeitfensterByImpfstoff[impfstoffId] || [];
|
||||
@@ -228,7 +245,7 @@ if (empty($verfuegbareImpfstoffe)) {
|
||||
if (!impfstoffId || optionen.length === 0) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = '';
|
||||
opt.textContent = '- Kein Zeitfenster verfuegbar -';
|
||||
opt.textContent = '- Keine Zeitfenster verfuegbar -';
|
||||
zeitraum.appendChild(opt);
|
||||
zeitraum.disabled = true;
|
||||
submit.disabled = true;
|
||||
@@ -244,7 +261,7 @@ if (empty($verfuegbareImpfstoffe)) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = String(eintrag.id || '');
|
||||
opt.textContent = eintrag.label || '';
|
||||
if (initialZeitraumId > 0 && Number(eintrag.id) === initialZeitraumId) {
|
||||
if (initialZeitraumIds.includes(Number(eintrag.id))) {
|
||||
opt.selected = true;
|
||||
}
|
||||
zeitraum.appendChild(opt);
|
||||
@@ -252,12 +269,24 @@ if (empty($verfuegbareImpfstoffe)) {
|
||||
|
||||
zeitraum.disabled = false;
|
||||
submit.disabled = false;
|
||||
checkLetzteImpfung();
|
||||
}
|
||||
|
||||
function checkLetzteImpfung() {
|
||||
const impfart = document.getElementById('impfart');
|
||||
const box = document.getElementById('letzteimpfung_box');
|
||||
const input = document.getElementById('letzteimpfung');
|
||||
const zeitraum = document.getElementById('zeitraum_ids');
|
||||
const submit = document.getElementById('submit_waitlist');
|
||||
const selectedCount = zeitraum && zeitraum.selectedOptions
|
||||
? Array.from(zeitraum.selectedOptions).filter((opt) => opt.value !== '').length
|
||||
: 0;
|
||||
|
||||
if (selectedCount === 0) {
|
||||
submit.disabled = true;
|
||||
} else if (zeitraum && !zeitraum.disabled) {
|
||||
submit.disabled = false;
|
||||
}
|
||||
|
||||
if (impfart.value === '' || impfart.value === '1') {
|
||||
box.style.display = 'none';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
$(function() {
|
||||
$("#user_input").autocomplete({
|
||||
source: "inc/suchepatient.php",
|
||||
source: "../inc/suchepatient.php",
|
||||
minLength: 3,
|
||||
select: function( event, ui ) {
|
||||
event.preventDefault();
|
||||
|
||||
+4
-4
@@ -29,7 +29,7 @@ fbq('track', 'PageView');
|
||||
src="https://www.facebook.com/tr?id=1304867248096206&ev=PageView&noscript=1"
|
||||
/></noscript>
|
||||
<!-- End Meta Pixel Code -->
|
||||
|
||||
<!--
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org/",
|
||||
@@ -87,7 +87,7 @@ src="https://www.facebook.com/tr?id=1304867248096206&ev=PageView&noscript=1"
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
-->
|
||||
|
||||
</head>
|
||||
<body >
|
||||
@@ -106,7 +106,7 @@ src="https://www.facebook.com/tr?id=1304867248096206&ev=PageView&noscript=1"
|
||||
</header>
|
||||
|
||||
<!-- Main -->
|
||||
|
||||
<!--
|
||||
<section id="main" class="container">
|
||||
<?php
|
||||
echo showHeaderpraxis();
|
||||
@@ -200,7 +200,7 @@ src="https://www.facebook.com/tr?id=1304867248096206&ev=PageView&noscript=1"
|
||||
<input type=submit class=button value="Zu den Stellenangeboten">
|
||||
</form>
|
||||
</section>
|
||||
|
||||
-->
|
||||
<?php
|
||||
|
||||
include_once('footer.php');
|
||||
|
||||
@@ -29,7 +29,7 @@ fbq('track', 'PageView');
|
||||
src="https://www.facebook.com/tr?id=1304867248096206&ev=PageView&noscript=1"
|
||||
/></noscript>
|
||||
<!-- End Meta Pixel Code -->
|
||||
|
||||
<!--
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org/",
|
||||
@@ -90,7 +90,7 @@ src="https://www.facebook.com/tr?id=1304867248096206&ev=PageView&noscript=1"
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
-->
|
||||
|
||||
|
||||
</head>
|
||||
@@ -120,6 +120,7 @@ src="https://www.facebook.com/tr?id=1304867248096206&ev=PageView&noscript=1"
|
||||
<div class="12u">
|
||||
|
||||
<!-- Form -->
|
||||
<!--
|
||||
<section class="box">
|
||||
<h3>Ihre Hausarztpraxis in Coppenbrügge und Bisperode</h3>
|
||||
|
||||
@@ -207,7 +208,7 @@ src="https://www.facebook.com/tr?id=1304867248096206&ev=PageView&noscript=1"
|
||||
<input type=submit class=button value="Zu den Stellenangeboten">
|
||||
</form>
|
||||
</section>
|
||||
|
||||
-->
|
||||
<?php
|
||||
|
||||
include_once('footer.php');
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<li><a href="praxis.php">Die Praxis</a></li>
|
||||
<li><a href="praxis.php#philosophie">Philosophie</a></li>
|
||||
<li><a href="praxis.php#team">Das Team</a></li>
|
||||
<li><a href="pcm.php">PCM</a></li>
|
||||
<li><a href="sprechzeiten.php#sprechzeiten">Sprechzeiten</a></li>
|
||||
|
||||
<li><a href="sprechzeiten.php#anfahrt">Anfahrt</a></li>
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<?php include('header.php'); ?>
|
||||
<title>Praxis Creutzburg - PCM</title>
|
||||
</head>
|
||||
<body>
|
||||
<header id="header" class="../skel-layers-fixed">
|
||||
<?php
|
||||
include('menu.php');
|
||||
include_once("inc/config.inc.php");
|
||||
include_once("inc/functions.inc.php");
|
||||
include_once('inc/functions.impfen.inc.php');
|
||||
include_once('inc/website_content.inc.php');
|
||||
$pcmContent = websiteContentGetByTitle($pdo, 'PCM - Seiteninhalt');
|
||||
?>
|
||||
</header>
|
||||
|
||||
<section id="main" class="container">
|
||||
<?php echo showHeaderpraxis(); ?>
|
||||
|
||||
<section class="box special features">
|
||||
<span class="image featured"><img src="images/praxis1.jpg" alt="Praxis Creutzburg"></span>
|
||||
<style>
|
||||
.pcm-intro {
|
||||
max-width: 860px;
|
||||
margin: 0 auto 1.8rem auto;
|
||||
text-align: center;
|
||||
}
|
||||
.pcm-intro .pcm-kicker {
|
||||
display: inline-flex;
|
||||
padding: 0.4rem 0.9rem;
|
||||
margin-bottom: 0.9rem;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(135deg, rgba(233, 241, 255, 0.95), rgba(245, 248, 252, 0.95));
|
||||
color: #29415f;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.pcm-intro h2 {
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
.pcm-intro p {
|
||||
font-size: 1.08rem;
|
||||
line-height: 1.8;
|
||||
color: #54626f;
|
||||
}
|
||||
.pcm-content {
|
||||
max-width: 880px;
|
||||
margin: 0 auto;
|
||||
text-align: left;
|
||||
}
|
||||
.pcm-content h3 {
|
||||
margin-top: 1.8rem;
|
||||
color: #30404f;
|
||||
}
|
||||
.pcm-content ul {
|
||||
margin: 0.8rem 0 0;
|
||||
padding-left: 1.15rem;
|
||||
line-height: 1.8;
|
||||
color: #4e5860;
|
||||
}
|
||||
.pcm-callout {
|
||||
margin: 1.3rem 0;
|
||||
padding: 1rem 1.15rem;
|
||||
border-left: 4px solid #6b8bb4;
|
||||
background: #f7f9fc;
|
||||
border-radius: 16px;
|
||||
color: #44505d;
|
||||
}
|
||||
.pcm-content p {
|
||||
line-height: 1.8;
|
||||
color: #4e5860;
|
||||
}
|
||||
</style>
|
||||
<div class="pcm-intro">
|
||||
<span class="pcm-kicker">Primary Care Management</span>
|
||||
<h2>PCM - die Qualifikation im Überblick</h2>
|
||||
<p>PCM stärkt Medizinische Fachangestellte in medizinischen, organisatorischen und kommunikativen Aufgaben rund um die hausärztliche Versorgung. Die ausführliche Einordnung dazu steht im folgenden Inhalt.</p>
|
||||
</div>
|
||||
<div class="pcm-content">
|
||||
<?php if (trim((string)($pcmContent['inhalt'] ?? '')) !== ''): ?>
|
||||
<?php echo $pcmContent['inhalt']; ?>
|
||||
<?php else: ?>
|
||||
<p>Die Inhalte zur Qualifikation PCM werden derzeit vorbereitet.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<p><a href="praxis.php#team" class="button alt">Zurück zum Team</a></p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<?php include_once('footer.php'); ?>
|
||||
</body>
|
||||
</html>
|
||||
+222
-10
@@ -11,7 +11,7 @@
|
||||
include('header.php');
|
||||
|
||||
?>
|
||||
<title>Praxis Creutzburg - Die Praxis</title>
|
||||
<title>Praxis Creutzburg - Die Praxis</title>
|
||||
|
||||
</head>
|
||||
<body >
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
<?php
|
||||
|
||||
include('menu.php');
|
||||
include('menu.php');
|
||||
include_once("inc/config.inc.php");
|
||||
include_once("inc/functions.inc.php");
|
||||
include_once('inc/functions.impfen.inc.php');
|
||||
@@ -79,15 +79,227 @@
|
||||
</section>
|
||||
|
||||
<section class="box special features" id="team">
|
||||
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
.team-intro {
|
||||
max-width: 920px;
|
||||
margin: 0 auto 2.2rem auto;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
.team-list {
|
||||
max-width: 1080px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.team-profile {
|
||||
padding: 2rem 0;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
.team-profile:first-child {
|
||||
border-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
.team-profile__role {
|
||||
display: block;
|
||||
margin-bottom: 0.55rem;
|
||||
color: #6c5320;
|
||||
font-size: 0.92rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.team-profile__name {
|
||||
margin: 0 0 0.2rem 0;
|
||||
font-size: 2.35rem;
|
||||
line-height: 1.06;
|
||||
color: #263545;
|
||||
}
|
||||
.team-profile__subtitle {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1.18rem;
|
||||
font-weight: 700;
|
||||
color: #495666;
|
||||
}
|
||||
.team-profile__body {
|
||||
display: grid;
|
||||
grid-template-columns: 240px 1fr;
|
||||
gap: 1.6rem;
|
||||
align-items: center;
|
||||
}
|
||||
.team-profile__media img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
aspect-ratio: 1 / 1;
|
||||
object-fit: cover;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
.team-profile__text p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
.team-profile__text p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.team-profile__points {
|
||||
margin: 1rem 0 0;
|
||||
padding-left: 1.1rem;
|
||||
}
|
||||
.team-profile__points li {
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
@media (max-width: 736px) {
|
||||
.team-profile {
|
||||
padding: 1.5rem 0;
|
||||
}
|
||||
.team-profile__body {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.team-profile__name {
|
||||
font-size: 1.9rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<h2>Praxis-Team</h2>
|
||||
<h3>Arzt</h3>
|
||||
Heiner Creutzburg
|
||||
<span class="image featured"><img src="images/arztbuero.jpg" alt="" title="">
|
||||
|
||||
<div class="team-intro">
|
||||
<p>Unser Team arbeitet eng zusammen und begleitet Sie vom Empfang bis zur Behandlung mit Ruhe, Struktur und einem freundlichen Miteinander. Neben der ärztlichen Versorgung ist gerade die Organisation im Hintergrund ein wichtiger Teil guter Praxisarbeit.</p>
|
||||
<p>Auf dieser Seite stellen wir Ihnen die Menschen vor, die den Praxisalltag prägen und für einen verlässlichen Ablauf sorgen.</p>
|
||||
</div>
|
||||
<div class="team-list">
|
||||
<section class="team-profile">
|
||||
<div class="team-profile__body">
|
||||
<div class="team-profile__media">
|
||||
<img src="images/heiner-creutzburg.jpg" alt="Heiner Creutzburg">
|
||||
</div>
|
||||
<div class="team-profile__text">
|
||||
<span class="team-profile__role">Arzt</span>
|
||||
<h3 class="team-profile__name">Heiner Creutzburg</h3>
|
||||
<p class="team-profile__subtitle">Facharzt für Innere Medizin</p>
|
||||
<p>Heiner Creutzburg steht für die hausärztliche und internistische Versorgung in unserer Praxis. Im Mittelpunkt stehen eine sorgfältige medizinische Einschätzung, eine klare Kommunikation und ein ganzheitlicher Blick auf Beschwerden, Verlauf und Therapie.</p>
|
||||
<ul class="team-profile__points">
|
||||
<li>Langjährige Erfahrung in der hausärztlichen Versorgung</li>
|
||||
<li>Interne Medizin und umfassende Diagnostik</li>
|
||||
<li>Verlässliche Begleitung in akuten und chronischen Fragen</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="team-profile">
|
||||
<div class="team-profile__body">
|
||||
<div class="team-profile__media">
|
||||
<img src="images/person-silhouette.svg" alt="Dalia Alayan-Ibrahim">
|
||||
</div>
|
||||
<div class="team-profile__text">
|
||||
<span class="team-profile__role">Medizinische Fachangestellte, Primary Care Management B.Sc.</span>
|
||||
<h3 class="team-profile__name">Dalia Alayan-Ibrahim</h3>
|
||||
<p class="team-profile__subtitle">Praxismanagerin, PCM B.Sc.</p>
|
||||
<p>Dalia Alayan-Ibrahim übernimmt in unserer Praxis die Organisation der Abläufe und die Verantwortung für den Personalbereich. Sie sorgt mit dafür, dass Termine, Kommunikation und interne Abläufe gut ineinandergreifen und Patientinnen und Patienten sich jederzeit gut begleitet fühlen.</p>
|
||||
<p>Mit ihrem abgeschlossenen Studium im Bereich Primary Care Management (Bachelor of Science) bringt sie zusätzlich vertiefte Kompetenzen in Praxisorganisation, koordinierter Versorgung und teamorientiertem Arbeiten mit. Darüber hinaus übernimmt sie alle delegierten Tätigkeiten, führt Patientengespräche und betreut eine eigene Sprechstunde im dafür vorgesehenen Rahmen. Das stärkt die Struktur im Alltag und schafft mehr Raum für eine verlässliche Betreuung.</p>
|
||||
<ul class="team-profile__points">
|
||||
<li>Praxisorganisation und Koordination der Abläufe</li>
|
||||
<li>Personalverantwortung und strukturierte Teamarbeit</li>
|
||||
<li>Delegierte Tätigkeiten, Patientengespräche und eigene Sprechstunde</li>
|
||||
<li>Zusätzliche Stärke durch das PCM Studium</li>
|
||||
</ul>
|
||||
<p><a href="pcm.php" class="button alt small">Mehr über PCM</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="team-profile">
|
||||
<div class="team-profile__body">
|
||||
<div class="team-profile__media">
|
||||
<img src="images/person-silhouette.svg" alt="Medizinische Fachangestellte">
|
||||
</div>
|
||||
<div class="team-profile__text">
|
||||
<span class="team-profile__role">Medizinische Fachangestellte</span>
|
||||
<h3 class="team-profile__name">Finja Baruth</h3>
|
||||
<p class="team-profile__subtitle">Anmeldung, Assistenz, Labortätigkeiten und Praxisorganisation</p>
|
||||
<p>Finja Baruth unterstützt unser Team in der Patientenaufnahme, in der täglichen Assistenz sowie bei vielen organisatorischen Abläufen im Praxisalltag. So bleibt der Ablauf auch an lebhaften Tagen verlässlich, freundlich und gut strukturiert.</p>
|
||||
<ul class="team-profile__points">
|
||||
<li>Assistenz und Unterstützung im Behandlungsalltag</li>
|
||||
<li>Organisation und Koordination an der Anmeldung</li>
|
||||
<li>Labortätigkeiten und Vorbereitung diagnostischer Abläufe</li>
|
||||
<li>Typische Standardaufgaben in einer modernen Arztpraxis</li>
|
||||
<li>Verlässliche Begleitung für Patienten und Team</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="team-profile">
|
||||
<div class="team-profile__body">
|
||||
<div class="team-profile__media">
|
||||
<img src="images/person-silhouette.svg" alt="Medizinische Fachangestellte">
|
||||
</div>
|
||||
<div class="team-profile__text">
|
||||
<span class="team-profile__role">Medizinische Fachangestellte</span>
|
||||
<h3 class="team-profile__name">Svenja Vespermann</h3>
|
||||
<p class="team-profile__subtitle">Assistenz, Labortätigkeiten und verlässliche Praxisabläufe</p>
|
||||
<p>Svenja Vespermann sorgt mit dafür, dass Vorbereitungen, organisatorische Aufgaben und die praktische Unterstützung im Alltag sauber ineinandergreifen. Das schafft Ruhe im Tagesgeschäft und hilft, Patientinnen und Patienten gut durch die Praxis zu begleiten.</p>
|
||||
<ul class="team-profile__points">
|
||||
<li>Unterstützung bei Praxisorganisation und Assistenz</li>
|
||||
<li>Labortätigkeiten und Mitwirkung bei diagnostischen Standardabläufen</li>
|
||||
<li>Sorgfältige Vor- und Nachbereitung im Alltag</li>
|
||||
<li>Typische Aufgaben in Anmeldung, Organisation und Behandlungsablauf</li>
|
||||
<li>Teamarbeit mit Blick auf einen freundlichen Ablauf</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="team-profile">
|
||||
<div class="team-profile__body">
|
||||
<div class="team-profile__media">
|
||||
<img src="images/person-silhouette.svg" alt="Auszubildende zur MFA">
|
||||
</div>
|
||||
<div class="team-profile__text">
|
||||
<span class="team-profile__role">Auszubildende zur Medizinische Fachangestellte</span>
|
||||
<h3 class="team-profile__name">Promise Nnodim</h3>
|
||||
<p class="team-profile__subtitle">Lernen, Mitwachsen und Praxisnähe</p>
|
||||
<p>Unsere Auszubildende zur Medizinischen Fachangestellten lernt die Abläufe in der Praxis Schritt für Schritt kennen und wächst dabei in die vielfältigen Aufgaben einer modernen Hausarztpraxis hinein. Begleitung, Lernen und Praxisnähe stehen dabei im Vordergrund.</p>
|
||||
<ul class="team-profile__points">
|
||||
<li>Einblick in Anmeldung, Organisation und Assistenz</li>
|
||||
<li>Begleitung durch erfahrene Kolleginnen und Kollegen</li>
|
||||
<li>Frühe Verankerung im Team und in den Praxisabläufen</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="team-profile">
|
||||
<div class="team-profile__body">
|
||||
<div class="team-profile__media">
|
||||
<img src="images/person-silhouette.svg" alt="Barbara Creutzburg">
|
||||
</div>
|
||||
<div class="team-profile__text">
|
||||
<span class="team-profile__role">Praxisassistentin</span>
|
||||
<h3 class="team-profile__name">Barbara Creutzburg</h3>
|
||||
<p class="team-profile__subtitle">Unterstützung im Praxisalltag und organisatorische Begleitung</p>
|
||||
<p>Barbara Creutzburg unterstützt die Praxis in organisatorischen und assistierenden Aufgaben und trägt dazu bei, dass Abläufe im Hintergrund zuverlässig vorbereitet und begleitet werden.</p>
|
||||
<ul class="team-profile__points">
|
||||
<li>Unterstützung bei organisatorischen Praxisaufgaben</li>
|
||||
<li>Assistierende Tätigkeiten im täglichen Ablauf</li>
|
||||
<li>Verlässliche Begleitung im Hintergrund</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="team-profile">
|
||||
<div class="team-profile__body">
|
||||
<div class="team-profile__media">
|
||||
<img src="images/clemenscreutzburg.jpg" alt="Clemens Creutzburg">
|
||||
</div>
|
||||
<div class="team-profile__text">
|
||||
<span class="team-profile__role">Webmaster und EDV-Verantwortlicher</span>
|
||||
<h3 class="team-profile__name">Clemens Creutzburg</h3>
|
||||
<p class="team-profile__subtitle">Technik, digitale Prozesse und Systembetreuung</p>
|
||||
<p>Clemens Creutzburg verantwortet die technische Betreuung der digitalen Systeme und unterstützt die Praxis bei Webseite, EDV-Struktur und technischen Abläufen.</p>
|
||||
<ul class="team-profile__points">
|
||||
<li>Betreuung der Webseite und digitaler Inhalte</li>
|
||||
<li>Verantwortung für EDV, Technik und Systemstruktur</li>
|
||||
<li>Unterstützung bei stabilen digitalen Praxisprozessen</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
<?php
|
||||
|
||||
|
||||
+2
-1
@@ -63,6 +63,7 @@ src="https://www.facebook.com/tr?id=1304867248096206&ev=PageView&noscript=1"
|
||||
Die Praxis Creutzburg hat aktuell die folgende Stellausschreibungen.<br>
|
||||
|
||||
</section>
|
||||
<!--
|
||||
<section class="box">
|
||||
<h2>Medizinische Fachangestellte (MFA) in Teilzeit (m/w/d)</h2>
|
||||
|
||||
@@ -118,7 +119,7 @@ src="https://www.facebook.com/tr?id=1304867248096206&ev=PageView&noscript=1"
|
||||
Schlossstraße 18<br>
|
||||
31863 Coppenbrügge<br>
|
||||
E-Mail: <a href="mailto:alayan@praxis-creutzburg.de">alayan@praxis-creutzburg.de</a></p>
|
||||
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once('inc/config.inc.php');
|
||||
require_once('inc/functions.inc.php');
|
||||
require_once __DIR__ . '/inc/vacation_absence.inc.php';
|
||||
|
||||
$user = check_user();
|
||||
if (!is_admin_user()) {
|
||||
die('Zugriff verweigert. Nur Chefs dürfen den Leitungskalender sehen.');
|
||||
}
|
||||
|
||||
$schemaWarning = '';
|
||||
try {
|
||||
vacationAbsenceEnsureSchema($pdo);
|
||||
} catch (Throwable $e) {
|
||||
$schemaWarning = 'Das Abwesenheitsschema konnte nicht automatisch aktualisiert werden: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
function adminAbsenceStatusLabel(?string $status): string
|
||||
{
|
||||
$status = trim((string)$status);
|
||||
if ($status === '' || $status === 'beantragt') {
|
||||
return 'Beantragt';
|
||||
}
|
||||
if ($status === 'genehmigt') {
|
||||
return 'Genehmigt';
|
||||
}
|
||||
if ($status === 'abgelehnt') {
|
||||
return 'Abgelehnt';
|
||||
}
|
||||
return ucfirst($status);
|
||||
}
|
||||
|
||||
function adminAbsenceColor(string $reason, string $status): string
|
||||
{
|
||||
$reason = vacationAbsenceNormalizeReason($reason);
|
||||
$status = trim(strtolower($status));
|
||||
|
||||
$palette = [
|
||||
'urlaub' => '#2f855a',
|
||||
'krankheit_mit_atest' => '#c53030',
|
||||
'krankheit_ohne_atest' => '#9b2c2c',
|
||||
'berufsschule' => '#dd6b20',
|
||||
'weiterbildung' => '#6b46c1',
|
||||
'persoenliche_gruende' => '#4a5568',
|
||||
'sonstiges' => '#718096',
|
||||
];
|
||||
|
||||
$color = $palette[$reason] ?? '#2d3748';
|
||||
if ($status === 'beantragt') {
|
||||
return $color;
|
||||
}
|
||||
|
||||
return $color;
|
||||
}
|
||||
|
||||
function adminAbsenceEventTitle(array $row): string
|
||||
{
|
||||
$employee = trim(($row['vorname'] ?? '') . ' ' . ($row['nachname'] ?? ''));
|
||||
$reasonLabel = vacationAbsenceReasonLabel($row['absence_reason'] ?? 'urlaub');
|
||||
$statusLabel = adminAbsenceStatusLabel($row['status'] ?? '');
|
||||
if ($employee === '') {
|
||||
return $reasonLabel . ' (' . $statusLabel . ')';
|
||||
}
|
||||
|
||||
if ($statusLabel !== 'Genehmigt') {
|
||||
return $employee . ' · ' . $reasonLabel . ' (' . $statusLabel . ')';
|
||||
}
|
||||
|
||||
return $employee . ' · ' . $reasonLabel;
|
||||
}
|
||||
|
||||
$events = [];
|
||||
|
||||
$vacStmt = $pdo->prepare("
|
||||
SELECT
|
||||
v.id,
|
||||
v.user_id,
|
||||
v.start_date,
|
||||
v.end_date,
|
||||
v.days,
|
||||
v.status,
|
||||
v.comment_user,
|
||||
v.absence_reason,
|
||||
u.vorname,
|
||||
u.nachname,
|
||||
u.email
|
||||
FROM vacations v
|
||||
JOIN users u ON u.id = v.user_id
|
||||
WHERE LOWER(TRIM(COALESCE(v.status, ''))) != 'abgelehnt'
|
||||
ORDER BY v.start_date ASC, v.end_date ASC, u.nachname ASC, u.vorname ASC
|
||||
");
|
||||
$vacStmt->execute();
|
||||
$vacations = $vacStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
foreach ($vacations as $row) {
|
||||
$endInclusive = (new DateTime($row['end_date']))->modify('+1 day')->format('Y-m-d');
|
||||
$reason = vacationAbsenceNormalizeReason($row['absence_reason'] ?? 'urlaub');
|
||||
$status = trim((string)($row['status'] ?? ''));
|
||||
$statusLabel = adminAbsenceStatusLabel($status);
|
||||
|
||||
$events[] = [
|
||||
'id' => 'vac_' . $row['id'],
|
||||
'title' => adminAbsenceEventTitle($row),
|
||||
'start' => $row['start_date'],
|
||||
'end' => $endInclusive,
|
||||
'allDay' => true,
|
||||
'backgroundColor' => adminAbsenceColor($reason, $status),
|
||||
'borderColor' => adminAbsenceColor($reason, $status),
|
||||
'extendedProps' => [
|
||||
'type' => 'absence',
|
||||
'user_id' => $row['user_id'],
|
||||
'employee_name' => trim(($row['vorname'] ?? '') . ' ' . ($row['nachname'] ?? '')),
|
||||
'email' => $row['email'] ?? '',
|
||||
'status' => $status,
|
||||
'status_label' => $statusLabel,
|
||||
'reason' => $reason,
|
||||
'reason_label' => vacationAbsenceReasonLabel($reason),
|
||||
'days' => (int)($row['days'] ?? 0),
|
||||
'comment' => $row['comment_user'] ?? '',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$companyStmt = $pdo->prepare("
|
||||
SELECT id, start_date, end_date, description
|
||||
FROM company_holidays
|
||||
ORDER BY start_date ASC, end_date ASC
|
||||
");
|
||||
$companyStmt->execute();
|
||||
$companyHolidays = $companyStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
foreach ($companyHolidays as $row) {
|
||||
$endInclusive = (new DateTime($row['end_date']))->modify('+1 day')->format('Y-m-d');
|
||||
$events[] = [
|
||||
'id' => 'com_' . $row['id'],
|
||||
'title' => $row['description'] ?: 'Betriebsurlaub',
|
||||
'start' => $row['start_date'],
|
||||
'end' => $endInclusive,
|
||||
'allDay' => true,
|
||||
'backgroundColor' => '#005f73',
|
||||
'borderColor' => '#005f73',
|
||||
'extendedProps' => [
|
||||
'type' => 'company',
|
||||
'description' => $row['description'] ?: 'Betriebsurlaub',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
include 'header.php';
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<h2>Leitungskalender</h2>
|
||||
|
||||
<?php if (!empty($schemaWarning)): ?>
|
||||
<div class="alert alert-warning"><?php echo htmlspecialchars($schemaWarning); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Hinweis:</strong> Dieser Kalender zeigt alle Abwesenheiten aller Personen sowie Betriebsurlaub.
|
||||
</div>
|
||||
|
||||
<div id="calendar"></div>
|
||||
<br>
|
||||
<div class="d-flex flex-wrap" style="gap: 0.5rem;">
|
||||
<span class="badge badge-success">Urlaub</span>
|
||||
<span class="badge badge-danger">Krankheit mit Attest</span>
|
||||
<span class="badge badge-dark">Krankheit ohne Attest</span>
|
||||
<span class="badge badge-warning">Berufsschule</span>
|
||||
<span class="badge badge-primary">Weiterbildung</span>
|
||||
<span class="badge badge-secondary">Persönliche Gründe</span>
|
||||
<span class="badge badge-light">Sonstiges</span>
|
||||
<span class="badge badge-info">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 events = <?php echo json_encode($events, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
|
||||
|
||||
function formatAllDayRange(start, end) {
|
||||
var startDate = new Date(start);
|
||||
var endDate = end ? new Date(end) : null;
|
||||
var startLabel = startDate.toLocaleDateString('de-DE');
|
||||
if (!endDate) {
|
||||
return startLabel;
|
||||
}
|
||||
|
||||
var inclusiveEnd = new Date(endDate.getTime() - 24 * 60 * 60 * 1000);
|
||||
return startLabel + ' - ' + inclusiveEnd.toLocaleDateString('de-DE');
|
||||
}
|
||||
|
||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
initialView: 'dayGridMonth',
|
||||
firstDay: 1,
|
||||
height: 700,
|
||||
events: events,
|
||||
eventClick: function(info) {
|
||||
var ev = info.event;
|
||||
var props = ev.extendedProps || {};
|
||||
var html = '<strong>' + ev.title + '</strong><br>' + formatAllDayRange(ev.start, ev.end) + '<br>';
|
||||
|
||||
if (props.type === 'absence') {
|
||||
html += 'Mitarbeiter: ' + (props.employee_name || '') + '<br>';
|
||||
html += 'Grund: ' + (props.reason_label || '') + '<br>';
|
||||
html += 'Status: ' + (props.status_label || '') + '<br>';
|
||||
html += 'Tage: ' + (props.days || 0) + '<br>';
|
||||
if (props.comment) {
|
||||
html += 'Kommentar: ' + props.comment + '<br>';
|
||||
}
|
||||
} else if (props.type === 'company') {
|
||||
html += 'Beschreibung: ' + (props.description || 'Betriebsurlaub') + '<br>';
|
||||
}
|
||||
|
||||
document.getElementById('detailsContent').innerHTML = html;
|
||||
document.getElementById('eventDetails').style.display = 'block';
|
||||
}
|
||||
});
|
||||
|
||||
calendar.render();
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php include 'footer.php'; ?>
|
||||
@@ -17,6 +17,13 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
# echo "Location: createPDF.php?id=$userId&month=$month&year=$year";
|
||||
header("Location: createPDF.php?id=$userId&month=$month&year=$year");
|
||||
exit();
|
||||
} elseif ($_POST["action"] == "Alle PDFs anzeigen") {
|
||||
$selectedMonth = $_POST["month"];
|
||||
$monthYear = explode("/", $selectedMonth);
|
||||
$month = $monthYear[0];
|
||||
$year = $monthYear[1];
|
||||
header("Location: createAllPDF.php?month=$month&year=$year");
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +127,9 @@ if ($selectedMonth && $selectedUser) {
|
||||
<input type="submit" value="Zeiten anzeigen" class="btn btn-primary btn-lg">
|
||||
<!-- Button zum Anzeigen der PDF -->
|
||||
<input type="submit" name="action" value="PDF anzeigen" class="btn btn-primary btn-lg" formtarget="_blank">
|
||||
<?php if (is_admin_user()): ?>
|
||||
<input type="submit" name="action" value="Alle PDFs anzeigen" class="btn btn-secondary btn-lg" formtarget="_blank">
|
||||
<?php endif; ?>
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
@@ -12,6 +12,10 @@ if (!isset($_SESSION['userid'])) {
|
||||
$user_id = $_SESSION['userid'];
|
||||
$user = check_user();
|
||||
|
||||
if (!is_admin_user()) {
|
||||
die("Keine Rechte für diese Ansicht.");
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<?php include 'header.php'; ?>
|
||||
@@ -25,6 +29,13 @@ $user = check_user();
|
||||
<div class="container">
|
||||
<h2 class="mb-4">Zeitbuchungsfehler Auswertung</h2>
|
||||
|
||||
<?php if (!empty($_SESSION['time_error_close_result'])): ?>
|
||||
<?php $closeResult = $_SESSION['time_error_close_result']; unset($_SESSION['time_error_close_result']); ?>
|
||||
<div class="alert alert-<?php echo htmlspecialchars($closeResult['type'], ENT_QUOTES, 'UTF-8'); ?>" role="alert">
|
||||
<?php echo htmlspecialchars($closeResult['message'], ENT_QUOTES, 'UTF-8'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
|
||||
// Benutzer aus der Datenbank erhalten
|
||||
@@ -132,6 +143,23 @@ foreach($users AS $user){
|
||||
|
||||
|
||||
<?php if (!empty($invalidDates)): ?>
|
||||
<form action="closeEmployeeTimeErrors.php" method="post" class="form-inline mb-3">
|
||||
<input type="hidden" name="employee_id" value="<?php echo (int)$user['id']; ?>">
|
||||
<label class="mr-2" for="hours_to_close_<?php echo (int)$user['id']; ?>">Fehlbuchungen automatisch schließen mit</label>
|
||||
<input
|
||||
type="number"
|
||||
step="0.25"
|
||||
min="0.25"
|
||||
class="form-control mr-2"
|
||||
id="hours_to_close_<?php echo (int)$user['id']; ?>"
|
||||
name="hours_to_close"
|
||||
value="8"
|
||||
required
|
||||
>
|
||||
<span class="mr-2">Stunden</span>
|
||||
<button type="submit" class="btn btn-primary">Alle schließen</button>
|
||||
</form>
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
|
||||
+378
-160
@@ -1,197 +1,415 @@
|
||||
<?php
|
||||
// API: returns JSON events for FullCalendar
|
||||
session_start();
|
||||
require_once(__DIR__ . '/../inc/config.inc.php');
|
||||
require_once(__DIR__ . '/../inc/functions.inc.php');
|
||||
require_once(__DIR__ . '/../inc/vacation_absence.inc.php');
|
||||
|
||||
// Enable full error reporting for API debugging
|
||||
ini_set('display_errors', '1');
|
||||
ini_set('display_startup_errors', '1');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
if (!function_exists('vacationApiTableHasColumn')) {
|
||||
function vacationApiTableHasColumn(PDO $pdo, string $table, string $column): bool
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = :table_name
|
||||
AND COLUMN_NAME = :column_name"
|
||||
);
|
||||
$stmt->execute([
|
||||
'table_name' => $table,
|
||||
'column_name' => $column,
|
||||
]);
|
||||
return (int)$stmt->fetchColumn() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('vacationApiLower')) {
|
||||
function vacationApiLower(?string $value): string
|
||||
{
|
||||
$value = trim((string)$value);
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (function_exists('mb_strtolower')) {
|
||||
return mb_strtolower($value, 'UTF-8');
|
||||
}
|
||||
|
||||
return strtolower($value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('vacationApiNormalizeAbsenceType')) {
|
||||
function vacationApiNormalizeAbsenceType(?string $type): string
|
||||
{
|
||||
$type = vacationApiLower($type);
|
||||
if ($type === '') {
|
||||
return vacationAbsenceDefaultReason();
|
||||
}
|
||||
|
||||
$type = strtr($type, [
|
||||
'ä' => 'ae',
|
||||
'ö' => 'oe',
|
||||
'ü' => 'ue',
|
||||
'ß' => 'ss',
|
||||
]);
|
||||
|
||||
$type = preg_replace('/[^a-z0-9]+/u', '_', $type);
|
||||
$type = trim((string)$type, '_');
|
||||
|
||||
$map = [
|
||||
'urlaub' => 'urlaub',
|
||||
'urlaubsantrag' => 'urlaub',
|
||||
'krankheit_mit_attest' => 'krankheit_mit_atest',
|
||||
'krankheit_mit_atest' => 'krankheit_mit_atest',
|
||||
'krank_mit_attest' => 'krankheit_mit_atest',
|
||||
'krankheit_ohne_attest' => 'krankheit_ohne_atest',
|
||||
'krankheit_ohne_atest' => 'krankheit_ohne_atest',
|
||||
'krank_ohne_attest' => 'krankheit_ohne_atest',
|
||||
'berufsschule' => 'berufsschule',
|
||||
'weiterbildung' => 'weiterbildung',
|
||||
'persoenliche_gruende' => 'persoenliche_gruende',
|
||||
'persoenliche_gruende_' => 'persoenliche_gruende',
|
||||
'persoenliche_gruende_mit' => 'persoenliche_gruende',
|
||||
'sonstiges' => 'sonstiges',
|
||||
];
|
||||
|
||||
return vacationAbsenceNormalizeReason($map[$type] ?? $type);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('vacationApiNormalizeStatus')) {
|
||||
function vacationApiNormalizeStatus(?string $status): string
|
||||
{
|
||||
$status = vacationApiLower($status);
|
||||
if ($status === '') {
|
||||
return 'beantragt';
|
||||
}
|
||||
|
||||
$status = preg_replace('/\s+/u', ' ', $status);
|
||||
return $status;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('vacationApiTypeLabel')) {
|
||||
function vacationApiTypeLabel(string $type): string
|
||||
{
|
||||
return vacationAbsenceReasonLabel($type);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('vacationApiStatusLabel')) {
|
||||
function vacationApiStatusLabel(string $status): string
|
||||
{
|
||||
$labels = [
|
||||
'genehmigt' => 'Genehmigt',
|
||||
'beantragt' => 'Beantragt',
|
||||
'abgelehnt' => 'Abgelehnt',
|
||||
];
|
||||
|
||||
return $labels[$status] ?? ucfirst($status);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('vacationApiStatusColor')) {
|
||||
function vacationApiStatusColor(string $status): string
|
||||
{
|
||||
$colors = [
|
||||
'genehmigt' => '#28a745',
|
||||
'beantragt' => '#f0ad4e',
|
||||
'abgelehnt' => '#6c757d',
|
||||
];
|
||||
|
||||
return $colors[$status] ?? '#17a2b8';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('vacationApiTypeColor')) {
|
||||
function vacationApiTypeColor(string $type): string
|
||||
{
|
||||
$colors = [
|
||||
'urlaub' => '#2f9e44',
|
||||
'krankheit_mit_atest' => '#d9480f',
|
||||
'krankheit_ohne_atest' => '#c92a2a',
|
||||
'berufsschule' => '#1971c2',
|
||||
'weiterbildung' => '#5f3dc4',
|
||||
'persoenliche_gruende' => '#e67700',
|
||||
'sonstiges' => '#495057',
|
||||
];
|
||||
|
||||
return $colors[$type] ?? '#0d6efd';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('vacationApiRequestedTypes')) {
|
||||
function vacationApiRequestedTypes(?string $rawTypes, string $scope): ?array
|
||||
{
|
||||
$rawTypes = trim((string)$rawTypes);
|
||||
if ($rawTypes === '') {
|
||||
if ($scope === 'team') {
|
||||
return ['urlaub'];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$normalized = vacationApiLower($rawTypes);
|
||||
if (in_array($normalized, ['all', '*'], true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$types = [];
|
||||
foreach (preg_split('/\s*,\s*/', $rawTypes) as $rawType) {
|
||||
$normalizedType = vacationApiNormalizeAbsenceType($rawType);
|
||||
if ($normalizedType !== '') {
|
||||
$types[$normalizedType] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$types = array_keys($types);
|
||||
sort($types);
|
||||
|
||||
return $types;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('vacationApiStatusAllowed')) {
|
||||
function vacationApiStatusAllowed(string $status, string $mode): bool
|
||||
{
|
||||
$mode = vacationApiLower($mode);
|
||||
$status = vacationApiNormalizeStatus($status);
|
||||
|
||||
switch ($mode) {
|
||||
case 'approved':
|
||||
return $status === 'genehmigt';
|
||||
case 'open':
|
||||
return $status === 'beantragt' || $status === '';
|
||||
case 'active':
|
||||
return $status === 'genehmigt' || $status === 'beantragt' || $status === '';
|
||||
case 'rejected':
|
||||
return $status === 'abgelehnt';
|
||||
case 'all':
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$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');
|
||||
$start = trim((string)($_GET['start'] ?? ''));
|
||||
$end = trim((string)($_GET['end'] ?? ''));
|
||||
|
||||
if (!$start || !$end) {
|
||||
if ($start === '' || $end === '') {
|
||||
http_response_code(400);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(['error' => 'start and end required']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$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');
|
||||
|
||||
$scope = strtolower(trim((string)($_GET['scope'] ?? '')));
|
||||
if ($scope === '') {
|
||||
if ($onlyPersonal) {
|
||||
$scope = 'personal';
|
||||
} elseif ($public || $publicAll) {
|
||||
$scope = 'team';
|
||||
} elseif ($isAdmin) {
|
||||
$scope = 'team';
|
||||
} else {
|
||||
$scope = 'personal';
|
||||
}
|
||||
}
|
||||
|
||||
if (!in_array($scope, ['personal', 'team', 'admin_all'], true)) {
|
||||
$scope = 'personal';
|
||||
}
|
||||
|
||||
if ($scope === 'admin_all' && !$isAdmin) {
|
||||
$scope = 'personal';
|
||||
}
|
||||
|
||||
$statusMode = strtolower(trim((string)($_GET['status_filter'] ?? '')));
|
||||
if ($statusMode === '') {
|
||||
if ($onlyApproved) {
|
||||
$statusMode = 'approved';
|
||||
} elseif ($includeRejected) {
|
||||
$statusMode = 'all';
|
||||
} elseif ($scope === 'personal' || $scope === 'admin_all') {
|
||||
$statusMode = 'all';
|
||||
} else {
|
||||
$statusMode = 'active';
|
||||
}
|
||||
}
|
||||
|
||||
$includeCompany = !isset($_GET['include_company']) || $_GET['include_company'] === '1' || $_GET['include_company'] === 'true';
|
||||
$requestedTypes = vacationApiRequestedTypes($_GET['absence_types'] ?? null, $scope);
|
||||
|
||||
$hasAbsenceReasonColumn = vacationApiTableHasColumn($pdo, 'vacations', 'absence_reason');
|
||||
$absenceTypeSelect = $hasAbsenceReasonColumn ? 'v.absence_reason' : "'" . vacationAbsenceDefaultReason() . "'";
|
||||
|
||||
$selectColumns = "
|
||||
SELECT
|
||||
v.id,
|
||||
v.user_id,
|
||||
v.start_date,
|
||||
v.end_date,
|
||||
v.days,
|
||||
v.status,
|
||||
v.comment_user,
|
||||
u.vorname,
|
||||
u.nachname,
|
||||
{$absenceTypeSelect} AS absence_type
|
||||
FROM vacations v
|
||||
JOIN users u ON v.user_id = u.id
|
||||
WHERE v.start_date <= ?
|
||||
AND v.end_date >= ?
|
||||
";
|
||||
|
||||
$params = [$end, $start];
|
||||
|
||||
if ($scope === 'personal') {
|
||||
$selectColumns .= " AND v.user_id = ?";
|
||||
$params[] = $_SESSION['userid'];
|
||||
} elseif ($scope === 'team' || $scope === 'admin_all') {
|
||||
// No further restriction here. Scope filters are applied in PHP so
|
||||
// the query remains flexible for future admin and team views.
|
||||
}
|
||||
|
||||
$selectColumns .= " ORDER BY v.start_date ASC, v.id ASC";
|
||||
|
||||
$stmt = $pdo->prepare($selectColumns);
|
||||
$stmt->execute($params);
|
||||
$vacations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$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]);
|
||||
foreach ($vacations as $v) {
|
||||
$absenceType = vacationApiNormalizeAbsenceType($v['absence_type'] ?? '');
|
||||
$status = vacationApiNormalizeStatus($v['status'] ?? '');
|
||||
|
||||
if ($scope === 'team' && $absenceType !== 'urlaub') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($requestedTypes !== null && !in_array($absenceType, $requestedTypes, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!vacationApiStatusAllowed($status, $statusMode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$employeeName = trim((string)($v['vorname'] ?? '') . ' ' . (string)($v['nachname'] ?? ''));
|
||||
$typeLabel = vacationApiTypeLabel($absenceType);
|
||||
$statusLabel = vacationApiStatusLabel($status);
|
||||
|
||||
if ($scope === 'personal') {
|
||||
$title = $typeLabel;
|
||||
} elseif ($employeeName !== '') {
|
||||
$title = $employeeName . ' - ' . $typeLabel;
|
||||
} 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
|
||||
];
|
||||
$title = $typeLabel;
|
||||
}
|
||||
|
||||
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'] ?? ''
|
||||
]
|
||||
];
|
||||
if ($status !== '' && $status !== 'genehmigt') {
|
||||
$title .= ' (' . $statusLabel . ')';
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
try {
|
||||
$endInclusive = (new DateTime($v['end_date']))->modify('+1 day')->format('Y-m-d');
|
||||
} catch (Exception $e) {
|
||||
$endInclusive = $v['start_date'];
|
||||
}
|
||||
|
||||
// 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();
|
||||
$backgroundColor = vacationApiTypeColor($absenceType);
|
||||
if ($status === 'abgelehnt') {
|
||||
$backgroundColor = vacationApiStatusColor($status);
|
||||
}
|
||||
|
||||
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'],
|
||||
'id' => 'vac_' . $v['id'],
|
||||
'title' => $title,
|
||||
'start' => $v['start_date'],
|
||||
'end' => $endInclusive,
|
||||
'allDay' => true,
|
||||
'color' => '#007bff',
|
||||
'backgroundColor' => $backgroundColor,
|
||||
'borderColor' => $backgroundColor,
|
||||
'textColor' => '#ffffff',
|
||||
'extendedProps' => [
|
||||
'type' => 'company',
|
||||
'description' => $h['description']
|
||||
]
|
||||
'type' => 'user',
|
||||
'user_id' => $v['user_id'],
|
||||
'employee_name' => $employeeName,
|
||||
'absence_type' => $absenceType,
|
||||
'absence_label' => $typeLabel,
|
||||
'status' => $v['status'],
|
||||
'status_label' => $statusLabel,
|
||||
'comment' => $v['comment_user'] ?? '',
|
||||
'scope' => $scope,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
if ($debugMode) {
|
||||
echo json_encode(['events' => $events, 'meta' => $meta]);
|
||||
} else {
|
||||
echo json_encode($events);
|
||||
if ($includeCompany) {
|
||||
$stmt = $pdo->prepare("SELECT id, start_date, end_date, description, vertretung, vertretertelefon, vertreteradresse, vertreterurl 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) {
|
||||
try {
|
||||
$endInclusive = (new DateTime($h['end_date']))->modify('+1 day')->format('Y-m-d');
|
||||
} catch (Exception $e) {
|
||||
$endInclusive = $h['start_date'];
|
||||
}
|
||||
|
||||
$description = trim((string)($h['description'] ?? ''));
|
||||
$title = $description !== '' ? $description : 'Betriebsurlaub';
|
||||
|
||||
$events[] = [
|
||||
'id' => 'com_' . $h['id'],
|
||||
'title' => $title,
|
||||
'start' => $h['start_date'],
|
||||
'end' => $endInclusive,
|
||||
'allDay' => true,
|
||||
'backgroundColor' => '#0b7285',
|
||||
'borderColor' => '#0b7285',
|
||||
'textColor' => '#ffffff',
|
||||
'extendedProps' => [
|
||||
'type' => 'company',
|
||||
'description' => $description,
|
||||
'vertretung' => $h['vertretung'] ?? '',
|
||||
'vertretertelefon' => $h['vertretertelefon'] ?? '',
|
||||
'vertreteradresse' => $h['vertreteradresse'] ?? '',
|
||||
'vertreterurl' => $h['vertreterurl'] ?? '',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$json = json_encode(
|
||||
$events,
|
||||
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE
|
||||
);
|
||||
|
||||
if ($json === false) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'error' => 'Kalenderdaten konnten nicht kodiert werden.',
|
||||
'json_error' => json_last_error_msg(),
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo $json;
|
||||
?>
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
session_start();
|
||||
require_once("inc/config.inc.php");
|
||||
require_once("inc/functions.inc.php");
|
||||
require_once("inc/vacation_absence.inc.php");
|
||||
|
||||
$user = check_user();
|
||||
if (!is_admin_user()) {
|
||||
die('Zugriff verweigert. Nur Chefs dürfen Anträge genehmigen.');
|
||||
}
|
||||
|
||||
vacationAbsenceEnsureSchema($pdo);
|
||||
|
||||
// Handle approve/reject actions
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['id']) && isset($_POST['action'])) {
|
||||
$id = (int)$_POST['id'];
|
||||
@@ -38,12 +41,13 @@ $requests = $stmt->fetchAll();
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<h2>Urlaubsanträge - Genehmigung</h2>
|
||||
<h2>Abwesenheitsanträge - Genehmigung</h2>
|
||||
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mitarbeiter</th>
|
||||
<th>Grund</th>
|
||||
<th>Von</th>
|
||||
<th>Bis</th>
|
||||
<th>Tage</th>
|
||||
@@ -56,10 +60,11 @@ $requests = $stmt->fetchAll();
|
||||
<?php foreach ($requests as $r): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($r['vorname'] . ' ' . $r['nachname']); ?></td>
|
||||
<td><?php echo $r['start_date']; ?></td>
|
||||
<td><?php echo $r['end_date']; ?></td>
|
||||
<td><?php echo $r['days']; ?></td>
|
||||
<td><?php echo htmlspecialchars($r['comment_user']); ?></td>
|
||||
<td><?php echo htmlspecialchars(vacationAbsenceReasonLabel($r['absence_reason'] ?? 'urlaub')); ?></td>
|
||||
<td><?php echo htmlspecialchars((string)$r['start_date']); ?></td>
|
||||
<td><?php echo htmlspecialchars((string)$r['end_date']); ?></td>
|
||||
<td><?php echo (int)$r['days']; ?></td>
|
||||
<td><?php echo htmlspecialchars((string)($r['comment_user'] ?? '')); ?></td>
|
||||
<td>
|
||||
<?php
|
||||
if ($r['status'] === 'beantragt' || $r['status'] === null) {
|
||||
@@ -74,7 +79,7 @@ $requests = $stmt->fetchAll();
|
||||
<td>
|
||||
<?php if ($r['status'] !== 'genehmigt'): ?>
|
||||
<form method="post" style="display:inline-block; margin-right:6px;">
|
||||
<input type="hidden" name="id" value="<?php echo $r['id']; ?>">
|
||||
<input type="hidden" name="id" value="<?php echo (int)$r['id']; ?>">
|
||||
<input type="hidden" name="action" value="approve">
|
||||
<button class="btn btn-sm btn-success" type="submit">Genehmigen</button>
|
||||
</form>
|
||||
@@ -82,13 +87,13 @@ $requests = $stmt->fetchAll();
|
||||
|
||||
<?php if ($r['status'] !== 'abgelehnt'): ?>
|
||||
<form method="post" style="display:inline-block;">
|
||||
<input type="hidden" name="id" value="<?php echo $r['id']; ?>">
|
||||
<input type="hidden" name="id" value="<?php echo (int)$r['id']; ?>">
|
||||
<input type="hidden" name="action" value="reject">
|
||||
<button class="btn btn-sm btn-danger" type="submit">Ablehnen</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<form method="post" style="display:inline-block; margin-left:6px;" onsubmit="return confirm('Wirklich löschen?');">
|
||||
<input type="hidden" name="id" value="<?php echo $r['id']; ?>">
|
||||
<input type="hidden" name="id" value="<?php echo (int)$r['id']; ?>">
|
||||
<input type="hidden" name="action" value="delete">
|
||||
<button class="btn btn-sm btn-outline-danger" type="submit">Löschen</button>
|
||||
</form>
|
||||
@@ -100,6 +105,4 @@ $requests = $stmt->fetchAll();
|
||||
|
||||
</div>
|
||||
|
||||
<?php include 'footer.php';
|
||||
|
||||
?>
|
||||
<?php include 'footer.php'; ?>
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
require_once("inc/config.inc.php");
|
||||
require_once("inc/functions.inc.php");
|
||||
|
||||
$user = check_user();
|
||||
if (!is_admin_user()) {
|
||||
http_response_code(403);
|
||||
die("Keine Berechtigung.");
|
||||
}
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
|
||||
die("Ungültige Anfrage.");
|
||||
}
|
||||
|
||||
$employeeId = isset($_POST['employee_id']) ? (int)$_POST['employee_id'] : 0;
|
||||
$hoursRaw = isset($_POST['hours_to_close']) ? str_replace(',', '.', trim((string)$_POST['hours_to_close'])) : '';
|
||||
$hoursToClose = is_numeric($hoursRaw) ? (float)$hoursRaw : 0.0;
|
||||
|
||||
if ($employeeId <= 0) {
|
||||
$_SESSION['time_error_close_result'] = [
|
||||
'type' => 'danger',
|
||||
'message' => 'Es wurde kein gültiger Mitarbeiter übergeben.',
|
||||
];
|
||||
header("Location: allefehlbuchungen.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($hoursToClose <= 0) {
|
||||
$_SESSION['time_error_close_result'] = [
|
||||
'type' => 'danger',
|
||||
'message' => 'Bitte eine gültige Stundenanzahl größer als 0 angeben.',
|
||||
];
|
||||
header("Location: allefehlbuchungen.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT id, vorname, nachname FROM users WHERE id = :employee_id LIMIT 1");
|
||||
$stmt->bindValue(':employee_id', $employeeId, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$employee = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$employee) {
|
||||
throw new RuntimeException('Mitarbeiter wurde nicht gefunden.');
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT
|
||||
DATE(timestamp_datetime) AS datum,
|
||||
GROUP_CONCAT(timestamp_type ORDER BY timestamp_datetime) AS day_sequence
|
||||
FROM timestamps
|
||||
WHERE employee_id = :employee_id
|
||||
GROUP BY DATE(timestamp_datetime)
|
||||
ORDER BY datum ASC"
|
||||
);
|
||||
$stmt->bindValue(':employee_id', $employeeId, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$days = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$invalidDates = [];
|
||||
foreach ($days as $day) {
|
||||
if (!isValidSequence((string)$day['day_sequence'])) {
|
||||
$invalidDates[] = (string)$day['datum'];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($invalidDates)) {
|
||||
$_SESSION['time_error_close_result'] = [
|
||||
'type' => 'info',
|
||||
'message' => 'Es wurden keine offenen Fehlbuchungen für den Mitarbeiter gefunden.',
|
||||
];
|
||||
header("Location: allefehlbuchungen.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$pdo->beginTransaction();
|
||||
|
||||
$insertedDates = [];
|
||||
$skippedDates = [];
|
||||
$secondsToClose = (int)round($hoursToClose * 3600);
|
||||
|
||||
$lastEntryStmt = $pdo->prepare(
|
||||
"SELECT timestamp_type, timestamp_datetime
|
||||
FROM timestamps
|
||||
WHERE employee_id = :employee_id
|
||||
AND DATE(timestamp_datetime) = :datum
|
||||
ORDER BY timestamp_datetime DESC, timestamp_id DESC
|
||||
LIMIT 1"
|
||||
);
|
||||
|
||||
$insertStmt = $pdo->prepare(
|
||||
"INSERT INTO timestamps (employee_id, timestamp_type, timestamp_datetime, timestamp_endpoint)
|
||||
VALUES (:employee_id, 'GEHEN', :timestamp_datetime, 0)"
|
||||
);
|
||||
|
||||
foreach ($invalidDates as $invalidDate) {
|
||||
$lastEntryStmt->bindValue(':employee_id', $employeeId, PDO::PARAM_INT);
|
||||
$lastEntryStmt->bindValue(':datum', $invalidDate);
|
||||
$lastEntryStmt->execute();
|
||||
$lastEntry = $lastEntryStmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$lastEntry || (string)$lastEntry['timestamp_type'] !== 'KOMMEN') {
|
||||
$skippedDates[] = $invalidDate;
|
||||
continue;
|
||||
}
|
||||
|
||||
$closeTimestamp = (new DateTimeImmutable((string)$lastEntry['timestamp_datetime']))
|
||||
->modify('+' . $secondsToClose . ' seconds')
|
||||
->format('Y-m-d H:i:s');
|
||||
|
||||
$insertStmt->bindValue(':employee_id', $employeeId, PDO::PARAM_INT);
|
||||
$insertStmt->bindValue(':timestamp_datetime', $closeTimestamp);
|
||||
$insertStmt->execute();
|
||||
|
||||
$insertedDates[] = $invalidDate;
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
$message = count($insertedDates) . ' Fehlbuchungstage für '
|
||||
. $employee['vorname'] . ' ' . $employee['nachname']
|
||||
. ' wurden mit ' . rtrim(rtrim(number_format($hoursToClose, 2, '.', ''), '0'), '.')
|
||||
. ' Stunden geschlossen.';
|
||||
|
||||
if (!empty($skippedDates)) {
|
||||
$message .= ' ' . count($skippedDates) . ' Tage konnten nicht automatisch geschlossen werden.';
|
||||
}
|
||||
|
||||
$_SESSION['time_error_close_result'] = [
|
||||
'type' => !empty($insertedDates) ? 'success' : 'warning',
|
||||
'message' => $message,
|
||||
];
|
||||
} catch (Throwable $e) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
|
||||
$_SESSION['time_error_close_result'] = [
|
||||
'type' => 'danger',
|
||||
'message' => 'Automatisches Schliessen fehlgeschlagen: ' . $e->getMessage(),
|
||||
];
|
||||
}
|
||||
|
||||
header("Location: allefehlbuchungen.php");
|
||||
exit;
|
||||
@@ -2,52 +2,115 @@
|
||||
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 dürfen Betriebsurlaub verwalten.');
|
||||
die('Zugriff verweigert. Nur Chefs duerfen Betriebsurlaub verwalten.');
|
||||
}
|
||||
|
||||
// Create table if not exists (optional helper)
|
||||
// Administrators can also run the SQL directly in DB. This is just a convenience.
|
||||
$error = '';
|
||||
$schemaError = '';
|
||||
|
||||
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 = $_POST['start_date'];
|
||||
$end = $_POST['end_date'];
|
||||
$desc = trim($_POST['description'] ?? 'Betriebsurlaub');
|
||||
$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'] ?? ''));
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO company_holidays (start_date, end_date, description, created_by) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([$start, $end, $desc, $_SESSION['userid']]);
|
||||
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();
|
||||
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();
|
||||
|
||||
$holidays = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
?>
|
||||
|
||||
<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>
|
||||
<?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-group mr-2">
|
||||
<label>Bis:</label>
|
||||
<input type="date" name="end_date" class="form-control" required>
|
||||
<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-group mr-2">
|
||||
<label>Beschreibung:</label>
|
||||
<input type="text" name="description" class="form-control" placeholder="z. B. Betriebsurlaub Weihnachten">
|
||||
<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">Hinzufügen</button>
|
||||
<button class="btn btn-primary">Hinzufuegen</button>
|
||||
</form>
|
||||
|
||||
<table class="table table-bordered">
|
||||
@@ -56,6 +119,8 @@ $holidays = $stmt->fetchAll();
|
||||
<th>Von</th>
|
||||
<th>Bis</th>
|
||||
<th>Beschreibung</th>
|
||||
<th>Vertretung</th>
|
||||
<th>Kontakt</th>
|
||||
<th>Erstellt von</th>
|
||||
<th>Aktion</th>
|
||||
</tr>
|
||||
@@ -63,19 +128,25 @@ $holidays = $stmt->fetchAll();
|
||||
<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 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();
|
||||
echo htmlspecialchars($u['vorname'] . ' ' . $u['nachname']);
|
||||
$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 löschen?');">
|
||||
<input type="hidden" name="id" value="<?php echo intval($h['id']); ?>">
|
||||
<button class="btn btn-sm btn-danger">Löschen</button>
|
||||
<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>
|
||||
@@ -85,6 +156,4 @@ $holidays = $stmt->fetchAll();
|
||||
|
||||
</div>
|
||||
|
||||
<?php include 'footer.php';
|
||||
|
||||
?>
|
||||
<?php include 'footer.php'; ?>
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once('./../admin/tcpdf/tcpdf.php');
|
||||
require_once("inc/config.inc.php");
|
||||
require_once("inc/functions.inc.php");
|
||||
|
||||
if (!isset($_SESSION['userid'])) {
|
||||
die("Kein Benutzer angemeldet.");
|
||||
}
|
||||
|
||||
$currentUser = check_user();
|
||||
if (!is_admin_user()) {
|
||||
die("Keine Berechtigung.");
|
||||
}
|
||||
|
||||
$selectedMonth = $_GET['month'] ?? date('m');
|
||||
$selectedYear = $_GET['year'] ?? date('Y');
|
||||
$firmaName = "Praxis Creutzburg";
|
||||
|
||||
try {
|
||||
$usersStmt = $pdo->prepare("
|
||||
SELECT DISTINCT u.id, u.vorname, u.nachname
|
||||
FROM users u
|
||||
INNER JOIN timestamps t ON t.employee_id = u.id
|
||||
WHERE u.zeiterfassung = '1'
|
||||
AND MONTH(t.timestamp_datetime) = :month
|
||||
AND YEAR(t.timestamp_datetime) = :year
|
||||
ORDER BY u.nachname ASC, u.vorname ASC
|
||||
");
|
||||
$usersStmt->bindValue(':month', (int)$selectedMonth, PDO::PARAM_INT);
|
||||
$usersStmt->bindValue(':year', (int)$selectedYear, PDO::PARAM_INT);
|
||||
$usersStmt->execute();
|
||||
$employees = $usersStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
|
||||
$pdf->SetCreator(PDF_CREATOR);
|
||||
$pdf->SetAuthor($currentUser['vorname'] . ' ' . $currentUser['nachname']);
|
||||
$pdf->SetTitle('Arbeitszeiten aller Mitarbeiter');
|
||||
$pdf->SetSubject('Arbeitszeiten aller Mitarbeiter für ' . $selectedMonth . '/' . $selectedYear);
|
||||
|
||||
$stempdate = $selectedYear . "-" . $selectedMonth . "-1";
|
||||
$date = new DateTime($stempdate);
|
||||
$formatter = new IntlDateFormatter(
|
||||
"de-DE",
|
||||
IntlDateFormatter::LONG,
|
||||
IntlDateFormatter::NONE,
|
||||
"Europe/Berlin",
|
||||
IntlDateFormatter::GREGORIAN,
|
||||
"MMMM"
|
||||
);
|
||||
$monthName = $formatter->format($date);
|
||||
|
||||
$sequenceStmt = $pdo->prepare("
|
||||
SELECT
|
||||
DATE(timestamp_datetime) AS datum,
|
||||
GROUP_CONCAT(timestamp_type ORDER BY timestamp_datetime) AS day_sequence
|
||||
FROM timestamps
|
||||
WHERE employee_id = :employee_id
|
||||
AND MONTH(timestamp_datetime) = :selectedMonth
|
||||
AND YEAR(timestamp_datetime) = :selectedYear
|
||||
GROUP BY DATE(timestamp_datetime)
|
||||
");
|
||||
|
||||
$timesStmt = $pdo->prepare("
|
||||
SELECT
|
||||
DATE(timestamp_datetime) AS day,
|
||||
MIN(CASE WHEN timestamp_type = 'KOMMEN' THEN timestamp_datetime END) AS first_come,
|
||||
MAX(CASE WHEN timestamp_type = 'GEHEN' THEN timestamp_datetime END) AS last_go,
|
||||
SEC_TO_TIME(SUM(
|
||||
CASE
|
||||
WHEN timestamp_type = 'GEHEN' THEN UNIX_TIMESTAMP(timestamp_datetime)
|
||||
WHEN timestamp_type = 'KOMMEN' THEN -UNIX_TIMESTAMP(timestamp_datetime)
|
||||
ELSE 0
|
||||
END
|
||||
)) AS total_time,
|
||||
SEC_TO_TIME(
|
||||
TIME_TO_SEC(
|
||||
SEC_TO_TIME(
|
||||
UNIX_TIMESTAMP(MAX(CASE WHEN timestamp_type = 'GEHEN' THEN timestamp_datetime END)) -
|
||||
UNIX_TIMESTAMP(MIN(CASE WHEN timestamp_type = 'KOMMEN' THEN timestamp_datetime END))
|
||||
)
|
||||
) - TIME_TO_SEC(SEC_TO_TIME(SUM(
|
||||
CASE
|
||||
WHEN timestamp_type = 'GEHEN' THEN UNIX_TIMESTAMP(timestamp_datetime)
|
||||
WHEN timestamp_type = 'KOMMEN' THEN -UNIX_TIMESTAMP(timestamp_datetime)
|
||||
ELSE 0
|
||||
END
|
||||
)))
|
||||
) AS difference_between_total_time_and_pause_time
|
||||
FROM timestamps
|
||||
WHERE employee_id = :employee_id
|
||||
AND MONTH(timestamp_datetime) = :month
|
||||
AND YEAR(timestamp_datetime) = :year
|
||||
GROUP BY DATE(timestamp_datetime)
|
||||
");
|
||||
|
||||
foreach ($employees as $employee) {
|
||||
$employeeId = (int)$employee['id'];
|
||||
$employeeName = trim($employee['vorname'] . ' ' . $employee['nachname']);
|
||||
|
||||
$sequenceStmt->bindValue(':employee_id', $employeeId, PDO::PARAM_INT);
|
||||
$sequenceStmt->bindValue(':selectedMonth', (int)$selectedMonth, PDO::PARAM_INT);
|
||||
$sequenceStmt->bindValue(':selectedYear', (int)$selectedYear, PDO::PARAM_INT);
|
||||
$sequenceStmt->execute();
|
||||
$sequenceRows = $sequenceStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$fehlerhafteTage = [];
|
||||
foreach ($sequenceRows as $row) {
|
||||
if (!isValidSequence((string)$row['day_sequence'])) {
|
||||
$fehlerhafteTage[] = (string)$row['datum'];
|
||||
}
|
||||
}
|
||||
|
||||
$pdf->AddPage();
|
||||
$html = '<h1>Arbeitszeiten - ' . $firmaName . '</h1>';
|
||||
$html .= '<h2>Mitarbeiter: ' . htmlspecialchars($employeeName, ENT_QUOTES, 'UTF-8') . '</h2>';
|
||||
$html .= '<h3>Monat: ' . htmlspecialchars((string)$monthName, ENT_QUOTES, 'UTF-8') . ' ' . htmlspecialchars((string)$selectedYear, ENT_QUOTES, 'UTF-8') . '</h3>';
|
||||
|
||||
if (!empty($fehlerhafteTage)) {
|
||||
$html .= '<p><strong>Fehlzeiten erkannt.</strong> Bitte erst beheben.</p>';
|
||||
$html .= '<p>Betroffene Tage: ' . htmlspecialchars(implode(', ', array_map(static function (string $day): string {
|
||||
return date('d.m.Y', strtotime($day));
|
||||
}, $fehlerhafteTage)), ENT_QUOTES, 'UTF-8') . '</p>';
|
||||
$pdf->writeHTML($html, true, false, true, false, '');
|
||||
continue;
|
||||
}
|
||||
|
||||
$timesStmt->bindValue(':employee_id', $employeeId, PDO::PARAM_INT);
|
||||
$timesStmt->bindValue(':month', (int)$selectedMonth, PDO::PARAM_INT);
|
||||
$timesStmt->bindValue(':year', (int)$selectedYear, PDO::PARAM_INT);
|
||||
$timesStmt->execute();
|
||||
|
||||
$timesData = [];
|
||||
while ($row = $timesStmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$day = date('d', strtotime($row['day']));
|
||||
$timesData[$day] = $row;
|
||||
}
|
||||
|
||||
$html .= '<table border="1" style="font-size:14px;" width="100%">';
|
||||
$html .= '<tr><th style="width: 7%;">Tag</th><th style="width: 12%;">Start</th><th style="width: 12%;">Pause</th><th style="width: 12%;">Ende</th><th>Gesamtzeit</th><th style="width: 20%;font-size:12px;">aufgezeichnet am:</th><th style="width: 23%;">Bemerkung</th></tr>';
|
||||
|
||||
$totalSeconds = 0;
|
||||
for ($day = 1; $day <= 31; $day++) {
|
||||
$daytwo = str_pad((string)$day, 2, '0', STR_PAD_LEFT);
|
||||
if (isset($timesData[$daytwo])) {
|
||||
$row = $timesData[$daytwo];
|
||||
$html .= '<tr><td>' . $day . '</td><td>' . date('H:i:s', strtotime($row['first_come'])) . '</td><td>' . $row['difference_between_total_time_and_pause_time'] . '</td><td>' . date('H:i:s', strtotime($row['last_go'])) . '</td><td>' . $row['total_time'] . '</td><td>' . date('d.m.Y', strtotime($row['day'])) . '</td><td></td></tr>';
|
||||
|
||||
if (!empty($row['total_time'])) {
|
||||
[$hours, $minutes, $seconds] = explode(':', $row['total_time']);
|
||||
$totalSeconds += ((int)$hours * 3600) + ((int)$minutes * 60) + (int)$seconds;
|
||||
}
|
||||
} else {
|
||||
$html .= '<tr><td>' . $day . '</td><td></td><td></td><td></td><td></td><td></td><td></td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
$hours = floor($totalSeconds / 3600);
|
||||
$mins = floor(($totalSeconds / 60) % 60);
|
||||
$secs = floor($totalSeconds % 60);
|
||||
$totalTime = sprintf('%02d:%02d:%02d', $hours, $mins, $secs);
|
||||
|
||||
$html .= '<tr><td></td><td></td><td></td><td><b>Gesamt</b></td><td><b>' . $totalTime . '</b></td><td></td><td></td></tr>';
|
||||
$html .= '</table>';
|
||||
|
||||
$pdf->writeHTML($html, true, false, true, false, '');
|
||||
}
|
||||
|
||||
$pdf->Output('Arbeitszeiten_alle_' . $selectedYear . '_' . str_pad((string)$selectedMonth, 2, '0', STR_PAD_LEFT) . '.pdf', 'I');
|
||||
} catch (PDOException $e) {
|
||||
echo "Datenbankfehler: " . $e->getMessage();
|
||||
}
|
||||
?>
|
||||
@@ -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,6 +19,7 @@ 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]);
|
||||
|
||||
|
||||
@@ -13,26 +13,22 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['id'])) {
|
||||
$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();
|
||||
$vac = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$vac) {
|
||||
die('Urlaubseintrag nicht gefunden.');
|
||||
}
|
||||
|
||||
$isAdmin = is_admin_user();
|
||||
|
||||
if (!$isAdmin && $vac['user_id'] != $_SESSION['userid']) {
|
||||
$canManageTeamVacations = can_manage_team_vacations();
|
||||
if (!$canManageTeamVacations && (int)$vac['user_id'] !== (int)$_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();
|
||||
|
||||
?>
|
||||
|
||||
@@ -41,19 +41,28 @@ if (!isset($user)) {
|
||||
<a class="nav-link" href="fehlbuchungen.php">Fehlbuchungen</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="../admin/index.php">Zur Admin-Oberfläche</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="../admin/zeiterfassung_hilfe.php">Hilfe</a>
|
||||
</li>
|
||||
|
||||
<!-- Urlaub Dropdown -->
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="urlaubDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Urlaub
|
||||
Abwesenheit
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="urlaubDropdown">
|
||||
<a class="dropdown-item" href="urlaubsantrag.php">Urlaubsantrag</a>
|
||||
<a class="dropdown-item" href="my_vacations_calendar.php">Mein Urlaubskalender</a>
|
||||
<a class="dropdown-item" href="vacations_calendar_all.php">Team Urlaubskalender</a>
|
||||
<a class="dropdown-item" href="urlaubsantrag.php">Abwesenheitsantrag</a>
|
||||
<a class="dropdown-item" href="my_vacations_calendar.php">Mein Abwesenheitskalender</a>
|
||||
<a class="dropdown-item" href="vacations_calendar_all.php">Team-Urlaubskalender</a>
|
||||
<?php if (is_admin_user()) : ?>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="vacations_overview.php">Urlaubsübersicht</a>
|
||||
<a class="dropdown-item" href="approveVacation.php">Urlaubsanträge genehmigen</a>
|
||||
<a class="dropdown-item" href="admin_absence_calendar.php">Leitungskalender</a>
|
||||
<a class="dropdown-item" href="vacations_overview.php">Abwesenheitsübersicht</a>
|
||||
<a class="dropdown-item" href="approveVacation.php">Abwesenheiten genehmigen</a>
|
||||
<a class="dropdown-item" href="company_holidays.php">Betriebsurlaub</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,517 @@
|
||||
<?php
|
||||
|
||||
use PHPMailer\PHPMailer\Exception;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
|
||||
require_once __DIR__ . '/../../inc/PHPMailer/src/Exception.php';
|
||||
require_once __DIR__ . '/../../inc/PHPMailer/src/PHPMailer.php';
|
||||
require_once __DIR__ . '/../../inc/PHPMailer/src/SMTP.php';
|
||||
|
||||
if (!function_exists('timeErrorNotificationsEnsureTables')) {
|
||||
function timeErrorNotificationsEnsureTables(PDO $pdo): void
|
||||
{
|
||||
$legacyColumnStmt = $pdo->prepare(
|
||||
"SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'time_error_notifications'
|
||||
AND COLUMN_NAME = 'error_date'"
|
||||
);
|
||||
$legacyColumnStmt->execute();
|
||||
if ((int)$legacyColumnStmt->fetchColumn() > 0) {
|
||||
$pdo->exec("DROP TABLE IF EXISTS time_error_notifications");
|
||||
}
|
||||
|
||||
$pdo->exec(
|
||||
"CREATE TABLE IF NOT EXISTS time_error_notification_state (
|
||||
employee_id INT NOT NULL,
|
||||
cycle_started_on DATE NOT NULL,
|
||||
first_error_date DATE NOT NULL,
|
||||
last_notification_stage VARCHAR(50) DEFAULT NULL,
|
||||
last_notification_sent_at DATETIME DEFAULT NULL,
|
||||
employee_day_1_sent_at DATETIME DEFAULT NULL,
|
||||
employee_day_3_sent_at DATETIME DEFAULT NULL,
|
||||
admin_day_7_sent_at DATETIME DEFAULT NULL,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (employee_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
|
||||
);
|
||||
|
||||
$pdo->exec(
|
||||
"CREATE TABLE IF NOT EXISTS time_error_notifications (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
employee_id INT NOT NULL,
|
||||
cycle_started_on DATE NOT NULL,
|
||||
notification_stage VARCHAR(50) NOT NULL,
|
||||
recipient_email VARCHAR(255) NOT NULL,
|
||||
sent_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uniq_time_error_notification (employee_id, cycle_started_on, notification_stage, recipient_email)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('timeErrorNotificationsSendMail')) {
|
||||
function timeErrorNotificationsSendMail(PDO $pdo, string $recipient, string $subject, string $body): bool
|
||||
{
|
||||
$stmt = $pdo->prepare("SELECT * FROM config LIMIT 1");
|
||||
$stmt->execute();
|
||||
$config = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$mail = new PHPMailer(true);
|
||||
|
||||
try {
|
||||
$mail->SMTPDebug = 0;
|
||||
$mail->isSMTP();
|
||||
$mail->Host = (string)$config['mailserver'];
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Username = (string)$config['mailUsername'];
|
||||
$mail->Password = (string)$config['mailPassword'];
|
||||
|
||||
if (!empty($config['mailSMTPSecure'])) {
|
||||
$mail->SMTPSecure = (string)$config['mailSMTPSecure'];
|
||||
}
|
||||
|
||||
$mail->Port = (int)$config['mailPort'];
|
||||
$mail->CharSet = 'UTF-8';
|
||||
$mail->setFrom((string)$config['mailFrom'], (string)$config['mailFromName']);
|
||||
$mail->addAddress($recipient);
|
||||
$mail->isHTML(true);
|
||||
$mail->Subject = $subject;
|
||||
$mail->Body = $body;
|
||||
$mail->AltBody = trim(html_entity_decode(strip_tags(str_replace(["<br>", "<br/>", "<br />"], "\n", $body)), ENT_QUOTES, 'UTF-8'));
|
||||
|
||||
$mail->send();
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('timeErrorNotificationsFetchInvalidEntries')) {
|
||||
function timeErrorNotificationsFetchInvalidEntries(PDO $pdo): array
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT
|
||||
u.id AS employee_id,
|
||||
u.email,
|
||||
u.vorname,
|
||||
u.nachname,
|
||||
DATE(t.timestamp_datetime) AS error_date,
|
||||
GROUP_CONCAT(t.timestamp_type ORDER BY t.timestamp_datetime) AS day_sequence
|
||||
FROM users u
|
||||
INNER JOIN timestamps t ON t.employee_id = u.id
|
||||
WHERE u.zeiterfassung = '1'
|
||||
AND DATE(t.timestamp_datetime) < CURDATE()
|
||||
GROUP BY u.id, u.email, u.vorname, u.nachname, DATE(t.timestamp_datetime)
|
||||
ORDER BY error_date ASC, u.nachname ASC, u.vorname ASC"
|
||||
);
|
||||
$stmt->execute();
|
||||
|
||||
$entries = [];
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
if (!isValidSequence((string)$row['day_sequence'])) {
|
||||
$entries[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
return $entries;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('timeErrorNotificationsGroupEntriesByEmployee')) {
|
||||
function timeErrorNotificationsGroupEntriesByEmployee(array $entries): array
|
||||
{
|
||||
$grouped = [];
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
$employeeId = (int)$entry['employee_id'];
|
||||
if (!isset($grouped[$employeeId])) {
|
||||
$grouped[$employeeId] = [
|
||||
'employee_id' => $employeeId,
|
||||
'email' => (string)$entry['email'],
|
||||
'vorname' => (string)$entry['vorname'],
|
||||
'nachname' => (string)$entry['nachname'],
|
||||
'first_error_date' => (string)$entry['error_date'],
|
||||
'error_dates' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$grouped[$employeeId]['error_dates'][] = (string)$entry['error_date'];
|
||||
if ((string)$entry['error_date'] < $grouped[$employeeId]['first_error_date']) {
|
||||
$grouped[$employeeId]['first_error_date'] = (string)$entry['error_date'];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($grouped as &$employee) {
|
||||
$employee['error_dates'] = array_values(array_unique($employee['error_dates']));
|
||||
sort($employee['error_dates']);
|
||||
}
|
||||
unset($employee);
|
||||
|
||||
return $grouped;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('timeErrorNotificationsFetchAdminRecipients')) {
|
||||
function timeErrorNotificationsFetchAdminRecipients(PDO $pdo): array
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT DISTINCT
|
||||
u.id,
|
||||
u.email,
|
||||
u.vorname,
|
||||
u.nachname
|
||||
FROM users u
|
||||
LEFT JOIN users_admin ua ON ua.userid = u.id
|
||||
WHERE (u.admin = 1 OR ua.userid IS NOT NULL)
|
||||
AND u.email IS NOT NULL
|
||||
AND u.email <> ''
|
||||
ORDER BY u.nachname ASC, u.vorname ASC"
|
||||
);
|
||||
$stmt->execute();
|
||||
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('timeErrorNotificationsFetchStateByEmployee')) {
|
||||
function timeErrorNotificationsFetchStateByEmployee(PDO $pdo): array
|
||||
{
|
||||
timeErrorNotificationsEnsureTables($pdo);
|
||||
|
||||
$stmt = $pdo->prepare("SELECT * FROM time_error_notification_state");
|
||||
$stmt->execute();
|
||||
|
||||
$states = [];
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$states[(int)$row['employee_id']] = $row;
|
||||
}
|
||||
|
||||
return $states;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('timeErrorNotificationsUpsertState')) {
|
||||
function timeErrorNotificationsUpsertState(PDO $pdo, array $employee, ?array $existingState): array
|
||||
{
|
||||
timeErrorNotificationsEnsureTables($pdo);
|
||||
|
||||
$cycleStartedOn = $existingState['cycle_started_on'] ?? $employee['first_error_date'];
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO time_error_notification_state (
|
||||
employee_id,
|
||||
cycle_started_on,
|
||||
first_error_date,
|
||||
last_notification_stage,
|
||||
last_notification_sent_at,
|
||||
employee_day_1_sent_at,
|
||||
employee_day_3_sent_at,
|
||||
admin_day_7_sent_at
|
||||
) VALUES (
|
||||
:employee_id,
|
||||
:cycle_started_on,
|
||||
:first_error_date,
|
||||
:last_notification_stage,
|
||||
:last_notification_sent_at,
|
||||
:employee_day_1_sent_at,
|
||||
:employee_day_3_sent_at,
|
||||
:admin_day_7_sent_at
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
cycle_started_on = VALUES(cycle_started_on),
|
||||
first_error_date = VALUES(first_error_date),
|
||||
last_notification_stage = VALUES(last_notification_stage),
|
||||
last_notification_sent_at = VALUES(last_notification_sent_at),
|
||||
employee_day_1_sent_at = VALUES(employee_day_1_sent_at),
|
||||
employee_day_3_sent_at = VALUES(employee_day_3_sent_at),
|
||||
admin_day_7_sent_at = VALUES(admin_day_7_sent_at)"
|
||||
);
|
||||
$stmt->execute([
|
||||
'employee_id' => $employee['employee_id'],
|
||||
'cycle_started_on' => $cycleStartedOn,
|
||||
'first_error_date' => $employee['first_error_date'],
|
||||
'last_notification_stage' => $existingState['last_notification_stage'] ?? null,
|
||||
'last_notification_sent_at' => $existingState['last_notification_sent_at'] ?? null,
|
||||
'employee_day_1_sent_at' => $existingState['employee_day_1_sent_at'] ?? null,
|
||||
'employee_day_3_sent_at' => $existingState['employee_day_3_sent_at'] ?? null,
|
||||
'admin_day_7_sent_at' => $existingState['admin_day_7_sent_at'] ?? null,
|
||||
]);
|
||||
|
||||
return [
|
||||
'employee_id' => $employee['employee_id'],
|
||||
'cycle_started_on' => $cycleStartedOn,
|
||||
'first_error_date' => $employee['first_error_date'],
|
||||
'last_notification_stage' => $existingState['last_notification_stage'] ?? null,
|
||||
'last_notification_sent_at' => $existingState['last_notification_sent_at'] ?? null,
|
||||
'employee_day_1_sent_at' => $existingState['employee_day_1_sent_at'] ?? null,
|
||||
'employee_day_3_sent_at' => $existingState['employee_day_3_sent_at'] ?? null,
|
||||
'admin_day_7_sent_at' => $existingState['admin_day_7_sent_at'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('timeErrorNotificationsClearResolvedStates')) {
|
||||
function timeErrorNotificationsClearResolvedStates(PDO $pdo, array $openEmployeeIds): int
|
||||
{
|
||||
timeErrorNotificationsEnsureTables($pdo);
|
||||
|
||||
if (empty($openEmployeeIds)) {
|
||||
$stmt = $pdo->prepare("DELETE FROM time_error_notification_state");
|
||||
$stmt->execute();
|
||||
return $stmt->rowCount();
|
||||
}
|
||||
|
||||
$placeholders = implode(',', array_fill(0, count($openEmployeeIds), '?'));
|
||||
$stmt = $pdo->prepare("DELETE FROM time_error_notification_state WHERE employee_id NOT IN ($placeholders)");
|
||||
$stmt->execute(array_values($openEmployeeIds));
|
||||
|
||||
return $stmt->rowCount();
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('timeErrorNotificationsMarkStageSent')) {
|
||||
function timeErrorNotificationsMarkStageSent(PDO $pdo, array $state, string $stage, string $recipientEmail): array
|
||||
{
|
||||
timeErrorNotificationsEnsureTables($pdo);
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO time_error_notifications (employee_id, cycle_started_on, notification_stage, recipient_email)
|
||||
VALUES (:employee_id, :cycle_started_on, :notification_stage, :recipient_email)
|
||||
ON DUPLICATE KEY UPDATE sent_at = sent_at"
|
||||
);
|
||||
$stmt->execute([
|
||||
'employee_id' => $state['employee_id'],
|
||||
'cycle_started_on' => $state['cycle_started_on'],
|
||||
'notification_stage' => $stage,
|
||||
'recipient_email' => $recipientEmail,
|
||||
]);
|
||||
|
||||
$now = (new DateTimeImmutable('now'))->format('Y-m-d H:i:s');
|
||||
$state['last_notification_stage'] = $stage;
|
||||
$state['last_notification_sent_at'] = $now;
|
||||
|
||||
if ($stage === 'employee_day_1') {
|
||||
$state['employee_day_1_sent_at'] = $now;
|
||||
} elseif ($stage === 'employee_day_3') {
|
||||
$state['employee_day_3_sent_at'] = $now;
|
||||
} elseif ($stage === 'admin_day_7') {
|
||||
$state['admin_day_7_sent_at'] = $now;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
"UPDATE time_error_notification_state
|
||||
SET last_notification_stage = :last_notification_stage,
|
||||
last_notification_sent_at = :last_notification_sent_at,
|
||||
employee_day_1_sent_at = :employee_day_1_sent_at,
|
||||
employee_day_3_sent_at = :employee_day_3_sent_at,
|
||||
admin_day_7_sent_at = :admin_day_7_sent_at
|
||||
WHERE employee_id = :employee_id"
|
||||
);
|
||||
$stmt->execute([
|
||||
'last_notification_stage' => $state['last_notification_stage'],
|
||||
'last_notification_sent_at' => $state['last_notification_sent_at'],
|
||||
'employee_day_1_sent_at' => $state['employee_day_1_sent_at'],
|
||||
'employee_day_3_sent_at' => $state['employee_day_3_sent_at'],
|
||||
'admin_day_7_sent_at' => $state['admin_day_7_sent_at'],
|
||||
'employee_id' => $state['employee_id'],
|
||||
]);
|
||||
|
||||
return $state;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('timeErrorNotificationsDaysSinceDate')) {
|
||||
function timeErrorNotificationsDaysSinceDate(string $date, ?DateTimeImmutable $today = null): int
|
||||
{
|
||||
$today = $today ?: new DateTimeImmutable('today');
|
||||
$reference = new DateTimeImmutable(substr($date, 0, 10));
|
||||
|
||||
return (int)$reference->diff($today)->days;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('timeErrorNotificationsFormatDateList')) {
|
||||
function timeErrorNotificationsFormatDateList(array $errorDates): string
|
||||
{
|
||||
$labels = array_map(static function (string $date): string {
|
||||
return date('d.m.Y', strtotime($date));
|
||||
}, $errorDates);
|
||||
|
||||
return implode(', ', $labels);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('timeErrorNotificationsBuildEmployeeMail')) {
|
||||
function timeErrorNotificationsGetTrackingUrl(): string
|
||||
{
|
||||
if (!empty($_SERVER['HTTP_HOST'])) {
|
||||
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https://' : 'http://';
|
||||
return $protocol . $_SERVER['HTTP_HOST'] . '/zeiterfassung/';
|
||||
}
|
||||
|
||||
return 'https://www.praxis-creutzburg.de/zeiterfassung/';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('timeErrorNotificationsBuildEmployeeMail')) {
|
||||
function timeErrorNotificationsBuildEmployeeMail(array $employee, string $stage): array
|
||||
{
|
||||
$name = trim($employee['vorname'] . ' ' . $employee['nachname']);
|
||||
$dateList = timeErrorNotificationsFormatDateList($employee['error_dates']);
|
||||
$trackingUrl = timeErrorNotificationsGetTrackingUrl();
|
||||
$subject = ($stage === 'employee_day_3')
|
||||
? 'Erneute Erinnerung zu offenen Zeitfehlern'
|
||||
: 'Erinnerung zu offenen Zeitfehlern';
|
||||
|
||||
$body = '<p>Hallo ' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . ',</p>'
|
||||
. '<p>in deiner Zeiterfassung gibt es weiterhin offene Buchungsfehler.</p>'
|
||||
. '<p><strong>Betroffene Tage:</strong> ' . htmlspecialchars($dateList, ENT_QUOTES, 'UTF-8') . '</p>'
|
||||
. '<p>Bitte korrigiere die Einträge in der Zeiterfassung. Solange die Fehler offen bleiben, wird der Vorgang weiter verfolgt.</p>'
|
||||
. '<p><a href="' . htmlspecialchars($trackingUrl, ENT_QUOTES, 'UTF-8') . '">Zur Zeiterfassung</a></p>';
|
||||
|
||||
return [
|
||||
'subject' => $subject,
|
||||
'body' => $body,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('timeErrorNotificationsBuildAdminMail')) {
|
||||
function timeErrorNotificationsBuildAdminMail(array $employee): array
|
||||
{
|
||||
$employeeName = trim($employee['vorname'] . ' ' . $employee['nachname']);
|
||||
$dateList = timeErrorNotificationsFormatDateList($employee['error_dates']);
|
||||
$trackingUrl = timeErrorNotificationsGetTrackingUrl();
|
||||
$subject = 'Eskalation: Offene Zeitfehler von ' . $employeeName;
|
||||
$body = '<p>Bei einem Mitarbeiter bestehen weiterhin offene Zeitfehler.</p>'
|
||||
. '<p><strong>Mitarbeiter:</strong> ' . htmlspecialchars($employeeName, ENT_QUOTES, 'UTF-8') . '<br>'
|
||||
. '<strong>E-Mail:</strong> ' . htmlspecialchars($employee['email'], ENT_QUOTES, 'UTF-8') . '<br>'
|
||||
. '<strong>Betroffene Tage:</strong> ' . htmlspecialchars($dateList, ENT_QUOTES, 'UTF-8') . '</p>'
|
||||
. '<p>Bitte prüfen Sie die Zeiterfassung und stimmen Sie die Korrektur mit dem Mitarbeiter ab.</p>'
|
||||
. '<p><a href="' . htmlspecialchars($trackingUrl, ENT_QUOTES, 'UTF-8') . '">Zur Zeiterfassung</a></p>';
|
||||
|
||||
return [
|
||||
'subject' => $subject,
|
||||
'body' => $body,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('timeErrorNotificationsProcess')) {
|
||||
function timeErrorNotificationsProcess(PDO $pdo, ?DateTimeImmutable $today = null): array
|
||||
{
|
||||
$today = $today ?: new DateTimeImmutable('today');
|
||||
timeErrorNotificationsEnsureTables($pdo);
|
||||
|
||||
$results = [
|
||||
'processed_errors' => 0,
|
||||
'affected_employees' => 0,
|
||||
'employee_day_1_sent' => 0,
|
||||
'employee_day_3_sent' => 0,
|
||||
'admin_day_7_sent' => 0,
|
||||
'resolved_states_cleared' => 0,
|
||||
'skipped_missing_employee_email' => 0,
|
||||
'skipped_missing_admin_email' => 0,
|
||||
'failed' => [],
|
||||
];
|
||||
|
||||
$entries = timeErrorNotificationsFetchInvalidEntries($pdo);
|
||||
$employees = timeErrorNotificationsGroupEntriesByEmployee($entries);
|
||||
$adminRecipients = timeErrorNotificationsFetchAdminRecipients($pdo);
|
||||
$states = timeErrorNotificationsFetchStateByEmployee($pdo);
|
||||
|
||||
$results['processed_errors'] = count($entries);
|
||||
$results['affected_employees'] = count($employees);
|
||||
$results['resolved_states_cleared'] = timeErrorNotificationsClearResolvedStates($pdo, array_keys($employees));
|
||||
|
||||
foreach ($employees as $employeeId => $employee) {
|
||||
$state = timeErrorNotificationsUpsertState($pdo, $employee, $states[$employeeId] ?? null);
|
||||
$daysSinceCycleStart = timeErrorNotificationsDaysSinceDate($state['cycle_started_on'], $today);
|
||||
$employeeEmail = trim($employee['email']);
|
||||
|
||||
if ($state['employee_day_1_sent_at'] === null && $daysSinceCycleStart >= 1) {
|
||||
if ($employeeEmail === '') {
|
||||
$results['skipped_missing_employee_email']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$mail = timeErrorNotificationsBuildEmployeeMail($employee, 'employee_day_1');
|
||||
if (timeErrorNotificationsSendMail($pdo, $employeeEmail, $mail['subject'], $mail['body'])) {
|
||||
$state = timeErrorNotificationsMarkStageSent($pdo, $state, 'employee_day_1', $employeeEmail);
|
||||
$results['employee_day_1_sent']++;
|
||||
} else {
|
||||
$results['failed'][] = [
|
||||
'stage' => 'employee_day_1',
|
||||
'employee_id' => $employeeId,
|
||||
'recipient_email' => $employeeEmail,
|
||||
];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($state['employee_day_3_sent_at'] === null && $daysSinceCycleStart >= 3) {
|
||||
if ($employeeEmail === '') {
|
||||
$results['skipped_missing_employee_email']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$mail = timeErrorNotificationsBuildEmployeeMail($employee, 'employee_day_3');
|
||||
if (timeErrorNotificationsSendMail($pdo, $employeeEmail, $mail['subject'], $mail['body'])) {
|
||||
$state = timeErrorNotificationsMarkStageSent($pdo, $state, 'employee_day_3', $employeeEmail);
|
||||
$results['employee_day_3_sent']++;
|
||||
} else {
|
||||
$results['failed'][] = [
|
||||
'stage' => 'employee_day_3',
|
||||
'employee_id' => $employeeId,
|
||||
'recipient_email' => $employeeEmail,
|
||||
];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($state['admin_day_7_sent_at'] !== null || empty($state['last_notification_sent_at'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$daysSinceLastNotification = timeErrorNotificationsDaysSinceDate($state['last_notification_sent_at'], $today);
|
||||
if ($daysSinceLastNotification < 7) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($adminRecipients)) {
|
||||
$results['skipped_missing_admin_email']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($adminRecipients as $adminRecipient) {
|
||||
$adminEmail = trim((string)$adminRecipient['email']);
|
||||
if ($adminEmail === '') {
|
||||
$results['skipped_missing_admin_email']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$mail = timeErrorNotificationsBuildAdminMail($employee);
|
||||
if (timeErrorNotificationsSendMail($pdo, $adminEmail, $mail['subject'], $mail['body'])) {
|
||||
$state = timeErrorNotificationsMarkStageSent($pdo, $state, 'admin_day_7', $adminEmail);
|
||||
$results['admin_day_7_sent']++;
|
||||
} else {
|
||||
$results['failed'][] = [
|
||||
'stage' => 'admin_day_7',
|
||||
'employee_id' => $employeeId,
|
||||
'recipient_email' => $adminEmail,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
if (!function_exists('vacationAbsenceTableExists')) {
|
||||
function vacationAbsenceTableExists(PDO $pdo, string $table): bool
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT COUNT(*)
|
||||
FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = :table_name"
|
||||
);
|
||||
$stmt->execute(['table_name' => $table]);
|
||||
return (int)$stmt->fetchColumn() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('vacationAbsenceTableHasColumn')) {
|
||||
function vacationAbsenceTableHasColumn(PDO $pdo, string $table, string $column): bool
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = :table_name
|
||||
AND COLUMN_NAME = :column_name"
|
||||
);
|
||||
$stmt->execute([
|
||||
'table_name' => $table,
|
||||
'column_name' => $column,
|
||||
]);
|
||||
return (int)$stmt->fetchColumn() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('vacationAbsenceReasonOptions')) {
|
||||
function vacationAbsenceReasonOptions(): array
|
||||
{
|
||||
return [
|
||||
'urlaub' => 'Urlaub',
|
||||
'krankheit_mit_atest' => 'Krankheit mit Attest',
|
||||
'krankheit_ohne_atest' => 'Krankheit ohne Attest',
|
||||
'berufsschule' => 'Berufsschule',
|
||||
'weiterbildung' => 'Weiterbildung',
|
||||
'persoenliche_gruende' => 'Persoenliche Gründe',
|
||||
'sonstiges' => 'Sonstiges',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('vacationAbsenceDefaultReason')) {
|
||||
function vacationAbsenceDefaultReason(): string
|
||||
{
|
||||
return 'urlaub';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('vacationAbsenceNormalizeReason')) {
|
||||
function vacationAbsenceNormalizeReason($reason): string
|
||||
{
|
||||
$reason = trim((string)$reason);
|
||||
$options = vacationAbsenceReasonOptions();
|
||||
|
||||
return array_key_exists($reason, $options) ? $reason : vacationAbsenceDefaultReason();
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('vacationAbsenceReasonLabel')) {
|
||||
function vacationAbsenceReasonLabel($reason): string
|
||||
{
|
||||
$reason = vacationAbsenceNormalizeReason($reason);
|
||||
$options = vacationAbsenceReasonOptions();
|
||||
|
||||
return $options[$reason] ?? $options[vacationAbsenceDefaultReason()];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('vacationAbsenceCountsAgainstEntitlement')) {
|
||||
function vacationAbsenceCountsAgainstEntitlement($reason): bool
|
||||
{
|
||||
return vacationAbsenceNormalizeReason($reason) === 'urlaub';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('vacationAbsenceEnsureSchema')) {
|
||||
function vacationAbsenceEnsureSchema(PDO $pdo): void
|
||||
{
|
||||
if (!vacationAbsenceTableExists($pdo, 'vacations')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!vacationAbsenceTableHasColumn($pdo, 'vacations', 'absence_reason')) {
|
||||
$pdo->exec("ALTER TABLE vacations ADD COLUMN absence_reason VARCHAR(50) NOT NULL DEFAULT 'urlaub'");
|
||||
}
|
||||
|
||||
$pdo->exec("
|
||||
UPDATE vacations
|
||||
SET absence_reason = 'urlaub'
|
||||
WHERE absence_reason IS NULL OR TRIM(absence_reason) = ''
|
||||
");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,21 +4,33 @@ require_once('inc/config.inc.php');
|
||||
require_once('inc/functions.inc.php');
|
||||
|
||||
$user = check_user();
|
||||
// personal calendar available to any logged-in user
|
||||
|
||||
include 'header.php';
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<h2>Mein Urlaubskalender</h2>
|
||||
<h2>Mein Abwesenheitskalender</h2>
|
||||
<p>Hier werden alle persönlichen Abwesenheiten angezeigt, inklusive Urlaub, Krankheit, Schule und Weiterbildung. Betriebsurlaub wird ebenfalls mit eingeblendet.</p>
|
||||
|
||||
<div id="calendar"></div>
|
||||
<br>
|
||||
<div>
|
||||
<span class="badge badge-success">genehmigt</span>
|
||||
<span class="badge badge-warning">beantragt</span>
|
||||
<div class="mb-3">
|
||||
<strong>Abwesenheitsarten:</strong><br>
|
||||
<span class="badge badge-success">Urlaub</span>
|
||||
<span class="badge badge-danger">Krankheit mit Attest</span>
|
||||
<span class="badge badge-dark">Krankheit ohne Attest</span>
|
||||
<span class="badge badge-primary">Berufsschule</span>
|
||||
<span class="badge badge-info">Weiterbildung</span>
|
||||
<span class="badge badge-warning">Persönliche Gründe</span>
|
||||
<span class="badge badge-secondary">Sonstiges</span>
|
||||
<span class="badge badge-primary">Betriebsurlaub</span>
|
||||
</div>
|
||||
<br>
|
||||
<div class="mb-3">
|
||||
<strong>Status:</strong><br>
|
||||
<span class="badge badge-success">Genehmigt</span>
|
||||
<span class="badge badge-warning">Beantragt</span>
|
||||
<span class="badge badge-secondary">Abgelehnt</span>
|
||||
</div>
|
||||
<div id="eventDetails" style="display:none;">
|
||||
<h4>Details</h4>
|
||||
<div id="detailsContent"></div>
|
||||
@@ -31,24 +43,43 @@ include 'header.php';
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
|
||||
function formatAllDayRange(start, end) {
|
||||
var startLabel = start.toLocaleDateString('de-DE');
|
||||
if (!end) {
|
||||
return startLabel;
|
||||
}
|
||||
|
||||
var inclusiveEnd = new Date(end.getTime() - 24 * 60 * 60 * 1000);
|
||||
return startLabel + ' - ' + inclusiveEnd.toLocaleDateString('de-DE');
|
||||
}
|
||||
|
||||
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 + '&only_personal=1';
|
||||
fetch(url).then(function(res){ return res.json(); }).then(function(data){ successCallback(data); }).catch(function(err){ failureCallback(err); });
|
||||
var url = 'api/vacations.php?start=' + info.startStr + '&end=' + info.endStr + '&scope=personal&include_company=1&status_filter=all';
|
||||
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>';
|
||||
var props = ev.extendedProps || {};
|
||||
var html = '<strong>' + ev.title + '</strong><br>' +
|
||||
formatAllDayRange(ev.start, ev.end) + '<br>';
|
||||
|
||||
if (props.type === 'user') {
|
||||
html += 'Status: ' + (props.status || '') + '<br>';
|
||||
html += 'Kommentar: ' + (props.comment || '') + '<br>';
|
||||
html += 'Abwesenheitsgrund: ' + (props.absence_label || props.absence_type || '') + '<br>';
|
||||
html += 'Status: ' + (props.status_label || 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';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
require_once __DIR__ . '/inc/config.inc.php';
|
||||
require_once __DIR__ . '/inc/functions.inc.php';
|
||||
require_once __DIR__ . '/inc/time_error_notifications.inc.php';
|
||||
|
||||
$isCli = PHP_SAPI === 'cli';
|
||||
|
||||
if (!$isCli) {
|
||||
$user = check_user();
|
||||
if (!$user || !is_admin_user()) {
|
||||
http_response_code(403);
|
||||
exit('Keine Berechtigung.');
|
||||
}
|
||||
}
|
||||
|
||||
$result = timeErrorNotificationsProcess($pdo);
|
||||
|
||||
if ($isCli) {
|
||||
echo "Zeiterfassungs-Benachrichtigungen ausgefuehrt\n";
|
||||
echo "Fehlerhafte Tage geprueft: " . $result['processed_errors'] . "\n";
|
||||
echo "Betroffene Mitarbeiter: " . $result['affected_employees'] . "\n";
|
||||
echo "Mitarbeiter-Erinnerungen Tag 1: " . $result['employee_day_1_sent'] . "\n";
|
||||
echo "Mitarbeiter-Erinnerungen Tag 3: " . $result['employee_day_3_sent'] . "\n";
|
||||
echo "Admin-Benachrichtigungen Tag 7: " . $result['admin_day_7_sent'] . "\n";
|
||||
echo "Bereinigte geloeste Status: " . $result['resolved_states_cleared'] . "\n";
|
||||
echo "Ohne Mitarbeiter-E-Mail uebersprungen: " . $result['skipped_missing_employee_email'] . "\n";
|
||||
echo "Ohne Admin-E-Mail uebersprungen: " . $result['skipped_missing_admin_email'] . "\n";
|
||||
echo "Fehlgeschlagene Sendungen: " . count($result['failed']) . "\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
include __DIR__ . '/header.php';
|
||||
?>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h2>Zeiterfassungs-Benachrichtigungen</h2>
|
||||
<div class="alert alert-info" role="alert">
|
||||
<strong>Gepruefte fehlerhafte Tage:</strong> <?php echo (int)$result['processed_errors']; ?><br>
|
||||
<strong>Betroffene Mitarbeiter:</strong> <?php echo (int)$result['affected_employees']; ?><br>
|
||||
<strong>Mitarbeiter-Erinnerungen Tag 1:</strong> <?php echo (int)$result['employee_day_1_sent']; ?><br>
|
||||
<strong>Mitarbeiter-Erinnerungen Tag 3:</strong> <?php echo (int)$result['employee_day_3_sent']; ?><br>
|
||||
<strong>Admin-Benachrichtigungen Tag 7:</strong> <?php echo (int)$result['admin_day_7_sent']; ?><br>
|
||||
<strong>Bereinigte geloeste Status:</strong> <?php echo (int)$result['resolved_states_cleared']; ?><br>
|
||||
<strong>Ohne Mitarbeiter-E-Mail uebersprungen:</strong> <?php echo (int)$result['skipped_missing_employee_email']; ?><br>
|
||||
<strong>Ohne Admin-E-Mail uebersprungen:</strong> <?php echo (int)$result['skipped_missing_admin_email']; ?><br>
|
||||
<strong>Fehlgeschlagene Sendungen:</strong> <?php echo count($result['failed']); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php include __DIR__ . '/footer.php'; ?>
|
||||
@@ -2,8 +2,10 @@
|
||||
session_start();
|
||||
require_once("inc/config.inc.php");
|
||||
require_once("inc/functions.inc.php");
|
||||
require_once("inc/vacation_absence.inc.php");
|
||||
|
||||
$user = check_user();
|
||||
vacationAbsenceEnsureSchema($pdo);
|
||||
|
||||
if (!isset($_SESSION['userid'])) {
|
||||
die("Kein Benutzer angemeldet.");
|
||||
@@ -21,7 +23,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['vacation_id'])) {
|
||||
|
||||
$vacation_id = (int)$_POST['vacation_id'];
|
||||
$action = $_POST['action'];
|
||||
$comment_admin = trim($_POST['comment_admin']);
|
||||
$comment_admin = trim((string)($_POST['comment_admin'] ?? ''));
|
||||
|
||||
if ($action == "genehmigen") {
|
||||
$status = "genehmigt";
|
||||
@@ -61,10 +63,10 @@ $antraege = $stmt->fetchAll();
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<h2>Urlaubsanträge genehmigen</h2>
|
||||
<h2>Abwesenheitsanträge genehmigen</h2>
|
||||
|
||||
<?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 if (count($antraege) == 0): ?>
|
||||
@@ -74,6 +76,7 @@ $antraege = $stmt->fetchAll();
|
||||
<table class="table table-bordered table-striped">
|
||||
<tr>
|
||||
<th>Mitarbeiter</th>
|
||||
<th>Grund</th>
|
||||
<th>Von</th>
|
||||
<th>Bis</th>
|
||||
<th>Tage</th>
|
||||
@@ -84,14 +87,15 @@ $antraege = $stmt->fetchAll();
|
||||
<?php foreach ($antraege as $a): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($a['vorname'] . " " . $a['nachname']); ?></td>
|
||||
<td><?php echo $a['start_date']; ?></td>
|
||||
<td><?php echo $a['end_date']; ?></td>
|
||||
<td><?php echo $a['days']; ?></td>
|
||||
<td><?php echo htmlspecialchars($a['comment_user']); ?></td>
|
||||
<td><?php echo htmlspecialchars(vacationAbsenceReasonLabel($a['absence_reason'] ?? 'urlaub')); ?></td>
|
||||
<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 echo htmlspecialchars((string)($a['comment_user'] ?? '')); ?></td>
|
||||
<td>
|
||||
|
||||
<form method="post" style="display:inline;">
|
||||
<input type="hidden" name="vacation_id" value="<?php echo $a['id']; ?>">
|
||||
<input type="hidden" name="vacation_id" value="<?php echo (int)$a['id']; ?>">
|
||||
<input type="hidden" name="action" value="genehmigen">
|
||||
<button type="submit" class="btn btn-success btn-sm">
|
||||
Genehmigen
|
||||
@@ -99,7 +103,7 @@ $antraege = $stmt->fetchAll();
|
||||
</form>
|
||||
|
||||
<form method="post" style="display:inline;">
|
||||
<input type="hidden" name="vacation_id" value="<?php echo $a['id']; ?>">
|
||||
<input type="hidden" name="vacation_id" value="<?php echo (int)$a['id']; ?>">
|
||||
<input type="hidden" name="action" value="ablehnen">
|
||||
<button type="submit" class="btn btn-danger btn-sm">
|
||||
Ablehnen
|
||||
|
||||
+146
-57
@@ -2,16 +2,36 @@
|
||||
session_start();
|
||||
require_once("inc/config.inc.php");
|
||||
require_once("inc/functions.inc.php");
|
||||
require_once("inc/vacation_absence.inc.php");
|
||||
|
||||
$user = check_user();
|
||||
vacationAbsenceEnsureSchema($pdo);
|
||||
|
||||
if (!isset($_SESSION['userid'])) {
|
||||
die("Kein Benutzer angemeldet.");
|
||||
}
|
||||
|
||||
$user_id = $_SESSION['userid'];
|
||||
$user_id = (int)$_SESSION['userid'];
|
||||
$canManageTeamVacations = can_manage_team_vacations();
|
||||
$message = "";
|
||||
$error = "";
|
||||
$selected_user_id = $user_id;
|
||||
$selected_absence_reason = vacationAbsenceDefaultReason();
|
||||
$start_date = '';
|
||||
$end_date = '';
|
||||
$comment = '';
|
||||
|
||||
$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);
|
||||
@@ -22,9 +42,8 @@ function calculateWorkingDays($start, $end) {
|
||||
$period = new DatePeriod($start, $interval, $end);
|
||||
|
||||
$workingDays = 0;
|
||||
|
||||
foreach ($period as $day) {
|
||||
if ($day->format('N') < 6) { // 1 (Mo) - 5 (Fr)
|
||||
if ($day->format('N') < 6) {
|
||||
$workingDays++;
|
||||
}
|
||||
}
|
||||
@@ -32,48 +51,74 @@ function calculateWorkingDays($start, $end) {
|
||||
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_absence_reason = vacationAbsenceNormalizeReason($_POST['absence_reason'] ?? vacationAbsenceDefaultReason());
|
||||
$selected_user_id = $canManageTeamVacations ? (int)($_POST['user_id'] ?? $user_id) : $user_id;
|
||||
|
||||
$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.";
|
||||
$selectedUser = null;
|
||||
if ($selected_user_id <= 0) {
|
||||
$error = "Bitte einen Mitarbeiter auswaehlen.";
|
||||
} 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)
|
||||
)
|
||||
$stmtSelectedUser = $pdo->prepare("
|
||||
SELECT id, vorname, nachname
|
||||
FROM users
|
||||
WHERE id = ?
|
||||
AND zeiterfassung = 1
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([$user_id, $start_date, $end_date, $start_date, $end_date, $start_date]);
|
||||
$exists = $stmt->fetchColumn();
|
||||
$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 === "" && vacationAbsenceCountsAgainstEntitlement($selected_absence_reason) && $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 überschneidet sich mit einem bestehenden Antrag.";
|
||||
$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 INTO vacations (user_id, start_date, end_date, days, comment_user, absence_reason)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
");
|
||||
$insert->execute([$selected_user_id, $start_date, $end_date, $days, $comment, $selected_absence_reason]);
|
||||
|
||||
$insert->execute([$user_id, $start_date, $end_date, $days, $comment]);
|
||||
|
||||
$message = "Urlaubsantrag erfolgreich eingereicht ($days Werktage).";
|
||||
$reasonLabel = vacationAbsenceReasonLabel($selected_absence_reason);
|
||||
if ($selected_user_id !== $user_id && $selectedUser) {
|
||||
$message = "Abwesenheit fuer " . $selectedUser['vorname'] . " " . $selectedUser['nachname'] . " erfolgreich eingetragen ($days Werktage, Grund: " . $reasonLabel . ").";
|
||||
} else {
|
||||
if ($selected_absence_reason === 'urlaub') {
|
||||
$message = "Urlaubsantrag erfolgreich eingereicht ($days Werktage).";
|
||||
} else {
|
||||
$message = "Abwesenheitsantrag erfolgreich eingereicht ($days Werktage, Grund: " . $reasonLabel . ").";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,57 +130,97 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
<div class="row">
|
||||
<div class="col-md-8 offset-md-2">
|
||||
|
||||
<h2>Urlaubsantrag</h2>
|
||||
<h2>Abwesenheitsantrag</h2>
|
||||
|
||||
<?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 if ($message): ?>
|
||||
<div class="alert alert-success"><?php echo $message; ?></div>
|
||||
<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>Abwesenheitsgrund:</label>
|
||||
<select name="absence_reason" class="form-control" required>
|
||||
<?php foreach (vacationAbsenceReasonOptions() as $reasonKey => $reasonLabel): ?>
|
||||
<option value="<?php echo htmlspecialchars($reasonKey); ?>" <?php echo ($selected_absence_reason === $reasonKey) ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($reasonLabel); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<small class="form-text text-muted">Nur Urlaub wird auf das Urlaubskontingent angerechnet.</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Von:</label>
|
||||
<input type="date" name="start_date" class="form-control" required>
|
||||
<input type="date" name="start_date" class="form-control" value="<?php echo htmlspecialchars($start_date); ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Bis:</label>
|
||||
<input type="date" name="end_date" class="form-control" required>
|
||||
<input type="date" name="end_date" class="form-control" value="<?php echo htmlspecialchars($end_date); ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Kommentar (optional):</label>
|
||||
<textarea name="comment" class="form-control"></textarea>
|
||||
<textarea name="comment" class="form-control"><?php echo htmlspecialchars($comment); ?></textarea>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-block">
|
||||
Urlaub beantragen
|
||||
<?php echo $canManageTeamVacations ? 'Abwesenheit eintragen' : 'Abwesenheit beantragen'; ?>
|
||||
</button>
|
||||
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>Meine Anträge</h4>
|
||||
<h4><?php echo $canManageTeamVacations ? 'Abwesenheitseintraege' : 'Meine Antraege'; ?></h4>
|
||||
|
||||
<?php
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT * FROM vacations
|
||||
WHERE user_id = ?
|
||||
ORDER BY created_at DESC
|
||||
");
|
||||
$stmt->execute([$user_id]);
|
||||
$antraege = $stmt->fetchAll();
|
||||
$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>Grund</th>
|
||||
<th>Von</th>
|
||||
<th>Bis</th>
|
||||
<th>Tage</th>
|
||||
@@ -145,14 +230,18 @@ $antraege = $stmt->fetchAll();
|
||||
|
||||
<?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>
|
||||
<?php if ($canManageTeamVacations): ?>
|
||||
<td><?php echo htmlspecialchars(trim($a['vorname'] . ' ' . $a['nachname'])); ?></td>
|
||||
<?php endif; ?>
|
||||
<td><?php echo htmlspecialchars(vacationAbsenceReasonLabel($a['absence_reason'] ?? 'urlaub')); ?></td>
|
||||
<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') {
|
||||
if ($a['status'] === 'beantragt' || $a['status'] === null || $a['status'] === '') {
|
||||
echo '<span class="badge badge-warning">Beantragt</span>';
|
||||
} elseif ($a['status'] == 'genehmigt') {
|
||||
} elseif ($a['status'] === 'genehmigt') {
|
||||
echo '<span class="badge badge-success">Genehmigt</span>';
|
||||
} else {
|
||||
echo '<span class="badge badge-danger">Abgelehnt</span>';
|
||||
@@ -160,10 +249,10 @@ $antraege = $stmt->fetchAll();
|
||||
?>
|
||||
</td>
|
||||
<td>
|
||||
<form method="post" action="deleteVacation.php" onsubmit="return confirm('Wirklich löschen?');">
|
||||
<input type="hidden" name="id" value="<?php echo $a['id']; ?>">
|
||||
<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">Löschen</button>
|
||||
<button type="submit" class="btn btn-sm btn-danger">Loeschen</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -13,14 +13,21 @@ include 'header.php';
|
||||
|
||||
<div class="container">
|
||||
<h2>Team Urlaubskalender</h2>
|
||||
<p>Admin-Sicht auf den Teamkalender. Angezeigt werden nur Urlaub von Personen sowie Betriebsurlaub.</p>
|
||||
|
||||
<div id="calendar"></div>
|
||||
<br>
|
||||
<div>
|
||||
<span class="badge badge-success">genehmigt</span>
|
||||
<span class="badge badge-warning">beantragt</span>
|
||||
<div class="mb-3">
|
||||
<strong>Anzeige:</strong><br>
|
||||
<span class="badge badge-success">Urlaub</span>
|
||||
<span class="badge badge-primary">Betriebsurlaub</span>
|
||||
</div>
|
||||
<br>
|
||||
<div class="mb-3">
|
||||
<strong>Status:</strong><br>
|
||||
<span class="badge badge-success">Genehmigt</span>
|
||||
<span class="badge badge-warning">Beantragt</span>
|
||||
<span class="badge badge-secondary">Abgelehnt</span>
|
||||
</div>
|
||||
<div id="eventDetails" style="display:none;">
|
||||
<h4>Details</h4>
|
||||
<div id="detailsContent"></div>
|
||||
@@ -33,24 +40,44 @@ include 'header.php';
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
|
||||
function formatAllDayRange(start, end) {
|
||||
var startLabel = start.toLocaleDateString('de-DE');
|
||||
if (!end) {
|
||||
return startLabel;
|
||||
}
|
||||
|
||||
var inclusiveEnd = new Date(end.getTime() - 24 * 60 * 60 * 1000);
|
||||
return startLabel + ' - ' + inclusiveEnd.toLocaleDateString('de-DE');
|
||||
}
|
||||
|
||||
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;
|
||||
fetch(url).then(function(res){ return res.json(); }).then(function(data){ successCallback(data); }).catch(function(err){ failureCallback(err); });
|
||||
var url = 'api/vacations.php?start=' + info.startStr + '&end=' + info.endStr + '&scope=team&include_company=1&status_filter=active';
|
||||
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>';
|
||||
var props = ev.extendedProps || {};
|
||||
var html = '<strong>' + ev.title + '</strong><br>' +
|
||||
formatAllDayRange(ev.start, ev.end) + '<br>';
|
||||
|
||||
if (props.type === 'user') {
|
||||
html += 'Status: ' + (props.status || '') + '<br>';
|
||||
html += 'Kommentar: ' + (props.comment || '') + '<br>';
|
||||
html += 'Mitarbeiter: ' + (props.employee_name || '') + '<br>';
|
||||
html += 'Abwesenheitsgrund: ' + (props.absence_label || props.absence_type || '') + '<br>';
|
||||
html += 'Status: ' + (props.status_label || 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';
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ 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';
|
||||
@@ -11,13 +10,21 @@ include 'header.php';
|
||||
|
||||
<div class="container">
|
||||
<h2>Team Urlaubskalender</h2>
|
||||
<p>Dieser Kalender zeigt nur Urlaub von Personen sowie Betriebsurlaub. Andere Abwesenheitsarten werden hier bewusst ausgeblendet.</p>
|
||||
|
||||
<div id="calendar"></div>
|
||||
<br>
|
||||
<div>
|
||||
<span class="badge badge-success">genehmigt</span>
|
||||
<div class="mb-3">
|
||||
<strong>Anzeige:</strong><br>
|
||||
<span class="badge badge-success">Urlaub</span>
|
||||
<span class="badge badge-primary">Betriebsurlaub</span>
|
||||
</div>
|
||||
<br>
|
||||
<div class="mb-3">
|
||||
<strong>Status:</strong><br>
|
||||
<span class="badge badge-success">Genehmigt</span>
|
||||
<span class="badge badge-warning">Beantragt</span>
|
||||
<span class="badge badge-secondary">Abgelehnt</span>
|
||||
</div>
|
||||
<div id="eventDetails" style="display:none;">
|
||||
<h4>Details</h4>
|
||||
<div id="detailsContent"></div>
|
||||
@@ -30,23 +37,44 @@ include 'header.php';
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
|
||||
function formatAllDayRange(start, end) {
|
||||
var startLabel = start.toLocaleDateString('de-DE');
|
||||
if (!end) {
|
||||
return startLabel;
|
||||
}
|
||||
|
||||
var inclusiveEnd = new Date(end.getTime() - 24 * 60 * 60 * 1000);
|
||||
return startLabel + ' - ' + inclusiveEnd.toLocaleDateString('de-DE');
|
||||
}
|
||||
|
||||
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); });
|
||||
var url = 'api/vacations.php?start=' + info.startStr + '&end=' + info.endStr + '&scope=team&include_company=1&status_filter=active';
|
||||
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>';
|
||||
var props = ev.extendedProps || {};
|
||||
var html = '<strong>' + ev.title + '</strong><br>' +
|
||||
formatAllDayRange(ev.start, ev.end) + '<br>';
|
||||
|
||||
if (props.type === 'user') {
|
||||
html += 'Mitarbeiter-ID: ' + (props.user_id || '') + '<br>';
|
||||
html += 'Mitarbeiter: ' + (props.employee_name || '') + '<br>';
|
||||
html += 'Abwesenheitsgrund: ' + (props.absence_label || props.absence_type || '') + '<br>';
|
||||
html += 'Status: ' + (props.status_label || 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';
|
||||
}
|
||||
|
||||
@@ -2,60 +2,167 @@
|
||||
session_start();
|
||||
require_once('inc/config.inc.php');
|
||||
require_once('inc/functions.inc.php');
|
||||
require_once __DIR__ . '/inc/vacation_absence.inc.php';
|
||||
|
||||
$user = check_user();
|
||||
if (!is_admin_user()) {
|
||||
die('Zugriff verweigert. Nur Chefs dürfen die Urlaubsübersicht sehen.');
|
||||
die('Zugriff verweigert. Nur Chefs dürfen die Abwesenheitsübersicht sehen.');
|
||||
}
|
||||
|
||||
$schemaWarning = '';
|
||||
try {
|
||||
vacationAbsenceEnsureSchema($pdo);
|
||||
} catch (Throwable $e) {
|
||||
$schemaWarning = 'Das Abwesenheitsschema konnte nicht automatisch aktualisiert werden: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
include 'header.php';
|
||||
|
||||
// Jahr für Auswertung
|
||||
$year = date('Y');
|
||||
$yearStart = $year . '-01-01';
|
||||
$nextYearStart = (string)((int)$year + 1) . '-01-01';
|
||||
|
||||
$reasonOptions = vacationAbsenceReasonOptions();
|
||||
$reasonKeys = array_keys($reasonOptions);
|
||||
|
||||
function vacationOverviewStatusLabel(?string $status): string
|
||||
{
|
||||
$status = trim((string)$status);
|
||||
if ($status === '' || $status === 'beantragt') {
|
||||
return 'Beantragt';
|
||||
}
|
||||
if ($status === 'genehmigt') {
|
||||
return 'Genehmigt';
|
||||
}
|
||||
if ($status === 'abgelehnt') {
|
||||
return 'Abgelehnt';
|
||||
}
|
||||
return ucfirst($status);
|
||||
}
|
||||
|
||||
// Lade alle Mitarbeiter
|
||||
$stmt = $pdo->prepare("SELECT id, vorname, nachname, email, urlaubstage FROM users ORDER BY nachname, vorname");
|
||||
$stmt->execute();
|
||||
$users = $stmt->fetchAll();
|
||||
|
||||
$statsStmt = $pdo->prepare("
|
||||
SELECT
|
||||
user_id,
|
||||
COALESCE(SUM(CASE WHEN LOWER(TRIM(COALESCE(absence_reason, 'urlaub'))) = 'urlaub' AND LOWER(TRIM(COALESCE(status, ''))) = 'genehmigt' THEN COALESCE(days, 0) ELSE 0 END), 0) AS urlaub_used_days,
|
||||
COALESCE(SUM(CASE WHEN LOWER(TRIM(COALESCE(status, ''))) != 'abgelehnt' THEN COALESCE(days, 0) ELSE 0 END), 0) AS total_absence_days,
|
||||
COALESCE(SUM(CASE WHEN LOWER(TRIM(COALESCE(status, ''))) = 'beantragt' OR TRIM(COALESCE(status, '')) = '' THEN 1 ELSE 0 END), 0) AS pending_count,
|
||||
COALESCE(SUM(CASE WHEN LOWER(TRIM(COALESCE(absence_reason, 'urlaub'))) = 'urlaub' AND LOWER(TRIM(COALESCE(status, ''))) != 'abgelehnt' THEN COALESCE(days, 0) ELSE 0 END), 0) AS urlaub_total_days,
|
||||
COALESCE(SUM(CASE WHEN LOWER(TRIM(COALESCE(absence_reason, 'urlaub'))) = 'krankheit_mit_atest' AND LOWER(TRIM(COALESCE(status, ''))) != 'abgelehnt' THEN COALESCE(days, 0) ELSE 0 END), 0) AS krankheit_mit_atest_days,
|
||||
COALESCE(SUM(CASE WHEN LOWER(TRIM(COALESCE(absence_reason, 'urlaub'))) = 'krankheit_ohne_atest' AND LOWER(TRIM(COALESCE(status, ''))) != 'abgelehnt' THEN COALESCE(days, 0) ELSE 0 END), 0) AS krankheit_ohne_atest_days,
|
||||
COALESCE(SUM(CASE WHEN LOWER(TRIM(COALESCE(absence_reason, 'urlaub'))) = 'berufsschule' AND LOWER(TRIM(COALESCE(status, ''))) != 'abgelehnt' THEN COALESCE(days, 0) ELSE 0 END), 0) AS berufsschule_days,
|
||||
COALESCE(SUM(CASE WHEN LOWER(TRIM(COALESCE(absence_reason, 'urlaub'))) = 'weiterbildung' AND LOWER(TRIM(COALESCE(status, ''))) != 'abgelehnt' THEN COALESCE(days, 0) ELSE 0 END), 0) AS weiterbildung_days,
|
||||
COALESCE(SUM(CASE WHEN LOWER(TRIM(COALESCE(absence_reason, 'urlaub'))) = 'persoenliche_gruende' AND LOWER(TRIM(COALESCE(status, ''))) != 'abgelehnt' THEN COALESCE(days, 0) ELSE 0 END), 0) AS persoenliche_gruende_days,
|
||||
COALESCE(SUM(CASE WHEN LOWER(TRIM(COALESCE(absence_reason, 'urlaub'))) = 'sonstiges' AND LOWER(TRIM(COALESCE(status, ''))) != 'abgelehnt' THEN COALESCE(days, 0) ELSE 0 END), 0) AS sonstiges_days
|
||||
FROM vacations
|
||||
WHERE start_date >= :year_start
|
||||
AND start_date < :next_year_start
|
||||
GROUP BY user_id
|
||||
");
|
||||
$statsStmt->execute([
|
||||
'year_start' => $yearStart,
|
||||
'next_year_start' => $nextYearStart,
|
||||
]);
|
||||
$statsRows = $statsStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
$statsByUser = [];
|
||||
foreach ($statsRows as $row) {
|
||||
$statsByUser[(int)$row['user_id']] = $row;
|
||||
}
|
||||
|
||||
$upcomingStmt = $pdo->prepare("
|
||||
SELECT
|
||||
v.user_id,
|
||||
v.start_date,
|
||||
v.end_date,
|
||||
v.days,
|
||||
v.status,
|
||||
v.absence_reason,
|
||||
u.vorname,
|
||||
u.nachname
|
||||
FROM vacations v
|
||||
JOIN users u ON u.id = v.user_id
|
||||
WHERE v.end_date >= CURDATE()
|
||||
AND LOWER(TRIM(COALESCE(v.status, ''))) != 'abgelehnt'
|
||||
ORDER BY v.start_date ASC, v.end_date ASC
|
||||
");
|
||||
$upcomingStmt->execute();
|
||||
$upcomingRows = $upcomingStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
$upcomingByUser = [];
|
||||
foreach ($upcomingRows as $row) {
|
||||
$uid = (int)$row['user_id'];
|
||||
if (!isset($upcomingByUser[$uid])) {
|
||||
$upcomingByUser[$uid] = [];
|
||||
}
|
||||
if (count($upcomingByUser[$uid]) < 5) {
|
||||
$upcomingByUser[$uid][] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
function vacationOverviewReasonValue(array $statsByUser, int $userId, string $key): int
|
||||
{
|
||||
$map = [
|
||||
'urlaub' => 'urlaub_total_days',
|
||||
'krankheit_mit_atest' => 'krankheit_mit_atest_days',
|
||||
'krankheit_ohne_atest' => 'krankheit_ohne_atest_days',
|
||||
'berufsschule' => 'berufsschule_days',
|
||||
'weiterbildung' => 'weiterbildung_days',
|
||||
'persoenliche_gruende' => 'persoenliche_gruende_days',
|
||||
'sonstiges' => 'sonstiges_days',
|
||||
];
|
||||
|
||||
$field = $map[$key] ?? null;
|
||||
if ($field === null || !isset($statsByUser[$userId])) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int)($statsByUser[$userId][$field] ?? 0);
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<h2>Urlaubsübersicht (<?php echo $year; ?>)</h2>
|
||||
<h2>Abwesenheitsübersicht (<?php echo $year; ?>)</h2>
|
||||
|
||||
<?php if (!empty($schemaWarning)): ?>
|
||||
<div class="alert alert-warning"><?php echo htmlspecialchars($schemaWarning); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Hinweis:</strong> Nur der Grund <em>Urlaub</em> zählt auf den Urlaubsanspruch. Alle anderen Abwesenheitsgründe werden hier zusätzlich pro Jahr ausgewertet.
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mitarbeiter</th>
|
||||
<th>Email</th>
|
||||
<th>Anspruch</th>
|
||||
<th>Genutzt (<?php echo $year; ?>)</th>
|
||||
<th>Urlaub genutzt (<?php echo $year; ?>)</th>
|
||||
<th>Verbleibend</th>
|
||||
<th>Ausstehend</th>
|
||||
<th>Bevorstehende Urlaube</th>
|
||||
<th>Alle Abwesenheiten</th>
|
||||
<th>Offene Anträge</th>
|
||||
<?php foreach ($reasonKeys as $reasonKey): ?>
|
||||
<th><?php echo htmlspecialchars(vacationAbsenceReasonLabel($reasonKey)); ?></th>
|
||||
<?php endforeach; ?>
|
||||
<th>Bevorstehende Einträge</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($users as $u):
|
||||
$uid = $u['id'];
|
||||
|
||||
// Genutzte Tage (genehmigt) im Jahr
|
||||
$s = $pdo->prepare("SELECT IFNULL(SUM(days),0) AS used FROM vacations WHERE user_id = ? AND status = 'genehmigt' AND YEAR(start_date) = ?");
|
||||
$s->execute([$uid, $year]);
|
||||
$used = (int)$s->fetchColumn();
|
||||
|
||||
// Ausstehende Anträge
|
||||
$p = $pdo->prepare("SELECT COUNT(*) FROM vacations WHERE user_id = ? AND status = 'beantragt'");
|
||||
$p->execute([$uid]);
|
||||
$pending = (int)$p->fetchColumn();
|
||||
|
||||
// Bevorstehende Urlaube (nächste 5)
|
||||
$n = $pdo->prepare("SELECT start_date, end_date, days, status FROM vacations WHERE user_id = ? AND end_date >= CURDATE() ORDER BY start_date LIMIT 5");
|
||||
$n->execute([$uid]);
|
||||
$upcoming = $n->fetchAll();
|
||||
|
||||
$entitlement = isset($u['urlaubstage']) ? (int)$u['urlaubstage'] : 0;
|
||||
$used = (int)($statsByUser[$uid]['urlaub_used_days'] ?? 0);
|
||||
$remaining = $entitlement - $used;
|
||||
$totalAbsences = (int)($statsByUser[$uid]['total_absence_days'] ?? 0);
|
||||
$pending = (int)($statsByUser[$uid]['pending_count'] ?? 0);
|
||||
$upcoming = $upcomingByUser[$uid] ?? [];
|
||||
?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($u['vorname'] . ' ' . $u['nachname']); ?></td>
|
||||
@@ -63,11 +170,17 @@ $users = $stmt->fetchAll();
|
||||
<td><?php echo $entitlement; ?></td>
|
||||
<td><?php echo $used; ?></td>
|
||||
<td><?php echo $remaining; ?></td>
|
||||
<td><?php echo $totalAbsences; ?></td>
|
||||
<td><?php echo $pending; ?></td>
|
||||
<?php foreach ($reasonKeys as $reasonKey): ?>
|
||||
<td><?php echo vacationOverviewReasonValue($statsByUser, (int)$uid, $reasonKey); ?></td>
|
||||
<?php endforeach; ?>
|
||||
<td>
|
||||
<?php if (count($upcoming) == 0) { echo '-'; } else {
|
||||
foreach ($upcoming as $up) {
|
||||
echo htmlspecialchars($up['start_date'] . ' → ' . $up['end_date'] . ' (' . $up['days'] . 'd) ' . ' [' . $up['status'] . ']');
|
||||
$reasonLabel = vacationAbsenceReasonLabel($up['absence_reason'] ?? 'urlaub');
|
||||
$statusLabel = vacationOverviewStatusLabel($up['status'] ?? null);
|
||||
echo htmlspecialchars($up['start_date'] . ' → ' . $up['end_date'] . ' (' . $up['days'] . 'd) ' . ' [' . $reasonLabel . ', ' . $statusLabel . ']');
|
||||
echo '<br>';
|
||||
}
|
||||
} ?>
|
||||
@@ -76,8 +189,9 @@ $users = $stmt->fetchAll();
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p>Hinweis: Ansprüche werden aus dem Feld <strong>users.urlaubstage</strong> gelesen. Falls dieses Feld leer ist, bitte in der Nutzerverwaltung pflegen.</p>
|
||||
<p>Hinweis: Der Urlaubsanspruch wird weiterhin aus <strong>users.urlaubstage</strong> gelesen. Die zusätzlichen Spalten zeigen die Abwesenheiten je Grund pro Jahr.</p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user