Terminplaner: Absagen, Verschieben, Widerruf durch Gaeste
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1289b5697b
commit
2d9b75b3bf
5 files changed
+1682
-24
No files matched your search
@@ -0,0 +1,85 @@
|
||||
import { pruefeNow, pruefeText } from './util.js'
|
||||
|
||||
/**
|
||||
* Grenzen des Terminplaners (Spezifikation 2026-10-02-terminplaner-design.md, Abschnitt 8): Zaehler im Speicher dieses
|
||||
* Prozesses, je Schluessel ein festes Fenster ab dem ersten gezaehlten Ereignis. Die Schluessel enthalten Adressen oder
|
||||
* IPs und verlassen den Speicher nie (kein Log, keine Datenbank). Hoechstens 10 000 Schluessel: ist die Map voll,
|
||||
* faellt der aelteste (zuerst begonnene) Zaehler weg; ein abgelaufenes Fenster wird beim naechsten Zaehlen neu begonnen.
|
||||
* Ein Neustart des Prozesses leert alles (die Grenzen sind Missbrauchsschutz, kein Buchungsrecht).
|
||||
*
|
||||
* Allgemein: zaehle / zaehleAlle (true = Ereignis erlaubt und gezaehlt, false = Grenze erreicht, nichts gezaehlt).
|
||||
* Mails des Widerrufs (Aufgabe 13): darfG08 und darfK21NichtZugeordnet. Die Grenzen je IP, siteKey und G01 ergaenzt
|
||||
* Aufgabe 16 in diesem Modul.
|
||||
*/
|
||||
|
||||
const MAX_SCHLUESSEL = 10000
|
||||
const STUNDE_MS = 60 * 60 * 1000
|
||||
const TAG_MS = 24 * STUNDE_MS
|
||||
|
||||
/** G08 (Eingangsbestaetigung des Widerrufs): je Empfaengeradresse und Tag, je Funktion und Stunde. */
|
||||
const G08_JE_ADRESSE = 3
|
||||
const G08_JE_FUNKTION = 20
|
||||
/** K21 "nicht zugeordnet": je Funktion und Stunde. */
|
||||
const K21_NICHT_ZUGEORDNET_JE_FUNKTION = 10
|
||||
|
||||
/** schluessel -> { start (ms), n }; die Einfuegereihenfolge ist die Reihenfolge der Fensterbeginne. */
|
||||
const zaehler = new Map()
|
||||
|
||||
/** Der Zaehler des Schluessels, wenn sein Fenster zum Zeitpunkt nowMs noch laeuft; sonst undefined. */
|
||||
function laufend(schluessel, fensterMs, nowMs) {
|
||||
const eintrag = zaehler.get(schluessel)
|
||||
return eintrag && nowMs < eintrag.start + fensterMs ? eintrag : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Zaehlt ein Ereignis je Regel, aber nur, wenn keine Regel ihre Grenze erreicht hat (alle oder keine).
|
||||
* @param {{ schluessel: string, max: number, fensterMs: number }[]} regeln
|
||||
* @param {Date} now
|
||||
* @returns {boolean} true = erlaubt (gezaehlt), false = mindestens eine Grenze erreicht
|
||||
*/
|
||||
export function zaehleAlle(regeln, now) {
|
||||
const nowMs = pruefeNow(now, 'zaehleAlle')
|
||||
if (!Array.isArray(regeln) || regeln.length === 0) throw new TypeError('zaehleAlle: regeln fehlen')
|
||||
for (const { schluessel, max, fensterMs } of regeln) {
|
||||
pruefeText(schluessel, 'schluessel')
|
||||
if (!Number.isInteger(max) || max < 0 || !(fensterMs > 0)) throw new TypeError('zaehleAlle: max oder fensterMs ungueltig')
|
||||
if ((laufend(schluessel, fensterMs, nowMs)?.n ?? 0) >= max) return false
|
||||
}
|
||||
for (const { schluessel, fensterMs } of regeln) {
|
||||
const eintrag = laufend(schluessel, fensterMs, nowMs)
|
||||
if (eintrag) {
|
||||
eintrag.n += 1
|
||||
continue
|
||||
}
|
||||
zaehler.delete(schluessel) // neu einfuegen = ans Ende der Reihenfolge
|
||||
if (zaehler.size >= MAX_SCHLUESSEL) zaehler.delete(zaehler.keys().next().value)
|
||||
zaehler.set(schluessel, { start: nowMs, n: 1 })
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** Ein Ereignis zaehlen: hoechstens max je Fenster. => true = erlaubt (gezaehlt), false = Grenze erreicht. */
|
||||
export function zaehle(schluessel, max, fensterMs, now) {
|
||||
return zaehleAlle([{ schluessel, max, fensterMs }], now)
|
||||
}
|
||||
|
||||
/** Darf eine G08 an diese (normalisierte) Adresse gehen? Hoechstens 3 je Adresse und Tag, 20 je Funktion und Stunde. */
|
||||
export function darfG08(featureId, adresse, now) {
|
||||
return zaehleAlle(
|
||||
[
|
||||
{ schluessel: `g08-adresse:${adresse}`, max: G08_JE_ADRESSE, fensterMs: TAG_MS },
|
||||
{ schluessel: `g08-funktion:${featureId}`, max: G08_JE_FUNKTION, fensterMs: STUNDE_MS },
|
||||
],
|
||||
now,
|
||||
)
|
||||
}
|
||||
|
||||
/** Darf eine K21 "nicht zugeordnet" an den Betrieb gehen? Hoechstens 10 je Funktion und Stunde. */
|
||||
export function darfK21NichtZugeordnet(featureId, now) {
|
||||
return zaehle(`k21-nicht-zugeordnet:${featureId}`, K21_NICHT_ZUGEORDNET_JE_FUNKTION, STUNDE_MS, now)
|
||||
}
|
||||
|
||||
/** Leert alle Zaehler (Tests). */
|
||||
export function resetTermineLimitsForTests() {
|
||||
zaehler.clear()
|
||||
}
|
||||
Reference in new issue
Block a user