Add workdays routes to backend and frontend; update routing and UI components for workdays tracking
This commit is contained in:
35
backend/src/controllers/WorkdaysController.js
Normal file
35
backend/src/controllers/WorkdaysController.js
Normal file
@@ -0,0 +1,35 @@
|
||||
const WorkdaysService = require('../services/WorkdaysService');
|
||||
|
||||
/**
|
||||
* Controller für Arbeitstage-Statistiken
|
||||
* Verarbeitet HTTP-Requests und delegiert an WorkdaysService
|
||||
*/
|
||||
class WorkdaysController {
|
||||
/**
|
||||
* Holt Arbeitstage-Statistiken für ein Jahr
|
||||
*/
|
||||
async getStatistics(req, res) {
|
||||
try {
|
||||
const userId = req.user?.id || 1;
|
||||
const year = parseInt(req.query.year) || new Date().getFullYear();
|
||||
|
||||
if (isNaN(year) || year < 1900 || year > 2100) {
|
||||
return res.status(400).json({
|
||||
message: 'Ungültiges Jahr'
|
||||
});
|
||||
}
|
||||
|
||||
const statistics = await WorkdaysService.getWorkdaysStatistics(userId, year);
|
||||
res.json(statistics);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen der Arbeitstage-Statistiken:', error);
|
||||
res.status(500).json({
|
||||
message: 'Fehler beim Abrufen der Arbeitstage-Statistiken',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new WorkdaysController();
|
||||
|
||||
@@ -78,6 +78,10 @@ app.use('/api/vacation', authenticateToken, vacationRouter);
|
||||
const sickRouter = require('./routes/sick');
|
||||
app.use('/api/sick', authenticateToken, sickRouter);
|
||||
|
||||
// Workdays routes (geschützt) - MIT ID-Hashing
|
||||
const workdaysRouter = require('./routes/workdays');
|
||||
app.use('/api/workdays', authenticateToken, workdaysRouter);
|
||||
|
||||
// Error handling middleware
|
||||
app.use((err, req, res, next) => {
|
||||
console.error(err.stack);
|
||||
|
||||
13
backend/src/routes/workdays.js
Normal file
13
backend/src/routes/workdays.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const WorkdaysController = require('../controllers/WorkdaysController');
|
||||
|
||||
/**
|
||||
* Routen für Arbeitstage-Statistiken
|
||||
*/
|
||||
|
||||
// GET /api/workdays?year=2025 - Statistiken für ein Jahr abrufen
|
||||
router.get('/', WorkdaysController.getStatistics.bind(WorkdaysController));
|
||||
|
||||
module.exports = router;
|
||||
|
||||
182
backend/src/services/WorkdaysService.js
Normal file
182
backend/src/services/WorkdaysService.js
Normal file
@@ -0,0 +1,182 @@
|
||||
const database = require('../config/database');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
/**
|
||||
* Service-Klasse für Arbeitstage-Statistiken
|
||||
* Berechnet verschiedene Tage-Typen für ein Jahr
|
||||
*/
|
||||
class WorkdaysService {
|
||||
constructor() {
|
||||
this.defaultUserId = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet Arbeitstage-Statistiken für ein Jahr
|
||||
* @param {number} userId - Benutzer-ID
|
||||
* @param {number} year - Jahr
|
||||
* @returns {Promise<Object>} Statistiken
|
||||
*/
|
||||
async getWorkdaysStatistics(userId, year) {
|
||||
const { Holiday, Sick, Vacation } = database.getModels();
|
||||
const sequelize = database.sequelize;
|
||||
|
||||
// Berechne Anzahl Werktage (Mo-Fr) im Jahr
|
||||
const workdays = this._countWorkdays(year);
|
||||
|
||||
// Hole alle Feiertage für das Jahr
|
||||
const holidays = await Holiday.findAll({
|
||||
where: {
|
||||
date: {
|
||||
[Op.gte]: `${year}-01-01`,
|
||||
[Op.lte]: `${year}-12-31`
|
||||
}
|
||||
},
|
||||
raw: true
|
||||
});
|
||||
|
||||
// Zähle nur Feiertage die auf Werktage fallen
|
||||
const holidayCount = holidays.filter(h => {
|
||||
const holidayDate = new Date(h.date + 'T00:00:00');
|
||||
const dayOfWeek = holidayDate.getDay();
|
||||
return dayOfWeek >= 1 && dayOfWeek <= 5; // Mo-Fr
|
||||
}).length;
|
||||
|
||||
// Hole alle Krankheitstage für das Jahr
|
||||
const sickEntries = await Sick.findAll({
|
||||
where: {
|
||||
user_id: userId,
|
||||
[Op.or]: [
|
||||
{
|
||||
first_day: {
|
||||
[Op.between]: [`${year}-01-01`, `${year}-12-31`]
|
||||
}
|
||||
},
|
||||
{
|
||||
last_day: {
|
||||
[Op.between]: [`${year}-01-01`, `${year}-12-31`]
|
||||
}
|
||||
},
|
||||
{
|
||||
[Op.and]: [
|
||||
{ first_day: { [Op.lte]: `${year}-01-01` } },
|
||||
{ last_day: { [Op.gte]: `${year}-12-31` } }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
raw: true
|
||||
});
|
||||
|
||||
// Zähle Krankheitstage (nur Werktage)
|
||||
let sickDays = 0;
|
||||
sickEntries.forEach(sick => {
|
||||
const start = new Date(Math.max(new Date(sick.first_day + 'T00:00:00'), new Date(`${year}-01-01T00:00:00`)));
|
||||
const end = new Date(Math.min(new Date(sick.last_day + 'T00:00:00'), new Date(`${year}-12-31T00:00:00`)));
|
||||
|
||||
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
|
||||
const dayOfWeek = d.getDay();
|
||||
if (dayOfWeek >= 1 && dayOfWeek <= 5) { // Mo-Fr
|
||||
sickDays++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Hole alle Urlaubstage für das Jahr
|
||||
const vacationEntries = await Vacation.findAll({
|
||||
where: {
|
||||
user_id: userId,
|
||||
[Op.or]: [
|
||||
{
|
||||
first_day: {
|
||||
[Op.between]: [`${year}-01-01`, `${year}-12-31`]
|
||||
}
|
||||
},
|
||||
{
|
||||
last_day: {
|
||||
[Op.between]: [`${year}-01-01`, `${year}-12-31`]
|
||||
}
|
||||
},
|
||||
{
|
||||
[Op.and]: [
|
||||
{ first_day: { [Op.lte]: `${year}-01-01` } },
|
||||
{ last_day: { [Op.gte]: `${year}-12-31` } }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
raw: true
|
||||
});
|
||||
|
||||
// Zähle Urlaubstage (nur Werktage, berücksichtige halbe Tage)
|
||||
let vacationDays = 0;
|
||||
vacationEntries.forEach(vac => {
|
||||
const start = new Date(Math.max(new Date(vac.first_day + 'T00:00:00'), new Date(`${year}-01-01T00:00:00`)));
|
||||
const end = new Date(Math.min(new Date(vac.last_day + 'T00:00:00'), new Date(`${year}-12-31T00:00:00`)));
|
||||
|
||||
const isHalfDay = vac.half_day === 1;
|
||||
|
||||
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
|
||||
const dayOfWeek = d.getDay();
|
||||
if (dayOfWeek >= 1 && dayOfWeek <= 5) { // Mo-Fr
|
||||
vacationDays += isHalfDay ? 0.5 : 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Zähle gearbeitete Tage (Tage mit worklog-Einträgen)
|
||||
const workedDaysQuery = `
|
||||
SELECT COUNT(DISTINCT DATE(tstamp)) as count
|
||||
FROM worklog
|
||||
WHERE user_id = :userId
|
||||
AND DATE(tstamp) BETWEEN :startDate AND :endDate
|
||||
AND state LIKE '%start work%'
|
||||
`;
|
||||
|
||||
const workedDaysResult = await sequelize.query(workedDaysQuery, {
|
||||
replacements: {
|
||||
userId,
|
||||
startDate: `${year}-01-01`,
|
||||
endDate: `${year}-12-31`
|
||||
},
|
||||
type: sequelize.QueryTypes.SELECT
|
||||
});
|
||||
|
||||
const workedDays = workedDaysResult[0]?.count || 0;
|
||||
|
||||
// Berechne Prozentsatz Krankheitstage
|
||||
const sickPercentage = workdays > 0 ? Math.round((sickDays / workdays) * 100) : 0;
|
||||
|
||||
return {
|
||||
year,
|
||||
workdays,
|
||||
holidays: holidayCount,
|
||||
sickDays,
|
||||
sickPercentage,
|
||||
vacationDays,
|
||||
workedDays
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Zählt Werktage (Mo-Fr) in einem Jahr
|
||||
* @param {number} year - Jahr
|
||||
* @returns {number} Anzahl Werktage
|
||||
*/
|
||||
_countWorkdays(year) {
|
||||
let count = 0;
|
||||
const start = new Date(year, 0, 1);
|
||||
const end = new Date(year, 11, 31);
|
||||
|
||||
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
|
||||
const dayOfWeek = d.getDay();
|
||||
if (dayOfWeek >= 1 && dayOfWeek <= 5) { // Mo-Fr
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new WorkdaysService();
|
||||
|
||||
Reference in New Issue
Block a user