Add profile routes to backend and frontend; implement user state handling for holidays and workdays, and update UI components for profile settings

This commit is contained in:
Torsten Schulz (local)
2025-10-17 23:03:29 +02:00
parent 67ddf812cd
commit b2cef4d306
11 changed files with 738 additions and 76 deletions

View File

@@ -212,6 +212,11 @@ class TimeEntryService {
async getStatistics(userId = null) {
const uid = userId || this.defaultUserId;
// Hole User-Daten (für daily_hours Fallback)
const { User } = database.getModels();
const user = await User.findByPk(uid, { attributes: ['daily_hours'], raw: true });
const userDailyHours = user?.daily_hours || 8;
let currentlyWorked = null;
let allEntries = [];
@@ -556,7 +561,7 @@ class TimeEntryService {
return true;
});
let daySollHours = 8; // Standard: 8h
let daySollHours = userDailyHours; // Fallback: User's daily_hours
if (applicableTimewish && applicableTimewish.wishtype === 2 && applicableTimewish.hours) {
daySollHours = applicableTimewish.hours;
}
@@ -638,7 +643,7 @@ class TimeEntryService {
return true;
});
let daySollHours = 8;
let daySollHours = userDailyHours; // Fallback: User's daily_hours
if (applicableTimewish && applicableTimewish.wishtype === 2 && applicableTimewish.hours) {
daySollHours = applicableTimewish.hours;
}
@@ -961,9 +966,10 @@ class TimeEntryService {
console.log('DEBUG _calculateTotalOvertime: Starte optimierte DB-basierte Berechnung...');
// Hole den Überstunden-Offset für diesen User
const user = await User.findByPk(userId, { attributes: ['overtime_offset_minutes'], raw: true });
// Hole den Überstunden-Offset und daily_hours für diesen User
const user = await User.findByPk(userId, { attributes: ['overtime_offset_minutes', 'daily_hours'], raw: true });
const overtimeOffsetMinutes = user?.overtime_offset_minutes || 0;
const userDailyHours = user?.daily_hours || 8;
if (overtimeOffsetMinutes !== 0) {
const offsetHours = Math.floor(Math.abs(overtimeOffsetMinutes) / 60);
@@ -1014,7 +1020,8 @@ class TimeEntryService {
return parseFloat(applicableTimewish.hours);
}
return 8.0; // Fallback
// Fallback: User's daily_hours
return parseFloat(userDailyHours);
};
// Berechne Endedatum: gestern (wie alte MySQL-Funktion)
@@ -1073,17 +1080,43 @@ class TimeEntryService {
console.log(`DEBUG: ${workDays.length} Arbeitstage gefunden (mit timefix-Korrekturen)`);
// Hole Feiertage
// Hole User-Bundesland
const { State: StateModel } = database.getModels();
const userForState = await User.findByPk(userId, {
attributes: ['state_id'],
raw: true
});
const userStateId = userForState?.state_id;
// Hole Feiertage mit States
const holidays = await Holiday.findAll({
attributes: ['date'],
where: {
date: {
[require('sequelize').Op.lte]: endDateStr
}
},
raw: true
include: [
{
model: StateModel,
as: 'states',
attributes: ['id'],
through: { attributes: [] },
required: false
}
]
});
// Filtere nur Feiertage die für den User gelten
const holidaySet = new Set();
holidays.forEach(h => {
const holidayStates = h.states || [];
const isFederal = holidayStates.length === 0;
const appliesToUser = isFederal || holidayStates.some(s => s.id === userStateId);
if (appliesToUser) {
holidaySet.add(h.date);
}
});
const holidaySet = new Set(holidays.map(h => h.date));
// Hole Krankheitstage (expandiert)
const sickDaysRaw = await Sick.findAll({
@@ -1307,8 +1340,8 @@ class TimeEntryService {
sickMap.set(sickKey, sick);
});
// Hole Feiertage für diese Woche
const holidays = await worklogRepository.getHolidaysInDateRange(weekStart, weekEnd);
// Hole Feiertage für diese Woche (nur die für das User-Bundesland gelten)
const holidays = await worklogRepository.getHolidaysInDateRange(weekStart, weekEnd, uid);
// Erstelle Map von Datum zu Holiday
const holidayMap = new Map();
@@ -1800,34 +1833,31 @@ class TimeEntryService {
Object.values(dayData).forEach(day => {
const dayOfWeek = new Date(day.date).getDay();
// Prüfe auf Krankheit (hat höchste Priorität, überschreibt alles außer Feiertage)
// Sammle alle Ereignisse für diesen Tag
const events = [];
// Prüfe auf Krankheit
const sick = sickMap.get(day.date);
if (sick) {
// Krankheitstage überschreiben geloggte Zeiten
day.sick = {
hours: 8,
type: sick.sick_type
};
// Lösche geloggte Arbeitszeit
day.workTime = null;
day.pauses = [];
day.totalWorkTime = null;
day.pauseTimes = [];
day.netWorkTime = null;
// Setze Status basierend auf Krankheitstyp
const sickLabels = {
'self': 'Krank',
'child': 'Kind krank',
'parents': 'Eltern krank',
'partner': 'Partner krank'
};
events.push(sickLabels[sick.sick_type] || 'Krank');
day.status = 'sick';
day.statusText = sickLabels[sick.sick_type] || 'Krank';
return; // Überspringe weitere Verarbeitung
// Bei Krankheit: Lösche geloggte Arbeitszeit
day.workTime = null;
day.pauses = [];
day.totalWorkTime = null;
day.pauseTimes = [];
day.netWorkTime = null;
}
// Prüfe auf Feiertag
@@ -1837,48 +1867,52 @@ class TimeEntryService {
hours: holiday.hours,
description: holiday.description
};
// Status setzen
if (!day.workTime) {
day.status = 'holiday';
day.statusText = holiday.description || 'Feiertag';
} else {
// Feiertag + Arbeit
day.status = 'holiday-work';
day.statusText = `${holiday.description || 'Feiertag'} + Arbeit`;
}
events.push(holiday.description || 'Feiertag');
}
// Prüfe auf Urlaub (nur wenn kein Feiertag)
if (!holiday) {
const vacation = vacationMap.get(day.date);
if (vacation) {
const isHalfDay = vacation.half_day === 1;
const vacationHours = isHalfDay ? 4 : 8;
day.vacation = {
hours: vacationHours,
halfDay: isHalfDay
};
// Status setzen (kann später überschrieben werden wenn auch gearbeitet wurde)
if (!day.workTime) {
day.status = isHalfDay ? 'vacation-half' : 'vacation-full';
day.statusText = isHalfDay ? 'Urlaub (halber Tag)' : 'Urlaub';
} else {
// Urlaub + Arbeit
day.status = 'vacation-work';
day.statusText = isHalfDay ? 'Urlaub (halber Tag) + Arbeit' : 'Urlaub + Arbeit';
// Prüfe auf Urlaub
const vacation = vacationMap.get(day.date);
if (vacation) {
const isHalfDay = vacation.half_day === 1;
const vacationHours = isHalfDay ? 4 : 8;
day.vacation = {
hours: vacationHours,
halfDay: isHalfDay
};
events.push(isHalfDay ? 'Urlaub (halber Tag)' : 'Urlaub');
}
// Prüfe auf Wochenende
const isWeekend = (dayOfWeek === 0 || dayOfWeek === 6);
if (isWeekend) {
events.push('Wochenende');
}
// Setze Status basierend auf gesammelten Ereignissen
if (events.length > 0) {
if (day.workTime) {
// Ereignis(se) + Arbeit
day.statusText = events.join(' + ') + ' + Arbeit';
day.status = 'mixed-work';
} else {
// Nur Ereignis(se), keine Arbeit
day.statusText = events.join(' + ');
// Status basierend auf primärem Ereignis
if (sick) {
day.status = 'sick';
} else if (holiday) {
day.status = 'holiday';
} else if (vacation) {
day.status = vacation.halfDay ? 'vacation-half' : 'vacation-full';
} else if (isWeekend) {
day.status = 'weekend';
}
}
}
// Wochenenden (nur wenn kein Urlaub, Feiertag oder Arbeit)
if (dayOfWeek === 0 || dayOfWeek === 6) {
if (!day.status) {
day.status = 'weekend';
day.statusText = 'Wochenende';
}
} else if (isWeekend) {
// Nur Wochenende, keine anderen Events
day.status = 'weekend';
day.statusText = 'Wochenende';
}
});