416 lines
12 KiB
PHP
416 lines
12 KiB
PHP
<?php
|
|
session_start();
|
|
require_once(__DIR__ . '/../inc/config.inc.php');
|
|
require_once(__DIR__ . '/../inc/functions.inc.php');
|
|
require_once(__DIR__ . '/../inc/vacation_absence.inc.php');
|
|
|
|
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 = trim((string)($_GET['start'] ?? ''));
|
|
$end = trim((string)($_GET['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 = [];
|
|
|
|
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 {
|
|
$title = $typeLabel;
|
|
}
|
|
|
|
if ($status !== '' && $status !== 'genehmigt') {
|
|
$title .= ' (' . $statusLabel . ')';
|
|
}
|
|
|
|
try {
|
|
$endInclusive = (new DateTime($v['end_date']))->modify('+1 day')->format('Y-m-d');
|
|
} catch (Exception $e) {
|
|
$endInclusive = $v['start_date'];
|
|
}
|
|
|
|
$backgroundColor = vacationApiTypeColor($absenceType);
|
|
if ($status === 'abgelehnt') {
|
|
$backgroundColor = vacationApiStatusColor($status);
|
|
}
|
|
|
|
$events[] = [
|
|
'id' => 'vac_' . $v['id'],
|
|
'title' => $title,
|
|
'start' => $v['start_date'],
|
|
'end' => $endInclusive,
|
|
'allDay' => true,
|
|
'backgroundColor' => $backgroundColor,
|
|
'borderColor' => $backgroundColor,
|
|
'textColor' => '#ffffff',
|
|
'extendedProps' => [
|
|
'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,
|
|
],
|
|
];
|
|
}
|
|
|
|
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;
|
|
?>
|