Overtime Corrections mit Historie und Grund
This commit is contained in:
@@ -587,194 +587,219 @@ function registerUserRoutes(app) {
|
||||
const arbeitstage = user.arbeitstage || 5;
|
||||
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`,
|
||||
// Korrekturen durch die Verwaltung (Historie) laden
|
||||
db.all(
|
||||
`SELECT correction_hours, corrected_at, reason
|
||||
FROM overtime_corrections
|
||||
WHERE user_id = ?
|
||||
ORDER BY corrected_at DESC`,
|
||||
[userId],
|
||||
(err, weeks) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Fehler beim Abrufen der Wochen' });
|
||||
}
|
||||
(correctionsErr, corrections) => {
|
||||
// Falls Tabelle noch nicht existiert (z. B. alte DB), nicht hart fehlschlagen
|
||||
const overtimeCorrections = correctionsErr ? [] : (corrections || []);
|
||||
|
||||
// Wenn keine Wochen vorhanden
|
||||
if (!weeks || weeks.length === 0) {
|
||||
return res.json({ weeks: [] });
|
||||
}
|
||||
// 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' });
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
// Wenn keine Wochen vorhanden
|
||||
if (!weeks || weeks.length === 0) {
|
||||
return res.json({
|
||||
weeks: [],
|
||||
overtime_offset_hours: overtimeOffsetHours,
|
||||
overtime_corrections: overtimeCorrections
|
||||
});
|
||||
}
|
||||
|
||||
// Konvertiere zurück zu Array
|
||||
const entries = Object.values(entriesByDate);
|
||||
const { getCalendarWeek } = require('../helpers/utils');
|
||||
const weekData = [];
|
||||
let processedWeeks = 0;
|
||||
let hasError = false;
|
||||
|
||||
// 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;
|
||||
// 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;
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
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() !== '';
|
||||
// Konvertiere zurück zu Array
|
||||
const entries = Object.values(entriesByDate);
|
||||
|
||||
if (isFullDayVacation || isSick || hasStartAndEnd) {
|
||||
filledWorkdays++;
|
||||
// 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 && arbeitstage > 0 ? wochenstunden / arbeitstage : 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 += fullDayHours;
|
||||
} else if (entry.vacation_type === 'half') {
|
||||
weekVacationDays += 0.5;
|
||||
weekVacationHours += fullDayHours / 2;
|
||||
// WICHTIG: total_hours enthält bereits Wochenend-Prozentsätze (aus timesheet.js)
|
||||
if (entry.total_hours && !isFullDayOvertime) {
|
||||
weekTotalHours += parseFloat(entry.total_hours) || 0;
|
||||
// Nur berechnen wenn alle Werktage ausgefüllt sind
|
||||
if (filledWorkdays < workdays) {
|
||||
processedWeeks++;
|
||||
if (processedWeeks === weeks.length && !hasError) {
|
||||
res.json({
|
||||
weeks: weekData,
|
||||
overtime_offset_hours: overtimeOffsetHours,
|
||||
overtime_corrections: overtimeCorrections
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// WICHTIG: total_hours enthält bereits Wochenend-Prozentsätze (aus timesheet.js)
|
||||
if (entry.total_hours && !isFullDayOvertime) {
|
||||
weekTotalHours += parseFloat(entry.total_hours) || 0;
|
||||
|
||||
// Berechnungen für diese Woche
|
||||
let weekTotalHours = 0;
|
||||
let weekOvertimeTaken = 0;
|
||||
let weekVacationDays = 0;
|
||||
let weekVacationHours = 0;
|
||||
|
||||
const fullDayHours = wochenstunden > 0 && arbeitstage > 0 ? wochenstunden / arbeitstage : 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 += fullDayHours;
|
||||
} else if (entry.vacation_type === 'half') {
|
||||
weekVacationDays += 0.5;
|
||||
weekVacationHours += fullDayHours / 2;
|
||||
// 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: (Wochenarbeitszeit / Arbeitstage) pro Werktag der ein Feiertag ist
|
||||
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 += fullDayHours;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Feiertagsstunden: (Wochenarbeitszeit / Arbeitstage) pro Werktag der ein Feiertag ist
|
||||
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 += fullDayHours;
|
||||
}
|
||||
}
|
||||
// Sollstunden berechnen
|
||||
const sollStunden = (wochenstunden / arbeitstage) * workdays;
|
||||
const weekTotalHoursWithVacation = weekTotalHours + weekVacationHours + holidayHours;
|
||||
const adjustedSollStunden = sollStunden - (fullDayOvertimeDays * fullDayHours);
|
||||
const weekOvertimeHours = weekTotalHoursWithVacation - adjustedSollStunden;
|
||||
|
||||
// Sollstunden berechnen
|
||||
const sollStunden = (wochenstunden / arbeitstage) * 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();
|
||||
|
||||
// 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
|
||||
});
|
||||
|
||||
// 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++;
|
||||
|
||||
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)
|
||||
// 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,
|
||||
overtime_corrections: overtimeCorrections
|
||||
});
|
||||
}
|
||||
}); // getHolidaysForDateRange.then
|
||||
}); // db.all (allEntries)
|
||||
}); // weeks.forEach
|
||||
}); // db.all (weeks)
|
||||
}
|
||||
);
|
||||
}); // db.get (user)
|
||||
}); // db.get (options)
|
||||
}); // app.get
|
||||
|
||||
Reference in New Issue
Block a user