Verwaltung Überstunden anzeige, Buttons am unteren ende der Seite
This commit is contained in:
195
services/overtime-service.js
Normal file
195
services/overtime-service.js
Normal file
@@ -0,0 +1,195 @@
|
||||
// Overtime Service: Berechnung der aktuellen Überstunden-Bilanz pro User
|
||||
// Wird von /api/user/stats und /api/verwaltung/employees/current-overtime genutzt.
|
||||
|
||||
const { getHolidaysForDateRange } = require('./feiertage-service');
|
||||
|
||||
/**
|
||||
* Berechnet die aktuelle Überstunden-Bilanz für einen User (nur eingereichte Wochen).
|
||||
* Entspricht der Anzeige "Aktuelle Überstunden" im Mitarbeiter-Dashboard.
|
||||
*
|
||||
* @param {number} userId - User-ID
|
||||
* @param {object} db - Database-Instanz (sqlite3)
|
||||
* @param {function} callback - (err, currentOvertime) currentOvertime in Stunden (kann negativ sein)
|
||||
*/
|
||||
function getCurrentOvertimeForUser(userId, db, callback) {
|
||||
db.get(
|
||||
'SELECT wochenstunden, arbeitstage, overtime_offset_hours FROM users WHERE id = ?',
|
||||
[userId],
|
||||
(err, user) => {
|
||||
if (err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
if (!user) {
|
||||
return callback(null, null);
|
||||
}
|
||||
|
||||
const wochenstunden = user.wochenstunden || 0;
|
||||
const arbeitstage = user.arbeitstage || 5;
|
||||
const overtimeOffsetHours = user.overtime_offset_hours ? parseFloat(user.overtime_offset_hours) : 0;
|
||||
|
||||
db.all(
|
||||
`SELECT DISTINCT week_start, week_end
|
||||
FROM weekly_timesheets
|
||||
WHERE user_id = ? AND status = 'eingereicht'
|
||||
ORDER BY week_start`,
|
||||
[userId],
|
||||
(err, weeks) => {
|
||||
if (err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
if (!weeks || weeks.length === 0) {
|
||||
return callback(null, overtimeOffsetHours);
|
||||
}
|
||||
|
||||
let totalOvertimeHours = 0;
|
||||
let totalOvertimeTaken = 0;
|
||||
let processedWeeks = 0;
|
||||
let hasError = false;
|
||||
let callbackDone = false;
|
||||
|
||||
function done(value) {
|
||||
if (callbackDone) return;
|
||||
callbackDone = true;
|
||||
callback(null, value);
|
||||
}
|
||||
|
||||
weeks.forEach((week) => {
|
||||
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;
|
||||
if (!callbackDone) {
|
||||
callbackDone = true;
|
||||
callback(err, null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
getHolidaysForDateRange(week.week_start, week.week_end)
|
||||
.catch(() => new Set())
|
||||
.then((holidaySet) => {
|
||||
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) {
|
||||
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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (filledWorkdays < workdays) {
|
||||
processedWeeks++;
|
||||
if (processedWeeks === weeks.length && !hasError) {
|
||||
done(totalOvertimeHours - totalOvertimeTaken + overtimeOffsetHours);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const fullDayHours = wochenstunden > 0 && arbeitstage > 0 ? wochenstunden / arbeitstage : 8;
|
||||
let weekTotalHours = 0;
|
||||
let weekOvertimeTaken = 0;
|
||||
let weekVacationHours = 0;
|
||||
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') {
|
||||
weekVacationHours += fullDayHours;
|
||||
} else if (entry.vacation_type === 'half') {
|
||||
weekVacationHours += fullDayHours / 2;
|
||||
if (entry.total_hours && !isFullDayOvertime) {
|
||||
weekTotalHours += parseFloat(entry.total_hours) || 0;
|
||||
}
|
||||
} else {
|
||||
if (entry.total_hours && !isFullDayOvertime) {
|
||||
weekTotalHours += parseFloat(entry.total_hours) || 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) {
|
||||
const dateStr = d.toISOString().split('T')[0];
|
||||
if (holidaySet.has(dateStr)) holidayHours += fullDayHours;
|
||||
}
|
||||
}
|
||||
|
||||
const sollStunden = (wochenstunden / arbeitstage) * workdays;
|
||||
const weekTotalHoursWithVacation = weekTotalHours + weekVacationHours + holidayHours;
|
||||
const adjustedSollStunden = sollStunden - fullDayOvertimeDays * fullDayHours;
|
||||
const weekOvertimeHours = weekTotalHoursWithVacation - adjustedSollStunden;
|
||||
|
||||
totalOvertimeHours += weekOvertimeHours;
|
||||
totalOvertimeTaken += weekOvertimeTaken;
|
||||
processedWeeks++;
|
||||
|
||||
if (processedWeeks === weeks.length && !hasError) {
|
||||
done(totalOvertimeHours - totalOvertimeTaken + overtimeOffsetHours);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getCurrentOvertimeForUser
|
||||
};
|
||||
Reference in New Issue
Block a user