Compare commits
2 Commits
1264a8fbc6
...
70c106ec1a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70c106ec1a | ||
|
|
f7b1322ae6 |
@@ -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
|
// Migration: Pausen-Zeiten für API-Zeiterfassung hinzufügen
|
||||||
db.run(`ALTER TABLE timesheet_entries ADD COLUMN pause_start_time TEXT`, (err) => {
|
db.run(`ALTER TABLE timesheet_entries ADD COLUMN pause_start_time TEXT`, (err) => {
|
||||||
// Fehler ignorieren wenn Spalte bereits existiert
|
// Fehler ignorieren wenn Spalte bereits existiert
|
||||||
|
|||||||
@@ -113,6 +113,19 @@ class LDAPService {
|
|||||||
return Array.isArray(attr.values) ? attr.values[0] : attr.values;
|
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
|
* 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
|
* Vollständige Synchronisation durchführen
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -261,6 +261,7 @@ function renderWeek() {
|
|||||||
const hours = entry.total_hours || 0;
|
const hours = entry.total_hours || 0;
|
||||||
const overtimeTaken = entry.overtime_taken_hours || '';
|
const overtimeTaken = entry.overtime_taken_hours || '';
|
||||||
const vacationType = entry.vacation_type || '';
|
const vacationType = entry.vacation_type || '';
|
||||||
|
const sickStatus = entry.sick_status || false;
|
||||||
|
|
||||||
// Tätigkeiten laden
|
// Tätigkeiten laden
|
||||||
const activities = [
|
const activities = [
|
||||||
@@ -272,26 +273,27 @@ function renderWeek() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Prüfen ob Werktag (Montag-Freitag, i < 5) ausgefüllt ist
|
// Prüfen ob Werktag (Montag-Freitag, i < 5) ausgefüllt ist
|
||||||
// Bei ganztägigem Urlaub gilt der Tag als ausgefüllt
|
// Bei ganztägigem Urlaub oder Krank gilt der Tag als ausgefüllt
|
||||||
if (i < 5 && vacationType !== 'full' && (!startTime || !endTime || startTime.trim() === '' || endTime.trim() === '')) {
|
if (i < 5 && vacationType !== 'full' && !sickStatus && (!startTime || !endTime || startTime.trim() === '' || endTime.trim() === '')) {
|
||||||
allWeekdaysFilled = false;
|
allWeekdaysFilled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stunden zur Summe hinzufügen
|
// 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
|
// Bei halbem Tag Urlaub werden die Urlaubsstunden später in der Überstunden-Berechnung hinzugezählt
|
||||||
totalHours += hours;
|
totalHours += hours;
|
||||||
|
|
||||||
// Bearbeitung ist immer möglich, auch nach Abschicken
|
// 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 isFullDayVacation = vacationType === 'full';
|
||||||
const timeFieldsDisabled = isFullDayVacation ? 'disabled' : '';
|
const isSick = sickStatus === true || sickStatus === 1;
|
||||||
|
const timeFieldsDisabled = (isFullDayVacation || isSick) ? 'disabled' : '';
|
||||||
const disabled = '';
|
const disabled = '';
|
||||||
|
|
||||||
html += `
|
html += `
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>${getWeekday(dateStr)}</strong></td>
|
<td><strong>${getWeekday(dateStr)}</strong></td>
|
||||||
<td>${formatDateDE(dateStr)}${isFullDayVacation ? ' <span style="color: #28a745;">(Urlaub - ganzer Tag)</span>' : ''}</td>
|
<td>${formatDateDE(dateStr)}${isFullDayVacation ? ' <span style="color: #28a745;">(Urlaub - ganzer Tag)</span>' : ''}${isSick ? ' <span style="color: #e74c3c;">(Krank)</span>' : ''}</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="time" value="${startTime}"
|
<input type="time" value="${startTime}"
|
||||||
data-date="${dateStr}" data-field="start_time"
|
data-date="${dateStr}" data-field="start_time"
|
||||||
@@ -309,7 +311,7 @@ function renderWeek() {
|
|||||||
data-date="${dateStr}" data-field="break_minutes"
|
data-date="${dateStr}" data-field="break_minutes"
|
||||||
${timeFieldsDisabled} ${disabled} oninput="saveEntry(this)" onchange="saveEntry(this)">
|
${timeFieldsDisabled} ${disabled} oninput="saveEntry(this)" onchange="saveEntry(this)">
|
||||||
</td>
|
</td>
|
||||||
<td><strong id="hours_${dateStr}">${isFullDayVacation ? '8.00 h (Urlaub)' : hours.toFixed(2) + ' h'}</strong></td>
|
<td><strong id="hours_${dateStr}">${isFullDayVacation ? '8.00 h (Urlaub)' : isSick ? '8.00 h (Krank)' : hours.toFixed(2) + ' h'}</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="activities-row">
|
<tr class="activities-row">
|
||||||
<td colspan="6" class="activities-cell">
|
<td colspan="6" class="activities-cell">
|
||||||
@@ -395,6 +397,21 @@ function renderWeek() {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="sick-control">
|
||||||
|
<button type="button" class="btn btn-secondary btn-sm" onclick="toggleSickStatus('${dateStr}')" style="margin-right: 5px;">
|
||||||
|
Krank
|
||||||
|
</button>
|
||||||
|
<div id="sick-checkbox-${dateStr}" style="display: ${sickStatus ? 'inline-block' : 'none'};">
|
||||||
|
<input type="checkbox"
|
||||||
|
data-date="${dateStr}"
|
||||||
|
data-field="sick_status"
|
||||||
|
${sickStatus ? 'checked' : ''}
|
||||||
|
${disabled}
|
||||||
|
onchange="saveEntry(this); updateOvertimeDisplay();"
|
||||||
|
style="margin-left: 5px;"
|
||||||
|
class="sick-checkbox">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -486,12 +503,16 @@ function updateOvertimeDisplay() {
|
|||||||
date.setDate(date.getDate() + i);
|
date.setDate(date.getDate() + i);
|
||||||
const dateStr = formatDate(date);
|
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 vacationSelect = document.querySelector(`select[data-date="${dateStr}"][data-field="vacation_type"]`);
|
||||||
const vacationType = vacationSelect ? vacationSelect.value : (currentEntries[dateStr]?.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') {
|
if (vacationType === 'full') {
|
||||||
totalHours += 8; // Ganzer Tag Urlaub = 8 Stunden
|
totalHours += 8; // Ganzer Tag Urlaub = 8 Stunden
|
||||||
|
} else if (sickStatus) {
|
||||||
|
totalHours += 8; // Krank = 8 Stunden
|
||||||
} else {
|
} else {
|
||||||
// Berechne Stunden direkt aus Start-/Endzeit und Pause
|
// Berechne Stunden direkt aus Start-/Endzeit und Pause
|
||||||
const startInput = document.querySelector(`input[data-date="${dateStr}"][data-field="start_time"]`);
|
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 notesInput = document.querySelector(`textarea[data-date="${date}"][data-field="notes"]`);
|
||||||
const vacationSelect = document.querySelector(`select[data-date="${date}"][data-field="vacation_type"]`);
|
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 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
|
// 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
|
// 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 notes = notesInput ? (notesInput.value || '') : (currentEntries[date].notes || '');
|
||||||
const vacation_type = vacationSelect && vacationSelect.value ? vacationSelect.value : (currentEntries[date].vacation_type || null);
|
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 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
|
// Activity-Felder aus DOM lesen
|
||||||
const activities = [];
|
const activities = [];
|
||||||
@@ -634,6 +657,7 @@ async function saveEntry(input) {
|
|||||||
currentEntries[date].notes = notes;
|
currentEntries[date].notes = notes;
|
||||||
currentEntries[date].vacation_type = vacation_type;
|
currentEntries[date].vacation_type = vacation_type;
|
||||||
currentEntries[date].overtime_taken_hours = overtime_taken_hours;
|
currentEntries[date].overtime_taken_hours = overtime_taken_hours;
|
||||||
|
currentEntries[date].sick_status = sick_status;
|
||||||
for (let i = 1; i <= 5; i++) {
|
for (let i = 1; i <= 5; i++) {
|
||||||
currentEntries[date][`activity${i}_desc`] = activities[i-1].desc;
|
currentEntries[date][`activity${i}_desc`] = activities[i-1].desc;
|
||||||
currentEntries[date][`activity${i}_hours`] = activities[i-1].hours;
|
currentEntries[date][`activity${i}_hours`] = activities[i-1].hours;
|
||||||
@@ -671,7 +695,8 @@ async function saveEntry(input) {
|
|||||||
activity5_hours: activities[4].hours,
|
activity5_hours: activities[4].hours,
|
||||||
activity5_project_number: activities[4].projectNumber,
|
activity5_project_number: activities[4].projectNumber,
|
||||||
overtime_taken_hours: overtime_taken_hours,
|
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);
|
date.setDate(date.getDate() + i);
|
||||||
const dateStr = formatDate(date);
|
const dateStr = formatDate(date);
|
||||||
|
|
||||||
// Prüfe Urlaub-Status
|
// Prüfe Urlaub-Status und Krank-Status
|
||||||
const entry = currentEntries[dateStr] || {};
|
const entry = currentEntries[dateStr] || {};
|
||||||
const vacationType = entry.vacation_type;
|
const vacationType = entry.vacation_type;
|
||||||
const vacationSelect = document.querySelector(`select[data-date="${dateStr}"][data-field="vacation_type"]`);
|
const vacationSelect = document.querySelector(`select[data-date="${dateStr}"][data-field="vacation_type"]`);
|
||||||
const vacationValue = vacationSelect ? vacationSelect.value : (vacationType || '');
|
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
|
// Wenn ganzer Tag Urlaub oder Krank, dann ist der Tag als ausgefüllt zu betrachten
|
||||||
if (vacationValue === 'full') {
|
if (vacationValue === 'full' || sickStatus) {
|
||||||
continue; // Tag ist ausgefüllt (ganzer Tag Urlaub)
|
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)
|
// 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 weekday = getWeekday(dateStr);
|
||||||
const dateDisplay = formatDateDE(dateStr);
|
const dateDisplay = formatDateDE(dateStr);
|
||||||
|
|
||||||
// Prüfe Urlaub-Status
|
// Prüfe Urlaub-Status und Krank-Status
|
||||||
const entry = currentEntries[dateStr] || {};
|
const entry = currentEntries[dateStr] || {};
|
||||||
const vacationSelect = document.querySelector(`select[data-date="${dateStr}"][data-field="vacation_type"]`);
|
const vacationSelect = document.querySelector(`select[data-date="${dateStr}"][data-field="vacation_type"]`);
|
||||||
const vacationValue = vacationSelect ? vacationSelect.value : (entry.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
|
// Wenn ganzer Tag Urlaub oder Krank, dann ist der Tag als ausgefüllt zu betrachten
|
||||||
if (vacationValue === 'full') {
|
if (vacationValue === 'full' || sickStatus) {
|
||||||
continue; // Tag ist ausgefüllt (ganzer Tag Urlaub)
|
continue; // Tag ist ausgefüllt (ganzer Tag Urlaub oder Krank)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prüfe IMMER direkt die Input-Felder im DOM - auch bei manueller Eingabe
|
// 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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
90
server.js
90
server.js
@@ -139,12 +139,61 @@ app.get('/login', (req, res) => {
|
|||||||
app.post('/login', (req, res) => {
|
app.post('/login', (req, res) => {
|
||||||
const { username, password } = req.body;
|
const { username, password } = req.body;
|
||||||
|
|
||||||
|
// Prüfe ob LDAP aktiviert ist
|
||||||
|
LDAPService.getConfig((err, ldapConfig) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Fehler beim Abrufen der LDAP-Konfiguration:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// LDAP nicht aktiviert - verwende lokale Authentifizierung
|
||||||
db.get('SELECT * FROM users WHERE username = ?', [username], (err, user) => {
|
db.get('SELECT * FROM users WHERE username = ?', [username], (err, user) => {
|
||||||
if (err || !user) {
|
if (err || !user) {
|
||||||
return res.render('login', { error: 'Ungültiger Benutzername oder Passwort' });
|
return res.render('login', { error: 'Ungültiger Benutzername oder Passwort' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bcrypt.compareSync(password, user.password)) {
|
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
|
// Rollen als JSON-Array parsen
|
||||||
let roles = [];
|
let roles = [];
|
||||||
try {
|
try {
|
||||||
@@ -183,11 +232,7 @@ app.post('/login', (req, res) => {
|
|||||||
} else {
|
} else {
|
||||||
res.redirect('/dashboard');
|
res.redirect('/dashboard');
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
res.render('login', { error: 'Ungültiger Benutzername oder Passwort' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Logout
|
// Logout
|
||||||
app.get('/logout', (req, res) => {
|
app.get('/logout', (req, res) => {
|
||||||
@@ -810,7 +855,7 @@ app.post('/api/timesheet/save', requireAuth, (req, res) => {
|
|||||||
activity3_desc, activity3_hours, activity3_project_number,
|
activity3_desc, activity3_hours, activity3_project_number,
|
||||||
activity4_desc, activity4_hours, activity4_project_number,
|
activity4_desc, activity4_hours, activity4_project_number,
|
||||||
activity5_desc, activity5_hours, activity5_project_number,
|
activity5_desc, activity5_hours, activity5_project_number,
|
||||||
overtime_taken_hours, vacation_type
|
overtime_taken_hours, vacation_type, sick_status
|
||||||
} = req.body;
|
} = req.body;
|
||||||
const userId = req.session.userId;
|
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 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);
|
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)
|
// 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 total_hours = 0;
|
||||||
|
let finalActivity1Desc = activity1_desc;
|
||||||
|
let finalActivity1Hours = parseFloat(activity1_hours) || 0;
|
||||||
|
|
||||||
if (vacation_type === 'full') {
|
if (vacation_type === 'full') {
|
||||||
total_hours = 8; // Ganzer Tag Urlaub = 8 Stunden normale Arbeitszeit
|
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) {
|
} else if (normalizedStartTime && normalizedEndTime) {
|
||||||
const start = new Date(`2000-01-01T${normalizedStartTime}`);
|
const start = new Date(`2000-01-01T${normalizedStartTime}`);
|
||||||
const end = new Date(`2000-01-01T${normalizedEndTime}`);
|
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 = ?,
|
activity3_desc = ?, activity3_hours = ?, activity3_project_number = ?,
|
||||||
activity4_desc = ?, activity4_hours = ?, activity4_project_number = ?,
|
activity4_desc = ?, activity4_hours = ?, activity4_project_number = ?,
|
||||||
activity5_desc = ?, activity5_hours = ?, activity5_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
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = ?`,
|
WHERE id = ?`,
|
||||||
[
|
[
|
||||||
normalizedStartTime, normalizedEndTime, break_minutes, total_hours, notes,
|
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,
|
activity2_desc || null, parseFloat(activity2_hours) || 0, activity2_project_number || null,
|
||||||
activity3_desc || null, parseFloat(activity3_hours) || 0, activity3_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,
|
activity4_desc || null, parseFloat(activity4_hours) || 0, activity4_project_number || null,
|
||||||
activity5_desc || null, parseFloat(activity5_hours) || 0, activity5_project_number || null,
|
activity5_desc || null, parseFloat(activity5_hours) || 0, activity5_project_number || null,
|
||||||
overtime_taken_hours ? parseFloat(overtime_taken_hours) : null,
|
overtime_taken_hours ? parseFloat(overtime_taken_hours) : null,
|
||||||
vacation_type || null,
|
vacation_type || null,
|
||||||
|
isSick ? 1 : 0,
|
||||||
row.id
|
row.id
|
||||||
],
|
],
|
||||||
(err) => {
|
(err) => {
|
||||||
@@ -874,17 +930,18 @@ app.post('/api/timesheet/save', requireAuth, (req, res) => {
|
|||||||
activity3_desc, activity3_hours, activity3_project_number,
|
activity3_desc, activity3_hours, activity3_project_number,
|
||||||
activity4_desc, activity4_hours, activity4_project_number,
|
activity4_desc, activity4_hours, activity4_project_number,
|
||||||
activity5_desc, activity5_hours, activity5_project_number,
|
activity5_desc, activity5_hours, activity5_project_number,
|
||||||
overtime_taken_hours, vacation_type)
|
overtime_taken_hours, vacation_type, sick_status)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
[
|
[
|
||||||
userId, date, normalizedStartTime, normalizedEndTime, break_minutes, total_hours, notes,
|
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,
|
activity2_desc || null, parseFloat(activity2_hours) || 0, activity2_project_number || null,
|
||||||
activity3_desc || null, parseFloat(activity3_hours) || 0, activity3_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,
|
activity4_desc || null, parseFloat(activity4_hours) || 0, activity4_project_number || null,
|
||||||
activity5_desc || null, parseFloat(activity5_hours) || 0, activity5_project_number || null,
|
activity5_desc || null, parseFloat(activity5_hours) || 0, activity5_project_number || null,
|
||||||
overtime_taken_hours ? parseFloat(overtime_taken_hours) : null,
|
overtime_taken_hours ? parseFloat(overtime_taken_hours) : null,
|
||||||
vacation_type || null
|
vacation_type || null,
|
||||||
|
isSick ? 1 : 0
|
||||||
],
|
],
|
||||||
(err) => {
|
(err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -1050,7 +1107,7 @@ app.post('/api/timesheet/submit', requireAuth, (req, res) => {
|
|||||||
const userId = req.session.userId;
|
const userId = req.session.userId;
|
||||||
|
|
||||||
// Validierung: Prüfen ob alle 7 Tage der Woche ausgefüllt sind
|
// 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
|
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 <= ?
|
WHERE user_id = ? AND date >= ? AND date <= ?
|
||||||
ORDER BY date, updated_at DESC, id DESC`,
|
ORDER BY date, updated_at DESC, id DESC`,
|
||||||
[userId, week_start, week_end],
|
[userId, week_start, week_end],
|
||||||
@@ -1097,8 +1154,9 @@ app.post('/api/timesheet/submit', requireAuth, (req, res) => {
|
|||||||
const dateStr = `${year}-${month}-${day}`;
|
const dateStr = `${year}-${month}-${day}`;
|
||||||
const entry = entriesByDate[dateStr];
|
const entry = entriesByDate[dateStr];
|
||||||
|
|
||||||
// Wenn ganztägiger Urlaub, dann ist der Tag als ausgefüllt zu betrachten
|
// Wenn ganztägiger Urlaub oder Krank, dann ist der Tag als ausgefüllt zu betrachten
|
||||||
if (entry && entry.vacation_type === 'full') {
|
const isSick = entry && (entry.sick_status === 1 || entry.sick_status === true);
|
||||||
|
if (entry && (entry.vacation_type === 'full' || isSick)) {
|
||||||
continue; // Tag ist ausgefüllt
|
continue; // Tag ist ausgefüllt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user