Inputvaledierung für die Projektnummern

This commit is contained in:
2026-03-12 17:24:01 +01:00
parent 929b3eae57
commit af20dc6bd8
5 changed files with 117 additions and 8 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -17,6 +17,10 @@
</div>
<div class="nav-right">
<span>Verwaltung: <%= user.firstname %> <%= user.lastname %></span>
<select id="verwaltungNav" class="role-switcher" style="margin-right: 10px; padding: 5px 10px; border-radius: 4px; border: 1px solid #ddd;">
<option value="/verwaltung">Postfach</option>
<option value="/verwaltung/projektauswertung" selected>Projektauswertung</option>
</select>
<% if (user.roles && user.roles.length > 1) { %>
<select id="roleSwitcher" class="role-switcher" style="margin-right: 10px; padding: 5px 10px; border-radius: 4px; border: 1px solid #ddd;">
<% const roleLabels = { 'mitarbeiter': 'Mitarbeiter', 'verwaltung': 'Verwaltung', 'admin': 'Administrator' }; %>
@@ -35,10 +39,6 @@
<h2>Projektauswertung nach Mitarbeitern</h2>
<p>Geben Sie eine Projektnummer ein, um alle erfassten Stunden pro Mitarbeiter für dieses Projekt auszuwerten.</p>
<div style="margin-bottom: 15px;">
<a href="/verwaltung" class="btn btn-secondary">&laquo; Zurück zur Verwaltung</a>
</div>
<form method="GET" action="/verwaltung/projektauswertung" class="projekt-filter-form" style="margin-bottom: 20px;">
<div class="form-group">
<label for="projectNumber"><strong>Projektnummer</strong></label>
@@ -48,14 +48,18 @@
name="project"
value="<%= projectNumber || '' %>"
class="form-control"
placeholder="z. B. 12345"
placeholder="z. B. 5260001"
required
style="max-width: 240px;">
</div>
<button type="submit" class="btn btn-primary">Auswerten</button>
</form>
<% if (projectNumber && !hasResults) { %>
<% if (projectNumberError) { %>
<div style="max-width: 480px; padding: 12px 16px; background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px; color: #721c24;">
<%= projectNumberError %>
</div>
<% } else if (projectNumber && !hasResults) { %>
<div class="empty-state">
<p>Für das Projekt <strong><%= projectNumber %></strong> wurden keine Stunden gefunden.</p>
</div>
@@ -167,6 +171,14 @@
});
}
const verwaltungNav = document.getElementById('verwaltungNav');
if (verwaltungNav) {
verwaltungNav.addEventListener('change', function() {
const url = this.value;
if (url) window.location.href = url;
});
}
// Mitarbeiter-Details (Aktivitäten) ein-/ausklappen
document.querySelectorAll('.toggle-details-btn').forEach(function(btn) {
btn.addEventListener('click', function() {

View File

@@ -17,7 +17,10 @@
</div>
<div class="nav-right">
<span>Verwaltung: <%= user.firstname %> <%= user.lastname %></span>
<a href="/verwaltung/projektauswertung" class="btn btn-secondary" style="margin-right: 10px;">Projektauswertung</a>
<select id="verwaltungNav" class="role-switcher" style="margin-right: 10px; padding: 5px 10px; border-radius: 4px; border: 1px solid #ddd;">
<option value="/verwaltung" selected>Postfach</option>
<option value="/verwaltung/projektauswertung">Projektauswertung</option>
</select>
<% if (user.roles && user.roles.length > 1) { %>
<select id="roleSwitcher" class="role-switcher" style="margin-right: 10px; padding: 5px 10px; border-radius: 4px; border: 1px solid #ddd;">
<% const roleLabels = { 'mitarbeiter': 'Mitarbeiter', 'verwaltung': 'Verwaltung', 'admin': 'Administrator' }; %>
@@ -1192,6 +1195,14 @@
}
});
}
const verwaltungNav = document.getElementById('verwaltungNav');
if (verwaltungNav) {
verwaltungNav.addEventListener('change', function() {
const url = this.value;
if (url) window.location.href = url;
});
}
});
</script>
<%- include('footer') %>