Add holidays routes to backend and frontend; implement holiday associations and update UI components for admin holidays management
This commit is contained in:
180
backend/src/services/HolidayService.js
Normal file
180
backend/src/services/HolidayService.js
Normal 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();
|
||||
|
||||
Reference in New Issue
Block a user