Ausschlussfristen nur hervorheben, wenn sie etwas von einem wollen

Auf dem Dashboard standen dauerhaft drei rote Kaesten mit Warndreieck und
Knopf "Frist jetzt bearbeiten": Aufwandspauschalen mit Verfall 2027, 2028
und 2029.

Alle anderen Bloecke des Dashboards filtern nach Dringlichkeit. Dieser
fragte nur, OB etwas eine Ausschlussfrist ist, und nie, WANN sie faellig
wird - eine Frist im Mai 2029 landete damit in derselben Aufmachung wie
eine, die morgen ablaeuft.

Die Daten sind richtig: Pflichtfristen werden bewusst drei Jahre im Voraus
angelegt. Sie gehoeren nur nicht in die Warnung, sondern in die Liste.

Neu ist ein Horizont von 90 Tagen. Er ist grosszuegig gewaehlt, weil die
kuerzeste hier vorkommende Ausschlussfrist sechs Monate laesst. Abgelaufene
warnen weiterhin immer - eine versaeumte Aufwandspauschale bleibt eine
schlechte Nachricht, auch wenn sie lange zurueckliegt.

Der Grund ist nicht Bequemlichkeit: 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.

Ein bestehender Test behauptete "unabhaengig vom Faelligkeitstopf" und
belegte das mit einer Frist 134 Tage in der Zukunft. Die Aussage stimmt
weiterhin, das Beispiel nicht mehr - es liegt jetzt innerhalb des
Horizonts, aber ausserhalb aller Faelligkeitstoepfe.

Gegen drei Mutationen geprueft: Filter entfernt, Abgelaufenes
ausgeschlossen, Horizont um einen Tag verschoben - jede wird rot.

Refs #40
This commit is contained in:
Florian Wilde
2026-09-01 15:50:17 +02:00
parent 3525777fb4
commit b3823094d6
2 changed files with 114 additions and 2 deletions
+37 -1
View File
@@ -24,6 +24,42 @@ export const WARTEZEIT_DRINGEND_TAGE = 21
/** Wie viele Ausschlussfristen zeigt die Warnung hoechstens? */
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
* 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'),
diesenMonat: eintraege.filter((e) => e.stufe === 'gelb'),
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,
}
}
+77 -1
View File
@@ -92,9 +92,10 @@ describe('Fristen-Ampel', () => {
[
frist({ id: 'normal', datum: dbTag('2026-08-20') }),
frist({
// Weit genug weg fuer "gruen", nah genug fuer die Warnung.
id: 'pauschale',
titel: 'Aufwandspauschale 2026',
datum: dbTag('2026-12-31'),
datum: dbTag('2026-10-15'),
ausschlussfrist: true,
rechtsgrundlage: '§ 1878 BGB',
}),
@@ -103,6 +104,81 @@ describe('Fristen-Ampel', () => {
)
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', () => {