diff --git a/database.js b/database.js
index ad8de1b..623e61e 100644
--- a/database.js
+++ b/database.js
@@ -148,6 +148,14 @@ function initDatabase() {
}
});
+ // Migration: Krank-Status hinzufügen
+ db.run(`ALTER TABLE timesheet_entries ADD COLUMN sick_status INTEGER DEFAULT 0`, (err) => {
+ // Fehler ignorieren wenn Spalte bereits existiert
+ if (err && !err.message.includes('duplicate column')) {
+ console.warn('Warnung beim Hinzufügen der Spalte sick_status:', err.message);
+ }
+ });
+
// Migration: Pausen-Zeiten für API-Zeiterfassung hinzufügen
db.run(`ALTER TABLE timesheet_entries ADD COLUMN pause_start_time TEXT`, (err) => {
// Fehler ignorieren wenn Spalte bereits existiert
diff --git a/ldap-service.js b/ldap-service.js
index 2a7ba06..5826fec 100644
--- a/ldap-service.js
+++ b/ldap-service.js
@@ -113,6 +113,19 @@ class LDAPService {
return Array.isArray(attr.values) ? attr.values[0] : attr.values;
}
+ /**
+ * Escaped einen Wert für LDAP-Filter (verhindert LDAP-Injection)
+ */
+ static escapeLDAPFilter(value) {
+ if (!value) return '';
+ return value
+ .replace(/\\/g, '\\5c')
+ .replace(/\*/g, '\\2a')
+ .replace(/\(/g, '\\28')
+ .replace(/\)/g, '\\29')
+ .replace(/\0/g, '\\00');
+ }
+
/**
* Benutzer in SQLite synchronisieren
*/
@@ -217,6 +230,83 @@ class LDAPService {
);
}
+ /**
+ * Benutzer gegen LDAP authentifizieren
+ */
+ static authenticate(username, password, callback) {
+ // Konfiguration abrufen
+ this.getConfig((err, config) => {
+ if (err || !config || !config.enabled) {
+ return callback(new Error('LDAP ist nicht aktiviert'), false);
+ }
+
+ // LDAP-Verbindung herstellen (mit Service-Account)
+ this.connect(config, (err, client) => {
+ if (err) {
+ return callback(err, false);
+ }
+
+ // Suche nach dem Benutzer in LDAP
+ const baseDN = config.base_dn || '';
+ const usernameAttr = config.username_attribute || 'cn';
+ const escapedUsername = this.escapeLDAPFilter(username);
+ const searchFilter = `(${usernameAttr}=${escapedUsername})`;
+ const searchOptions = {
+ filter: searchFilter,
+ scope: 'sub',
+ attributes: ['dn', usernameAttr]
+ };
+
+ let userDN = null;
+
+ client.search(baseDN, searchOptions, (err, res) => {
+ if (err) {
+ client.unbind();
+ return callback(err, false);
+ }
+
+ res.on('searchEntry', (entry) => {
+ userDN = entry.dn.toString();
+ });
+
+ res.on('error', (err) => {
+ client.unbind();
+ callback(err, false);
+ });
+
+ res.on('end', (result) => {
+ // Service-Account-Verbindung schließen
+ client.unbind();
+
+ if (!userDN) {
+ return callback(new Error('Benutzer nicht gefunden'), false);
+ }
+
+ // Versuche, sich mit den Benutzer-Credentials zu binden
+ const authClient = ldap.createClient({
+ url: config.url,
+ timeout: 10000,
+ connectTimeout: 10000
+ });
+
+ authClient.on('error', (err) => {
+ authClient.unbind();
+ callback(err, false);
+ });
+
+ authClient.bind(userDN, password, (err) => {
+ authClient.unbind();
+ if (err) {
+ return callback(new Error('Ungültiges Passwort'), false);
+ }
+ callback(null, true);
+ });
+ });
+ });
+ });
+ });
+ }
+
/**
* Vollständige Synchronisation durchführen
*/
diff --git a/public/js/dashboard.js b/public/js/dashboard.js
index 020328d..f4a5ccf 100644
--- a/public/js/dashboard.js
+++ b/public/js/dashboard.js
@@ -261,6 +261,7 @@ function renderWeek() {
const hours = entry.total_hours || 0;
const overtimeTaken = entry.overtime_taken_hours || '';
const vacationType = entry.vacation_type || '';
+ const sickStatus = entry.sick_status || false;
// Tätigkeiten laden
const activities = [
@@ -272,26 +273,27 @@ function renderWeek() {
];
// Prüfen ob Werktag (Montag-Freitag, i < 5) ausgefüllt ist
- // Bei ganztägigem Urlaub gilt der Tag als ausgefüllt
- if (i < 5 && vacationType !== 'full' && (!startTime || !endTime || startTime.trim() === '' || endTime.trim() === '')) {
+ // Bei ganztägigem Urlaub oder Krank gilt der Tag als ausgefüllt
+ if (i < 5 && vacationType !== 'full' && !sickStatus && (!startTime || !endTime || startTime.trim() === '' || endTime.trim() === '')) {
allWeekdaysFilled = false;
}
// Stunden zur Summe hinzufügen
- // Bei ganztägigem Urlaub sollten es bereits 8 Stunden sein (vom Backend gesetzt)
+ // Bei ganztägigem Urlaub oder Krank sollten es bereits 8 Stunden sein (vom Backend gesetzt)
// Bei halbem Tag Urlaub werden die Urlaubsstunden später in der Überstunden-Berechnung hinzugezählt
totalHours += hours;
// Bearbeitung ist immer möglich, auch nach Abschicken
- // Bei ganztägigem Urlaub werden Zeitfelder deaktiviert
+ // Bei ganztägigem Urlaub oder Krank werden Zeitfelder deaktiviert
const isFullDayVacation = vacationType === 'full';
- const timeFieldsDisabled = isFullDayVacation ? 'disabled' : '';
+ const isSick = sickStatus === true || sickStatus === 1;
+ const timeFieldsDisabled = (isFullDayVacation || isSick) ? 'disabled' : '';
const disabled = '';
html += `
| ${getWeekday(dateStr)} |
- ${formatDateDE(dateStr)}${isFullDayVacation ? ' (Urlaub - ganzer Tag)' : ''} |
+ ${formatDateDE(dateStr)}${isFullDayVacation ? ' (Urlaub - ganzer Tag)' : ''}${isSick ? ' (Krank)' : ''} |
|
- ${isFullDayVacation ? '8.00 h (Urlaub)' : hours.toFixed(2) + ' h'} |
+ ${isFullDayVacation ? '8.00 h (Urlaub)' : isSick ? '8.00 h (Krank)' : hours.toFixed(2) + ' h'} |
|
@@ -395,6 +397,21 @@ function renderWeek() {
+
|
@@ -486,12 +503,16 @@ function updateOvertimeDisplay() {
date.setDate(date.getDate() + i);
const dateStr = formatDate(date);
- // Prüfe Urlaub-Status
+ // Prüfe Urlaub-Status und Krank-Status
const vacationSelect = document.querySelector(`select[data-date="${dateStr}"][data-field="vacation_type"]`);
const vacationType = vacationSelect ? vacationSelect.value : (currentEntries[dateStr]?.vacation_type || '');
+ const sickCheckbox = document.querySelector(`input[data-date="${dateStr}"][data-field="sick_status"]`);
+ const sickStatus = sickCheckbox ? sickCheckbox.checked : (currentEntries[dateStr]?.sick_status || false);
if (vacationType === 'full') {
totalHours += 8; // Ganzer Tag Urlaub = 8 Stunden
+ } else if (sickStatus) {
+ totalHours += 8; // Krank = 8 Stunden
} else {
// Berechne Stunden direkt aus Start-/Endzeit und Pause
const startInput = document.querySelector(`input[data-date="${dateStr}"][data-field="start_time"]`);
@@ -594,6 +615,7 @@ async function saveEntry(input) {
const notesInput = document.querySelector(`textarea[data-date="${date}"][data-field="notes"]`);
const vacationSelect = document.querySelector(`select[data-date="${date}"][data-field="vacation_type"]`);
const overtimeInput = document.querySelector(`input[data-date="${date}"][data-field="overtime_taken_hours"]`);
+ const sickCheckbox = document.querySelector(`input[data-date="${date}"][data-field="sick_status"]`);
// Wenn das aktuelle Input-Element das gesuchte Feld ist, verwende dessen Wert direkt
// Das stellt sicher, dass der Wert auch bei oninput/onchange sofort verfügbar ist
@@ -612,6 +634,7 @@ async function saveEntry(input) {
const notes = notesInput ? (notesInput.value || '') : (currentEntries[date].notes || '');
const vacation_type = vacationSelect && vacationSelect.value ? vacationSelect.value : (currentEntries[date].vacation_type || null);
const overtime_taken_hours = overtimeInput && overtimeInput.value ? overtimeInput.value : (currentEntries[date].overtime_taken_hours || null);
+ const sick_status = sickCheckbox ? (sickCheckbox.checked ? true : false) : (currentEntries[date].sick_status || false);
// Activity-Felder aus DOM lesen
const activities = [];
@@ -634,6 +657,7 @@ async function saveEntry(input) {
currentEntries[date].notes = notes;
currentEntries[date].vacation_type = vacation_type;
currentEntries[date].overtime_taken_hours = overtime_taken_hours;
+ currentEntries[date].sick_status = sick_status;
for (let i = 1; i <= 5; i++) {
currentEntries[date][`activity${i}_desc`] = activities[i-1].desc;
currentEntries[date][`activity${i}_hours`] = activities[i-1].hours;
@@ -671,7 +695,8 @@ async function saveEntry(input) {
activity5_hours: activities[4].hours,
activity5_project_number: activities[4].projectNumber,
overtime_taken_hours: overtime_taken_hours,
- vacation_type: vacation_type
+ vacation_type: vacation_type,
+ sick_status: sick_status
})
});
@@ -727,15 +752,17 @@ function checkWeekComplete() {
date.setDate(date.getDate() + i);
const dateStr = formatDate(date);
- // Prüfe Urlaub-Status
+ // Prüfe Urlaub-Status und Krank-Status
const entry = currentEntries[dateStr] || {};
const vacationType = entry.vacation_type;
const vacationSelect = document.querySelector(`select[data-date="${dateStr}"][data-field="vacation_type"]`);
const vacationValue = vacationSelect ? vacationSelect.value : (vacationType || '');
+ const sickCheckbox = document.querySelector(`input[data-date="${dateStr}"][data-field="sick_status"]`);
+ const sickStatus = sickCheckbox ? sickCheckbox.checked : (entry.sick_status || false);
- // Wenn ganzer Tag Urlaub, dann ist der Tag als ausgefüllt zu betrachten
- if (vacationValue === 'full') {
- continue; // Tag ist ausgefüllt (ganzer Tag Urlaub)
+ // Wenn ganzer Tag Urlaub oder Krank, dann ist der Tag als ausgefüllt zu betrachten
+ if (vacationValue === 'full' || sickStatus) {
+ continue; // Tag ist ausgefüllt (ganzer Tag Urlaub oder Krank)
}
// Prüfe IMMER direkt die Input-Felder im DOM (das ist die zuverlässigste Quelle)
@@ -832,14 +859,16 @@ async function submitWeek() {
const weekday = getWeekday(dateStr);
const dateDisplay = formatDateDE(dateStr);
- // Prüfe Urlaub-Status
+ // Prüfe Urlaub-Status und Krank-Status
const entry = currentEntries[dateStr] || {};
const vacationSelect = document.querySelector(`select[data-date="${dateStr}"][data-field="vacation_type"]`);
const vacationValue = vacationSelect ? vacationSelect.value : (entry.vacation_type || '');
+ const sickCheckbox = document.querySelector(`input[data-date="${dateStr}"][data-field="sick_status"]`);
+ const sickStatus = sickCheckbox ? sickCheckbox.checked : (entry.sick_status || false);
- // Wenn ganzer Tag Urlaub, dann ist der Tag als ausgefüllt zu betrachten
- if (vacationValue === 'full') {
- continue; // Tag ist ausgefüllt (ganzer Tag Urlaub)
+ // Wenn ganzer Tag Urlaub oder Krank, dann ist der Tag als ausgefüllt zu betrachten
+ if (vacationValue === 'full' || sickStatus) {
+ continue; // Tag ist ausgefüllt (ganzer Tag Urlaub oder Krank)
}
// Prüfe IMMER direkt die Input-Felder im DOM - auch bei manueller Eingabe
@@ -1105,3 +1134,36 @@ function toggleVacationSelect(dateStr) {
}
}
}
+
+// Krank-Status ein-/ausblenden
+function toggleSickStatus(dateStr) {
+ const checkboxDiv = document.getElementById(`sick-checkbox-${dateStr}`);
+ if (checkboxDiv) {
+ if (checkboxDiv.style.display === 'none' || !checkboxDiv.style.display) {
+ checkboxDiv.style.display = 'inline-block';
+ const checkbox = checkboxDiv.querySelector('input[type="checkbox"]');
+ if (checkbox) {
+ // Prüfe aktuellen Status aus currentEntries
+ const currentSickStatus = currentEntries[dateStr]?.sick_status || false;
+ checkbox.checked = currentSickStatus || true; // Wenn nicht gesetzt, auf true setzen
+ checkbox.focus();
+ // Sofort speichern wenn aktiviert
+ if (!currentSickStatus) {
+ saveEntry(checkbox);
+ }
+ }
+ } else {
+ // Wert löschen wenn ausgeblendet
+ const checkbox = checkboxDiv.querySelector('input[type="checkbox"]');
+ if (checkbox) {
+ checkbox.checked = false;
+ // Speichern
+ if (currentEntries[dateStr]) {
+ currentEntries[dateStr].sick_status = false;
+ saveEntry(checkbox);
+ }
+ }
+ checkboxDiv.style.display = 'none';
+ }
+ }
+}
diff --git a/server.js b/server.js
index 806af8e..b09ee4d 100644
--- a/server.js
+++ b/server.js
@@ -139,56 +139,101 @@ app.get('/login', (req, res) => {
app.post('/login', (req, res) => {
const { username, password } = req.body;
- db.get('SELECT * FROM users WHERE username = ?', [username], (err, user) => {
- if (err || !user) {
- return res.render('login', { error: 'Ungültiger Benutzername oder Passwort' });
+ // Prüfe ob LDAP aktiviert ist
+ LDAPService.getConfig((err, ldapConfig) => {
+ if (err) {
+ console.error('Fehler beim Abrufen der LDAP-Konfiguration:', err);
}
- if (bcrypt.compareSync(password, user.password)) {
- // Rollen als JSON-Array parsen
- let roles = [];
- try {
- roles = JSON.parse(user.role);
- if (!Array.isArray(roles)) {
- // Fallback: Falls kein Array, erstelle Array mit vorhandener Rolle
- roles = [user.role];
+ const isLDAPEnabled = ldapConfig && ldapConfig.enabled === 1;
+
+ // Wenn LDAP aktiviert ist, authentifiziere gegen LDAP
+ if (isLDAPEnabled) {
+ LDAPService.authenticate(username, password, (authErr, authSuccess) => {
+ if (authErr || !authSuccess) {
+ // LDAP-Authentifizierung fehlgeschlagen - prüfe lokale Datenbank als Fallback
+ db.get('SELECT * FROM users WHERE username = ?', [username], (err, user) => {
+ if (err || !user) {
+ return res.render('login', { error: 'Ungültiger Benutzername oder Passwort' });
+ }
+
+ // Versuche lokale Authentifizierung
+ if (bcrypt.compareSync(password, user.password)) {
+ handleSuccessfulLogin(req, res, user);
+ } else {
+ res.render('login', { error: 'Ungültiger Benutzername oder Passwort' });
+ }
+ });
+ } else {
+ // LDAP-Authentifizierung erfolgreich - hole Benutzer aus Datenbank
+ db.get('SELECT * FROM users WHERE username = ?', [username], (err, user) => {
+ if (err || !user) {
+ return res.render('login', { error: 'Benutzer nicht in der Datenbank gefunden. Bitte führen Sie eine LDAP-Synchronisation durch.' });
+ }
+
+ handleSuccessfulLogin(req, res, user);
+ });
}
- } catch (e) {
- // Fallback: Falls kein JSON, erstelle Array mit vorhandener Rolle
- roles = [user.role || 'mitarbeiter'];
- }
-
- // Standard-Rolle bestimmen: Immer "mitarbeiter" wenn vorhanden, sonst höchste Priorität
- let defaultRole;
- if (roles.includes('mitarbeiter')) {
- defaultRole = 'mitarbeiter';
- } else {
- defaultRole = getDefaultRole(roles);
- }
-
- req.session.userId = user.id;
- req.session.username = user.username;
- req.session.roles = roles;
- req.session.currentRole = defaultRole;
- req.session.firstname = user.firstname;
- req.session.lastname = user.lastname;
-
- // Redirect: Immer zu Dashboard wenn Mitarbeiter-Rolle vorhanden, sonst basierend auf Standard-Rolle
- if (roles.includes('mitarbeiter')) {
- res.redirect('/dashboard');
- } else if (defaultRole === 'admin') {
- res.redirect('/admin');
- } else if (defaultRole === 'verwaltung') {
- res.redirect('/verwaltung');
- } else {
- res.redirect('/dashboard');
- }
+ });
} else {
- res.render('login', { error: 'Ungültiger Benutzername oder Passwort' });
+ // LDAP nicht aktiviert - verwende lokale Authentifizierung
+ db.get('SELECT * FROM users WHERE username = ?', [username], (err, user) => {
+ if (err || !user) {
+ return res.render('login', { error: 'Ungültiger Benutzername oder Passwort' });
+ }
+
+ if (bcrypt.compareSync(password, user.password)) {
+ handleSuccessfulLogin(req, res, user);
+ } else {
+ res.render('login', { error: 'Ungültiger Benutzername oder Passwort' });
+ }
+ });
}
});
});
+// Helper-Funktion für erfolgreiche Anmeldung
+function handleSuccessfulLogin(req, res, user) {
+ // Rollen als JSON-Array parsen
+ let roles = [];
+ try {
+ roles = JSON.parse(user.role);
+ if (!Array.isArray(roles)) {
+ // Fallback: Falls kein Array, erstelle Array mit vorhandener Rolle
+ roles = [user.role];
+ }
+ } catch (e) {
+ // Fallback: Falls kein JSON, erstelle Array mit vorhandener Rolle
+ roles = [user.role || 'mitarbeiter'];
+ }
+
+ // Standard-Rolle bestimmen: Immer "mitarbeiter" wenn vorhanden, sonst höchste Priorität
+ let defaultRole;
+ if (roles.includes('mitarbeiter')) {
+ defaultRole = 'mitarbeiter';
+ } else {
+ defaultRole = getDefaultRole(roles);
+ }
+
+ req.session.userId = user.id;
+ req.session.username = user.username;
+ req.session.roles = roles;
+ req.session.currentRole = defaultRole;
+ req.session.firstname = user.firstname;
+ req.session.lastname = user.lastname;
+
+ // Redirect: Immer zu Dashboard wenn Mitarbeiter-Rolle vorhanden, sonst basierend auf Standard-Rolle
+ if (roles.includes('mitarbeiter')) {
+ res.redirect('/dashboard');
+ } else if (defaultRole === 'admin') {
+ res.redirect('/admin');
+ } else if (defaultRole === 'verwaltung') {
+ res.redirect('/verwaltung');
+ } else {
+ res.redirect('/dashboard');
+ }
+}
+
// Logout
app.get('/logout', (req, res) => {
req.session.destroy();
@@ -810,7 +855,7 @@ app.post('/api/timesheet/save', requireAuth, (req, res) => {
activity3_desc, activity3_hours, activity3_project_number,
activity4_desc, activity4_hours, activity4_project_number,
activity5_desc, activity5_hours, activity5_project_number,
- overtime_taken_hours, vacation_type
+ overtime_taken_hours, vacation_type, sick_status
} = req.body;
const userId = req.session.userId;
@@ -818,11 +863,21 @@ app.post('/api/timesheet/save', requireAuth, (req, res) => {
const normalizedEndTime = (end_time && typeof end_time === 'string' && end_time.trim() !== '') ? end_time.trim() : (end_time || null);
const normalizedStartTime = (start_time && typeof start_time === 'string' && start_time.trim() !== '') ? start_time.trim() : (start_time || null);
+ // Normalisiere sick_status: Boolean oder 1/0 zu Boolean
+ const isSick = sick_status === true || sick_status === 1 || sick_status === 'true' || sick_status === '1';
+
// Gesamtstunden berechnen (aus Start- und Endzeit, nicht aus Tätigkeiten)
- // Wenn ganzer Tag Urlaub, dann zählt dieser als 8 Stunden normale Arbeitszeit
+ // Wenn ganzer Tag Urlaub oder Krank, dann zählt dieser als 8 Stunden normale Arbeitszeit
let total_hours = 0;
+ let finalActivity1Desc = activity1_desc;
+ let finalActivity1Hours = parseFloat(activity1_hours) || 0;
+
if (vacation_type === 'full') {
total_hours = 8; // Ganzer Tag Urlaub = 8 Stunden normale Arbeitszeit
+ } else if (isSick) {
+ total_hours = 8; // Krank = 8 Stunden normale Arbeitszeit
+ finalActivity1Desc = 'Krank';
+ finalActivity1Hours = 8;
} else if (normalizedStartTime && normalizedEndTime) {
const start = new Date(`2000-01-01T${normalizedStartTime}`);
const end = new Date(`2000-01-01T${normalizedEndTime}`);
@@ -844,18 +899,19 @@ app.post('/api/timesheet/save', requireAuth, (req, res) => {
activity3_desc = ?, activity3_hours = ?, activity3_project_number = ?,
activity4_desc = ?, activity4_hours = ?, activity4_project_number = ?,
activity5_desc = ?, activity5_hours = ?, activity5_project_number = ?,
- overtime_taken_hours = ?, vacation_type = ?,
+ overtime_taken_hours = ?, vacation_type = ?, sick_status = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ?`,
[
normalizedStartTime, normalizedEndTime, break_minutes, total_hours, notes,
- activity1_desc || null, parseFloat(activity1_hours) || 0, activity1_project_number || null,
+ finalActivity1Desc || null, finalActivity1Hours, activity1_project_number || null,
activity2_desc || null, parseFloat(activity2_hours) || 0, activity2_project_number || null,
activity3_desc || null, parseFloat(activity3_hours) || 0, activity3_project_number || null,
activity4_desc || null, parseFloat(activity4_hours) || 0, activity4_project_number || null,
activity5_desc || null, parseFloat(activity5_hours) || 0, activity5_project_number || null,
overtime_taken_hours ? parseFloat(overtime_taken_hours) : null,
vacation_type || null,
+ isSick ? 1 : 0,
row.id
],
(err) => {
@@ -874,17 +930,18 @@ app.post('/api/timesheet/save', requireAuth, (req, res) => {
activity3_desc, activity3_hours, activity3_project_number,
activity4_desc, activity4_hours, activity4_project_number,
activity5_desc, activity5_hours, activity5_project_number,
- overtime_taken_hours, vacation_type)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
+ overtime_taken_hours, vacation_type, sick_status)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
userId, date, normalizedStartTime, normalizedEndTime, break_minutes, total_hours, notes,
- activity1_desc || null, parseFloat(activity1_hours) || 0, activity1_project_number || null,
+ finalActivity1Desc || null, finalActivity1Hours, activity1_project_number || null,
activity2_desc || null, parseFloat(activity2_hours) || 0, activity2_project_number || null,
activity3_desc || null, parseFloat(activity3_hours) || 0, activity3_project_number || null,
activity4_desc || null, parseFloat(activity4_hours) || 0, activity4_project_number || null,
activity5_desc || null, parseFloat(activity5_hours) || 0, activity5_project_number || null,
overtime_taken_hours ? parseFloat(overtime_taken_hours) : null,
- vacation_type || null
+ vacation_type || null,
+ isSick ? 1 : 0
],
(err) => {
if (err) {
@@ -1049,10 +1106,10 @@ app.post('/api/timesheet/submit', requireAuth, (req, res) => {
const { week_start, week_end, version_reason } = req.body;
const userId = req.session.userId;
- // Validierung: Prüfen ob alle 7 Tage der Woche ausgefüllt sind
- db.all(`SELECT id, date, start_time, end_time, vacation_type, updated_at FROM timesheet_entries
- WHERE user_id = ? AND date >= ? AND date <= ?
- ORDER BY date, updated_at DESC, id DESC`,
+ // Validierung: Prüfen ob alle 7 Tage der Woche ausgefüllt sind
+ db.all(`SELECT id, date, start_time, end_time, vacation_type, sick_status, updated_at FROM timesheet_entries
+ WHERE user_id = ? AND date >= ? AND date <= ?
+ ORDER BY date, updated_at DESC, id DESC`,
[userId, week_start, week_end],
(err, entries) => {
if (err) {
@@ -1097,8 +1154,9 @@ app.post('/api/timesheet/submit', requireAuth, (req, res) => {
const dateStr = `${year}-${month}-${day}`;
const entry = entriesByDate[dateStr];
- // Wenn ganztägiger Urlaub, dann ist der Tag als ausgefüllt zu betrachten
- if (entry && entry.vacation_type === 'full') {
+ // Wenn ganztägiger Urlaub oder Krank, dann ist der Tag als ausgefüllt zu betrachten
+ const isSick = entry && (entry.sick_status === 1 || entry.sick_status === true);
+ if (entry && (entry.vacation_type === 'full' || isSick)) {
continue; // Tag ist ausgefüllt
}