To-DO abgearbeitet

This commit is contained in:
2026-02-04 19:03:42 +01:00
parent 76f63ed4ec
commit 063fb68b12
5 changed files with 158 additions and 91 deletions

View File

@@ -10,8 +10,8 @@
- Wenn bereits heruntergeladen wurde und neue version da ist Meldung an Verwaltung. -> DONE Muss getestet werden - Wenn bereits heruntergeladen wurde und neue version da ist Meldung an Verwaltung. -> DONE Muss getestet werden
- Wenn ganzer Tag Urlaub gesetzt wird steht erst 8h (Urlaub) und dann nur noch 8h - Wenn ganzer Tag Urlaub gesetzt wird steht erst 8h (Urlaub) und dann nur noch 8h
- Feiertage im PDF anzeigen -> DONE Testen noch nicht depoyed - Feiertage im PDF anzeigen -> DONE Testen noch nicht depoyed
- Oben wenn woche eingereicht anzeigen als hilfestellung - Oben wenn woche eingereicht anzeigen als hilfestellung -> DONE
- Ausgefüllte Tage anhand der Tage pro woche gültig setzten - Ausgefüllte Tage anhand der Tage pro woche gültig setzten -> DONE Testen
- Überstunden müssen anhand der Tagesstunden auch auf gültig setzten (Tag ausgefüllt wenn weniger als 8h) - Überstunden müssen anhand der Tagesstunden auch auf gültig setzten (Tag ausgefüllt wenn weniger als 8h) -> DONE sollte passen
- Verplante Urlaubstage müssen auf abgezogen werden, wenn die Woche die gepalnt war eingereicht wurde. - Verplante Urlaubstage müssen auf abgezogen werden, wenn die Woche die gepalnt war eingereicht wurde. -> DONE Testen

View File

