Überstunden Details
This commit is contained in:
244
routes/user.js
244
routes/user.js
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user