feat(kalender): Kalender mit Urlauben und Feiertagen fuer alle

Neuer Menuepunkt "Kalender" fuer alle Rollen: genehmigte Urlaube aller
Mitarbeiter, auch in der Zukunft, dazu Feiertage. Monatsansicht (Standard)
und Zeitleiste, filterbar nach Team und Name.

- Eigene Tabelle pulse_calendar_days, gefuellt von lib/calendarSync.js im
  HRworks-Lauf (Fenster bis 400 Tage voraus). Buchungen entstehen weiter
  erst am jeweiligen Tag.
- Nur validatedOk und nur freigegebene Arten (calendar_types, Haken in den
  HRworks-Einstellungen). Krankmeldungen werden dafuer nicht abgefragt.
- GET /api/calendar fuer alle Angemeldeten, ohne Mails und Profilbilder.
- Timer laeuft zusaetzlich um 12:00 Uhr.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
schakal00783
2026-09-25 11:37:26 +02:00
co-authored by Claude Opus 5.5
parent 6ae5a01bda
commit c0eb00faee
21 changed files with 996 additions and 33 deletions
+49
View File
@@ -0,0 +1,49 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { kalenderEintrag } from '../src/lib/calendarSync.js';
import { holidayName } from '../src/lib/hrworks.js';
const tag = (extra = {}) => ({ holiday: false, holiday_name: null, absences: [], sick_leaves: [], regular_hours: 8, available_hours: 0, ...extra });
const meta = new Map([
['30_1', { type: '1', name: 'Urlaub', status: 'validatedOk' }],
['30_2', { type: '1', name: 'Urlaub', status: 'requested' }],
['30_3', { type: '3', name: 'Elternzeit', status: 'validatedOk' }],
]);
const types = ['absence:1'];
test('Kalender: nur genehmigter Urlaub der freigegebenen Arten', () => {
assert.deepEqual(kalenderEintrag(tag({ absences: ['30_1'] }), meta, types, { 'absence:1': 'Urlaub' }), {
kind: 'vacation',
label: 'Urlaub',
half: false,
});
assert.equal(kalenderEintrag(tag({ absences: ['30_2'] }), meta, types), null, 'nur beantragt');
assert.equal(kalenderEintrag(tag({ absences: ['30_3'] }), meta, types), null, 'Art nicht freigegeben');
assert.equal(kalenderEintrag(tag({ absences: ['99_9'] }), meta, types), null, 'unbekannt');
assert.equal(kalenderEintrag(tag(), meta, types), null);
});
test('Kalender: Feiertag vor Urlaub, Wochenende ohne Urlaub, halbe Tage', () => {
assert.deepEqual(kalenderEintrag(tag({ holiday: true, holiday_name: 'Tag der Deutschen Einheit', absences: ['30_1'] }), meta, types), {
kind: 'holiday',
label: 'Tag der Deutschen Einheit',
half: false,
});
assert.equal(kalenderEintrag(tag({ holiday: true }), meta, types).label, 'Feiertag');
assert.equal(kalenderEintrag(tag({ absences: ['30_1'], regular_hours: 0 }), meta, types), null);
assert.equal(kalenderEintrag(tag({ absences: ['30_1'], available_hours: 4 }), meta, types).half, true);
});
test('Kalender: eine Krankmeldung verraet sich nicht', () => {
// Urlaub bleibt stehen, auch wenn an dem Tag eine Krankmeldung haengt ...
assert.equal(kalenderEintrag(tag({ absences: ['30_1'], sick_leaves: ['31_9'] }), meta, types).kind, 'vacation');
// ... und Krankheit allein erscheint nie.
assert.equal(kalenderEintrag(tag({ sick_leaves: ['31_9'] }), meta, types), null);
});
test('Feiertagsname aus unterschiedlichen Formen', () => {
assert.equal(holidayName([{ name: 'Neujahr' }]), 'Neujahr');
assert.equal(holidayName(['Karfreitag']), 'Karfreitag');
assert.equal(holidayName([{}]), null);
assert.equal(holidayName(undefined), null);
});