Überstunden Details

This commit is contained in:
2026-01-30 21:00:32 +01:00
parent f16593a345
commit 3d02fd56ea
4 changed files with 540 additions and 14 deletions

View File

@@ -431,25 +431,15 @@ function registerUserRoutes(app) {
weekVacationDays += 0.5;
weekVacationHours += 4; // Halber Tag = 4 Stunden
// Bei halbem Tag Urlaub können noch Arbeitsstunden vorhanden sein
// WICHTIG: total_hours enthält bereits Wochenend-Prozentsätze (aus timesheet.js)
if (entry.total_hours && !isFullDayOvertime) {
let hours = entry.total_hours;
// Wochenend-Prozentsatz anwenden (nur auf tatsächlich gearbeitete Stunden)
const weekendPercentage = getWeekendPercentage(entry.date);
if (weekendPercentage >= 100 && hours > 0 && !entry.sick_status) {
hours = hours * (weekendPercentage / 100);
}
weekTotalHours += hours;
weekTotalHours += parseFloat(entry.total_hours) || 0;
}
} else {
// Kein Urlaub - zähle nur Arbeitsstunden (wenn nicht 8 Überstunden)
// WICHTIG: total_hours enthält bereits Wochenend-Prozentsätze (aus timesheet.js)
if (entry.total_hours && !isFullDayOvertime) {
let hours = entry.total_hours;
// Wochenend-Prozentsatz anwenden (nur auf tatsächlich gearbeitete Stunden, nicht auf Krankheit)
const weekendPercentage = getWeekendPercentage(entry.date);
if (weekendPercentage > 0 && hours > 0 && !entry.sick_status) {
hours = hours * (1 + weekendPercentage / 100);
}
weekTotalHours += hours;
weekTotalHours += parseFloat(entry.total_hours) || 0;
}
}
});
@@ -511,6 +501,232 @@ function registerUserRoutes(app) {
}); // db.get (user)
}); // db.get (options)
}); // app.get
// API: Wöchentliche Überstunden-Aufschlüsselung
app.get('/api/user/overtime-breakdown', requireAuth, (req, res) => {
const userId = req.session.userId;
// Wochenend-Prozentsätze laden
db.get('SELECT saturday_percentage, sunday_percentage FROM system_options WHERE id = 1', (err, options) => {
if (err) {
return res.status(500).json({ error: 'Fehler beim Laden der Optionen' });
}
const saturdayPercentage = options?.saturday_percentage || 100;
const sundayPercentage = options?.sunday_percentage || 100;
// Hilfsfunktion: Prüft ob ein Datum ein Wochenendtag ist und gibt den Prozentsatz zurück
function getWeekendPercentage(dateStr) {
const date = new Date(dateStr);
const day = date.getDay();
if (day === 6) { // Samstag
return saturdayPercentage;
} else if (day === 0) { // Sonntag
return sundayPercentage;
}
return 100; // Kein Wochenende = 100% (normal)
}
// User-Daten abrufen
db.get('SELECT wochenstunden, overtime_offset_hours FROM users WHERE id = ?', [userId], (err, user) => {
if (err || !user) {
return res.status(500).json({ error: 'Fehler beim Abrufen der User-Daten' });
}
const wochenstunden = user.wochenstunden || 0;
const overtimeOffsetHours = user.overtime_offset_hours ? parseFloat(user.overtime_offset_hours) : 0;
// Alle eingereichten Wochen abrufen
db.all(`SELECT DISTINCT week_start, week_end
FROM weekly_timesheets
WHERE user_id = ? AND status = 'eingereicht'
ORDER BY week_start DESC`,
[userId],
(err, weeks) => {
if (err) {
return res.status(500).json({ error: 'Fehler beim Abrufen der Wochen' });
}
// Wenn keine Wochen vorhanden
if (!weeks || weeks.length === 0) {
return res.json({ weeks: [] });
}
const { getCalendarWeek } = require('../helpers/utils');
const weekData = [];
let processedWeeks = 0;
let hasError = false;
// Für jede Woche die Statistiken berechnen
weeks.forEach((week) => {
// Einträge für diese Woche abrufen (nur neueste pro Tag)
db.all(`SELECT id, date, total_hours, overtime_taken_hours, vacation_type, sick_status, start_time, end_time, updated_at
FROM timesheet_entries
WHERE user_id = ? AND date >= ? AND date <= ?
ORDER BY date, updated_at DESC, id DESC`,
[userId, week.week_start, week.week_end],
(err, allEntries) => {
if (hasError) return;
if (err) {
hasError = true;
return res.status(500).json({ error: 'Fehler beim Abrufen der Einträge' });
}
// Filtere auf neuesten Eintrag pro Tag
const entriesByDate = {};
(allEntries || []).forEach(entry => {
const existing = entriesByDate[entry.date];
if (!existing) {
entriesByDate[entry.date] = entry;
} else {
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)) {
entriesByDate[entry.date] = entry;
}
}
});
// Konvertiere zurück zu Array
const entries = Object.values(entriesByDate);
// Feiertage für die Woche laden
getHolidaysForDateRange(week.week_start, week.week_end)
.catch(() => new Set())
.then((holidaySet) => {
// Prüfe alle 5 Werktage (Montag-Freitag)
const startDate = new Date(week.week_start);
const endDate = new Date(week.week_end);
let workdays = 0;
let filledWorkdays = 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)) {
filledWorkdays++;
continue;
}
const entry = entriesByDate[dateStr];
if (entry) {
const isFullDayVacation = entry.vacation_type === 'full';
const isSick = entry.sick_status === 1 || entry.sick_status === true;
const hasStartAndEnd = entry.start_time && entry.end_time &&
entry.start_time.toString().trim() !== '' &&
entry.end_time.toString().trim() !== '';
if (isFullDayVacation || isSick || hasStartAndEnd) {
filledWorkdays++;
}
}
}
}
// Nur berechnen wenn alle Werktage ausgefüllt sind
if (filledWorkdays < workdays) {
processedWeeks++;
if (processedWeeks === weeks.length && !hasError) {
res.json({ weeks: weekData });
}
return;
}
// Berechnungen für diese Woche
let weekTotalHours = 0;
let weekOvertimeTaken = 0;
let weekVacationDays = 0;
let weekVacationHours = 0;
const fullDayHours = wochenstunden > 0 ? wochenstunden / 5 : 8;
let fullDayOvertimeDays = 0;
entries.forEach(entry => {
const overtimeValue = entry.overtime_taken_hours ? parseFloat(entry.overtime_taken_hours) : 0;
const isFullDayOvertime = overtimeValue > 0 && Math.abs(overtimeValue - fullDayHours) < 0.01;
if (entry.overtime_taken_hours) {
weekOvertimeTaken += parseFloat(entry.overtime_taken_hours) || 0;
}
if (isFullDayOvertime) {
fullDayOvertimeDays++;
}
if (entry.vacation_type === 'full') {
weekVacationDays += 1;
weekVacationHours += 8;
} else if (entry.vacation_type === 'half') {
weekVacationDays += 0.5;
weekVacationHours += 4;
// WICHTIG: total_hours enthält bereits Wochenend-Prozentsätze (aus timesheet.js)
if (entry.total_hours && !isFullDayOvertime) {
weekTotalHours += parseFloat(entry.total_hours) || 0;
}
} else {
// WICHTIG: total_hours enthält bereits Wochenend-Prozentsätze (aus timesheet.js)
if (entry.total_hours && !isFullDayOvertime) {
weekTotalHours += parseFloat(entry.total_hours) || 0;
}
}
});
// Feiertagsstunden
let holidayHours = 0;
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
const day = d.getDay();
if (day >= 1 && day <= 5) {
const dateStr = d.toISOString().split('T')[0];
if (holidaySet.has(dateStr)) holidayHours += 8;
}
}
// Sollstunden berechnen
const sollStunden = (wochenstunden / 5) * workdays;
const weekTotalHoursWithVacation = weekTotalHours + weekVacationHours + holidayHours;
const adjustedSollStunden = sollStunden - (fullDayOvertimeDays * fullDayHours);
const weekOvertimeHours = weekTotalHoursWithVacation - adjustedSollStunden;
// Kalenderwoche berechnen
const calendarWeek = getCalendarWeek(week.week_start);
const year = new Date(week.week_start).getFullYear();
// Wochen-Daten hinzufügen
weekData.push({
week_start: week.week_start,
week_end: week.week_end,
calendar_week: calendarWeek,
year: year,
overtime_hours: parseFloat(weekOvertimeHours.toFixed(2)),
overtime_taken: parseFloat(weekOvertimeTaken.toFixed(2)),
total_hours: parseFloat(weekTotalHoursWithVacation.toFixed(2)),
soll_stunden: parseFloat(adjustedSollStunden.toFixed(2)),
vacation_days: parseFloat(weekVacationDays.toFixed(1)),
workdays: workdays
});
processedWeeks++;
// Wenn alle Wochen verarbeitet wurden, Antwort senden
if (processedWeeks === weeks.length && !hasError) {
// Sortiere nach Datum (neueste zuerst)
weekData.sort((a, b) => {
if (a.year !== b.year) return b.year - a.year;
if (a.calendar_week !== b.calendar_week) return b.calendar_week - a.calendar_week;
return new Date(b.week_start) - new Date(a.week_start);
});
res.json({ weeks: weekData, overtime_offset_hours: overtimeOffsetHours });
}
}); // getHolidaysForDateRange.then
}); // db.all (allEntries)
}); // weeks.forEach
}); // db.all (weeks)
}); // db.get (user)
}); // db.get (options)
}); // app.get
}
module.exports = registerUserRoutes;