Umstellung auf Arbeitstage

This commit is contained in:
2026-02-03 22:32:49 +01:00
parent 4be9a365b3
commit a3efbb43ae
11 changed files with 304 additions and 157 deletions

View File

@@ -3,10 +3,14 @@
- Überstunden nehmen - Überstunden nehmen
- Wenn Überstunden eingetragen > (Wochenarbeitszeit / Arbeitstage) -> Muss noch Start und Ende und Pause eingetragen werden - Wenn Überstunden eingetragen > (Wochenarbeitszeit / Arbeitstage) -> Muss noch Start und Ende und Pause eingetragen werden
- Wenn Überstunden = (Wochenarbeitszeit / Arbeitstage) -> Tag als ausgefüllt zu betrachten - 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 - Urlaub
- Wird als ausgefüllt betrachtet - Wird als ausgefüllt betrachtet wenn ganzer Urlaubstag
- Stunden werden als (Wochenarbeitszeit / Arbeitstage) gerechnet - 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 - Wird von Verbleibendem Urlaub abgezogen
- Krank - Krank

View File

@@ -1,7 +1,7 @@
- Mitarbeiter Name in den QR code Sheets -> DONE - Mitarbeiter Name in den QR code Sheets -> DONE
- Pause vorbelegen (einstellbar in der Admin) -> DONE Wird anhand der Gesetztlichen vorgaben berechnet - Pause vorbelegen (einstellbar in der Admin) -> DONE Wird anhand der Gesetztlichen vorgaben berechnet
- Offset für die Verwaltung für Urlaubstage -> DONE - 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 - Reisen für Wochenende -> DONE
- LDAP Prüfung - LDAP Prüfung
- DSGVO Sicherheit - DSGVO Sicherheit

View File

