diff --git a/Stunderfassung todo.txt b/Stunderfassung todo.txt
index dd3e983..77d24c5 100644
--- a/Stunderfassung todo.txt
+++ b/Stunderfassung todo.txt
@@ -10,8 +10,8 @@
- 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
-- Feiertage im PDF anzeigen -> DONE Testen noch nicht depoyed
-- Oben wenn woche eingereicht anzeigen als hilfestellung
-- Ausgefüllte Tage anhand der Tage pro woche gültig setzten
-- Überstunden müssen anhand der Tagesstunden auch auf gültig setzten (Tag ausgefüllt wenn weniger als 8h)
-- Verplante Urlaubstage müssen auf abgezogen werden, wenn die Woche die gepalnt war eingereicht wurde.
\ No newline at end of file
+- Feiertage im PDF anzeigen -> DONE Testen noch nicht depoyed
+- Oben wenn woche eingereicht anzeigen als hilfestellung -> DONE
+- 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) -> DONE sollte passen
+- Verplante Urlaubstage müssen auf abgezogen werden, wenn die Woche die gepalnt war eingereicht wurde. -> DONE Testen
\ No newline at end of file
diff --git a/helpers/utils.js b/helpers/utils.js
index f3bfb61..62ea7f0 100644
--- a/helpers/utils.js
+++ b/helpers/utils.js
@@ -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 = {
hasRole,
getDefaultRole,
@@ -127,5 +142,6 @@ module.exports = {
formatDate,
formatDateTime,
getCalendarWeek,
- getWeekDatesFromCalendarWeek
+ getWeekDatesFromCalendarWeek,
+ getWeekStart
};
diff --git a/public/js/dashboard.js b/public/js/dashboard.js
index 06d6f2e..ef4697f 100644
--- a/public/js/dashboard.js
+++ b/public/js/dashboard.js
@@ -374,8 +374,11 @@ function renderWeek() {
endDate.setDate(endDate.getDate() + 6);
const calendarWeek = getCalendarWeek(currentWeekStart);
+ const submittedText = currentEntries._weekSubmitted === true
+ ? `
(bereits Abgegeben)`
+ : '';
document.getElementById('weekTitle').innerHTML =
- `Kalenderwoche ${calendarWeek}
${formatDateDE(currentWeekStart)} - ${formatDateDE(formatDate(endDate))}`;
+ `Kalenderwoche ${calendarWeek}
${formatDateDE(currentWeekStart)} - ${formatDateDE(formatDate(endDate))}${submittedText}`;
let html = `
@@ -1410,9 +1413,10 @@ function checkWeekComplete() {
let allWeekdaysFilled = true;
const missingFields = [];
- // Prüfe nur Werktage (Montag-Freitag, i < 5)
- // Samstag und Sonntag (i >= 5) sind optional
- for (let i = 0; i < 5; i++) {
+ // Prüfe nur so viele Tage wie Arbeitstage pro Woche festgelegt sind
+ // Samstag und Sonntag sind optional
+ const requiredDays = userArbeitstage || 5; // Fallback auf 5 wenn nicht gesetzt
+ for (let i = 0; i < requiredDays; i++) {
const date = new Date(startDate);
date.setDate(date.getDate() + i);
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 submitButton = document.getElementById('submitWeek');
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) {
- submitButton.title = 'Diese Woche wurde bereits eingereicht und kann nicht mehr geändert werden.';
- } else if (!allWeekdaysFilled) {
- submitButton.title = `Bitte füllen Sie alle Werktage (Montag bis Freitag) aus (Start- und Endzeit). Wochenende ist optional. Fehlend: ${missingFields.join(', ')}`;
+ if (!allWeekdaysFilled) {
+ const requiredDaysText = requiredDays === 1 ? '1 Tag' : `${requiredDays} Tage`;
+ submitButton.title = `Bitte füllen Sie alle ${requiredDaysText} (Start- und Endzeit) aus. 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 {
submitButton.title = '';
}
@@ -1545,7 +1552,7 @@ async function submitWeek() {
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 firstMissingInput = null;
@@ -1556,7 +1563,8 @@ async function submitWeek() {
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);
date.setDate(date.getDate() + i);
const dateStr = formatDate(date);
@@ -1644,7 +1652,8 @@ async function submitWeek() {
}
// 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` +
`Die fehlenden Felder wurden rot markiert.\n` +
`Hinweis: Samstag und Sonntag sind optional.`;
diff --git a/routes/timesheet-routes.js b/routes/timesheet-routes.js
index 490bc06..5b7125d 100644
--- a/routes/timesheet-routes.js
+++ b/routes/timesheet-routes.js
@@ -262,7 +262,7 @@ function registerTimesheetRoutes(app) {
// Füge Status-Info hinzu (Bearbeitung ist immer möglich)
const entriesWithStatus = (entries || []).map(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,
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
// 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
@@ -330,7 +330,7 @@ function registerTimesheetRoutes(app) {
.then((holidaySet) => {
let missingDays = [];
- for (let i = 0; i < 5; i++) {
+ for (let i = 0; i < arbeitstage; i++) {
// Datum direkt berechnen ohne Zeitzonenprobleme
const date = new Date(startYear, startMonth, startDay + i);
const year = date.getFullYear();
@@ -380,8 +380,9 @@ function registerTimesheetRoutes(app) {
}
if (missingDays.length > 0) {
+ const requiredDaysText = arbeitstage === 1 ? '1 Tag' : `${arbeitstage} Tage`;
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.`
});
}
diff --git a/routes/user-routes.js b/routes/user-routes.js
index 7635fbd..a955f26 100644
--- a/routes/user-routes.js
+++ b/routes/user-routes.js
@@ -154,48 +154,73 @@ function registerUserRoutes(app) {
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) => {
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
- WHERE user_id = ? AND vacation_type IS NOT NULL AND vacation_type != ''`,
+ // Zuerst alle eingereichten Wochen abrufen
+ db.all(`SELECT DISTINCT week_start FROM weekly_timesheets
+ WHERE user_id = ? AND status = 'eingereicht'`,
[userId],
- (err, entries) => {
+ (err, submittedWeeks) => {
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;
- const weeksMap = {}; // { KW: { year: YYYY, week: KW, days: X } }
+ // Set für schnelle Suche nach eingereichten Wochen
+ const submittedWeekStarts = new Set(
+ (submittedWeeks || []).map(w => w.week_start)
+ );
- entries.forEach(entry => {
- const dayValue = entry.vacation_type === 'full' ? 1 : 0.5;
- plannedDays += dayValue;
+ // Alle Urlaubseinträge abrufen
+ db.all(`SELECT date, vacation_type FROM timesheet_entries
+ 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
- const date = new Date(entry.date);
- const year = date.getFullYear();
- const week = getCalendarWeek(entry.date);
- const weekKey = `${year}-KW${week}`;
+ let plannedDays = 0;
+ const weeksMap = {}; // { KW: { year: YYYY, week: KW, days: X } }
- if (!weeksMap[weekKey]) {
- weeksMap[weekKey] = { year, week, days: 0 };
+ entries.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;
+ 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 vacationOffsetDays = user.vacation_offset_days ? parseFloat(user.vacation_offset_days) : 0;
- // Verplante Urlaubstage berechnen (alle Wochen, auch nicht-eingereichte)
- const { getCalendarWeek } = require('../helpers/utils');
- db.all(`SELECT date, vacation_type FROM timesheet_entries
- WHERE user_id = ? AND vacation_type IS NOT NULL AND vacation_type != ''`,
+ // Verplante Urlaubstage berechnen (nur nicht-eingereichte Wochen)
+ const { getCalendarWeek, getWeekStart } = require('../helpers/utils');
+
+ // 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],
- (err, allVacationEntries) => {
+ (err, weeks) => {
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;
- const weeksMap = {}; // { KW: { year: YYYY, week: KW, days: X } }
+ // Set für schnelle Suche nach eingereichten Wochen
+ const submittedWeekStarts = new Set(
+ (weeks || []).map(w => w.week_start)
+ );
- (allVacationEntries || []).forEach(entry => {
- 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;
- });
-
- // 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`,
+ // Alle Urlaubseinträge abrufen
+ db.all(`SELECT date, vacation_type FROM timesheet_entries
+ WHERE user_id = ? AND vacation_type IS NOT NULL AND vacation_type != ''`,
[userId],
- (err, weeks) => {
+ (err, allVacationEntries) => {
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
if (!weeks || weeks.length === 0) {
return res.json({