Terminplaner: Betriebspruefung mit Abkuehlzeit fuer die Team-Mail

pruefeBetrieb (neu: termine/betrieb.js) prueft am Ende jedes Minutenjobs
Mailbudget (ab 70 %), Warteschlange (aeltester faelliger Eintrag > 10 Min
ueberfaellig, neu outbox.warteschlangeAlter) und Anmeldepause
(pausiertBis). Der gemeinsame Melder meldeVorfall schreibt je Schluessel
hoechstens stuendlich ein Warnlog und schickt die Team-Mail T20 beim
Beginn und danach fruehestens nach 6 Stunden; Lesefehler und Mailfehler
stehen nur im Log. T20 steht jetzt auch im Test der Team-Fusszeile.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-04 11:01:16 +00:00
1 parent 3a3319ec14
commit 689a21f9ac
5 files changed
+691 -4

No files matched your search

+26 -1
View File
@@ -1,5 +1,5 @@
import { config } from '../../config.js'
import { Query, createDocument, deleteDocument, getDocument, listAllDocuments, listDocuments, updateDocument } from '../appwriteAdmin.js'
import { Query, countDocuments, createDocument, deleteDocument, getDocument, listAllDocuments, listDocuments, updateDocument } from '../appwriteAdmin.js'
import { withLocks } from '../keyedLock.js'
import { sendGuestMail } from '../mailer.js'
import { renderMail } from '../mailTemplates.js'
@@ -457,6 +457,31 @@ export async function verarbeite(now) {
return { faellig: lauf.faellig, gesendet: lauf.gesendet }
}
/**
* Stand der Warteschlange fuer die Betriebspruefung und die Statusroute: nur Zahlen, nie ein Mailtext. `wartet` = alle Eintraege mit
* Status `wartet` (auch Wiederholungen, die erst spaeter faellig werden), `aeltesteMin` = ganze Minuten (abgerundet), die der aelteste
* faellige Eintrag (naechsterVersuch <= jetzt) schon ueberfaellig ist, 0 ohne faelligen Eintrag. Zwei kurze Abfragen ueber den Index
* idx_status_next, parallel: die Anzahl und der aelteste faellige Eintrag (limit 1, nur $id und naechsterVersuch). Scheitert eine,
* wirft die Funktion; der Aufrufer meldet den Lesefehler.
* @param {Date} now
* @returns {Promise<{ wartet: number|null, aeltesteMin: number }>} wartet ist null, wenn Appwrite keine Anzahl liefert
*/
export async function warteschlangeAlter(now) {
const nowMs = pruefeNow(now, 'warteschlangeAlter')
const [wartet, aelteste] = await Promise.all([
countDocuments(col(), [Query.equal('status', 'wartet')]),
listDocuments(col(), [
Query.equal('status', 'wartet'),
Query.lessThanEqual('naechsterVersuch', iso(nowMs)),
Query.orderAsc('naechsterVersuch'),
Query.limit(1),
Query.select(['$id', 'naechsterVersuch']),
]),
])
const faelligSeit = aelteste.length > 0 ? zeitMs(aelteste[0].naechsterVersuch) : Number.NaN
return { wartet, aeltesteMin: Number.isNaN(faelligSeit) ? 0 : Math.max(0, Math.floor((nowMs - faelligSeit) / MIN_MS)) }
}
/** Nur fuer Tests: wartet, bis alle laufenden Sendeversuche (auch die aus einreihen) beendet sind. */
export async function warteAufVersand() {
while (laufend.size > 0) await Promise.allSettled([...laufend])