diff --git a/Stundenregeln.txt b/Stundenregeln.txt
index 9b5709c..3fb515e 100644
--- a/Stundenregeln.txt
+++ b/Stundenregeln.txt
@@ -3,10 +3,14 @@
- Überstunden nehmen
- Wenn Überstunden eingetragen > (Wochenarbeitszeit / Arbeitstage) -> Muss noch Start und Ende und Pause eingetragen werden
- Wenn Überstunden = (Wochenarbeitszeit / Arbeitstage) -> Tag als ausgefüllt zu betrachten
+ - Wird nur für die anzeige benötigt
+ - Stunden werden (Ende - Start ) - Pause berechnet.
- Urlaub
- - Wird als ausgefüllt betrachtet
- - Stunden werden als (Wochenarbeitszeit / Arbeitstage) gerechnet
+ - Wird als ausgefüllt betrachtet wenn ganzer Urlaubstag
+ - Wenn halber Urlaubstag muss (Ende - Start ) - Pause eingetragen werden.
+ - Stunden werden als (Wochenarbeitszeit / Arbeitstage) gerechnet wenn ganzer Tag
+ - Wenn halber tag Ulaub ((Wochenarbeitszeit / Arbeitstage) / 2) zur ((Ende - Start ) - Pause) addiert
- Wird von Verbleibendem Urlaub abgezogen
- Krank
diff --git a/Stunderfassung todo.txt b/Stunderfassung todo.txt
index a84957d..0d76971 100644
--- a/Stunderfassung todo.txt
+++ b/Stunderfassung todo.txt
@@ -1,7 +1,7 @@
- Mitarbeiter Name in den QR code Sheets -> DONE
- Pause vorbelegen (einstellbar in der Admin) -> DONE Wird anhand der Gesetztlichen vorgaben berechnet
- Offset für die Verwaltung für Urlaubstage -> DONE
-- Stunden pro Tag und wie viele Tage arbeit
+- Stunden pro Tag und wie viele Tage arbeit -> DONE
- Reisen für Wochenende -> DONE
- LDAP Prüfung
- DSGVO Sicherheit
diff --git a/database.js b/database.js
index 117ca7a..01401a8 100644
--- a/database.js
+++ b/database.js
@@ -216,6 +216,14 @@ function initDatabase() {
}
});
+ // Migration: Arbeitstage pro Woche hinzufügen
+ db.run(`ALTER TABLE users ADD COLUMN arbeitstage INTEGER DEFAULT 5`, (err) => {
+ // Fehler ignorieren wenn Spalte bereits existiert
+ if (err && !err.message.includes('duplicate column')) {
+ console.warn('Warnung beim Hinzufügen der Spalte arbeitstage:', err.message);
+ }
+ });
+
// Migration: ping_ip Spalte hinzufügen
db.run(`ALTER TABLE users ADD COLUMN ping_ip TEXT`, (err) => {
// Fehler ignorieren wenn Spalte bereits existiert
diff --git a/public/js/admin.js b/public/js/admin.js
index 32abd31..f27c4d7 100644
--- a/public/js/admin.js
+++ b/public/js/admin.js
@@ -25,6 +25,7 @@ document.addEventListener('DOMContentLoaded', function() {
roles: roles,
personalnummer: document.getElementById('personalnummer').value,
wochenstunden: document.getElementById('wochenstunden').value,
+ arbeitstage: document.getElementById('arbeitstage').value,
urlaubstage: document.getElementById('urlaubstage').value
};
@@ -315,6 +316,7 @@ async function saveUser(userId) {
const personalnummer = row.querySelector('input[data-field="personalnummer"]').value;
const wochenstunden = row.querySelector('input[data-field="wochenstunden"]').value;
+ const arbeitstage = row.querySelector('input[data-field="arbeitstage"]').value;
const urlaubstage = row.querySelector('input[data-field="urlaubstage"]').value;
// Rollen aus Checkboxen sammeln
@@ -336,6 +338,7 @@ async function saveUser(userId) {
body: JSON.stringify({
personalnummer: personalnummer || null,
wochenstunden: wochenstunden || null,
+ arbeitstage: arbeitstage || 5,
urlaubstage: urlaubstage || null,
roles: roles
})
diff --git a/public/js/dashboard.js b/public/js/dashboard.js
index 7c6710c..4d95f6a 100644
--- a/public/js/dashboard.js
+++ b/public/js/dashboard.js
@@ -4,6 +4,7 @@ let currentWeekStart = getMonday(new Date());
let currentEntries = {};
let currentHolidayDates = new Set(); // Feiertage der aktuellen Woche (YYYY-MM-DD)
let userWochenstunden = 0; // Wochenstunden des Users
+let userArbeitstage = 5; // Arbeitstage pro Woche des Users (Standard: 5)
let weekendPercentages = { saturday: 100, sunday: 100 }; // Wochenend-Prozentsätze (100% = normal)
let latestSubmittedTimesheetId = null; // ID der neuesten eingereichten Version
@@ -303,17 +304,24 @@ function getWeekday(dateStr) {
return days[d.getDay()];
}
+// Hilfsfunktion: Berechnet die Stunden pro Tag (Wochenarbeitszeit / Arbeitstage)
+function getFullDayHours() {
+ return userWochenstunden && userArbeitstage ? (userWochenstunden / userArbeitstage) : 8;
+}
+
// Woche laden
async function loadWeek() {
try {
- // User-Daten laden (Wochenstunden)
+ // User-Daten laden (Wochenstunden, Arbeitstage)
try {
const userResponse = await fetch('/api/user/data');
const userData = await userResponse.json();
userWochenstunden = userData.wochenstunden || 0;
+ userArbeitstage = userData.arbeitstage || 5;
} catch (error) {
console.warn('Konnte User-Daten nicht laden:', error);
userWochenstunden = 0;
+ userArbeitstage = 5;
}
const parts = currentWeekStart.split('-');
@@ -420,7 +428,7 @@ function renderWeek() {
// Bei Feiertag, ganztägigem Urlaub oder Krank gilt der Tag als ausgefüllt
// Bei 8 Überstunden (ganzer Tag) gilt der Tag auch als ausgefüllt
const overtimeValue = overtimeTaken ? parseFloat(overtimeTaken) : 0;
- const fullDayHours = userWochenstunden ? (userWochenstunden / 5) : 8;
+ const fullDayHours = getFullDayHours();
const isFullDayOvertime = overtimeValue > 0 && Math.abs(overtimeValue - fullDayHours) < 0.01;
if (i < 5 && !isHoliday && vacationType !== 'full' && !sickStatus && !isFullDayOvertime && (!startTime || !endTime || startTime.trim() === '' || endTime.trim() === '')) {
@@ -434,7 +442,7 @@ function renderWeek() {
// Wochenend-Prozentsätze: Nur auf tatsächlich gearbeitete Stunden anwenden (nicht auf Urlaub, Krankheit, Feiertage)
let hoursToAdd = 0;
if (isHoliday) {
- hoursToAdd = 8 + (hours || 0); // 8h Feiertag + gearbeitete Stunden (= Überstunden)
+ hoursToAdd = fullDayHours + (hours || 0); // (Wochenarbeitszeit / Arbeitstage) Feiertag + gearbeitete Stunden (= Überstunden)
} else {
hoursToAdd = hours || 0;
// Wochenend-Prozentsatz anwenden (nur wenn weekend_travel aktiviert UND es ist ein Wochenendtag)
@@ -482,7 +490,7 @@ function renderWeek() {
data-date="${dateStr}" data-field="break_minutes"
${timeFieldsDisabled} ${disabled} oninput="saveEntry(this)" onchange="saveEntry(this)">
-
|
@@ -640,15 +648,16 @@ function renderWeek() {
}
// Sollstunden berechnen
- const sollStunden = (userWochenstunden / 5) * workdays;
+ const sollStunden = (userWochenstunden / userArbeitstage) * workdays;
// Urlaubsstunden berechnen (Urlaub zählt als normale Arbeitszeit)
let vacationHours = 0;
+ const fullDayHours = getFullDayHours();
Object.values(currentEntries).forEach(e => {
if (e.vacation_type === 'full') {
- vacationHours += 8; // Ganzer Tag = 8 Stunden
+ vacationHours += fullDayHours; // Ganzer Tag = (Wochenarbeitszeit / Arbeitstage) Stunden
} else if (e.vacation_type === 'half') {
- vacationHours += 4; // Halber Tag = 4 Stunden
+ vacationHours += fullDayHours / 2; // Halber Tag = (Wochenarbeitszeit / Arbeitstage) / 2 Stunden
}
});
@@ -684,7 +693,7 @@ function updateOvertimeDisplay() {
}
// Sollstunden berechnen
- const sollStunden = (userWochenstunden / 5) * workdays;
+ const sollStunden = (userWochenstunden / userArbeitstage) * workdays;
// Gesamtstunden berechnen - direkt aus DOM-Elementen lesen für Echtzeit-Aktualisierung
let totalHours = 0;
@@ -715,9 +724,9 @@ function updateOvertimeDisplay() {
// Wenn Urlaub oder Krank, zähle nur diese Stunden (nicht zusätzlich Arbeitsstunden)
if (vacationType === 'full') {
- vacationHours += 8; // Ganzer Tag Urlaub = 8 Stunden
+ vacationHours += fullDayHours; // Ganzer Tag Urlaub = (Wochenarbeitszeit / Arbeitstage) Stunden
} else if (vacationType === 'half') {
- vacationHours += 4; // Halber Tag Urlaub = 4 Stunden
+ vacationHours += fullDayHours / 2; // Halber Tag Urlaub = (Wochenarbeitszeit / Arbeitstage) / 2 Stunden
// Bei halbem Tag Urlaub können noch Arbeitsstunden vorhanden sein
const startInput = document.querySelector(`input[data-date="${dateStr}"][data-field="start_time"]`);
const endInput = document.querySelector(`input[data-date="${dateStr}"][data-field="end_time"]`);
@@ -776,9 +785,9 @@ function updateOvertimeDisplay() {
totalHours += hours;
}
} else if (sickStatus) {
- totalHours += 8; // Krank = 8 Stunden
+ totalHours += fullDayHours; // Krank = (Wochenarbeitszeit / Arbeitstage) Stunden
} else if (currentHolidayDates.has(dateStr)) {
- // Feiertag: 8h Basis + gearbeitete Stunden (jede Stunde = Überstunde)
+ // Feiertag: (Wochenarbeitszeit / Arbeitstage) Basis + gearbeitete Stunden (jede Stunde = Überstunde)
const startInput = document.querySelector(`input[data-date="${dateStr}"][data-field="start_time"]`);
const endInput = document.querySelector(`input[data-date="${dateStr}"][data-field="end_time"]`);
const startTime = startInput ? startInput.value : '';
@@ -793,7 +802,7 @@ function updateOvertimeDisplay() {
} else if (currentEntries[dateStr]?.total_hours) {
worked = parseFloat(currentEntries[dateStr].total_hours) || 0;
}
- totalHours += 8 + worked; // 8h Feiertag + gearbeitete Stunden (= Überstunden)
+ totalHours += fullDayHours + worked; // (Wochenarbeitszeit / Arbeitstage) Feiertag + gearbeitete Stunden (= Überstunden)
} else {
// Wenn 8 Überstunden (ganzer Tag) eingetragen sind, zählt der Tag als 0 Stunden
if (isFullDayOvertime) {
@@ -902,7 +911,7 @@ function handleOvertimeChange(dateStr, overtimeHours) {
return;
}
- const fullDayHours = userWochenstunden / 5;
+ const fullDayHours = getFullDayHours();
const overtimeValue = parseFloat(overtimeHours) || 0;
// Prüfe ob ganzer Tag Überstunden
@@ -1204,11 +1213,11 @@ function checkWeekComplete() {
const overtimeValue = overtimeInput ? parseFloat(overtimeInput.value) || 0 : (entry.overtime_taken_hours ? parseFloat(entry.overtime_taken_hours) : 0);
// Berechne fullDayHours (normalerweise 8 Stunden)
- const fullDayHours = userWochenstunden ? (userWochenstunden / 5) : 8;
+ const fullDayHours = getFullDayHours();
- // Wenn 8 Überstunden (ganzer Tag) eingetragen sind, ist der Tag ausgefüllt
+ // Wenn Überstunden (ganzer Tag) eingetragen sind, ist der Tag ausgefüllt
if (overtimeValue > 0 && Math.abs(overtimeValue - fullDayHours) < 0.01) {
- continue; // Tag ist ausgefüllt (8 Überstunden = ganzer Tag)
+ continue; // Tag ist ausgefüllt (Überstunden = ganzer Tag)
}
// Prüfe IMMER direkt die Input-Felder im DOM (das ist die zuverlässigste Quelle)
@@ -1231,8 +1240,17 @@ function checkWeekComplete() {
fullDayHours: fullDayHours
});
+ // Wenn Überstunden > fullDayHours, dann müssen Start/Ende vorhanden sein
+ if (overtimeValue > fullDayHours) {
+ if (!startTime || !endTime || startTime === '' || endTime === '') {
+ allWeekdaysFilled = false;
+ missingFields.push(formatDateDE(dateStr) + ' (bei Überstunden > ' + fullDayHours.toFixed(2) + 'h müssen Start/Ende vorhanden sein)');
+ continue; // Weiter zum nächsten Tag
+ }
+ }
+
// Bei halbem Tag Urlaub oder keinem Urlaub müssen Start- und Endzeit vorhanden sein
- // (außer wenn 8 Überstunden eingetragen sind, dann sind Start/Ende nicht nötig)
+ // (außer wenn Überstunden = fullDayHours eingetragen sind, dann sind Start/Ende nicht nötig)
if (!startTime || !endTime || startTime === '' || endTime === '') {
allWeekdaysFilled = false;
missingFields.push(formatDateDE(dateStr));
@@ -1338,7 +1356,7 @@ async function submitWeek() {
// Prüfe ob 8 Überstunden eingetragen sind (dann ist der Tag auch ausgefüllt, Start/Ende nicht nötig)
const overtimeInput = document.querySelector(`input[data-date="${dateStr}"][data-field="overtime_taken_hours"]`);
const overtimeValue = overtimeInput ? parseFloat(overtimeInput.value) || 0 : (entry.overtime_taken_hours ? parseFloat(entry.overtime_taken_hours) : 0);
- const fullDayHours = userWochenstunden ? (userWochenstunden / 5) : 8;
+ const fullDayHours = getFullDayHours();
// Wenn 8 Überstunden (ganzer Tag) eingetragen sind, ist der Tag ausgefüllt
if (overtimeValue > 0 && Math.abs(overtimeValue - fullDayHours) < 0.01) {
diff --git a/routes/admin-routes.js b/routes/admin-routes.js
index 49ffe49..347d2fb 100644
--- a/routes/admin-routes.js
+++ b/routes/admin-routes.js
@@ -8,7 +8,7 @@ const { requireAdmin } = require('../middleware/auth');
function registerAdminRoutes(app) {
// Admin-Bereich
app.get('/admin', requireAdmin, (req, res) => {
- db.all('SELECT id, username, firstname, lastname, role, personalnummer, wochenstunden, urlaubstage, created_at FROM users ORDER BY created_at DESC',
+ db.all('SELECT id, username, firstname, lastname, role, personalnummer, wochenstunden, urlaubstage, arbeitstage, created_at FROM users ORDER BY created_at DESC',
(err, users) => {
// LDAP-Konfiguration, Sync-Log und Optionen abrufen
db.get('SELECT * FROM ldap_config WHERE id = 1', (err, ldapConfig) => {
@@ -48,13 +48,14 @@ function registerAdminRoutes(app) {
// Benutzer erstellen
app.post('/admin/users', requireAdmin, (req, res) => {
- const { username, password, firstname, lastname, roles, personalnummer, wochenstunden, urlaubstage } = req.body;
+ const { username, password, firstname, lastname, roles, personalnummer, wochenstunden, urlaubstage, arbeitstage } = req.body;
const hashedPassword = bcrypt.hashSync(password, 10);
// Normalisiere die optionalen Felder
const normalizedPersonalnummer = personalnummer && personalnummer.trim() !== '' ? personalnummer.trim() : null;
const normalizedWochenstunden = wochenstunden && wochenstunden !== '' ? parseFloat(wochenstunden) : null;
const normalizedUrlaubstage = urlaubstage && urlaubstage !== '' ? parseFloat(urlaubstage) : null;
+ const normalizedArbeitstage = arbeitstage && arbeitstage !== '' ? parseInt(arbeitstage) : 5;
// Rollen verarbeiten: Erwarte Array, konvertiere zu JSON-String
let rolesArray = [];
@@ -72,8 +73,8 @@ function registerAdminRoutes(app) {
const rolesJson = JSON.stringify(rolesArray);
- db.run('INSERT INTO users (username, password, firstname, lastname, role, personalnummer, wochenstunden, urlaubstage) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
- [username, hashedPassword, firstname, lastname, rolesJson, normalizedPersonalnummer, normalizedWochenstunden, normalizedUrlaubstage],
+ db.run('INSERT INTO users (username, password, firstname, lastname, role, personalnummer, wochenstunden, urlaubstage, arbeitstage) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
+ [username, hashedPassword, firstname, lastname, rolesJson, normalizedPersonalnummer, normalizedWochenstunden, normalizedUrlaubstage, normalizedArbeitstage],
(err) => {
if (err) {
return res.status(400).json({ error: 'Benutzername existiert bereits' });
@@ -102,7 +103,7 @@ function registerAdminRoutes(app) {
// Benutzer aktualisieren (Personalnummer, Wochenstunden, Urlaubstage, Rollen)
app.put('/admin/users/:id', requireAdmin, (req, res) => {
const userId = req.params.id;
- const { personalnummer, wochenstunden, urlaubstage, roles } = req.body;
+ const { personalnummer, wochenstunden, urlaubstage, arbeitstage, roles } = req.body;
// Rollen verarbeiten falls vorhanden
let rolesJson = null;
@@ -121,11 +122,12 @@ function registerAdminRoutes(app) {
// SQL-Query dynamisch zusammenstellen
if (rolesJson !== null) {
// Aktualisiere auch Rollen
- db.run('UPDATE users SET personalnummer = ?, wochenstunden = ?, urlaubstage = ?, role = ? WHERE id = ?',
+ db.run('UPDATE users SET personalnummer = ?, wochenstunden = ?, urlaubstage = ?, arbeitstage = ?, role = ? WHERE id = ?',
[
personalnummer || null,
wochenstunden ? parseFloat(wochenstunden) : null,
urlaubstage ? parseFloat(urlaubstage) : null,
+ arbeitstage ? parseInt(arbeitstage) : 5,
rolesJson,
userId
],
@@ -137,11 +139,12 @@ function registerAdminRoutes(app) {
});
} else {
// Nur andere Felder aktualisieren
- db.run('UPDATE users SET personalnummer = ?, wochenstunden = ?, urlaubstage = ? WHERE id = ?',
+ db.run('UPDATE users SET personalnummer = ?, wochenstunden = ?, urlaubstage = ?, arbeitstage = ? WHERE id = ?',
[
personalnummer || null,
wochenstunden ? parseFloat(wochenstunden) : null,
urlaubstage ? parseFloat(urlaubstage) : null,
+ arbeitstage ? parseInt(arbeitstage) : 5,
userId
],
(err) => {
diff --git a/routes/timesheet-routes.js b/routes/timesheet-routes.js
index ad89899..490bc06 100644
--- a/routes/timesheet-routes.js
+++ b/routes/timesheet-routes.js
@@ -55,15 +55,16 @@ function registerTimesheetRoutes(app) {
}
// User-Daten laden (für Überstunden-Berechnung)
- db.get('SELECT wochenstunden FROM users WHERE id = ?', [userId], (err, user) => {
+ db.get('SELECT wochenstunden, arbeitstage FROM users WHERE id = ?', [userId], (err, user) => {
if (err) {
console.error('Fehler beim Laden der User-Daten:', err);
return res.status(500).json({ error: 'Fehler beim Laden der User-Daten' });
}
const wochenstunden = user?.wochenstunden || 0;
+ const arbeitstage = user?.arbeitstage || 5;
const overtimeValue = overtime_taken_hours ? parseFloat(overtime_taken_hours) : 0;
- const fullDayHours = wochenstunden > 0 ? wochenstunden / 5 : 0;
+ const fullDayHours = wochenstunden > 0 && arbeitstage > 0 ? wochenstunden / arbeitstage : 0;
// Überstunden-Logik: Prüfe ob ganzer Tag oder weniger
let isFullDayOvertime = false;
@@ -96,11 +97,11 @@ function registerTimesheetRoutes(app) {
finalEndTime = null;
// Keine Tätigkeit setzen - Überstunden werden über overtime_taken_hours in der PDF angezeigt
} else if (vacation_type === 'full') {
- total_hours = 8; // Ganzer Tag Urlaub = 8 Stunden normale Arbeitszeit
+ total_hours = fullDayHours; // Ganzer Tag Urlaub = (Wochenarbeitszeit / Arbeitstage) Stunden normale Arbeitszeit
} else if (isSick) {
- total_hours = 8; // Krank = 8 Stunden normale Arbeitszeit
+ total_hours = fullDayHours; // Krank = (Wochenarbeitszeit / Arbeitstage) Stunden normale Arbeitszeit
finalActivity1Desc = 'Krank';
- finalActivity1Hours = 8;
+ finalActivity1Hours = fullDayHours;
} else if (normalizedStartTime && normalizedEndTime) {
const start = new Date(`2000-01-01T${normalizedStartTime}`);
const end = new Date(`2000-01-01T${normalizedEndTime}`);
@@ -314,13 +315,14 @@ function registerTimesheetRoutes(app) {
const startDay = parseInt(startDateParts[2]);
// User-Daten laden für Überstunden-Berechnung
- db.get('SELECT wochenstunden FROM users WHERE id = ?', [userId], (err, user) => {
+ db.get('SELECT wochenstunden, arbeitstage FROM users WHERE id = ?', [userId], (err, user) => {
if (err) {
return res.status(500).json({ error: 'Fehler beim Laden der User-Daten' });
}
const wochenstunden = user?.wochenstunden || 0;
- const fullDayHours = wochenstunden > 0 ? wochenstunden / 5 : 8;
+ const arbeitstage = user?.arbeitstage || 5;
+ const fullDayHours = wochenstunden > 0 && arbeitstage > 0 ? wochenstunden / arbeitstage : 8;
// Feiertage laden: Feiertag zählt als ausgefüllt (kein Start/Ende nötig)
getHolidaysForDateRange(week_start, week_end)
@@ -348,12 +350,23 @@ function registerTimesheetRoutes(app) {
continue; // Tag ist ausgefüllt
}
- // Prüfe ob 8 Überstunden (ganzer Tag) eingetragen sind
+ // Prüfe ob Überstunden (ganzer Tag) eingetragen sind
const overtimeValue = entry && entry.overtime_taken_hours ? parseFloat(entry.overtime_taken_hours) : 0;
const isFullDayOvertime = overtimeValue > 0 && Math.abs(overtimeValue - fullDayHours) < 0.01;
if (isFullDayOvertime) {
- continue; // Tag ist ausgefüllt (8 Überstunden = ganzer Tag)
+ continue; // Tag ist ausgefüllt (Überstunden = ganzer Tag)
+ }
+
+ // Wenn Überstunden > fullDayHours, dann müssen Start/Ende vorhanden sein
+ if (overtimeValue > fullDayHours) {
+ const hasStartTime = entry && entry.start_time && entry.start_time.toString().trim() !== '';
+ const hasEndTime = entry && entry.end_time && entry.end_time.toString().trim() !== '';
+
+ if (!entry || !hasStartTime || !hasEndTime) {
+ missingDays.push(dateStr);
+ continue; // Weiter zum nächsten Tag
+ }
}
// Bei halbem Tag Urlaub oder keinem Urlaub müssen Start- und Endzeit vorhanden sein
diff --git a/routes/user-routes.js b/routes/user-routes.js
index e9b428f..7635fbd 100644
--- a/routes/user-routes.js
+++ b/routes/user-routes.js
@@ -57,12 +57,15 @@ function registerUserRoutes(app) {
app.get('/api/user/data', requireAuth, (req, res) => {
const userId = req.session.userId;
- db.get('SELECT wochenstunden FROM users WHERE id = ?', [userId], (err, user) => {
+ db.get('SELECT wochenstunden, arbeitstage FROM users WHERE id = ?', [userId], (err, user) => {
if (err) {
return res.status(500).json({ error: 'Fehler beim Abrufen der User-Daten' });
}
- res.json({ wochenstunden: user?.wochenstunden || 0 });
+ res.json({
+ wochenstunden: user?.wochenstunden || 0,
+ arbeitstage: user?.arbeitstage || 5
+ });
});
});
@@ -223,12 +226,13 @@ function registerUserRoutes(app) {
}
// User-Daten abrufen
- db.get('SELECT wochenstunden, urlaubstage, overtime_offset_hours, vacation_offset_days FROM users WHERE id = ?', [userId], (err, user) => {
+ db.get('SELECT wochenstunden, urlaubstage, overtime_offset_hours, vacation_offset_days, arbeitstage FROM users WHERE id = ?', [userId], (err, user) => {
if (err || !user) {
return res.status(500).json({ error: 'Fehler beim Abrufen der User-Daten' });
}
const wochenstunden = user.wochenstunden || 0;
+ const arbeitstage = user.arbeitstage || 5;
const urlaubstage = user.urlaubstage || 0;
const overtimeOffsetHours = user.overtime_offset_hours ? parseFloat(user.overtime_offset_hours) : 0;
const vacationOffsetDays = user.vacation_offset_days ? parseFloat(user.vacation_offset_days) : 0;
@@ -407,7 +411,7 @@ function registerUserRoutes(app) {
let weekVacationDays = 0;
let weekVacationHours = 0;
- const fullDayHours = wochenstunden > 0 ? wochenstunden / 5 : 8;
+ const fullDayHours = wochenstunden > 0 && arbeitstage > 0 ? wochenstunden / arbeitstage : 8;
let fullDayOvertimeDays = 0; // Anzahl Tage mit 8 Überstunden
entries.forEach(entry => {
@@ -428,11 +432,11 @@ function registerUserRoutes(app) {
// Urlaub hat Priorität - wenn Urlaub, zähle nur Urlaubsstunden, nicht zusätzlich Arbeitsstunden
if (entry.vacation_type === 'full') {
weekVacationDays += 1;
- weekVacationHours += 8; // Ganzer Tag = 8 Stunden
+ weekVacationHours += fullDayHours; // Ganzer Tag = (Wochenarbeitszeit / Arbeitstage) Stunden
// Bei vollem Tag Urlaub werden keine Arbeitsstunden gezählt
} else if (entry.vacation_type === 'half') {
weekVacationDays += 0.5;
- weekVacationHours += 4; // Halber Tag = 4 Stunden
+ weekVacationHours += fullDayHours / 2; // Halber Tag = (Wochenarbeitszeit / Arbeitstage) / 2 Stunden
// Bei halbem Tag Urlaub können noch Arbeitsstunden vorhanden sein
// WICHTIG: total_hours enthält bereits Wochenend-Prozentsätze (aus timesheet.js)
if (entry.total_hours && !isFullDayOvertime) {
@@ -447,18 +451,18 @@ function registerUserRoutes(app) {
}
});
- // Feiertagsstunden: 8h pro Werktag der ein Feiertag ist
+ // Feiertagsstunden: (Wochenarbeitszeit / Arbeitstage) pro Werktag der ein Feiertag ist
let holidayHours = 0;
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
const day = d.getDay();
if (day >= 1 && day <= 5) {
const dateStr = d.toISOString().split('T')[0];
- if (holidaySet.has(dateStr)) holidayHours += 8;
+ if (holidaySet.has(dateStr)) holidayHours += fullDayHours;
}
}
// Sollstunden berechnen
- const sollStunden = (wochenstunden / 5) * workdays;
+ const sollStunden = (wochenstunden / arbeitstage) * workdays;
// Überstunden für diese Woche: (totalHours + vacationHours + holidayHours) - adjustedSollStunden
const weekTotalHoursWithVacation = weekTotalHours + weekVacationHours + holidayHours;
@@ -532,12 +536,13 @@ function registerUserRoutes(app) {
}
// User-Daten abrufen
- db.get('SELECT wochenstunden, overtime_offset_hours FROM users WHERE id = ?', [userId], (err, user) => {
+ db.get('SELECT wochenstunden, overtime_offset_hours, arbeitstage FROM users WHERE id = ?', [userId], (err, user) => {
if (err || !user) {
return res.status(500).json({ error: 'Fehler beim Abrufen der User-Daten' });
}
const wochenstunden = user.wochenstunden || 0;
+ const arbeitstage = user.arbeitstage || 5;
const overtimeOffsetHours = user.overtime_offset_hours ? parseFloat(user.overtime_offset_hours) : 0;
// Alle eingereichten Wochen abrufen
@@ -645,7 +650,7 @@ function registerUserRoutes(app) {
let weekVacationDays = 0;
let weekVacationHours = 0;
- const fullDayHours = wochenstunden > 0 ? wochenstunden / 5 : 8;
+ const fullDayHours = wochenstunden > 0 && arbeitstage > 0 ? wochenstunden / arbeitstage : 8;
let fullDayOvertimeDays = 0;
entries.forEach(entry => {
@@ -662,10 +667,10 @@ function registerUserRoutes(app) {
if (entry.vacation_type === 'full') {
weekVacationDays += 1;
- weekVacationHours += 8;
+ weekVacationHours += fullDayHours;
} else if (entry.vacation_type === 'half') {
weekVacationDays += 0.5;
- weekVacationHours += 4;
+ weekVacationHours += fullDayHours / 2;
// WICHTIG: total_hours enthält bereits Wochenend-Prozentsätze (aus timesheet.js)
if (entry.total_hours && !isFullDayOvertime) {
weekTotalHours += parseFloat(entry.total_hours) || 0;
@@ -678,18 +683,18 @@ function registerUserRoutes(app) {
}
});
- // Feiertagsstunden
+ // Feiertagsstunden: (Wochenarbeitszeit / Arbeitstage) pro Werktag der ein Feiertag ist
let holidayHours = 0;
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
const day = d.getDay();
if (day >= 1 && day <= 5) {
const dateStr = d.toISOString().split('T')[0];
- if (holidaySet.has(dateStr)) holidayHours += 8;
+ if (holidaySet.has(dateStr)) holidayHours += fullDayHours;
}
}
// Sollstunden berechnen
- const sollStunden = (wochenstunden / 5) * workdays;
+ const sollStunden = (wochenstunden / arbeitstage) * workdays;
const weekTotalHoursWithVacation = weekTotalHours + weekVacationHours + holidayHours;
const adjustedSollStunden = sollStunden - (fullDayOvertimeDays * fullDayHours);
const weekOvertimeHours = weekTotalHoursWithVacation - adjustedSollStunden;
diff --git a/routes/verwaltung-routes.js b/routes/verwaltung-routes.js
index 0cfdf3a..0a53a7d 100644
--- a/routes/verwaltung-routes.js
+++ b/routes/verwaltung-routes.js
@@ -12,7 +12,7 @@ function registerVerwaltungRoutes(app) {
// Verwaltungs-Bereich
app.get('/verwaltung', requireVerwaltung, (req, res) => {
db.all(`
- SELECT wt.*, u.firstname, u.lastname, u.username, u.personalnummer, u.wochenstunden, u.urlaubstage, u.overtime_offset_hours, u.vacation_offset_days,
+ SELECT wt.*, u.firstname, u.lastname, u.username, u.personalnummer, u.wochenstunden, u.urlaubstage, u.overtime_offset_hours, u.vacation_offset_days, u.arbeitstage,
dl.firstname as downloaded_by_firstname,
dl.lastname as downloaded_by_lastname,
(SELECT COUNT(*) FROM weekly_timesheets wt2
@@ -217,115 +217,191 @@ function registerVerwaltungRoutes(app) {
}
// User-Daten abrufen
- db.get('SELECT wochenstunden, urlaubstage, overtime_offset_hours, vacation_offset_days FROM users WHERE id = ?', [userId], (err, user) => {
+ db.get('SELECT wochenstunden, urlaubstage, overtime_offset_hours, vacation_offset_days, arbeitstage FROM users WHERE id = ?', [userId], (err, user) => {
if (err || !user) {
return res.status(500).json({ error: 'Fehler beim Abrufen der User-Daten' });
}
const wochenstunden = user.wochenstunden || 0;
+ const arbeitstage = user.arbeitstage || 5;
+ const fullDayHours = wochenstunden > 0 && arbeitstage > 0 ? wochenstunden / arbeitstage : 8;
const urlaubstage = user.urlaubstage || 0;
const overtimeOffsetHours = user.overtime_offset_hours ? parseFloat(user.overtime_offset_hours) : 0;
const vacationOffsetDays = user.vacation_offset_days ? parseFloat(user.vacation_offset_days) : 0;
- // Einträge für die Woche abrufen
- db.all(`SELECT date, total_hours, overtime_taken_hours, vacation_type, sick_status
- FROM timesheet_entries
- WHERE user_id = ? AND date >= ? AND date <= ?
- ORDER BY date`,
- [userId, week_start, week_end],
- (err, entries) => {
+ // Alle bereits genommenen Urlaubstage aus eingereichten Wochen berechnen
+ // Zuerst: Alle eingereichten Wochen abrufen
+ db.all(`SELECT DISTINCT week_start, week_end
+ FROM weekly_timesheets
+ WHERE user_id = ? AND status = 'eingereicht'
+ ORDER BY week_start`,
+ [userId],
+ (err, weeks) => {
if (err) {
- return res.status(500).json({ error: 'Fehler beim Abrufen der Einträge' });
+ return res.status(500).json({ error: 'Fehler beim Abrufen der Wochen' });
}
- // Berechnungen
- let totalHours = 0;
- let overtimeTaken = 0;
- let vacationDays = 0;
- let vacationHours = 0;
- let sickDays = 0;
+ // Für jede Woche die neuesten Einträge abrufen
+ let processedWeeks = 0;
+ let totalVacationDays = 0;
+ const vacationByDate = {};
- entries.forEach(entry => {
- if (entry.overtime_taken_hours) {
- overtimeTaken += entry.overtime_taken_hours;
- }
-
- // Krankheitstage zählen
- if (entry.sick_status === 1 || entry.sick_status === true) {
- sickDays += 1;
- }
-
- // Urlaub hat Priorität - wenn Urlaub, zähle nur Urlaubsstunden, nicht zusätzlich Arbeitsstunden
- if (entry.vacation_type === 'full') {
- vacationDays += 1;
- vacationHours += 8; // Ganzer Tag = 8 Stunden
- // Bei vollem Tag Urlaub werden keine Arbeitsstunden gezählt
- } else if (entry.vacation_type === 'half') {
- vacationDays += 0.5;
- vacationHours += 4; // Halber Tag = 4 Stunden
- // Bei halbem Tag Urlaub können noch Arbeitsstunden vorhanden sein
- // WICHTIG: total_hours enthält bereits Wochenend-Prozentsätze (aus timesheet.js)
- if (entry.total_hours) {
- totalHours += parseFloat(entry.total_hours) || 0;
- }
- } else {
- // Kein Urlaub - zähle nur Arbeitsstunden
- // WICHTIG: total_hours enthält bereits Wochenend-Prozentsätze (aus timesheet.js)
- if (entry.total_hours) {
- totalHours += parseFloat(entry.total_hours) || 0;
- }
- }
- });
-
- // Feiertage für die Woche laden (8h pro Feiertag; Arbeit an Feiertag = Überstunden)
- getHolidaysForDateRange(week_start, week_end)
- .catch(() => new Set())
- .then((holidaySet) => {
- // Anzahl Werktage berechnen (Montag-Freitag)
- const startDate = new Date(week_start);
- const endDate = new Date(week_end);
- let workdays = 0;
- let holidayHours = 0;
- for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
- const day = d.getDay();
- if (day >= 1 && day <= 5) { // Montag bis Freitag
- workdays++;
- const dateStr = d.toISOString().split('T')[0];
- if (holidaySet.has(dateStr)) holidayHours += 8;
- }
- }
-
- // Sollstunden berechnen
- const sollStunden = (wochenstunden / 5) * workdays;
-
- // Überstunden: (Tatsächliche Stunden + Urlaubsstunden + Feiertagsstunden) - Sollstunden
- const totalHoursWithVacation = totalHours + vacationHours + holidayHours;
- const overtimeHours = totalHoursWithVacation - sollStunden;
- const remainingOvertime = overtimeHours - overtimeTaken;
- const remainingOvertimeWithOffset = remainingOvertime + overtimeOffsetHours;
-
- // Verbleibende Urlaubstage
- const remainingVacation = urlaubstage - vacationDays + vacationOffsetDays;
-
- res.json({
- wochenstunden,
- urlaubstage,
- totalHours,
- sollStunden,
- overtimeHours,
- overtimeTaken,
- remainingOvertime,
- overtimeOffsetHours,
- remainingOvertimeWithOffset,
- vacationDays,
- vacationOffsetDays,
- remainingVacation,
- sickDays,
- workdays
- });
+ if (!weeks || weeks.length === 0) {
+ // Keine eingereichten Wochen - setze totalVacationDays auf 0
+ totalVacationDays = 0;
+ // Weiter mit der normalen Verarbeitung der aktuellen Woche
+ processCurrentWeek(0);
+ } else {
+ weeks.forEach((week) => {
+ // Einträge für diese Woche abrufen (nur neueste pro Tag)
+ db.all(`SELECT date, vacation_type, updated_at, id
+ FROM timesheet_entries
+ WHERE user_id = ? AND date >= ? AND date <= ?
+ AND vacation_type IS NOT NULL
+ AND vacation_type != ''
+ ORDER BY date, updated_at DESC, id DESC`,
+ [userId, week.week_start, week.week_end],
+ (err, weekEntries) => {
+ if (err) {
+ return res.status(500).json({ error: 'Fehler beim Abrufen der Einträge' });
+ }
+
+ // Filtere auf neuesten Eintrag pro Tag
+ (weekEntries || []).forEach(entry => {
+ const existing = vacationByDate[entry.date];
+ if (!existing) {
+ vacationByDate[entry.date] = entry;
+ } else {
+ // Vergleiche updated_at (falls vorhanden) oder id (höhere ID = neuer)
+ const existingTime = existing.updated_at ? new Date(existing.updated_at).getTime() : 0;
+ const currentTime = entry.updated_at ? new Date(entry.updated_at).getTime() : 0;
+ if (currentTime > existingTime || (currentTime === existingTime && entry.id > existing.id)) {
+ vacationByDate[entry.date] = entry;
+ }
+ }
+ });
+
+ processedWeeks++;
+ if (processedWeeks === weeks.length) {
+ // Alle Wochen verarbeitet - summiere Urlaubstage
+ Object.values(vacationByDate).forEach(entry => {
+ if (entry.vacation_type === 'full') {
+ totalVacationDays += 1;
+ } else if (entry.vacation_type === 'half') {
+ totalVacationDays += 0.5;
+ }
+ });
+
+ // Weiter mit der normalen Verarbeitung der aktuellen Woche
+ processCurrentWeek(totalVacationDays);
+ }
+ });
});
+ }
+
+ function processCurrentWeek(totalVacationDays) {
+ // Einträge für die Woche abrufen
+ db.all(`SELECT date, total_hours, overtime_taken_hours, vacation_type, sick_status
+ FROM timesheet_entries
+ WHERE user_id = ? AND date >= ? AND date <= ?
+ ORDER BY date`,
+ [userId, week_start, week_end],
+ (err, entries) => {
+ if (err) {
+ return res.status(500).json({ error: 'Fehler beim Abrufen der Einträge' });
+ }
+
+ // Berechnungen
+ let totalHours = 0;
+ let overtimeTaken = 0;
+ let vacationDays = 0;
+ let vacationHours = 0;
+ let sickDays = 0;
+
+ entries.forEach(entry => {
+ if (entry.overtime_taken_hours) {
+ overtimeTaken += entry.overtime_taken_hours;
+ }
+
+ // Krankheitstage zählen
+ if (entry.sick_status === 1 || entry.sick_status === true) {
+ sickDays += 1;
+ }
+
+ // Urlaub hat Priorität - wenn Urlaub, zähle nur Urlaubsstunden, nicht zusätzlich Arbeitsstunden
+ if (entry.vacation_type === 'full') {
+ vacationDays += 1;
+ vacationHours += fullDayHours; // Ganzer Tag = (Wochenarbeitszeit / Arbeitstage) Stunden
+ // Bei vollem Tag Urlaub werden keine Arbeitsstunden gezählt
+ } else if (entry.vacation_type === 'half') {
+ vacationDays += 0.5;
+ vacationHours += fullDayHours / 2; // Halber Tag = (Wochenarbeitszeit / Arbeitstage) / 2 Stunden
+ // Bei halbem Tag Urlaub können noch Arbeitsstunden vorhanden sein
+ // WICHTIG: total_hours enthält bereits Wochenend-Prozentsätze (aus timesheet.js)
+ if (entry.total_hours) {
+ totalHours += parseFloat(entry.total_hours) || 0;
+ }
+ } else {
+ // Kein Urlaub - zähle nur Arbeitsstunden
+ // WICHTIG: total_hours enthält bereits Wochenend-Prozentsätze (aus timesheet.js)
+ if (entry.total_hours) {
+ totalHours += parseFloat(entry.total_hours) || 0;
+ }
+ }
+ });
+
+ // Feiertage für die Woche laden ((Wochenarbeitszeit / Arbeitstage) pro Feiertag; Arbeit an Feiertag = Überstunden)
+ getHolidaysForDateRange(week_start, week_end)
+ .catch(() => new Set())
+ .then((holidaySet) => {
+ // Anzahl Werktage berechnen (Montag-Freitag)
+ const startDate = new Date(week_start);
+ const endDate = new Date(week_end);
+ let workdays = 0;
+ let holidayHours = 0;
+ for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
+ const day = d.getDay();
+ if (day >= 1 && day <= 5) { // Montag bis Freitag
+ workdays++;
+ const dateStr = d.toISOString().split('T')[0];
+ if (holidaySet.has(dateStr)) holidayHours += fullDayHours;
+ }
+ }
+
+ // Sollstunden berechnen
+ const sollStunden = (wochenstunden / arbeitstage) * workdays;
+
+ // Überstunden: (Tatsächliche Stunden + Urlaubsstunden + Feiertagsstunden) - Sollstunden
+ const totalHoursWithVacation = totalHours + vacationHours + holidayHours;
+ const overtimeHours = totalHoursWithVacation - sollStunden;
+ const remainingOvertime = overtimeHours - overtimeTaken;
+ const remainingOvertimeWithOffset = remainingOvertime + overtimeOffsetHours;
+
+ // Verbleibende Urlaubstage (berücksichtigt alle eingereichten Wochen, nicht nur die aktuelle)
+ const remainingVacation = urlaubstage - totalVacationDays + vacationOffsetDays;
+
+ res.json({
+ wochenstunden,
+ urlaubstage,
+ totalHours,
+ sollStunden,
+ overtimeHours,
+ overtimeTaken,
+ remainingOvertime,
+ overtimeOffsetHours,
+ remainingOvertimeWithOffset,
+ vacationDays,
+ vacationOffsetDays,
+ remainingVacation,
+ totalVacationDays,
+ sickDays,
+ workdays
+ });
+ });
+ });
+ }
});
- });
+ });
});
});
diff --git a/services/pdf-service.js b/services/pdf-service.js
index 452dd13..202aca4 100644
--- a/services/pdf-service.js
+++ b/services/pdf-service.js
@@ -19,7 +19,7 @@ function getCalendarWeek(dateStr) {
// PDF generieren
function generatePDF(timesheetId, req, res) {
- db.get(`SELECT wt.*, u.firstname, u.lastname, u.username, u.wochenstunden
+ db.get(`SELECT wt.*, u.firstname, u.lastname, u.username, u.wochenstunden, u.arbeitstage
FROM weekly_timesheets wt
JOIN users u ON wt.user_id = u.id
WHERE wt.id = ?`, [timesheetId], (err, timesheet) => {
@@ -73,11 +73,13 @@ function generatePDF(timesheetId, req, res) {
let holidayHours = 0;
const start = new Date(timesheet.week_start);
const end = new Date(timesheet.week_end);
+ const arbeitstage = timesheet.arbeitstage || 5;
+ const fullDayHours = timesheet.wochenstunden > 0 && arbeitstage > 0 ? timesheet.wochenstunden / arbeitstage : 8;
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
const day = d.getDay();
if (day >= 1 && day <= 5) {
const dateStr = d.toISOString().split('T')[0];
- if (holidaySet.has(dateStr)) holidayHours += 8;
+ if (holidaySet.has(dateStr)) holidayHours += fullDayHours;
}
}
return { holidaySet, holidayHours };
@@ -241,11 +243,13 @@ function generatePDF(timesheetId, req, res) {
}
// Urlaub hat Priorität - wenn Urlaub, zähle nur Urlaubsstunden, nicht zusätzlich Arbeitsstunden
+ const arbeitstage = timesheet.arbeitstage || 5;
+ const fullDayHours = timesheet.wochenstunden > 0 && arbeitstage > 0 ? timesheet.wochenstunden / arbeitstage : 8;
if (entry.vacation_type === 'full') {
- vacationHours += 8; // Ganzer Tag = 8 Stunden
+ vacationHours += fullDayHours; // Ganzer Tag = (Wochenarbeitszeit / Arbeitstage) Stunden
// Bei vollem Tag Urlaub werden keine Arbeitsstunden gezählt
} else if (entry.vacation_type === 'half') {
- vacationHours += 4; // Halber Tag = 4 Stunden
+ vacationHours += fullDayHours / 2; // Halber Tag = (Wochenarbeitszeit / Arbeitstage) / 2 Stunden
// Bei halbem Tag Urlaub können noch Arbeitsstunden vorhanden sein
if (entry.total_hours) {
totalHours += entry.total_hours;
@@ -299,7 +303,7 @@ function generatePDF(timesheetId, req, res) {
// PDF als Buffer generieren (für ZIP-Downloads)
function generatePDFToBuffer(timesheetId, req) {
return new Promise((resolve, reject) => {
- db.get(`SELECT wt.*, u.firstname, u.lastname, u.username, u.wochenstunden
+ db.get(`SELECT wt.*, u.firstname, u.lastname, u.username, u.wochenstunden, u.arbeitstage
FROM weekly_timesheets wt
JOIN users u ON wt.user_id = u.id
WHERE wt.id = ?`, [timesheetId], (err, timesheet) => {
@@ -348,11 +352,13 @@ function generatePDFToBuffer(timesheetId, req) {
let holidayHours = 0;
const start = new Date(timesheet.week_start);
const end = new Date(timesheet.week_end);
+ const arbeitstage = timesheet.arbeitstage || 5;
+ const fullDayHours = timesheet.wochenstunden > 0 && arbeitstage > 0 ? timesheet.wochenstunden / arbeitstage : 8;
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
const day = d.getDay();
if (day >= 1 && day <= 5) {
const dateStr = d.toISOString().split('T')[0];
- if (holidaySet.has(dateStr)) holidayHours += 8;
+ if (holidaySet.has(dateStr)) holidayHours += fullDayHours;
}
}
return { holidaySet, holidayHours };
@@ -477,11 +483,13 @@ function generatePDFToBuffer(timesheetId, req) {
}
// Urlaub hat Priorität - wenn Urlaub, zähle nur Urlaubsstunden, nicht zusätzlich Arbeitsstunden
+ const arbeitstage = timesheet.arbeitstage || 5;
+ const fullDayHours = timesheet.wochenstunden > 0 && arbeitstage > 0 ? timesheet.wochenstunden / arbeitstage : 8;
if (entry.vacation_type === 'full') {
- vacationHours += 8; // Ganzer Tag = 8 Stunden
+ vacationHours += fullDayHours; // Ganzer Tag = (Wochenarbeitszeit / Arbeitstage) Stunden
// Bei vollem Tag Urlaub werden keine Arbeitsstunden gezählt
} else if (entry.vacation_type === 'half') {
- vacationHours += 4; // Halber Tag = 4 Stunden
+ vacationHours += fullDayHours / 2; // Halber Tag = (Wochenarbeitszeit / Arbeitstage) / 2 Stunden
// Bei halbem Tag Urlaub können noch Arbeitsstunden vorhanden sein
if (entry.total_hours) {
totalHours += entry.total_hours;
diff --git a/views/admin.ejs b/views/admin.ejs
index 4a86d7c..fa00a63 100644
--- a/views/admin.ejs
+++ b/views/admin.ejs
@@ -98,6 +98,10 @@
+
+
+
+
@@ -129,6 +133,7 @@
| Rolle |
Personalnummer |
Wochenstunden |
+ Arbeitstage |
Urlaubstage |
Erstellt am |
Aktionen |
@@ -180,6 +185,10 @@
<%= u.wochenstunden || '-' %>
+
+ <%= u.arbeitstage || 5 %>
+
+ |
<%= u.urlaubstage || '-' %>
|