// User API Routes const { db } = require('../database'); const { hasRole, getCurrentDate } = require('../helpers/utils'); const { requireAuth } = require('../middleware/auth'); // Routes registrieren function registerUserRoutes(app) { // API: Letzte bearbeitete Woche abrufen app.get('/api/user/last-week', requireAuth, (req, res) => { const userId = req.session.userId; db.get('SELECT last_week_start FROM users WHERE id = ?', [userId], (err, user) => { if (err) { return res.status(500).json({ error: 'Fehler beim Abrufen der letzten Woche' }); } res.json({ last_week_start: user?.last_week_start || null }); }); }); // API: Letzte bearbeitete Woche speichern app.post('/api/user/last-week', requireAuth, (req, res) => { const userId = req.session.userId; const { week_start } = req.body; if (!week_start) { return res.status(400).json({ error: 'week_start ist erforderlich' }); } db.run('UPDATE users SET last_week_start = ? WHERE id = ?', [week_start, userId], (err) => { if (err) { return res.status(500).json({ error: 'Fehler beim Speichern der letzten Woche' }); } res.json({ success: true }); }); }); // API: User-Daten abrufen (Wochenstunden) app.get('/api/user/data', requireAuth, (req, res) => { const userId = req.session.userId; db.get('SELECT wochenstunden FROM users WHERE id = ?', [userId], (err, user) => { if (err) { return res.status(500).json({ error: 'Fehler beim Abrufen der User-Daten' }); } res.json({ wochenstunden: user?.wochenstunden || 0 }); }); }); // API: Ping-IP abrufen app.get('/api/user/ping-ip', requireAuth, (req, res) => { const userId = req.session.userId; db.get('SELECT ping_ip FROM users WHERE id = ?', [userId], (err, user) => { if (err) { return res.status(500).json({ error: 'Fehler beim Abrufen der IP-Adresse' }); } res.json({ ping_ip: user?.ping_ip || null }); }); }); // API: Ping-IP speichern app.post('/api/user/ping-ip', requireAuth, (req, res) => { const userId = req.session.userId; const { ping_ip } = req.body; // Validierung: IPv4 Format (einfache Prüfung) const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/; if (ping_ip && ping_ip.trim() !== '' && !ipv4Regex.test(ping_ip.trim())) { return res.status(400).json({ error: 'Ungültige IP-Adresse. Bitte geben Sie eine gültige IPv4-Adresse ein.' }); } // Normalisiere: Leere Strings werden zu null const normalizedPingIp = (ping_ip && ping_ip.trim() !== '') ? ping_ip.trim() : null; db.run('UPDATE users SET ping_ip = ? WHERE id = ?', [normalizedPingIp, userId], (err) => { if (err) { return res.status(500).json({ error: 'Fehler beim Speichern der IP-Adresse' }); } // Wenn IP entfernt wurde, lösche auch den Ping-Status für heute if (!normalizedPingIp) { const currentDate = getCurrentDate(); db.run('DELETE FROM ping_status WHERE user_id = ? AND date = ?', [userId, currentDate], (err) => { // Fehler ignorieren }); } res.json({ success: true, ping_ip: normalizedPingIp }); }); }); // API: Rollenwechsel app.post('/api/user/switch-role', requireAuth, (req, res) => { const { role } = req.body; if (!role) { return res.status(400).json({ error: 'Rolle ist erforderlich' }); } // Prüfe ob User diese Rolle hat if (!hasRole(req, role)) { return res.status(403).json({ error: 'Sie haben keine Berechtigung für diese Rolle' }); } // Validiere dass die Rolle eine gültige Rolle ist const validRoles = ['mitarbeiter', 'verwaltung', 'admin']; if (!validRoles.includes(role)) { return res.status(400).json({ error: 'Ungültige Rolle' }); } // Setze aktuelle Rolle req.session.currentRole = role; res.json({ success: true, currentRole: role }); }); // API: Gesamtstatistiken für Mitarbeiter (Überstunden und Urlaubstage) app.get('/api/user/stats', requireAuth, (req, res) => { const userId = req.session.userId; // User-Daten abrufen db.get('SELECT wochenstunden, urlaubstage, 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 urlaubstage = user.urlaubstage || 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`, [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({ currentOvertime: overtimeOffsetHours, remainingVacation: urlaubstage, totalOvertimeHours: 0, totalOvertimeTaken: 0, totalVacationDays: 0, urlaubstage: urlaubstage, overtimeOffsetHours: overtimeOffsetHours }); } let totalOvertimeHours = 0; let totalOvertimeTaken = 0; let totalVacationDays = 0; 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; // Wenn bereits ein Fehler aufgetreten ist, ignoriere weitere Ergebnisse 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 { // Vergleiche updated_at (falls vorhanden) oder id (höhere ID = neuer) 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); // Prüfe ob Woche vollständig ausgefüllt ist (alle 5 Werktage) // 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]; const entry = entriesByDate[dateStr]; // Tag gilt als ausgefüllt wenn: // - Ganzer Tag Urlaub (vacation_type = 'full') // - Krank (sick_status = 1) // - ODER Start- und End-Zeit vorhanden sind 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) { // Woche nicht vollständig - überspringe diese Woche processedWeeks++; if (processedWeeks === weeks.length && !hasError) { const currentOvertime = (totalOvertimeHours - totalOvertimeTaken) + overtimeOffsetHours; const remainingVacation = urlaubstage - totalVacationDays; res.json({ currentOvertime: currentOvertime, remainingVacation: remainingVacation, totalOvertimeHours: totalOvertimeHours, totalOvertimeTaken: totalOvertimeTaken, totalVacationDays: totalVacationDays, urlaubstage: urlaubstage, overtimeOffsetHours: overtimeOffsetHours }); } return; // Überspringe diese Woche } // Berechnungen für diese Woche (nur wenn vollständig ausgefüllt) let weekTotalHours = 0; let weekOvertimeTaken = 0; let weekVacationDays = 0; let weekVacationHours = 0; entries.forEach(entry => { if (entry.overtime_taken_hours) { weekOvertimeTaken += entry.overtime_taken_hours; } // Urlaub hat Priorität - wenn Urlaub, zähle nur Urlaubsstunden, nicht zusätzlich Arbeitsstunden if (entry.vacation_type === 'full') { weekVacationDays += 1; weekVacationHours += 8; // Ganzer Tag = 8 Stunden // Bei vollem Tag Urlaub werden keine Arbeitsstunden gezählt } else if (entry.vacation_type === 'half') { weekVacationDays += 0.5; weekVacationHours += 4; // Halber Tag = 4 Stunden // Bei halbem Tag Urlaub können noch Arbeitsstunden vorhanden sein if (entry.total_hours) { weekTotalHours += entry.total_hours; } } else { // Kein Urlaub - zähle nur Arbeitsstunden if (entry.total_hours) { weekTotalHours += entry.total_hours; } } }); // Sollstunden berechnen const sollStunden = (wochenstunden / 5) * workdays; // Überstunden für diese Woche: Urlaub zählt als normale Arbeitszeit const weekTotalHoursWithVacation = weekTotalHours + weekVacationHours; const weekOvertimeHours = weekTotalHoursWithVacation - sollStunden; // Kumulativ addieren totalOvertimeHours += weekOvertimeHours; totalOvertimeTaken += weekOvertimeTaken; totalVacationDays += weekVacationDays; processedWeeks++; // Wenn alle Wochen verarbeitet wurden, Antwort senden if (processedWeeks === weeks.length && !hasError) { const currentOvertime = (totalOvertimeHours - totalOvertimeTaken) + overtimeOffsetHours; const remainingVacation = urlaubstage - totalVacationDays; res.json({ currentOvertime: currentOvertime, remainingVacation: remainingVacation, totalOvertimeHours: totalOvertimeHours, totalOvertimeTaken: totalOvertimeTaken, totalVacationDays: totalVacationDays, urlaubstage: urlaubstage, overtimeOffsetHours: overtimeOffsetHours }); } }); }); }); }); }); } module.exports = registerUserRoutes;