@@ -117,6 +117,21 @@ function getWeekDatesFromCalendarWeek(year, weekNumber) {
}; };
} }
// Helper: Berechnet week_start (Montag) aus einem Datum
function getWeekStart(dateStr) {
const date = new Date(dateStr);
const day = date.getDay(); // 0 = Sonntag, 1 = Montag, ..., 6 = Samstag
const diff = day === 0 ? -6 : 1 - day; // Montag = 1, Sonntag = 0
const monday = new Date(date);
monday.setDate(date.getDate() + diff);
// Format: YYYY-MM-DD
const year = monday.getFullYear();
const month = String(monday.getMonth() + 1).padStart(2, '0');
const dayOfMonth = String(monday.getDate()).padStart(2, '0');
return `${year}-${month}-${dayOfMonth}`;
}
module.exports = { module.exports = {
hasRole, hasRole,
getDefaultRole, getDefaultRole,
@@ -127,5 +142,6 @@ module.exports = {
formatDate, formatDate,
formatDateTime, formatDateTime,
getCalendarWeek, getCalendarWeek,
getWeekDatesFromCalendarWeek getWeekDatesFromCalendarWeek,
getWeekStart
}; };

View File

@@ -374,8 +374,11 @@ function renderWeek() {
endDate.setDate(endDate.getDate() + 6); endDate.setDate(endDate.getDate() + 6);
const calendarWeek = getCalendarWeek(currentWeekStart); const calendarWeek = getCalendarWeek(currentWeekStart);
const submittedText = currentEntries._weekSubmitted === true
? `<br><span style="color: #28a745;">(bereits Abgegeben)</span>`
: '';
document.getElementById('weekTitle').innerHTML = document.getElementById('weekTitle').innerHTML =
`Kalenderwoche ${calendarWeek}<br>${formatDateDE(currentWeekStart)} - ${formatDateDE(formatDate(endDate))}`; `Kalenderwoche ${calendarWeek}<br>${formatDateDE(currentWeekStart)} - ${formatDateDE(formatDate(endDate))}${submittedText}`;
let html = ` let html = `
<div class="timesheet-grid"> <div class="timesheet-grid">
@@ -1410,9 +1413,10 @@ function checkWeekComplete() {
let allWeekdaysFilled = true; let allWeekdaysFilled = true;
const missingFields = []; const missingFields = [];
// Prüfe nur Werktage (Montag-Freitag, i < 5) // Prüfe nur so viele Tage wie Arbeitstage pro Woche festgelegt sind
// Samstag und Sonntag (i >= 5) sind optional // Samstag und Sonntag sind optional
for (let i = 0; i < 5; i++) { const requiredDays = userArbeitstage || 5; // Fallback auf 5 wenn nicht gesetzt
for (let i = 0; i < requiredDays; i++) {
const date = new Date(startDate); const date = new Date(startDate);
date.setDate(date.getDate() + i); date.setDate(date.getDate() + i);
const dateStr = formatDate(date); const dateStr = formatDate(date);
@@ -1482,16 +1486,19 @@ function checkWeekComplete() {
} }
} }
// Prüfe ob die Woche bereits eingereicht wurde (nicht der Status einzelner Einträge!) // Prüfe ob die Woche bereits eingereicht wurde (für Anzeige, aber Button bleibt aktiv für neue Versionen)
const weekIsSubmitted = currentEntries._weekSubmitted === true; const weekIsSubmitted = currentEntries._weekSubmitted === true;
const submitButton = document.getElementById('submitWeek'); const submitButton = document.getElementById('submitWeek');
if (submitButton) { if (submitButton) {
submitButton.disabled = weekIsSubmitted || !allWeekdaysFilled; // Button nur deaktivieren wenn nicht alle Felder ausgefüllt sind
// Resubmission ist erlaubt, da Versionierung unterstützt wird
submitButton.disabled = !allWeekdaysFilled;
if (weekIsSubmitted) { if (!allWeekdaysFilled) {
submitButton.title = 'Diese Woche wurde bereits eingereicht und kann nicht mehr geändert werden.'; const requiredDaysText = requiredDays === 1 ? '1 Tag' : `${requiredDays} Tage`;
} else if (!allWeekdaysFilled) { submitButton.title = `Bitte füllen Sie alle ${requiredDaysText} (Start- und Endzeit) aus. Wochenende ist optional. Fehlend: ${missingFields.join(', ')}`;
submitButton.title = `Bitte füllen Sie alle Werktage (Montag bis Freitag) aus (Start- und Endzeit). Wochenende ist optional. Fehlend: ${missingFields.join(', ')}`; } else if (weekIsSubmitted) {
submitButton.title = 'Diese Woche wurde bereits eingereicht. Beim erneuten Abschicken wird eine neue Version erstellt.';
} else { } else {
submitButton.title = ''; submitButton.title = '';
} }
@@ -1545,7 +1552,7 @@ async function submitWeek() {
console.log('Prüfe Validierung für Woche:', currentWeekStart); console.log('Prüfe Validierung für Woche:', currentWeekStart);
// Frontend-Validierung: Prüfen ob alle Werktage (Montag-Freitag) ausgefüllt sind // Frontend-Validierung: Prüfen ob so viele Tage ausgefüllt sind wie Arbeitstage pro Woche festgelegt sind
let missingFields = []; let missingFields = [];
let firstMissingInput = null; let firstMissingInput = null;
@@ -1556,7 +1563,8 @@ async function submitWeek() {
el.style.backgroundColor = ''; el.style.backgroundColor = '';
}); });
for (let i = 0; i < 5; i++) { const requiredDays = userArbeitstage || 5; // Fallback auf 5 wenn nicht gesetzt
for (let i = 0; i < requiredDays; i++) {
const date = new Date(startDate); const date = new Date(startDate);
date.setDate(date.getDate() + i); date.setDate(date.getDate() + i);
const dateStr = formatDate(date); const dateStr = formatDate(date);
@@ -1644,7 +1652,8 @@ async function submitWeek() {
} }
// Detaillierte Fehlermeldung // Detaillierte Fehlermeldung
const message = `❌ Bitte füllen Sie alle Werktage (Montag bis Freitag) vollständig aus!\n\n` + const requiredDaysText = requiredDays === 1 ? '1 Tag' : `${requiredDays} Tage`;
const message = `❌ Bitte füllen Sie alle ${requiredDaysText} vollständig aus!\n\n` +
`Fehlende Eingaben:\n${missingFields.map((field, index) => `\n${index + 1}. ${field}`).join('')}\n\n` + `Fehlende Eingaben:\n${missingFields.map((field, index) => `\n${index + 1}. ${field}`).join('')}\n\n` +
`Die fehlenden Felder wurden rot markiert.\n` + `Die fehlenden Felder wurden rot markiert.\n` +
`Hinweis: Samstag und Sonntag sind optional.`; `Hinweis: Samstag und Sonntag sind optional.`;

View File

@@ -262,7 +262,7 @@ function registerTimesheetRoutes(app) {
// Füge Status-Info hinzu (Bearbeitung ist immer möglich) // Füge Status-Info hinzu (Bearbeitung ist immer möglich)
const entriesWithStatus = (entries || []).map(entry => ({ const entriesWithStatus = (entries || []).map(entry => ({
...entry, ...entry,
week_submitted: false, // Immer false, damit Bearbeitung möglich ist week_submitted: hasSubmittedVersion, // Woche wurde eingereicht wenn weekly_timesheet existiert
latest_version: latestVersion, latest_version: latestVersion,
has_existing_version: latestVersion > 0 has_existing_version: latestVersion > 0
})); }));
@@ -304,7 +304,7 @@ function registerTimesheetRoutes(app) {
} }
}); });
// Prüfe nur Werktage (Montag-Freitag, erste 5 Tage) // Prüfe nur so viele Tage wie Arbeitstage pro Woche festgelegt sind
// Samstag und Sonntag sind optional // Samstag und Sonntag sind optional
// Bei ganztägigem Urlaub (vacation_type = 'full') ist der Tag als ausgefüllt zu betrachten // Bei ganztägigem Urlaub (vacation_type = 'full') ist der Tag als ausgefüllt zu betrachten
// Bei 8 Überstunden (ganzer Tag) ist der Tag auch als ausgefüllt zu betrachten // Bei 8 Überstunden (ganzer Tag) ist der Tag auch als ausgefüllt zu betrachten
@@ -330,7 +330,7 @@ function registerTimesheetRoutes(app) {
.then((holidaySet) => { .then((holidaySet) => {
let missingDays = []; let missingDays = [];
for (let i = 0; i < 5; i++) { for (let i = 0; i < arbeitstage; i++) {
// Datum direkt berechnen ohne Zeitzonenprobleme // Datum direkt berechnen ohne Zeitzonenprobleme
const date = new Date(startYear, startMonth, startDay + i); const date = new Date(startYear, startMonth, startDay + i);
const year = date.getFullYear(); const year = date.getFullYear();
@@ -380,8 +380,9 @@ function registerTimesheetRoutes(app) {
} }
if (missingDays.length > 0) { if (missingDays.length > 0) {
const requiredDaysText = arbeitstage === 1 ? '1 Tag' : `${arbeitstage} Tage`;
return res.status(400).json({ return res.status(400).json({
error: `Nicht alle Werktage (Montag bis Freitag) sind ausgefüllt. Fehlende Tage: ${missingDays.join(', ')}. Bitte füllen Sie alle Werktage mit Start- und Endzeit aus. Wochenende ist optional.` error: `Nicht alle ${requiredDaysText} sind ausgefüllt. Fehlende Tage: ${missingDays.join(', ')}. Bitte füllen Sie alle ${requiredDaysText} mit Start- und Endzeit aus. Wochenende ist optional.`
}); });
} }

View File

@@ -154,48 +154,73 @@ function registerUserRoutes(app) {
res.json({ success: true, currentRole: role }); res.json({ success: true, currentRole: role });
}); });
// API: Verplante Urlaubstage (alle Wochen, auch nicht-eingereichte) // API: Verplante Urlaubstage (nur nicht-eingereichte Wochen)
app.get('/api/user/planned-vacation', requireAuth, (req, res) => { app.get('/api/user/planned-vacation', requireAuth, (req, res) => {
const userId = req.session.userId; const userId = req.session.userId;
const { getCalendarWeek } = require('../helpers/utils'); const { getCalendarWeek, getWeekStart } = require('../helpers/utils');
db.all(`SELECT date, vacation_type FROM timesheet_entries // Zuerst alle eingereichten Wochen abrufen
WHERE user_id = ? AND vacation_type IS NOT NULL AND vacation_type != ''`, db.all(`SELECT DISTINCT week_start FROM weekly_timesheets
WHERE user_id = ? AND status = 'eingereicht'`,
[userId], [userId],
(err, entries) => { (err, submittedWeeks) => {
if (err) { if (err) {
return res.status(500).json({ error: 'Fehler beim Abrufen der verplanten Tage' }); return res.status(500).json({ error: 'Fehler beim Abrufen der eingereichten Wochen' });
} }
let plannedDays = 0; // Set für schnelle Suche nach eingereichten Wochen
const weeksMap = {}; // { KW: { year: YYYY, week: KW, days: X } } const submittedWeekStarts = new Set(
(submittedWeeks || []).map(w => w.week_start)
);
entries.forEach(entry => { // Alle Urlaubseinträge abrufen
const dayValue = entry.vacation_type === 'full' ? 1 : 0.5; db.all(`SELECT date, vacation_type FROM timesheet_entries
plannedDays += dayValue; WHERE user_id = ? AND vacation_type IS NOT NULL AND vacation_type != ''`,
[userId],
(err, entries) => {
if (err) {
return res.status(500).json({ error: 'Fehler beim Abrufen der verplanten Tage' });
}
// Berechne Kalenderwoche let plannedDays = 0;
const date = new Date(entry.date); const weeksMap = {}; // { KW: { year: YYYY, week: KW, days: X } }
const year = date.getFullYear();
const week = getCalendarWeek(entry.date);
const weekKey = `${year}-KW${week}`;
if (!weeksMap[weekKey]) { entries.forEach(entry => {
weeksMap[weekKey] = { year, week, days: 0 }; // Berechne week_start für diesen Eintrag
const weekStart = getWeekStart(entry.date);
// Überspringe Einträge aus eingereichten Wochen
if (submittedWeekStarts.has(weekStart)) {
return;
}
const dayValue = entry.vacation_type === 'full' ? 1 : 0.5;
plannedDays += dayValue;
// Berechne Kalenderwoche
const date = new Date(entry.date);
const year = date.getFullYear();
const week = getCalendarWeek(entry.date);
const weekKey = `${year}-KW${week}`;
if (!weeksMap[weekKey]) {
weeksMap[weekKey] = { year, week, days: 0 };
}
weeksMap[weekKey].days += dayValue;
});
// Konvertiere zu sortiertem Array
const weeks = Object.values(weeksMap).sort((a, b) => {
if (a.year !== b.year) return a.year - b.year;
return a.week - b.week;
});
res.json({
plannedVacationDays: plannedDays,
weeks: weeks
});
} }
weeksMap[weekKey].days += dayValue; );
});
// Konvertiere zu sortiertem Array
const weeks = Object.values(weeksMap).sort((a, b) => {
if (a.year !== b.year) return a.year - b.year;
return a.week - b.week;
});
res.json({
plannedVacationDays: plannedDays,
weeks: weeks
});
} }
); );
}); });
@@ -237,52 +262,68 @@ function registerUserRoutes(app) {
const overtimeOffsetHours = user.overtime_offset_hours ? parseFloat(user.overtime_offset_hours) : 0; const overtimeOffsetHours = user.overtime_offset_hours ? parseFloat(user.overtime_offset_hours) : 0;
const vacationOffsetDays = user.vacation_offset_days ? parseFloat(user.vacation_offset_days) : 0; const vacationOffsetDays = user.vacation_offset_days ? parseFloat(user.vacation_offset_days) : 0;
// Verplante Urlaubstage berechnen (alle Wochen, auch nicht-eingereichte) // Verplante Urlaubstage berechnen (nur nicht-eingereichte Wochen)
const { getCalendarWeek } = require('../helpers/utils'); const { getCalendarWeek, getWeekStart } = require('../helpers/utils');
db.all(`SELECT date, vacation_type FROM timesheet_entries
WHERE user_id = ? AND vacation_type IS NOT NULL AND vacation_type != ''`, // Zuerst 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], [userId],
(err, allVacationEntries) => { (err, weeks) => {
if (err) { if (err) {
return res.status(500).json({ error: 'Fehler beim Abrufen der verplanten Tage' }); return res.status(500).json({ error: 'Fehler beim Abrufen der Wochen' });
} }
let plannedVacationDays = 0; // Set für schnelle Suche nach eingereichten Wochen
const weeksMap = {}; // { KW: { year: YYYY, week: KW, days: X } } const submittedWeekStarts = new Set(
(weeks || []).map(w => w.week_start)
);
(allVacationEntries || []).forEach(entry => { // Alle Urlaubseinträge abrufen
const dayValue = entry.vacation_type === 'full' ? 1 : 0.5; db.all(`SELECT date, vacation_type FROM timesheet_entries
plannedVacationDays += dayValue; WHERE user_id = ? AND vacation_type IS NOT NULL AND vacation_type != ''`,
// Berechne Kalenderwoche
const date = new Date(entry.date);
const year = date.getFullYear();
const week = getCalendarWeek(entry.date);
const weekKey = `${year}-KW${week}`;
if (!weeksMap[weekKey]) {
weeksMap[weekKey] = { year, week, days: 0 };
}
weeksMap[weekKey].days += dayValue;
});
// Konvertiere zu sortiertem Array
const plannedWeeks = Object.values(weeksMap).sort((a, b) => {
if (a.year !== b.year) return a.year - b.year;
return a.week - b.week;
});
// 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], [userId],
(err, weeks) => { (err, allVacationEntries) => {
if (err) { if (err) {
return res.status(500).json({ error: 'Fehler beim Abrufen der Wochen' }); return res.status(500).json({ error: 'Fehler beim Abrufen der verplanten Tage' });
} }
let plannedVacationDays = 0;
const weeksMap = {}; // { KW: { year: YYYY, week: KW, days: X } }
(allVacationEntries || []).forEach(entry => {
// Berechne week_start für diesen Eintrag
const weekStart = getWeekStart(entry.date);
// Überspringe Einträge aus eingereichten Wochen
if (submittedWeekStarts.has(weekStart)) {
return;
}
const dayValue = entry.vacation_type === 'full' ? 1 : 0.5;
plannedVacationDays += dayValue;
// Berechne Kalenderwoche
const date = new Date(entry.date);
const year = date.getFullYear();
const week = getCalendarWeek(entry.date);
const weekKey = `${year}-KW${week}`;
if (!weeksMap[weekKey]) {
weeksMap[weekKey] = { year, week, days: 0 };
}
weeksMap[weekKey].days += dayValue;
});
// Konvertiere zu sortiertem Array
const plannedWeeks = Object.values(weeksMap).sort((a, b) => {
if (a.year !== b.year) return a.year - b.year;
return a.week - b.week;
});
// Weiter mit der Verarbeitung der eingereichten Wochen (weeks ist bereits verfügbar)
// Wenn keine Wochen vorhanden // Wenn keine Wochen vorhanden
if (!weeks || weeks.length === 0) { if (!weeks || weeks.length === 0) {
return res.json({ return res.json({