Anpassung in der Verwaltung mit der Berechnung

This commit is contained in:
2026-02-06 16:17:50 +01:00
parent d82b144b86
commit c396fe7d0e
3 changed files with 141 additions and 18 deletions

5
.cursor/worktrees.json Normal file
View File

@@ -0,0 +1,5 @@
{
"setup-worktree": [
"npm install"
]
}

View File

@@ -243,12 +243,19 @@ function registerVerwaltungRoutes(app) {
// Nur Wochen bis Ende der angezeigten Kalenderwoche (Stand Urlaub = Ende dieser KW)
const weeksUpToDisplayed = (weeks || []).filter((w) => w.week_end <= week_end);
// Wochen VOR der aktuellen Woche für kumulative Überstunden-Berechnung
const weeksBeforeCurrent = (weeks || []).filter((w) => w.week_end < week_end);
let processedWeeks = 0;
let totalVacationDays = 0;
const vacationByDate = {};
// Kumulative Überstunden über alle Wochen VOR der aktuellen Woche
let cumulativeOvertimeHours = 0;
let cumulativeOvertimeTaken = 0;
// Urlaubstage für alle Wochen bis zur aktuellen Woche (inklusive)
if (weeksUpToDisplayed.length === 0) {
processCurrentWeek(0);
processCurrentWeek(0, 0, 0);
} else {
weeksUpToDisplayed.forEach((week) => {
db.all(`SELECT date, vacation_type, updated_at, id
@@ -285,13 +292,111 @@ function registerVerwaltungRoutes(app) {
totalVacationDays += 0.5;
}
});
processCurrentWeek(totalVacationDays);
// Berechne Überstunden für alle Wochen VOR der aktuellen Woche
if (weeksBeforeCurrent.length === 0) {
processCurrentWeek(totalVacationDays, 0, 0);
} else {
let processedOvertimeWeeks = 0;
weeksBeforeCurrent.forEach((week) => {
calculateWeekOvertime(week.week_start, week.week_end, (weekOvertime, weekOvertimeTaken) => {
cumulativeOvertimeHours += weekOvertime;
cumulativeOvertimeTaken += weekOvertimeTaken;
processedOvertimeWeeks++;
if (processedOvertimeWeeks === weeksBeforeCurrent.length) {
processCurrentWeek(totalVacationDays, cumulativeOvertimeHours, cumulativeOvertimeTaken);
}
});
});
}
}
});
});
}
function processCurrentWeek(totalVacationDays) {
function calculateWeekOvertime(weekStart, weekEnd, callback) {
db.all(`SELECT id, date, updated_at, total_hours, overtime_taken_hours, vacation_type, sick_status
FROM timesheet_entries
WHERE user_id = ? AND date >= ? AND date <= ?
ORDER BY date, updated_at DESC, id DESC`,
[userId, weekStart, weekEnd],
(err, allEntries) => {
if (err) {
return callback(0, 0);
}
// Nur neuesten Eintrag pro Tag zählen
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 entries = Object.values(entriesByDate);
let weekTotalHours = 0;
let weekOvertimeTaken = 0;
let weekVacationHours = 0;
entries.forEach(entry => {
if (entry.overtime_taken_hours) {
weekOvertimeTaken += parseFloat(entry.overtime_taken_hours) || 0;
}
if (entry.vacation_type === 'full') {
weekVacationHours += fullDayHours;
} else if (entry.vacation_type === 'half') {
weekVacationHours += fullDayHours / 2;
if (entry.total_hours) {
weekTotalHours += parseFloat(entry.total_hours) || 0;
}
} else {
if (entry.total_hours) {
weekTotalHours += parseFloat(entry.total_hours) || 0;
}
}
});
getHolidaysForDateRange(weekStart, weekEnd)
.catch(() => new Set())
.then((holidaySet) => {
const startDate = new Date(weekStart);
const endDate = new Date(weekEnd);
let workdays = 0;
let holidayDays = 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) {
workdays++;
const dateStr = d.toISOString().split('T')[0];
if (holidaySet.has(dateStr)) {
holidayDays++;
// Feiertagsstunden für alle Feiertage hinzufügen (wie im PDF)
holidayHours += fullDayHours;
}
}
}
// Sollstunden = Wochenstunden (Feiertage reduzieren Soll nicht, da bezahlt)
const sollStunden = wochenstunden;
const totalHoursWithVacation = weekTotalHours + weekVacationHours + holidayHours;
const weekOvertimeHours = totalHoursWithVacation - sollStunden;
callback(weekOvertimeHours, weekOvertimeTaken);
});
});
}
function processCurrentWeek(totalVacationDays, cumulativeOvertimeHours, cumulativeOvertimeTaken) {
// Einträge für die Woche abrufen (id/updated_at für neuesten pro Tag)
db.all(`SELECT id, date, updated_at, total_hours, overtime_taken_hours, vacation_type, sick_status
FROM timesheet_entries
@@ -366,23 +471,35 @@ function registerVerwaltungRoutes(app) {
const startDate = new Date(week_start);
const endDate = new Date(week_end);
let workdays = 0;
let holidayDays = 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;
if (holidaySet.has(dateStr)) {
holidayDays++;
// Feiertagsstunden für alle Feiertage hinzufügen (wie im PDF)
holidayHours += fullDayHours;
}
}
}
// Sollstunden berechnen
const sollStunden = (wochenstunden / arbeitstage) * workdays;
// Sollstunden = Wochenstunden (Feiertage reduzieren Soll nicht, da bezahlt)
const sollStunden = wochenstunden;
// Überstunden: (Tatsächliche Stunden + Urlaubsstunden + Feiertagsstunden) - Sollstunden
// Überstunden für die aktuelle Woche: (Tatsächliche Stunden + Urlaubsstunden + Feiertagsstunden) - Sollstunden
const totalHoursWithVacation = totalHours + vacationHours + holidayHours;
const overtimeHours = totalHoursWithVacation - sollStunden;
const remainingOvertime = overtimeHours - overtimeTaken;
const weekOvertimeHours = totalHoursWithVacation - sollStunden;
// Kumulative Überstunden: Summe aller Wochen bis zur aktuellen Woche
// cumulativeOvertimeHours enthält bereits alle vorherigen Wochen
const totalCumulativeOvertimeHours = cumulativeOvertimeHours + weekOvertimeHours;
const totalCumulativeOvertimeTaken = cumulativeOvertimeTaken + overtimeTaken;
// Verbleibende Überstunden = kumulative Überstunden - kumulative genommene Überstunden
const remainingOvertime = totalCumulativeOvertimeHours - totalCumulativeOvertimeTaken;
const remainingOvertimeWithOffset = remainingOvertime + overtimeOffsetHours;
// Verbleibende Urlaubstage (berücksichtigt alle eingereichten Wochen, nicht nur die aktuelle)
@@ -393,8 +510,9 @@ function registerVerwaltungRoutes(app) {
urlaubstage,
totalHours,
sollStunden,
overtimeHours,
overtimeTaken,
weekOvertimeHours, // Überstunden nur für diese Woche
overtimeHours: totalCumulativeOvertimeHours, // Kumulative Überstunden
overtimeTaken: totalCumulativeOvertimeTaken, // Kumulative genommene Überstunden
remainingOvertime,
overtimeOffsetHours,
remainingOvertimeWithOffset,

View File

@@ -321,11 +321,11 @@
// Statistiken anzeigen
let statsHTML = '';
if (data.overtimeHours !== undefined) {
if (data.weekOvertimeHours !== undefined) {
const weekOvertimeColor = data.weekOvertimeHours < 0 ? '#dc3545' : (data.weekOvertimeHours > 0 ? '#28a745' : '#666');
const sign = data.weekOvertimeHours >= 0 ? '+' : '';
statsHTML += `<div class="stats-inline" style="display: inline-block; margin-right: 20px;">
<strong>Überstunden:</strong> <span>${data.overtimeHours.toFixed(2)} h</span>
${data.overtimeTaken > 0 ? `<span style="color: #666;">(davon genommen: ${data.overtimeTaken.toFixed(2)} h)</span>` : ''}
${data.remainingOvertime !== data.overtimeHours ? `<span style="color: #28a745;">(verbleibend: ${data.remainingOvertime.toFixed(2)} h)</span>` : ''}
<strong>Überstunden:</strong> <span style="color: ${weekOvertimeColor};">${sign}${data.weekOvertimeHours.toFixed(2)} h</span>
</div>`;
}
if (data.overtimeOffsetHours !== undefined && data.overtimeOffsetHours !== 0) {
@@ -338,9 +338,9 @@
const totalTaken = data.totalVacationDays !== undefined ? data.totalVacationDays : 0;
const inWeek = data.vacationDays !== undefined ? data.vacationDays : 0;
statsHTML += `<div class="stats-inline" style="display: inline-block; margin-right: 20px;">
<strong>Urlaub genommen (kumuliert bis Ende KW):</strong> <span>${Number(totalTaken).toFixed(1)} Tag${totalTaken !== 1 ? 'e' : ''}</span>
${inWeek > 0 ? ` <span style="color: #666;">(davon in dieser Woche: ${inWeek.toFixed(1)})</span>` : ''}
${data.remainingVacation !== undefined ? ` <span style="color: #28a745;">(verbleibend Stand Ende KW: ${Number(data.remainingVacation).toFixed(1)} Tage)</span>` : ''}
<strong>Urlaub genommen (dieses Jahr):</strong> <span>${Number(totalTaken).toFixed(1)} Tag${totalTaken !== 1 ? 'e' : ''}</span>
${inWeek > 0 ? ` <span style="color: #666;">(diese Woche: ${inWeek.toFixed(1)})</span>` : ''}
${data.remainingVacation !== undefined ? ` <span style="color: #28a745;">(verbleibend: ${Number(data.remainingVacation).toFixed(1)} Tage)</span>` : ''}
</div>`;
}
if (data.vacationOffsetDays !== undefined && data.vacationOffsetDays !== 0) {