@@ -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 // Migration: ping_ip Spalte hinzufügen
db.run(`ALTER TABLE users ADD COLUMN ping_ip TEXT`, (err) => { db.run(`ALTER TABLE users ADD COLUMN ping_ip TEXT`, (err) => {
// Fehler ignorieren wenn Spalte bereits existiert // Fehler ignorieren wenn Spalte bereits existiert

View File

@@ -25,6 +25,7 @@ document.addEventListener('DOMContentLoaded', function() {
roles: roles, roles: roles,
personalnummer: document.getElementById('personalnummer').value, personalnummer: document.getElementById('personalnummer').value,
wochenstunden: document.getElementById('wochenstunden').value, wochenstunden: document.getElementById('wochenstunden').value,
arbeitstage: document.getElementById('arbeitstage').value,
urlaubstage: document.getElementById('urlaubstage').value urlaubstage: document.getElementById('urlaubstage').value
}; };
@@ -315,6 +316,7 @@ async function saveUser(userId) {
const personalnummer = row.querySelector('input[data-field="personalnummer"]').value; const personalnummer = row.querySelector('input[data-field="personalnummer"]').value;
const wochenstunden = row.querySelector('input[data-field="wochenstunden"]').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; const urlaubstage = row.querySelector('input[data-field="urlaubstage"]').value;
// Rollen aus Checkboxen sammeln // Rollen aus Checkboxen sammeln
@@ -336,6 +338,7 @@ async function saveUser(userId) {
body: JSON.stringify({ body: JSON.stringify({
personalnummer: personalnummer || null, personalnummer: personalnummer || null,
wochenstunden: wochenstunden || null, wochenstunden: wochenstunden || null,
arbeitstage: arbeitstage || 5,
urlaubstage: urlaubstage || null, urlaubstage: urlaubstage || null,
roles: roles roles: roles
}) })

View File

@@ -4,6 +4,7 @@ let currentWeekStart = getMonday(new Date());
let currentEntries = {}; let currentEntries = {};
let currentHolidayDates = new Set(); // Feiertage der aktuellen Woche (YYYY-MM-DD) let currentHolidayDates = new Set(); // Feiertage der aktuellen Woche (YYYY-MM-DD)
let userWochenstunden = 0; // Wochenstunden des Users 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 weekendPercentages = { saturday: 100, sunday: 100 }; // Wochenend-Prozentsätze (100% = normal)
let latestSubmittedTimesheetId = null; // ID der neuesten eingereichten Version let latestSubmittedTimesheetId = null; // ID der neuesten eingereichten Version
@@ -303,17 +304,24 @@ function getWeekday(dateStr) {
return days[d.getDay()]; return days[d.getDay()];
} }
// Hilfsfunktion: Berechnet die Stunden pro Tag (Wochenarbeitszeit / Arbeitstage)
function getFullDayHours() {
return userWochenstunden && userArbeitstage ? (userWochenstunden / userArbeitstage) : 8;
}
// Woche laden // Woche laden
async function loadWeek() { async function loadWeek() {
try { try {
// User-Daten laden (Wochenstunden) // User-Daten laden (Wochenstunden, Arbeitstage)
try { try {
const userResponse = await fetch('/api/user/data'); const userResponse = await fetch('/api/user/data');
const userData = await userResponse.json(); const userData = await userResponse.json();
userWochenstunden = userData.wochenstunden || 0; userWochenstunden = userData.wochenstunden || 0;
userArbeitstage = userData.arbeitstage || 5;
} catch (error) { } catch (error) {
console.warn('Konnte User-Daten nicht laden:', error); console.warn('Konnte User-Daten nicht laden:', error);
userWochenstunden = 0; userWochenstunden = 0;
userArbeitstage = 5;
} }
const parts = currentWeekStart.split('-'); const parts = currentWeekStart.split('-');
@@ -420,7 +428,7 @@ function renderWeek() {
// Bei Feiertag, ganztägigem Urlaub oder Krank gilt der Tag als ausgefüllt // 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 // Bei 8 Überstunden (ganzer Tag) gilt der Tag auch als ausgefüllt
const overtimeValue = overtimeTaken ? parseFloat(overtimeTaken) : 0; 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; const isFullDayOvertime = overtimeValue > 0 && Math.abs(overtimeValue - fullDayHours) < 0.01;
if (i < 5 && !isHoliday && vacationType !== 'full' && !sickStatus && !isFullDayOvertime && (!startTime || !endTime || startTime.trim() === '' || endTime.trim() === '')) { 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) // Wochenend-Prozentsätze: Nur auf tatsächlich gearbeitete Stunden anwenden (nicht auf Urlaub, Krankheit, Feiertage)
let hoursToAdd = 0; let hoursToAdd = 0;
if (isHoliday) { if (isHoliday) {
hoursToAdd = 8 + (hours || 0); // 8h Feiertag + gearbeitete Stunden (= Überstunden) hoursToAdd = fullDayHours + (hours || 0); // (Wochenarbeitszeit / Arbeitstage) Feiertag + gearbeitete Stunden (= Überstunden)
} else { } else {
hoursToAdd = hours || 0; hoursToAdd = hours || 0;
// Wochenend-Prozentsatz anwenden (nur wenn weekend_travel aktiviert UND es ist ein Wochenendtag) // 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" 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)' : isSick ? '8.00 h (Krank)' : isHoliday && !hours ? '8.00 h (Feiertag)' : isHoliday && hours ? '8.00 + ' + hours.toFixed(2) + ' h (Überst.)' : hours.toFixed(2) + ' h'}</strong></td> <td><strong id="hours_${dateStr}">${isFullDayVacation ? fullDayHours.toFixed(2) + ' h (Urlaub)' : isSick ? fullDayHours.toFixed(2) + ' h (Krank)' : isHoliday && !hours ? fullDayHours.toFixed(2) + ' h (Feiertag)' : isHoliday && hours ? fullDayHours.toFixed(2) + ' + ' + hours.toFixed(2) + ' h (Überst.)' : 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">
@@ -640,15 +648,16 @@ function renderWeek() {
} }
// Sollstunden berechnen // Sollstunden berechnen
const sollStunden = (userWochenstunden / 5) * workdays; const sollStunden = (userWochenstunden / userArbeitstage) * workdays;
// Urlaubsstunden berechnen (Urlaub zählt als normale Arbeitszeit) // Urlaubsstunden berechnen (Urlaub zählt als normale Arbeitszeit)
let vacationHours = 0; let vacationHours = 0;
const fullDayHours = getFullDayHours();
Object.values(currentEntries).forEach(e => { Object.values(currentEntries).forEach(e => {
if (e.vacation_type === 'full') { if (e.vacation_type === 'full') {
vacationHours += 8; // Ganzer Tag = 8 Stunden vacationHours += fullDayHours; // Ganzer Tag = (Wochenarbeitszeit / Arbeitstage) Stunden
} else if (e.vacation_type === 'half') { } 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 // Sollstunden berechnen
const sollStunden = (userWochenstunden / 5) * workdays; const sollStunden = (userWochenstunden / userArbeitstage) * workdays;
// Gesamtstunden berechnen - direkt aus DOM-Elementen lesen für Echtzeit-Aktualisierung // Gesamtstunden berechnen - direkt aus DOM-Elementen lesen für Echtzeit-Aktualisierung
let totalHours = 0; let totalHours = 0;
@@ -715,9 +724,9 @@ function updateOvertimeDisplay() {
// Wenn Urlaub oder Krank, zähle nur diese Stunden (nicht zusätzlich Arbeitsstunden) // Wenn Urlaub oder Krank, zähle nur diese Stunden (nicht zusätzlich Arbeitsstunden)
if (vacationType === 'full') { if (vacationType === 'full') {
vacationHours += 8; // Ganzer Tag Urlaub = 8 Stunden vacationHours += fullDayHours; // Ganzer Tag Urlaub = (Wochenarbeitszeit / Arbeitstage) Stunden
} else if (vacationType === 'half') { } 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 // Bei halbem Tag Urlaub können noch Arbeitsstunden vorhanden sein
const startInput = document.querySelector(`input[data-date="${dateStr}"][data-field="start_time"]`); 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 endInput = document.querySelector(`input[data-date="${dateStr}"][data-field="end_time"]`);
@@ -776,9 +785,9 @@ function updateOvertimeDisplay() {
totalHours += hours; totalHours += hours;
} }
} else if (sickStatus) { } else if (sickStatus) {
totalHours += 8; // Krank = 8 Stunden totalHours += fullDayHours; // Krank = (Wochenarbeitszeit / Arbeitstage) Stunden
} else if (currentHolidayDates.has(dateStr)) { } 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 startInput = document.querySelector(`input[data-date="${dateStr}"][data-field="start_time"]`);
const endInput = document.querySelector(`input[data-date="${dateStr}"][data-field="end_time"]`); const endInput = document.querySelector(`input[data-date="${dateStr}"][data-field="end_time"]`);
const startTime = startInput ? startInput.value : ''; const startTime = startInput ? startInput.value : '';
@@ -793,7 +802,7 @@ function updateOvertimeDisplay() {
} else if (currentEntries[dateStr]?.total_hours) { } else if (currentEntries[dateStr]?.total_hours) {
worked = parseFloat(currentEntries[dateStr].total_hours) || 0; 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 { } else {
// Wenn 8 Überstunden (ganzer Tag) eingetragen sind, zählt der Tag als 0 Stunden // Wenn 8 Überstunden (ganzer Tag) eingetragen sind, zählt der Tag als 0 Stunden
if (isFullDayOvertime) { if (isFullDayOvertime) {
@@ -902,7 +911,7 @@ function handleOvertimeChange(dateStr, overtimeHours) {
return; return;
} }
const fullDayHours = userWochenstunden / 5; const fullDayHours = getFullDayHours();
const overtimeValue = parseFloat(overtimeHours) || 0; const overtimeValue = parseFloat(overtimeHours) || 0;
// Prüfe ob ganzer Tag Überstunden // 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); const overtimeValue = overtimeInput ? parseFloat(overtimeInput.value) || 0 : (entry.overtime_taken_hours ? parseFloat(entry.overtime_taken_hours) : 0);
// Berechne fullDayHours (normalerweise 8 Stunden) // 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) { 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) // Prüfe IMMER direkt die Input-Felder im DOM (das ist die zuverlässigste Quelle)
@@ -1231,8 +1240,17 @@ function checkWeekComplete() {
fullDayHours: fullDayHours 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 // 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 === '') { if (!startTime || !endTime || startTime === '' || endTime === '') {
allWeekdaysFilled = false; allWeekdaysFilled = false;
missingFields.push(formatDateDE(dateStr)); 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) // 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 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 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 // Wenn 8 Überstunden (ganzer Tag) eingetragen sind, ist der Tag ausgefüllt
if (overtimeValue > 0 && Math.abs(overtimeValue - fullDayHours) < 0.01) { if (overtimeValue > 0 && Math.abs(overtimeValue - fullDayHours) < 0.01) {

View File

@@ -8,7 +8,7 @@ const { requireAdmin } = require('../middleware/auth');
function registerAdminRoutes(app) { function registerAdminRoutes(app) {
// Admin-Bereich // Admin-Bereich
app.get('/admin', requireAdmin, (req, res) => { 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) => { (err, users) => {
// LDAP-Konfiguration, Sync-Log und Optionen abrufen // LDAP-Konfiguration, Sync-Log und Optionen abrufen
db.get('SELECT * FROM ldap_config WHERE id = 1', (err, ldapConfig) => { db.get('SELECT * FROM ldap_config WHERE id = 1', (err, ldapConfig) => {
@@ -48,13 +48,14 @@ function registerAdminRoutes(app) {
// Benutzer erstellen // Benutzer erstellen
app.post('/admin/users', requireAdmin, (req, res) => { 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); const hashedPassword = bcrypt.hashSync(password, 10);
// Normalisiere die optionalen Felder // Normalisiere die optionalen Felder
const normalizedPersonalnummer = personalnummer && personalnummer.trim() !== '' ? personalnummer.trim() : null; const normalizedPersonalnummer = personalnummer && personalnummer.trim() !== '' ? personalnummer.trim() : null;
const normalizedWochenstunden = wochenstunden && wochenstunden !== '' ? parseFloat(wochenstunden) : null; const normalizedWochenstunden = wochenstunden && wochenstunden !== '' ? parseFloat(wochenstunden) : null;
const normalizedUrlaubstage = urlaubstage && urlaubstage !== '' ? parseFloat(urlaubstage) : null; const normalizedUrlaubstage = urlaubstage && urlaubstage !== '' ? parseFloat(urlaubstage) : null;
const normalizedArbeitstage = arbeitstage && arbeitstage !== '' ? parseInt(arbeitstage) : 5;
// Rollen verarbeiten: Erwarte Array, konvertiere zu JSON-String // Rollen verarbeiten: Erwarte Array, konvertiere zu JSON-String
let rolesArray = []; let rolesArray = [];
@@ -72,8 +73,8 @@ function registerAdminRoutes(app) {
const rolesJson = JSON.stringify(rolesArray); const rolesJson = JSON.stringify(rolesArray);
db.run('INSERT INTO users (username, password, firstname, lastname, role, personalnummer, wochenstunden, urlaubstage) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', db.run('INSERT INTO users (username, password, firstname, lastname, role, personalnummer, wochenstunden, urlaubstage, arbeitstage) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
[username, hashedPassword, firstname, lastname, rolesJson, normalizedPersonalnummer, normalizedWochenstunden, normalizedUrlaubstage], [username, hashedPassword, firstname, lastname, rolesJson, normalizedPersonalnummer, normalizedWochenstunden, normalizedUrlaubstage, normalizedArbeitstage],
(err) => { (err) => {
if (err) { if (err) {
return res.status(400).json({ error: 'Benutzername existiert bereits' }); return res.status(400).json({ error: 'Benutzername existiert bereits' });
@@ -102,7 +103,7 @@ function registerAdminRoutes(app) {
// Benutzer aktualisieren (Personalnummer, Wochenstunden, Urlaubstage, Rollen) // Benutzer aktualisieren (Personalnummer, Wochenstunden, Urlaubstage, Rollen)
app.put('/admin/users/:id', requireAdmin, (req, res) => { app.put('/admin/users/:id', requireAdmin, (req, res) => {
const userId = req.params.id; const userId = req.params.id;
const { personalnummer, wochenstunden, urlaubstage, roles } = req.body; const { personalnummer, wochenstunden, urlaubstage, arbeitstage, roles } = req.body;
// Rollen verarbeiten falls vorhanden // Rollen verarbeiten falls vorhanden
let rolesJson = null; let rolesJson = null;
@@ -121,11 +122,12 @@ function registerAdminRoutes(app) {
// SQL-Query dynamisch zusammenstellen // SQL-Query dynamisch zusammenstellen
if (rolesJson !== null) { if (rolesJson !== null) {
// Aktualisiere auch Rollen // 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, personalnummer || null,
wochenstunden ? parseFloat(wochenstunden) : null, wochenstunden ? parseFloat(wochenstunden) : null,
urlaubstage ? parseFloat(urlaubstage) : null, urlaubstage ? parseFloat(urlaubstage) : null,
arbeitstage ? parseInt(arbeitstage) : 5,
rolesJson, rolesJson,
userId userId
], ],
@@ -137,11 +139,12 @@ function registerAdminRoutes(app) {
}); });
} else { } else {
// Nur andere Felder aktualisieren // 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, personalnummer || null,
wochenstunden ? parseFloat(wochenstunden) : null, wochenstunden ? parseFloat(wochenstunden) : null,
urlaubstage ? parseFloat(urlaubstage) : null, urlaubstage ? parseFloat(urlaubstage) : null,
arbeitstage ? parseInt(arbeitstage) : 5,
userId userId
], ],
(err) => { (err) => {

View File

@@ -55,15 +55,16 @@ function registerTimesheetRoutes(app) {
} }
// User-Daten laden (für Überstunden-Berechnung) // 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) { if (err) {
console.error('Fehler beim Laden der User-Daten:', err); console.error('Fehler beim Laden der User-Daten:', err);
return res.status(500).json({ error: 'Fehler beim Laden der User-Daten' }); return res.status(500).json({ error: 'Fehler beim Laden der User-Daten' });
} }
const wochenstunden = user?.wochenstunden || 0; const wochenstunden = user?.wochenstunden || 0;
const arbeitstage = user?.arbeitstage || 5;
const overtimeValue = overtime_taken_hours ? parseFloat(overtime_taken_hours) : 0; 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 // Überstunden-Logik: Prüfe ob ganzer Tag oder weniger
let isFullDayOvertime = false; let isFullDayOvertime = false;
@@ -96,11 +97,11 @@ function registerTimesheetRoutes(app) {
finalEndTime = null; finalEndTime = null;
// Keine Tätigkeit setzen - Überstunden werden über overtime_taken_hours in der PDF angezeigt // Keine Tätigkeit setzen - Überstunden werden über overtime_taken_hours in der PDF angezeigt
} else if (vacation_type === 'full') { } 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) { } else if (isSick) {
total_hours = 8; // Krank = 8 Stunden normale Arbeitszeit total_hours = fullDayHours; // Krank = (Wochenarbeitszeit / Arbeitstage) Stunden normale Arbeitszeit
finalActivity1Desc = 'Krank'; finalActivity1Desc = 'Krank';
finalActivity1Hours = 8; finalActivity1Hours = fullDayHours;
} 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}`);
@@ -314,13 +315,14 @@ function registerTimesheetRoutes(app) {
const startDay = parseInt(startDateParts[2]); const startDay = parseInt(startDateParts[2]);
// User-Daten laden für Überstunden-Berechnung // 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) { if (err) {
return res.status(500).json({ error: 'Fehler beim Laden der User-Daten' }); return res.status(500).json({ error: 'Fehler beim Laden der User-Daten' });
} }
const wochenstunden = user?.wochenstunden || 0; 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) // Feiertage laden: Feiertag zählt als ausgefüllt (kein Start/Ende nötig)
getHolidaysForDateRange(week_start, week_end) getHolidaysForDateRange(week_start, week_end)
@@ -348,12 +350,23 @@ function registerTimesheetRoutes(app) {
continue; // Tag ist ausgefüllt 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 overtimeValue = entry && entry.overtime_taken_hours ? parseFloat(entry.overtime_taken_hours) : 0;
const isFullDayOvertime = overtimeValue > 0 && Math.abs(overtimeValue - fullDayHours) < 0.01; const isFullDayOvertime = overtimeValue > 0 && Math.abs(overtimeValue - fullDayHours) < 0.01;
if (isFullDayOvertime) { 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 // Bei halbem Tag Urlaub oder keinem Urlaub müssen Start- und Endzeit vorhanden sein

View File

@@ -57,12 +57,15 @@ function registerUserRoutes(app) {
app.get('/api/user/data', requireAuth, (req, res) => { app.get('/api/user/data', requireAuth, (req, res) => {
const userId = req.session.userId; 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) { if (err) {
return res.status(500).json({ error: 'Fehler beim Abrufen der User-Daten' }); 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 // 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) { if (err || !user) {
return res.status(500).json({ error: 'Fehler beim Abrufen der User-Daten' }); return res.status(500).json({ error: 'Fehler beim Abrufen der User-Daten' });
} }
const wochenstunden = user.wochenstunden || 0; const wochenstunden = user.wochenstunden || 0;
const arbeitstage = user.arbeitstage || 5;
const urlaubstage = user.urlaubstage || 0; const urlaubstage = user.urlaubstage || 0;
const overtimeOffsetHours = user.overtime_offset_hours ? parseFloat(user.overtime_offset_hours) : 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; const vacationOffsetDays = user.vacation_offset_days ? parseFloat(user.vacation_offset_days) : 0;
@@ -407,7 +411,7 @@ function registerUserRoutes(app) {
let weekVacationDays = 0; let weekVacationDays = 0;
let weekVacationHours = 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 let fullDayOvertimeDays = 0; // Anzahl Tage mit 8 Überstunden
entries.forEach(entry => { entries.forEach(entry => {
@@ -428,11 +432,11 @@ function registerUserRoutes(app) {
// Urlaub hat Priorität - wenn Urlaub, zähle nur Urlaubsstunden, nicht zusätzlich Arbeitsstunden // Urlaub hat Priorität - wenn Urlaub, zähle nur Urlaubsstunden, nicht zusätzlich Arbeitsstunden
if (entry.vacation_type === 'full') { if (entry.vacation_type === 'full') {
weekVacationDays += 1; weekVacationDays += 1;
weekVacationHours += 8; // Ganzer Tag = 8 Stunden weekVacationHours += fullDayHours; // Ganzer Tag = (Wochenarbeitszeit / Arbeitstage) Stunden
// Bei vollem Tag Urlaub werden keine Arbeitsstunden gezählt // Bei vollem Tag Urlaub werden keine Arbeitsstunden gezählt
} else if (entry.vacation_type === 'half') { } else if (entry.vacation_type === 'half') {
weekVacationDays += 0.5; 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 // Bei halbem Tag Urlaub können noch Arbeitsstunden vorhanden sein
// WICHTIG: total_hours enthält bereits Wochenend-Prozentsätze (aus timesheet.js) // WICHTIG: total_hours enthält bereits Wochenend-Prozentsätze (aus timesheet.js)
if (entry.total_hours && !isFullDayOvertime) { 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; let holidayHours = 0;
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) { for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
const day = d.getDay(); const day = d.getDay();
if (day >= 1 && day <= 5) { if (day >= 1 && day <= 5) {
const dateStr = d.toISOString().split('T')[0]; const dateStr = d.toISOString().split('T')[0];
if (holidaySet.has(dateStr)) holidayHours += 8; if (holidaySet.has(dateStr)) holidayHours += fullDayHours;
} }
} }
// Sollstunden berechnen // Sollstunden berechnen
const sollStunden = (wochenstunden / 5) * workdays; const sollStunden = (wochenstunden / arbeitstage) * workdays;
// Überstunden für diese Woche: (totalHours + vacationHours + holidayHours) - adjustedSollStunden // Überstunden für diese Woche: (totalHours + vacationHours + holidayHours) - adjustedSollStunden
const weekTotalHoursWithVacation = weekTotalHours + weekVacationHours + holidayHours; const weekTotalHoursWithVacation = weekTotalHours + weekVacationHours + holidayHours;
@@ -532,12 +536,13 @@ function registerUserRoutes(app) {
} }
// User-Daten abrufen // 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) { if (err || !user) {
return res.status(500).json({ error: 'Fehler beim Abrufen der User-Daten' }); return res.status(500).json({ error: 'Fehler beim Abrufen der User-Daten' });
} }
const wochenstunden = user.wochenstunden || 0; const wochenstunden = user.wochenstunden || 0;
const arbeitstage = user.arbeitstage || 5;
const overtimeOffsetHours = user.overtime_offset_hours ? parseFloat(user.overtime_offset_hours) : 0; const overtimeOffsetHours = user.overtime_offset_hours ? parseFloat(user.overtime_offset_hours) : 0;
// Alle eingereichten Wochen abrufen // Alle eingereichten Wochen abrufen
@@ -645,7 +650,7 @@ function registerUserRoutes(app) {
let weekVacationDays = 0; let weekVacationDays = 0;
let weekVacationHours = 0; let weekVacationHours = 0;
const fullDayHours = wochenstunden > 0 ? wochenstunden / 5 : 8; const fullDayHours = wochenstunden > 0 && arbeitstage > 0 ? wochenstunden / arbeitstage : 8;
let fullDayOvertimeDays = 0; let fullDayOvertimeDays = 0;
entries.forEach(entry => { entries.forEach(entry => {
@@ -662,10 +667,10 @@ function registerUserRoutes(app) {
if (entry.vacation_type === 'full') { if (entry.vacation_type === 'full') {
weekVacationDays += 1; weekVacationDays += 1;
weekVacationHours += 8; weekVacationHours += fullDayHours;
} else if (entry.vacation_type === 'half') { } else if (entry.vacation_type === 'half') {
weekVacationDays += 0.5; weekVacationDays += 0.5;
weekVacationHours += 4; weekVacationHours += fullDayHours / 2;
// WICHTIG: total_hours enthält bereits Wochenend-Prozentsätze (aus timesheet.js) // WICHTIG: total_hours enthält bereits Wochenend-Prozentsätze (aus timesheet.js)
if (entry.total_hours && !isFullDayOvertime) { if (entry.total_hours && !isFullDayOvertime) {
weekTotalHours += parseFloat(entry.total_hours) || 0; 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; let holidayHours = 0;
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) { for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
const day = d.getDay(); const day = d.getDay();
if (day >= 1 && day <= 5) { if (day >= 1 && day <= 5) {
const dateStr = d.toISOString().split('T')[0]; const dateStr = d.toISOString().split('T')[0];
if (holidaySet.has(dateStr)) holidayHours += 8; if (holidaySet.has(dateStr)) holidayHours += fullDayHours;
} }
} }
// Sollstunden berechnen // Sollstunden berechnen
const sollStunden = (wochenstunden / 5) * workdays; const sollStunden = (wochenstunden / arbeitstage) * workdays;
const weekTotalHoursWithVacation = weekTotalHours + weekVacationHours + holidayHours; const weekTotalHoursWithVacation = weekTotalHours + weekVacationHours + holidayHours;
const adjustedSollStunden = sollStunden - (fullDayOvertimeDays * fullDayHours); const adjustedSollStunden = sollStunden - (fullDayOvertimeDays * fullDayHours);
const weekOvertimeHours = weekTotalHoursWithVacation - adjustedSollStunden; const weekOvertimeHours = weekTotalHoursWithVacation - adjustedSollStunden;

View File

@@ -12,7 +12,7 @@ function registerVerwaltungRoutes(app) {
// Verwaltungs-Bereich // Verwaltungs-Bereich
app.get('/verwaltung', requireVerwaltung, (req, res) => { app.get('/verwaltung', requireVerwaltung, (req, res) => {
db.all(` 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.firstname as downloaded_by_firstname,
dl.lastname as downloaded_by_lastname, dl.lastname as downloaded_by_lastname,
(SELECT COUNT(*) FROM weekly_timesheets wt2 (SELECT COUNT(*) FROM weekly_timesheets wt2
@@ -217,115 +217,191 @@ function registerVerwaltungRoutes(app) {
} }
// User-Daten abrufen // 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) { if (err || !user) {
return res.status(500).json({ error: 'Fehler beim Abrufen der User-Daten' }); return res.status(500).json({ error: 'Fehler beim Abrufen der User-Daten' });
} }
const wochenstunden = user.wochenstunden || 0; 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 urlaubstage = user.urlaubstage || 0;
const overtimeOffsetHours = user.overtime_offset_hours ? parseFloat(user.overtime_offset_hours) : 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; const vacationOffsetDays = user.vacation_offset_days ? parseFloat(user.vacation_offset_days) : 0;
// Einträge für die Woche abrufen // Alle bereits genommenen Urlaubstage aus eingereichten Wochen berechnen
db.all(`SELECT date, total_hours, overtime_taken_hours, vacation_type, sick_status // Zuerst: Alle eingereichten Wochen abrufen
FROM timesheet_entries db.all(`SELECT DISTINCT week_start, week_end
WHERE user_id = ? AND date >= ? AND date <= ? FROM weekly_timesheets
ORDER BY date`, WHERE user_id = ? AND status = 'eingereicht'
[userId, week_start, week_end], ORDER BY week_start`,
(err, entries) => { [userId],
(err, weeks) => {
if (err) { 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 // Für jede Woche die neuesten Einträge abrufen
let totalHours = 0; let processedWeeks = 0;
let overtimeTaken = 0; let totalVacationDays = 0;
let vacationDays = 0; const vacationByDate = {};
let vacationHours = 0;
let sickDays = 0;
entries.forEach(entry => { if (!weeks || weeks.length === 0) {
if (entry.overtime_taken_hours) { // Keine eingereichten Wochen - setze totalVacationDays auf 0
overtimeTaken += entry.overtime_taken_hours; 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' });
}
// Krankheitstage zählen // Filtere auf neuesten Eintrag pro Tag
if (entry.sick_status === 1 || entry.sick_status === true) { (weekEntries || []).forEach(entry => {
sickDays += 1; 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;
}
}
});
// Urlaub hat Priorität - wenn Urlaub, zähle nur Urlaubsstunden, nicht zusätzlich Arbeitsstunden processedWeeks++;
if (entry.vacation_type === 'full') { if (processedWeeks === weeks.length) {
vacationDays += 1; // Alle Wochen verarbeitet - summiere Urlaubstage
vacationHours += 8; // Ganzer Tag = 8 Stunden Object.values(vacationByDate).forEach(entry => {
// Bei vollem Tag Urlaub werden keine Arbeitsstunden gezählt if (entry.vacation_type === 'full') {
} else if (entry.vacation_type === 'half') { totalVacationDays += 1;
vacationDays += 0.5; } else if (entry.vacation_type === 'half') {
vacationHours += 4; // Halber Tag = 4 Stunden totalVacationDays += 0.5;
// 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) // Weiter mit der normalen Verarbeitung der aktuellen Woche
getHolidaysForDateRange(week_start, week_end) processCurrentWeek(totalVacationDays);
.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
});
}); });
}
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
});
});
});
}
}); });
}); });
}); });
}); });

View File

@@ -19,7 +19,7 @@ function getCalendarWeek(dateStr) {
// PDF generieren // PDF generieren
function generatePDF(timesheetId, req, res) { 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 FROM weekly_timesheets wt
JOIN users u ON wt.user_id = u.id JOIN users u ON wt.user_id = u.id
WHERE wt.id = ?`, [timesheetId], (err, timesheet) => { WHERE wt.id = ?`, [timesheetId], (err, timesheet) => {
@@ -73,11 +73,13 @@ function generatePDF(timesheetId, req, res) {
let holidayHours = 0; let holidayHours = 0;
const start = new Date(timesheet.week_start); const start = new Date(timesheet.week_start);
const end = new Date(timesheet.week_end); 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)) { for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
const day = d.getDay(); const day = d.getDay();
if (day >= 1 && day <= 5) { if (day >= 1 && day <= 5) {
const dateStr = d.toISOString().split('T')[0]; const dateStr = d.toISOString().split('T')[0];
if (holidaySet.has(dateStr)) holidayHours += 8; if (holidaySet.has(dateStr)) holidayHours += fullDayHours;
} }
} }
return { holidaySet, holidayHours }; 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 // 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') { 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 // Bei vollem Tag Urlaub werden keine Arbeitsstunden gezählt
} else if (entry.vacation_type === 'half') { } 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 // Bei halbem Tag Urlaub können noch Arbeitsstunden vorhanden sein
if (entry.total_hours) { if (entry.total_hours) {
totalHours += entry.total_hours; totalHours += entry.total_hours;
@@ -299,7 +303,7 @@ function generatePDF(timesheetId, req, res) {
// PDF als Buffer generieren (für ZIP-Downloads) // PDF als Buffer generieren (für ZIP-Downloads)
function generatePDFToBuffer(timesheetId, req) { function generatePDFToBuffer(timesheetId, req) {
return new Promise((resolve, reject) => { 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 FROM weekly_timesheets wt
JOIN users u ON wt.user_id = u.id JOIN users u ON wt.user_id = u.id
WHERE wt.id = ?`, [timesheetId], (err, timesheet) => { WHERE wt.id = ?`, [timesheetId], (err, timesheet) => {
@@ -348,11 +352,13 @@ function generatePDFToBuffer(timesheetId, req) {
let holidayHours = 0; let holidayHours = 0;
const start = new Date(timesheet.week_start); const start = new Date(timesheet.week_start);
const end = new Date(timesheet.week_end); 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)) { for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
const day = d.getDay(); const day = d.getDay();
if (day >= 1 && day <= 5) { if (day >= 1 && day <= 5) {
const dateStr = d.toISOString().split('T')[0]; const dateStr = d.toISOString().split('T')[0];
if (holidaySet.has(dateStr)) holidayHours += 8; if (holidaySet.has(dateStr)) holidayHours += fullDayHours;
} }
} }
return { holidaySet, holidayHours }; 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 // 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') { 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 // Bei vollem Tag Urlaub werden keine Arbeitsstunden gezählt
} else if (entry.vacation_type === 'half') { } 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 // Bei halbem Tag Urlaub können noch Arbeitsstunden vorhanden sein
if (entry.total_hours) { if (entry.total_hours) {
totalHours += entry.total_hours; totalHours += entry.total_hours;

View File

@@ -98,6 +98,10 @@
<input type="number" id="wochenstunden" name="wochenstunden" step="0.5" min="0" placeholder="z.B. 40"> <input type="number" id="wochenstunden" name="wochenstunden" step="0.5" min="0" placeholder="z.B. 40">
</div> </div>
<div class="form-group">
<label for="arbeitstage">Arbeitstage pro Woche</label>
<input type="number" id="arbeitstage" name="arbeitstage" step="1" min="1" max="7" value="5" placeholder="z.B. 5">
</div>
<div class="form-group"> <div class="form-group">
<label for="urlaubstage">Urlaubstage</label> <label for="urlaubstage">Urlaubstage</label>
<input type="number" id="urlaubstage" name="urlaubstage" step="0.5" min="0" placeholder="z.B. 25"> <input type="number" id="urlaubstage" name="urlaubstage" step="0.5" min="0" placeholder="z.B. 25">
@@ -129,6 +133,7 @@
<th>Rolle</th> <th>Rolle</th>
<th>Personalnummer</th> <th>Personalnummer</th>
<th>Wochenstunden</th> <th>Wochenstunden</th>
<th>Arbeitstage</th>
<th>Urlaubstage</th> <th>Urlaubstage</th>
<th>Erstellt am</th> <th>Erstellt am</th>
<th>Aktionen</th> <th>Aktionen</th>
@@ -180,6 +185,10 @@
<span class="user-field-display" data-field="wochenstunden"><%= u.wochenstunden || '-' %></span> <span class="user-field-display" data-field="wochenstunden"><%= u.wochenstunden || '-' %></span>
<input type="number" step="0.5" class="user-field-edit" data-field="wochenstunden" data-user-id="<%= u.id %>" value="<%= u.wochenstunden || '' %>" style="display: none; width: 80px;"> <input type="number" step="0.5" class="user-field-edit" data-field="wochenstunden" data-user-id="<%= u.id %>" value="<%= u.wochenstunden || '' %>" style="display: none; width: 80px;">
</td> </td>
<td>
<span class="user-field-display" data-field="arbeitstage"><%= u.arbeitstage || 5 %></span>
<input type="number" step="1" min="1" max="7" class="user-field-edit" data-field="arbeitstage" data-user-id="<%= u.id %>" value="<%= u.arbeitstage || 5 %>" style="display: none; width: 80px;">
</td>
<td> <td>
<span class="user-field-display" data-field="urlaubstage"><%= u.urlaubstage || '-' %></span> <span class="user-field-display" data-field="urlaubstage"><%= u.urlaubstage || '-' %></span>
<input type="number" step="0.5" class="user-field-edit" data-field="urlaubstage" data-user-id="<%= u.id %>" value="<%= u.urlaubstage || '' %>" style="display: none; width: 80px;"> <input type="number" step="0.5" class="user-field-edit" data-field="urlaubstage" data-user-id="<%= u.id %>" value="<%= u.urlaubstage || '' %>" style="display: none; width: 80px;">