Add timewish routes to backend and frontend; implement routing and UI components for timewish settings

This commit is contained in:
Torsten Schulz (local)
2025-10-17 23:20:13 +02:00
parent 4fe6b27b8f
commit 876c2964dd
7 changed files with 750 additions and 0 deletions

View File

@@ -0,0 +1,167 @@
const database = require('../config/database');
const { Op } = require('sequelize');
/**
* Service-Klasse für Zeitwünsche (Timewish)
* Verwaltet gewünschte Arbeitszeiten pro Wochentag und Zeitraum
*/
class TimewishService {
/**
* Holt alle Zeitwünsche eines Users
* @param {number} userId - Benutzer-ID
* @returns {Promise<Array>} Array von Zeitwünschen
*/
async getAllTimewishes(userId) {
const { Timewish } = database.getModels();
const timewishes = await Timewish.findAll({
where: {
user_id: userId
},
order: [['day', 'ASC'], ['start_date', 'DESC']],
raw: true
});
// Konvertiere DB-Format zu Frontend-Format
const dayNames = {
1: 'Montag',
2: 'Dienstag',
3: 'Mittwoch',
4: 'Donnerstag',
5: 'Freitag',
6: 'Samstag',
7: 'Sonntag'
};
const wishtypeNames = {
0: 'Kein Wunsch',
1: 'Frei',
2: 'Arbeit'
};
return timewishes.map(tw => ({
id: tw.id,
day: tw.day,
dayName: dayNames[tw.day] || `Tag ${tw.day}`,
wishtype: tw.wishtype,
wishtypeName: wishtypeNames[tw.wishtype] || 'Unbekannt',
hours: tw.hours,
startDate: tw.start_date,
endDate: tw.end_date
}));
}
/**
* Erstellt einen neuen Zeitwunsch
* @param {number} userId - Benutzer-ID
* @param {number} day - Wochentag (1=Mo, 7=So)
* @param {number} wishtype - Typ (0=Kein Wunsch, 1=Frei, 2=Arbeit)
* @param {number} hours - Stunden (nur bei wishtype=2)
* @param {string} startDate - Startdatum (YYYY-MM-DD)
* @param {string} endDate - Enddatum (YYYY-MM-DD, optional)
* @returns {Promise<Object>} Erstellter Zeitwunsch
*/
async createTimewish(userId, day, wishtype, hours, startDate, endDate) {
const { Timewish } = database.getModels();
// Validierung
if (day < 1 || day > 7) {
throw new Error('Wochentag muss zwischen 1 (Montag) und 7 (Sonntag) liegen');
}
if (wishtype < 0 || wishtype > 2) {
throw new Error('Ungültiger Wunschtyp');
}
if (wishtype === 2 && (!hours || hours < 0 || hours > 24)) {
throw new Error('Arbeitsstunden müssen zwischen 0 und 24 liegen');
}
if (!startDate) {
throw new Error('Startdatum ist erforderlich');
}
// Prüfe auf Überschneidungen
const overlapping = await Timewish.findOne({
where: {
user_id: userId,
day: day,
[Op.or]: [
// Fall 1: Neuer Eintrag hat kein Enddatum (gilt ab start_date)
endDate ? {} : {
[Op.or]: [
// Bestehender Eintrag hat kein Enddatum UND start_date <= neuer start_date
{
end_date: null,
start_date: { [Op.lte]: startDate }
},
// Bestehender Eintrag hat Enddatum UND überlappt
{
end_date: { [Op.gte]: startDate }
}
]
},
// Fall 2: Neuer Eintrag hat Enddatum
endDate ? {
[Op.or]: [
// Bestehender Eintrag hat kein Enddatum UND start_date <= neues end_date
{
end_date: null,
start_date: { [Op.lte]: endDate }
},
// Bestehender Eintrag hat Enddatum UND überlappt
{
start_date: { [Op.lte]: endDate },
end_date: { [Op.gte]: startDate }
}
]
} : {}
]
}
});
if (overlapping) {
throw new Error(`Überschneidung mit bestehendem Zeitwunsch: ${overlapping.start_date} - ${overlapping.end_date || 'unbegrenzt'}`);
}
const timewish = await Timewish.create({
user_id: userId,
day,
wishtype,
hours: wishtype === 2 ? hours : null,
start_date: startDate,
end_date: endDate || null,
version: 0
});
// Formatiere für Response
const allTimewishes = await this.getAllTimewishes(userId);
return allTimewishes.find(tw => tw.id === timewish.id);
}
/**
* Löscht einen Zeitwunsch
* @param {number} userId - Benutzer-ID
* @param {number} timewishId - Timewish-ID
* @returns {Promise<void>}
*/
async deleteTimewish(userId, timewishId) {
const { Timewish } = database.getModels();
const timewish = await Timewish.findByPk(timewishId);
if (!timewish) {
throw new Error('Zeitwunsch nicht gefunden');
}
// Prüfe Berechtigung
if (timewish.user_id !== userId) {
throw new Error('Keine Berechtigung für diesen Eintrag');
}
await timewish.destroy();
}
}
module.exports = new TimewishService();