Merge #40: Ausschlussfristen nur bei Fälligkeit hervorheben
This commit was merged in pull request #47.
This commit is contained in:
@@ -24,6 +24,42 @@ export const WARTEZEIT_DRINGEND_TAGE = 21
|
|||||||
/** Wie viele Ausschlussfristen zeigt die Warnung hoechstens? */
|
/** Wie viele Ausschlussfristen zeigt die Warnung hoechstens? */
|
||||||
const AUSSCHLUSSFRISTEN_MAX = 3
|
const AUSSCHLUSSFRISTEN_MAX = 3
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wie weit im Voraus eine Ausschlussfrist die Warnung ausloest.
|
||||||
|
*
|
||||||
|
* Frueher gab es diese Grenze nicht: Der Warnblock fragte nur, OB etwas
|
||||||
|
* eine Ausschlussfrist ist, nie WANN sie faellig wird. Auf dem Dashboard
|
||||||
|
* standen deshalb dauerhaft drei rote Kaesten mit Warndreieck - fuer
|
||||||
|
* Aufwandspauschalen, die 2027, 2028 und 2029 ablaufen. Die Pflichtfristen
|
||||||
|
* werden bewusst drei Jahre im Voraus angelegt (jahreVoraus in
|
||||||
|
* pflichtfristen.ts), und alle landeten ungefiltert in der Warnung.
|
||||||
|
*
|
||||||
|
* Das ist nicht nur laestig, sondern gefaehrlich: Eine Warnung, die
|
||||||
|
* monatelang dasteht und nichts von einem will, stumpft ab. Man uebersieht
|
||||||
|
* die vierte, die etwas will - und bei § 1878 BGB ist das die
|
||||||
|
* Jahrespauschale, die ersatzlos erlischt.
|
||||||
|
*
|
||||||
|
* 90 Tage, weil die kuerzeste hier vorkommende Ausschlussfrist sechs
|
||||||
|
* Monate Zeit laesst. Drei Monate Vorlauf sind reichlich, und solange
|
||||||
|
* nichts ansteht, bleibt der Kasten leer - dann bedeutet er etwas.
|
||||||
|
*
|
||||||
|
* Verschwinden tut nichts: Was ausserhalb des Horizonts liegt, steht
|
||||||
|
* weiterhin in der Fristenliste.
|
||||||
|
*/
|
||||||
|
const AUSSCHLUSSFRIST_HORIZONT_TAGE = 90
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gehoert diese Frist in den hervorgehobenen Warnblock?
|
||||||
|
*
|
||||||
|
* Abgelaufenes immer - eine versaeumte Ausschlussfrist bleibt eine
|
||||||
|
* schlechte Nachricht, auch wenn sie lange zurueckliegt. Sonst nur, was
|
||||||
|
* innerhalb des Horizonts faellig wird.
|
||||||
|
*/
|
||||||
|
function warntJetzt(eintrag: { ausschlussfrist: boolean; tage: number }): boolean {
|
||||||
|
if (!eintrag.ausschlussfrist) return false
|
||||||
|
return eintrag.tage < 0 || eintrag.tage <= AUSSCHLUSSFRIST_HORIZONT_TAGE
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Datumsfelder ohne Uhrzeit (@db.Date) kommen als UTC-Mitternacht aus der
|
* Datumsfelder ohne Uhrzeit (@db.Date) kommen als UTC-Mitternacht aus der
|
||||||
* Datenbank. Wer sie unbesehen mit einem lokalen "heute" vergleicht,
|
* Datenbank. Wer sie unbesehen mit einem lokalen "heute" vergleicht,
|
||||||
@@ -126,7 +162,7 @@ export function fristenEinordnen(fristen: FristRoh[], heute: Date): FristenAmpel
|
|||||||
dieseWoche: eintraege.filter((e) => e.stufe === 'orange'),
|
dieseWoche: eintraege.filter((e) => e.stufe === 'orange'),
|
||||||
diesenMonat: eintraege.filter((e) => e.stufe === 'gelb'),
|
diesenMonat: eintraege.filter((e) => e.stufe === 'gelb'),
|
||||||
weitereOffen: eintraege.filter((e) => e.stufe === 'gruen').length,
|
weitereOffen: eintraege.filter((e) => e.stufe === 'gruen').length,
|
||||||
ausschlussfristen: eintraege.filter((e) => e.ausschlussfrist).slice(0, AUSSCHLUSSFRISTEN_MAX),
|
ausschlussfristen: eintraege.filter(warntJetzt).slice(0, AUSSCHLUSSFRISTEN_MAX),
|
||||||
gesamtOffen: eintraege.length,
|
gesamtOffen: eintraege.length,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+77
-1
@@ -92,9 +92,10 @@ describe('Fristen-Ampel', () => {
|
|||||||
[
|
[
|
||||||
frist({ id: 'normal', datum: dbTag('2026-08-20') }),
|
frist({ id: 'normal', datum: dbTag('2026-08-20') }),
|
||||||
frist({
|
frist({
|
||||||
|
// Weit genug weg fuer "gruen", nah genug fuer die Warnung.
|
||||||
id: 'pauschale',
|
id: 'pauschale',
|
||||||
titel: 'Aufwandspauschale 2026',
|
titel: 'Aufwandspauschale 2026',
|
||||||
datum: dbTag('2026-12-31'),
|
datum: dbTag('2026-10-15'),
|
||||||
ausschlussfrist: true,
|
ausschlussfrist: true,
|
||||||
rechtsgrundlage: '§ 1878 BGB',
|
rechtsgrundlage: '§ 1878 BGB',
|
||||||
}),
|
}),
|
||||||
@@ -103,6 +104,81 @@ describe('Fristen-Ampel', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
expect(daten.ausschlussfristen.map((e) => e.id)).toEqual(['pauschale'])
|
expect(daten.ausschlussfristen.map((e) => e.id)).toEqual(['pauschale'])
|
||||||
|
// Nicht in einem der Faelligkeitstoepfe - der Warnblock steht daneben.
|
||||||
|
expect(daten.weitereOffen).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Ausschlussfristen: nur warnen, wenn es etwas zu tun gibt', () => {
|
||||||
|
/**
|
||||||
|
* Der Anlass: Auf dem Dashboard standen dauerhaft drei rote Kaesten mit
|
||||||
|
* Warndreieck - Aufwandspauschalen mit Verfall 2027, 2028 und 2029. Die
|
||||||
|
* Pflichtfristen werden bewusst drei Jahre im Voraus angelegt, und der
|
||||||
|
* Warnblock fragte nur, OB etwas eine Ausschlussfrist ist, nie WANN sie
|
||||||
|
* faellig wird.
|
||||||
|
*
|
||||||
|
* Eine Warnung, die monatelang dasteht und nichts von einem will,
|
||||||
|
* stumpft ab. Genau davor schuetzen diese Tests.
|
||||||
|
*/
|
||||||
|
function pauschale(id: string, datum: string) {
|
||||||
|
return frist({
|
||||||
|
id,
|
||||||
|
titel: `Aufwandspauschale ${id}`,
|
||||||
|
datum: dbTag(datum),
|
||||||
|
ausschlussfrist: true,
|
||||||
|
rechtsgrundlage: '§ 1878 BGB',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
it('lässt Fristen weit in der Zukunft aus der Warnung heraus', () => {
|
||||||
|
const daten = fristenEinordnen(
|
||||||
|
[
|
||||||
|
pauschale('2027', '2027-05-06'),
|
||||||
|
pauschale('2028', '2028-05-06'),
|
||||||
|
pauschale('2029', '2029-05-06'),
|
||||||
|
],
|
||||||
|
HEUTE,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(daten.ausschlussfristen).toEqual([])
|
||||||
|
// Verschwunden sind sie deshalb nicht - sie stehen in der Liste.
|
||||||
|
expect(daten.weitereOffen).toBe(3)
|
||||||
|
expect(daten.gesamtOffen).toBe(3)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('warnt, sobald eine in den Horizont rückt', () => {
|
||||||
|
// 90 Tage nach dem 19.08.2026 ist der 17.11.2026.
|
||||||
|
const daten = fristenEinordnen([pauschale('bald', '2026-11-17')], HEUTE)
|
||||||
|
|
||||||
|
expect(daten.ausschlussfristen.map((e) => e.id)).toEqual(['bald'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('warnt einen Tag davor noch nicht', () => {
|
||||||
|
const daten = fristenEinordnen([pauschale('knapp-drueber', '2026-11-18')], HEUTE)
|
||||||
|
|
||||||
|
expect(daten.ausschlussfristen).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('warnt bei einer versäumten Frist immer, egal wie lange sie zurückliegt', () => {
|
||||||
|
// Eine verfallene Aufwandspauschale bleibt eine schlechte Nachricht.
|
||||||
|
// Sie stillschweigend aus der Warnung zu nehmen, waere das Gegenteil
|
||||||
|
// von dem, was hier gewollt ist.
|
||||||
|
const daten = fristenEinordnen([pauschale('verpasst', '2024-05-06')], HEUTE)
|
||||||
|
|
||||||
|
expect(daten.ausschlussfristen.map((e) => e.id)).toEqual(['verpasst'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('mischt Fälliges und Fernes richtig', () => {
|
||||||
|
const daten = fristenEinordnen(
|
||||||
|
[
|
||||||
|
pauschale('verpasst', '2025-05-06'),
|
||||||
|
pauschale('bald', '2026-10-01'),
|
||||||
|
pauschale('fern', '2029-05-06'),
|
||||||
|
],
|
||||||
|
HEUTE,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(daten.ausschlussfristen.map((e) => e.id)).toEqual(['verpasst', 'bald'])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('warnt bei einer Ausschlussfrist ausdrücklich vor dem ersatzlosen Verfall', () => {
|
it('warnt bei einer Ausschlussfrist ausdrücklich vor dem ersatzlosen Verfall', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user