Add holidays routes to backend and frontend; implement holiday associations and update UI components for admin holidays management

This commit is contained in:
Torsten Schulz (local)
2025-10-17 22:35:31 +02:00
parent 6a0b23e694
commit 67ddf812cd
15 changed files with 1058 additions and 2 deletions

View File

@@ -0,0 +1,180 @@
const database = require('../config/database');
const { Op } = require('sequelize');
/**
* Service-Klasse für Feiertage
* Verwaltet bundesweite und regionale Feiertage
*/
class HolidayService {
/**
* Holt alle verfügbaren Bundesländer
* @returns {Promise<Array>} Array von States
*/
async getAllStates() {
const { State } = database.getModels();
const states = await State.findAll({
order: [['state_name', 'ASC']],
raw: true
});
return states.map(s => ({
id: s.id,
name: s.state_name
}));
}
/**
* Holt alle Feiertage, aufgeteilt in zukünftige und vergangene
* Vergangene: Nur aktuelles Jahr und maximal 3 Monate zurück
* @returns {Promise<Object>} { future: [], past: [] }
*/
async getAllHolidays() {
const { Holiday, State, HolidayState } = database.getModels();
const today = new Date();
const currentYear = today.getFullYear();
const todayStr = `${currentYear}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
// Berechne Startdatum für vergangene Feiertage
const threeMonthsAgo = new Date(today);
threeMonthsAgo.setMonth(today.getMonth() - 3);
const yearStart = new Date(currentYear, 0, 1);
const pastStartDate = yearStart < threeMonthsAgo ? yearStart : threeMonthsAgo;
const pastStartDateStr = `${pastStartDate.getFullYear()}-${String(pastStartDate.getMonth() + 1).padStart(2, '0')}-${String(pastStartDate.getDate()).padStart(2, '0')}`;
// Zukünftige Feiertage (inkl. heute) mit States
const futureHolidays = await Holiday.findAll({
where: {
date: {
[Op.gte]: todayStr
}
},
include: [
{
model: State,
as: 'states',
attributes: ['id', 'state_name'],
through: { attributes: [] } // Keine Attribute der Junction Table
}
],
order: [['date', 'ASC']]
});
// Vergangene Feiertage (nur aktuelles Jahr / letzte 3 Monate) mit States
const pastHolidays = await Holiday.findAll({
where: {
date: {
[Op.gte]: pastStartDateStr,
[Op.lt]: todayStr
}
},
include: [
{
model: State,
as: 'states',
attributes: ['id', 'state_name'],
through: { attributes: [] }
}
],
order: [['date', 'DESC']]
});
return {
future: futureHolidays.map(h => this._formatHoliday(h)),
past: pastHolidays.map(h => this._formatHoliday(h))
};
}
/**
* Erstellt einen neuen Feiertag
* @param {string} date - Datum (YYYY-MM-DD)
* @param {number} hours - Freie Stunden (Standard: 8)
* @param {string} description - Beschreibung
* @param {Array<number>} stateIds - Array von State-IDs (leer = bundesweit)
* @returns {Promise<Object>} Erstellter Feiertag
*/
async createHoliday(date, hours, description, stateIds = []) {
const { Holiday, State, HolidayState } = database.getModels();
// Prüfe ob schon ein Feiertag an diesem Datum existiert
const existing = await Holiday.findOne({
where: { date }
});
if (existing) {
throw new Error('An diesem Datum existiert bereits ein Feiertag');
}
const holiday = await Holiday.create({
date,
hours: hours || 8,
description,
version: 0
});
// Verknüpfe mit States (falls angegeben)
if (stateIds && stateIds.length > 0) {
for (const stateId of stateIds) {
await HolidayState.create({
holiday_id: holiday.id,
state_id: stateId
});
}
}
// Lade Holiday mit States neu
const holidayWithStates = await Holiday.findByPk(holiday.id, {
include: [
{
model: State,
as: 'states',
attributes: ['id', 'state_name'],
through: { attributes: [] }
}
]
});
return this._formatHoliday(holidayWithStates);
}
/**
* Löscht einen Feiertag
* @param {number} id - Holiday-ID
* @returns {Promise<void>}
*/
async deleteHoliday(id) {
const { Holiday } = database.getModels();
const holiday = await Holiday.findByPk(id);
if (!holiday) {
throw new Error('Feiertag nicht gefunden');
}
await holiday.destroy();
}
/**
* Formatiert einen Feiertag für die API
*/
_formatHoliday(holiday) {
// Holiday kann ein Plain Object (raw: true) oder eine Sequelize Instance sein
const states = holiday.states || [];
const stateNames = Array.isArray(states)
? states.map(s => s.state_name || s.name).filter(Boolean)
: [];
return {
id: holiday.id,
date: holiday.date,
hours: holiday.hours,
description: holiday.description,
states: stateNames,
isFederal: stateNames.length === 0 // Kein State = Bundesfeiertag
};
}
}
module.exports = new HolidayService();