diff --git a/public/js/dashboard.js b/public/js/dashboard.js index 4154733..f295d62 100644 --- a/public/js/dashboard.js +++ b/public/js/dashboard.js @@ -38,6 +38,21 @@ function getWeekendPercentage(date) { return 100; // Kein Wochenende = 100% (normal) } +/** Plausibilitätsprüfung Projektnummer: 7 Ziffern, beginnt mit 5, dann YY (Jahr), dann 4 Ziffern (z.B. 5260001). */ +function isValidProjectNumber(value) { + if (value === null || value === undefined || String(value).trim() === '') return true; + return /^5\d{6}$/.test(String(value).trim()); +} + +function validateActivityProjectNumbers(activities) { + for (let i = 0; i < activities.length; i++) { + if (!isValidProjectNumber(activities[i].projectNumber)) { + return { valid: false, activityIndex: i + 1, value: activities[i].projectNumber }; + } + } + return { valid: true }; +} + // Statistiken laden async function loadUserStats() { try { @@ -683,7 +698,8 @@ function renderWeek() { data-date="${dateStr}" data-field="activity${idx + 1}_project_number" value="${activity.projectNumber || ''}" - placeholder="Projektnummer" + placeholder="z. B. 5260001" + title="7 Ziffern: 5 + Jahr (YY) + 4 Ziffern" ${timeFieldsDisabled} ${disabled} onblur="saveEntry(this)" class="activity-project-input"> @@ -1274,6 +1290,19 @@ async function saveEntry(input) { projectNumber: projectInput ? (projectInput.value || null) : (currentEntries[date][`activity${i}_project_number`] || null) }); } + + const projectNumberCheck = validateActivityProjectNumbers(activities); + if (!projectNumberCheck.valid) { + const idx = projectNumberCheck.activityIndex; + const invalidInput = document.querySelector(`input[data-date="${date}"][data-field="activity${idx}_project_number"]`); + if (invalidInput) { + invalidInput.value = ''; + } + if (activities[idx - 1]) { + activities[idx - 1].projectNumber = null; + } + alert(`Ungültige Projektnummer in Tätigkeit ${projectNumberCheck.activityIndex}: Die Projektnummer muss 7 Ziffern haben, mit 5 beginnen, gefolgt vom Jahr (YY) und 4 Ziffern (z.B. 5260001). Die Eingabe wurde geleert.`); + } // Aktualisiere currentEntries mit den DOM-Werten currentEntries[date].start_time = start_time; @@ -1538,6 +1567,11 @@ async function saveEntry(input) { }); const result = await response.json(); + + if (!response.ok) { + alert(result.error || 'Fehler beim Speichern'); + return; + } if (result.success) { // Aktualisiere Stunden-Anzeige diff --git a/routes/timesheet-routes.js b/routes/timesheet-routes.js index bee7172..fe7318e 100644 --- a/routes/timesheet-routes.js +++ b/routes/timesheet-routes.js @@ -6,6 +6,29 @@ const { generatePDF } = require('../services/pdf-service'); const { getHolidaysForDateRange } = require('../services/feiertage-service'); const { hasRole } = require('../helpers/utils'); +/** Plausibilitätsprüfung Projektnummer: 7 Ziffern, beginnt mit 5, dann YY (Jahr), dann 4 freie Ziffern (z.B. 5260001). */ +function isValidProjectNumber(value) { + if (value === null || value === undefined || String(value).trim() === '') return true; + const s = String(value).trim(); + return /^5\d{6}$/.test(s); +} + +function validateProjectNumbers(body) { + const numbers = [ + body.activity1_project_number, + body.activity2_project_number, + body.activity3_project_number, + body.activity4_project_number, + body.activity5_project_number + ]; + for (let i = 0; i < numbers.length; i++) { + if (!isValidProjectNumber(numbers[i])) { + return { valid: false, activityIndex: i + 1, value: numbers[i] }; + } + } + return { valid: true }; +} + // Routes registrieren function registerTimesheetRoutes(app) { // API: Stundenerfassung speichern @@ -20,6 +43,13 @@ function registerTimesheetRoutes(app) { overtime_taken_hours, vacation_type, sick_status, weekend_travel } = req.body; const userId = req.session.userId; + + const projectNumberCheck = validateProjectNumbers(req.body); + if (!projectNumberCheck.valid) { + return res.status(400).json({ + error: `Ungültige Projektnummer in Tätigkeit ${projectNumberCheck.activityIndex}: Die Projektnummer muss 7 Ziffern haben, mit 5 beginnen, gefolgt vom Jahr (YY) und 4 Ziffern (z.B. 5260001).` + }); + } const hasExplicitBreakMinutes = break_minutes !== undefined && break_minutes !== null && break_minutes !== ''; const parsedRequestedBreakMinutes = hasExplicitBreakMinutes ? parseInt(break_minutes, 10) : null; const requestedBreakMinutes = Number.isFinite(parsedRequestedBreakMinutes) && parsedRequestedBreakMinutes >= 0 diff --git a/routes/verwaltung-routes.js b/routes/verwaltung-routes.js index 93efd97..5c15517 100644 --- a/routes/verwaltung-routes.js +++ b/routes/verwaltung-routes.js @@ -144,6 +144,12 @@ function registerVerwaltungRoutes(app) { }); }); + // Plausibilitätsprüfung Projektnummer: 7 Ziffern, beginnt mit 5, dann YY, dann 4 Ziffern + function isValidProjectNumber(value) { + if (!value || String(value).trim() === '') return false; + return /^5\d{6}$/.test(String(value).trim()); + } + // Projektauswertung nach Mitarbeitern für eine Projektnummer app.get('/verwaltung/projektauswertung', requireVerwaltung, (req, res) => { const projectNumberRaw = req.query.project ? String(req.query.project).trim() : ''; @@ -165,6 +171,22 @@ function registerVerwaltungRoutes(app) { }); } + if (!isValidProjectNumber(projectNumber)) { + return res.render('projekt-auswertung', { + user: { + firstname: req.session.firstname, + lastname: req.session.lastname, + roles: req.session.roles || [], + currentRole: req.session.currentRole || 'verwaltung' + }, + projectNumber: projectNumberRaw, + results: [], + totalProjectHours: 0, + hasResults: false, + projectNumberError: 'Die Projektnummer muss 7 Ziffern haben, mit 5 beginnen, gefolgt vom Jahr (YY) und 4 Ziffern (z.B. 5260001).' + }); + } + // Aggregation der Projektstunden pro Mitarbeiter über alle 5 Aktivitäten const sql = ` SELECT diff --git a/views/projekt-auswertung.ejs b/views/projekt-auswertung.ejs index 1404051..96676fd 100644 --- a/views/projekt-auswertung.ejs +++ b/views/projekt-auswertung.ejs @@ -17,6 +17,10 @@