import { gleicheZeitenAboAb } from './aboAbgleich.js' import { erinnereFeiertage } from './erinnerung.js' /** * Hintergrund-Jobs von Öffnungszeiten live (Spezifikation 2026-10-04-oeffnungszeiten-live-design.md, Abschnitte 5 und 7). * server/index.js ruft sie mit new Date() auf; `now` ist hier immer ein Parameter. * * zeitenAboJob täglich (240 s nach dem Start beginnend) Abo-Abgleich (aboAbgleich.js): Abo-Ende erkennen, nach 30 Tagen * abschalten, bei Rückkehr wieder einschalten, nach weiteren 30 Tagen * löschen. Eigener Tagesjob, nicht an den Schalter ZEITEN_ENABLED * gekoppelt (wie beim Terminplaner): ein beendetes Abo muss auch bei * Schalter off zum Abschalten und zur Löschung führen. Ohne Zeiten- * Dokumente kostet er eine Abfrage. * zeitenErinnerungsJob stündlich (300 s nach dem Start beginnend) Feiertags-Erinnerung (erinnerung.js): nur bei Schalter pilot/on, * nur 08:00 bis 20:00 Berliner Zeit, Merker älter als 30 Tage löschen * * Gemeinsame Regeln: * - Keine überlappenden Läufe, je Job: läuft ein Job noch, kehrt der nächste Takt sofort mit { uebersprungen: true } zurück (einmal * im Log je Überlappung). Die Sperre ist ein Merker im Speicher des Prozesses (ein Server, ein Prozess); die Jobs sperren einander nicht. * - Ein Job wirft nie: ein unerwarteter Fehler (etwa ein ungültiges `now`) steht im Log, das Ergebnis ist { fehler: 1 }. Die Bausteine * melden einzelne Fehler selbst im Ergebnis (`fehler`). * - Das Log nennt weder Namen noch Adressen von Kunden. */ const laufend = new Set() const ueberlappGemeldet = new Set() const fehlerText = (err) => `${err?.name ?? 'Fehler'} ${String(err?.message ?? '').slice(0, 200)}` /** Führt arbeit() aus, sofern dieser Job nicht schon läuft; fängt jeden Fehler. */ async function fuehreAus(job, arbeit) { if (laufend.has(job)) { if (!ueberlappGemeldet.has(job)) { ueberlappGemeldet.add(job) console.warn(`[zeiten-jobs] ${job}: der vorige Lauf ist noch nicht fertig, dieser Takt wird übersprungen`) } return { uebersprungen: true } } laufend.add(job) try { return await arbeit() } catch (err) { console.error(`[zeiten-jobs] ${job} fehlgeschlagen: ${fehlerText(err)}`) return { fehler: 1 } } finally { laufend.delete(job) ueberlappGemeldet.delete(job) } } /** Täglicher Abo-Abgleich. => Ergebnis von gleicheZeitenAboAb | { uebersprungen: true } | { fehler: 1 } */ export const zeitenAboJob = (now) => fuehreAus('zeitenAboJob', () => gleicheZeitenAboAb(now)) /** Stündliche Feiertags-Erinnerung. => Ergebnis von erinnereFeiertage | { uebersprungen: true } | { fehler: 1 } */ export const zeitenErinnerungsJob = (now) => fuehreAus('zeitenErinnerungsJob', () => erinnereFeiertage(now)) /** Nur für Tests: alle Sperren freigeben. */ export function resetZeitenJobsForTests() { laufend.clear() ueberlappGemeldet.clear() }