Inputvaledierung für die Projektnummern
This commit is contained in:
@@ -38,6 +38,21 @@ function getWeekendPercentage(date) {
|
|||||||
return 100; // Kein Wochenende = 100% (normal)
|
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
|
// Statistiken laden
|
||||||
async function loadUserStats() {
|
async function loadUserStats() {
|
||||||
try {
|
try {
|
||||||
@@ -683,7 +698,8 @@ function renderWeek() {
|
|||||||
data-date="${dateStr}"
|
data-date="${dateStr}"
|
||||||
data-field="activity${idx + 1}_project_number"
|
data-field="activity${idx + 1}_project_number"
|
||||||
value="${activity.projectNumber || ''}"
|
value="${activity.projectNumber || ''}"
|
||||||
placeholder="Projektnummer"
|
placeholder="z. B. 5260001"
|
||||||
|
title="7 Ziffern: 5 + Jahr (YY) + 4 Ziffern"
|
||||||
${timeFieldsDisabled} ${disabled}
|
${timeFieldsDisabled} ${disabled}
|
||||||
onblur="saveEntry(this)"
|
onblur="saveEntry(this)"
|
||||||
class="activity-project-input">
|
class="activity-project-input">
|
||||||
@@ -1275,6 +1291,19 @@ async function saveEntry(input) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// Aktualisiere currentEntries mit den DOM-Werten
|
||||||
currentEntries[date].start_time = start_time;
|
currentEntries[date].start_time = start_time;
|
||||||
currentEntries[date].end_time = end_time;
|
currentEntries[date].end_time = end_time;
|
||||||
@@ -1539,6 +1568,11 @@ async function saveEntry(input) {
|
|||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
alert(result.error || 'Fehler beim Speichern');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
// Aktualisiere Stunden-Anzeige
|
// Aktualisiere Stunden-Anzeige
|
||||||
const hoursElement = document.getElementById(`hours_${date}`);
|
const hoursElement = document.getElementById(`hours_${date}`);
|
||||||
|
|||||||
@@ -6,6 +6,29 @@ const { generatePDF } = require('../services/pdf-service');
|
|||||||
const { getHolidaysForDateRange } = require('../services/feiertage-service');
|
const { getHolidaysForDateRange } = require('../services/feiertage-service');
|
||||||
const { hasRole } = require('../helpers/utils');
|
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
|
// Routes registrieren
|
||||||
function registerTimesheetRoutes(app) {
|
function registerTimesheetRoutes(app) {
|
||||||
// API: Stundenerfassung speichern
|
// API: Stundenerfassung speichern
|
||||||
@@ -20,6 +43,13 @@ function registerTimesheetRoutes(app) {
|
|||||||
overtime_taken_hours, vacation_type, sick_status, weekend_travel
|
overtime_taken_hours, vacation_type, sick_status, weekend_travel
|
||||||
} = req.body;
|
} = req.body;
|
||||||
const userId = req.session.userId;
|
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 hasExplicitBreakMinutes = break_minutes !== undefined && break_minutes !== null && break_minutes !== '';
|
||||||
const parsedRequestedBreakMinutes = hasExplicitBreakMinutes ? parseInt(break_minutes, 10) : null;
|
const parsedRequestedBreakMinutes = hasExplicitBreakMinutes ? parseInt(break_minutes, 10) : null;
|
||||||
const requestedBreakMinutes = Number.isFinite(parsedRequestedBreakMinutes) && parsedRequestedBreakMinutes >= 0
|
const requestedBreakMinutes = Number.isFinite(parsedRequestedBreakMinutes) && parsedRequestedBreakMinutes >= 0
|
||||||
|
|||||||
@@ -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
|
// Projektauswertung nach Mitarbeitern für eine Projektnummer
|
||||||
app.get('/verwaltung/projektauswertung', requireVerwaltung, (req, res) => {
|
app.get('/verwaltung/projektauswertung', requireVerwaltung, (req, res) => {
|
||||||
const projectNumberRaw = req.query.project ? String(req.query.project).trim() : '';
|
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
|
// Aggregation der Projektstunden pro Mitarbeiter über alle 5 Aktivitäten
|
||||||
const sql = `
|
const sql = `
|
||||||
SELECT
|
SELECT
|
||||||
|
|||||||
@@ -17,6 +17,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="nav-right">
|
<div class="nav-right">
|
||||||
<span>Verwaltung: <%= user.firstname %> <%= user.lastname %></span>
|
<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) { %>
|
<% 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;">
|
<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' }; %>
|
<% const roleLabels = { 'mitarbeiter': 'Mitarbeiter', 'verwaltung': 'Verwaltung', 'admin': 'Administrator' }; %>
|
||||||
@@ -35,10 +39,6 @@
|
|||||||
<h2>Projektauswertung nach Mitarbeitern</h2>
|
<h2>Projektauswertung nach Mitarbeitern</h2>
|
||||||
<p>Geben Sie eine Projektnummer ein, um alle erfassten Stunden pro Mitarbeiter für dieses Projekt auszuwerten.</p>
|
<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">« Zurück zur Verwaltung</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form method="GET" action="/verwaltung/projektauswertung" class="projekt-filter-form" style="margin-bottom: 20px;">
|
<form method="GET" action="/verwaltung/projektauswertung" class="projekt-filter-form" style="margin-bottom: 20px;">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="projectNumber"><strong>Projektnummer</strong></label>
|
<label for="projectNumber"><strong>Projektnummer</strong></label>
|
||||||
@@ -48,14 +48,18 @@
|
|||||||
name="project"
|
name="project"
|
||||||
value="<%= projectNumber || '' %>"
|
value="<%= projectNumber || '' %>"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
placeholder="z. B. 12345"
|
placeholder="z. B. 5260001"
|
||||||
required
|
required
|
||||||
style="max-width: 240px;">
|
style="max-width: 240px;">
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">Auswerten</button>
|
<button type="submit" class="btn btn-primary">Auswerten</button>
|
||||||
</form>
|
</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">
|
<div class="empty-state">
|
||||||
<p>Für das Projekt <strong><%= projectNumber %></strong> wurden keine Stunden gefunden.</p>
|
<p>Für das Projekt <strong><%= projectNumber %></strong> wurden keine Stunden gefunden.</p>
|
||||||
</div>
|
</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
|
// Mitarbeiter-Details (Aktivitäten) ein-/ausklappen
|
||||||
document.querySelectorAll('.toggle-details-btn').forEach(function(btn) {
|
document.querySelectorAll('.toggle-details-btn').forEach(function(btn) {
|
||||||
btn.addEventListener('click', function() {
|
btn.addEventListener('click', function() {
|
||||||
|
|||||||
@@ -17,7 +17,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="nav-right">
|
<div class="nav-right">
|
||||||
<span>Verwaltung: <%= user.firstname %> <%= user.lastname %></span>
|
<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) { %>
|
<% 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;">
|
<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' }; %>
|
<% 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>
|
</script>
|
||||||
<%- include('footer') %>
|
<%- include('footer') %>
|
||||||
|
|||||||
Reference in New Issue
Block a user