Projektauswertung hinzugefügt

This commit is contained in:
2026-03-12 15:25:20 +01:00
parent b28bd93f26
commit 929b3eae57
7 changed files with 451 additions and 3 deletions

View File

@@ -10,6 +10,18 @@ const { getCurrentOvertimeForUser } = require('../services/overtime-service');
// Routes registrieren
function registerVerwaltungRoutes(app) {
// Helper: Minuten in Format h:mm (z. B. 90 -> "1:30", -45 -> "-0:45")
function minutesToHhMm(totalMinutes) {
if (totalMinutes == null || !Number.isFinite(Number(totalMinutes))) return '0:00';
const n = Number(totalMinutes);
const sign = n < 0 ? -1 : 1;
const absVal = Math.abs(n);
let h = Math.floor(absVal / 60);
let min = Math.round(absVal - h * 60);
const prefix = sign < 0 ? '-' : '';
return prefix + h + ':' + String(min).padStart(2, '0');
}
// Verwaltungs-Bereich
app.get('/verwaltung', requireVerwaltung, (req, res) => {
db.all(`
@@ -132,6 +144,204 @@ function registerVerwaltungRoutes(app) {
});
});
// Projektauswertung nach Mitarbeitern für eine Projektnummer
app.get('/verwaltung/projektauswertung', requireVerwaltung, (req, res) => {
const projectNumberRaw = req.query.project ? String(req.query.project).trim() : '';
const projectNumber = projectNumberRaw || null;
if (!projectNumber) {
// Nur Formular anzeigen, noch keine Auswertung
return res.render('projekt-auswertung', {
user: {
firstname: req.session.firstname,
lastname: req.session.lastname,
roles: req.session.roles || [],
currentRole: req.session.currentRole || 'verwaltung'
},
projectNumber: '',
results: [],
totalProjectHours: 0,
hasResults: false
});
}
// Aggregation der Projektstunden pro Mitarbeiter über alle 5 Aktivitäten
const sql = `
SELECT
u.id AS user_id,
u.firstname,
u.lastname,
(
SUM(CASE WHEN te.activity1_project_number = ? THEN COALESCE(te.activity1_hours, 0) ELSE 0 END) +
SUM(CASE WHEN te.activity2_project_number = ? THEN COALESCE(te.activity2_hours, 0) ELSE 0 END) +
SUM(CASE WHEN te.activity3_project_number = ? THEN COALESCE(te.activity3_hours, 0) ELSE 0 END) +
SUM(CASE WHEN te.activity4_project_number = ? THEN COALESCE(te.activity4_hours, 0) ELSE 0 END) +
SUM(CASE WHEN te.activity5_project_number = ? THEN COALESCE(te.activity5_hours, 0) ELSE 0 END)
) AS total_hours,
ROUND(
(
SUM(CASE WHEN te.activity1_project_number = ? THEN COALESCE(te.activity1_hours, 0) ELSE 0 END) +
SUM(CASE WHEN te.activity2_project_number = ? THEN COALESCE(te.activity2_hours, 0) ELSE 0 END) +
SUM(CASE WHEN te.activity3_project_number = ? THEN COALESCE(te.activity3_hours, 0) ELSE 0 END) +
SUM(CASE WHEN te.activity4_project_number = ? THEN COALESCE(te.activity4_hours, 0) ELSE 0 END) +
SUM(CASE WHEN te.activity5_project_number = ? THEN COALESCE(te.activity5_hours, 0) ELSE 0 END)
) * 60
) AS total_minutes
FROM timesheet_entries te
JOIN users u ON u.id = te.user_id
GROUP BY u.id, u.firstname, u.lastname
HAVING total_minutes <> 0
ORDER BY u.lastname, u.firstname
`;
const params = [
projectNumber, projectNumber, projectNumber, projectNumber, projectNumber,
projectNumber, projectNumber, projectNumber, projectNumber, projectNumber
];
db.all(sql, params, (err, rows) => {
if (err) {
console.error('Fehler bei der Projektauswertung:', err);
return res.status(500).send('Fehler bei der Projektauswertung');
}
const rawResults = (rows || []).map((row) => {
const totalMinutes = row.total_minutes || 0;
const totalHours = row.total_hours || 0;
return {
userId: row.user_id,
firstname: row.firstname,
lastname: row.lastname,
totalHours,
totalMinutes,
totalHoursFormatted: minutesToHhMm(totalMinutes)
};
});
const results = rawResults;
const totalProjectMinutes = results.reduce((sum, r) => sum + (r.totalMinutes || 0), 0);
const totalProjectHours = totalProjectMinutes / 60;
const totalProjectHoursFormatted = minutesToHhMm(totalProjectMinutes);
if (results.length === 0) {
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,
totalProjectHoursFormatted,
hasResults: false,
breakdownByUser: {}
});
}
// Details pro Mitarbeiter (Aktivitäten) laden
const breakdownByUser = {};
const userIds = results.map((r) => r.userId);
let pending = userIds.length;
const detailSql = `
SELECT
date,
activity1_desc, activity1_hours, activity1_project_number,
activity2_desc, activity2_hours, activity2_project_number,
activity3_desc, activity3_hours, activity3_project_number,
activity4_desc, activity4_hours, activity4_project_number,
activity5_desc, activity5_hours, activity5_project_number
FROM timesheet_entries
WHERE user_id = ?
AND (
activity1_project_number = ? OR
activity2_project_number = ? OR
activity3_project_number = ? OR
activity4_project_number = ? OR
activity5_project_number = ?
)
ORDER BY date
`;
userIds.forEach((userId) => {
db.all(
detailSql,
[userId, projectNumber, projectNumber, projectNumber, projectNumber, projectNumber],
(detailErr, rowsDetail) => {
if (detailErr) {
console.error('Fehler beim Laden der Projekt-Aktivitäten:', detailErr);
breakdownByUser[userId] = [];
} else {
const activities = [];
(rowsDetail || []).forEach((row) => {
const date = row.date;
const pushActivity = (desc, hours) => {
if (!hours || !Number.isFinite(Number(hours))) return;
const decimal = Number(hours);
const minutes = Math.round(decimal * 60);
if (minutes === 0) return;
activities.push({
date,
description: desc || '',
hoursDecimal: decimal,
minutes,
formatted: minutesToHhMm(minutes)
});
};
if (row.activity1_project_number === projectNumber) {
pushActivity(row.activity1_desc, row.activity1_hours);
}
if (row.activity2_project_number === projectNumber) {
pushActivity(row.activity2_desc, row.activity2_hours);
}
if (row.activity3_project_number === projectNumber) {
pushActivity(row.activity3_desc, row.activity3_hours);
}
if (row.activity4_project_number === projectNumber) {
pushActivity(row.activity4_desc, row.activity4_hours);
}
if (row.activity5_project_number === projectNumber) {
pushActivity(row.activity5_desc, row.activity5_hours);
}
});
// Nach Datum sortieren
activities.sort((a, b) => {
if (a.date === b.date) return 0;
return a.date < b.date ? -1 : 1;
});
breakdownByUser[userId] = activities;
}
pending -= 1;
if (pending === 0) {
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,
totalProjectHoursFormatted,
hasResults: results.length > 0,
breakdownByUser
});
}
}
);
});
});
});
// API: Überstunden-Offset für einen User setzen (positiv/negativ)
app.put('/api/verwaltung/user/:id/overtime-offset', requireVerwaltung, (req, res) => {
const userId = req.params.id;