Terminplaner: Status-Route fuer das Team

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-04 11:24:02 +00:00
1 parent 4601c5ef3b
commit 39225594e5
4 files changed
+477 -1

No files matched your search

+3
View File
@@ -8,6 +8,7 @@ import { takeoverAdminRouter } from '../takeoverCredits.js'
import customersRoutes from './customers.js'
import filesRoutes from './files.js'
import previewRoutes from './preview.js'
import termineTeamRoutes from './termine.js'
// Team-Eingang fuer das Ticketsystem: alle Mitarbeiter, Appwrite-JWT (Spec 2026-10-01 Admin-Panel ins Ticketsystem)
const router = Router()
@@ -22,5 +23,7 @@ router.use('/takeover-credits', takeoverAdminRouter)
router.use('/offers', offersAdminRouter)
router.use('/storage-tasks', storageTasksAdminRouter)
router.use('/funktionen', funktionenAdminRouter)
// Terminplaner-Betrieb (Status fuer Budget, Warteschlange, Anmeldepause, Tagesjob)
router.use('/termine', termineTeamRoutes)
export default router
+24
View File
@@ -0,0 +1,24 @@
import { Router } from 'express'
import { statusDaten } from '../../services/termine/betrieb.js'
/**
* Terminplaner fuers Team (Ticketsystem). Eingehaengt in routes/team/index.js unter /api/team/termine, dort hinter requireStaffJwt:
* ohne Mitarbeiter-JWT kommt niemand hierher, ein Kunden-Cookie des Portals zaehlt nicht.
*/
const router = Router()
/**
* Betriebsstand: Mailbudget, Warteschlange, Anmeldepause, letzter Tagesjob, Zahl der aktiven Betriebe (nur Zaehlungen, nie Gast-Daten).
* Lesefehler ergeben null-Werte und ein Log (statusDaten), nie einen 500; nie zwischenspeichern.
*/
router.get('/status', async (_req, res, next) => {
try {
const daten = await statusDaten(new Date())
res.set('Cache-Control', 'no-store')
res.json(daten)
} catch (err) {
next(err)
}
})
export default router
+49 -1
View File
@@ -1,7 +1,7 @@
import { config } from '../../config.js'
import { Query, countDocuments } from '../appwriteAdmin.js'
import { sendAdminNotice } from '../mailer.js'
import { mailBudget, pausiertBis, warteschlangeAlter } from './outbox.js'
import { mailBudget, pausiertBis, verworfeneG01, warteschlangeAlter } from './outbox.js'
import { pruefeNow, pruefeText } from './util.js'
import { datumKurz, uhrzeit } from './zeit.js'
@@ -31,6 +31,9 @@ import { datumKurz, uhrzeit } from './zeit.js'
*
* Lesefehler (Appwrite) ergeben keinen Vorfall und keine Mail, nur ein Log (ebenfalls hoechstens stuendlich je Pruefung).
* `now` ist immer ein Parameter (feste Uhr in Tests).
*
* Statusroute (statusDaten, GET /api/team/termine/status): derselbe Stand auf Abruf fuers Team, ohne Vorfall, Mail oder
* Abkuehlzeit; Lesefehler ergeben null-Werte und ein Log, nie einen Fehler.
*/
const LOG = '[termine-betrieb]'
@@ -253,6 +256,51 @@ export async function meldeTagesjob(ergebnis, now) {
return gemeldet
}
// ---------------------------------------------------------------- Statusroute
/**
* Liest einen Wert fuer die Statusroute: ein Lesefehler ergibt `ersatz` und ein Log (hoechstens stuendlich je Wert, eigener
* Merkzettel `status-<wert>`), nie einen Fehler.
*/
async function leseOderErsatz(wert, nowMs, lese, ersatz) {
try {
return await lese()
} catch (err) {
meldeFehler(`status-${wert}`, err, nowMs)
return ersatz
}
}
/**
* Stand des Terminplaner-Betriebs fuer die Team-Route GET /api/team/termine/status: nur Zahlen, Wahrheitswerte, Zeitpunkte und
* Funktions-IDs, nie eine Adresse oder ein Name eines Gastes.
* budget mailBudget(now) plus verworfeneG01 (veraltete G01, die dieser Prozess seit dem Start verworfen hat)
* queue { wartet, aeltesteMin } wie warteschlangeAlter; bei einem Lesefehler beide null
* pause { aktiv, bis }: aktiv, solange das Ende der Anmeldepause nach `now` liegt, dann ist bis dessen Zeitpunkt (ISO), sonst null
* letzterTagesjob null (seit dem Start lief noch kein Tagesjob) oder { at (ISO), fehler, gedeckelt, geloescht }
* aktiveBetriebe Zahl der aktiven Termine-Dokumente (zaehleAktiveBetriebe); null bei einem Lesefehler
* Wirft nie (ausser mit TypeError bei ungueltigem `now`, wie alle Termine-Bausteine): was nicht lesbar war, ist null und steht im Log.
* @param {Date} now
* @returns {Promise<{ budget: object, queue: { wartet: number|null, aeltesteMin: number|null }, pause: { aktiv: boolean, bis: string|null },
* letzterTagesjob: { at: string, fehler: number, gedeckelt: number, geloescht: number }|null, aktiveBetriebe: number|null }>}
*/
export async function statusDaten(now) {
const nowMs = pruefeNow(now, 'statusDaten')
const [queue, aktiveBetriebe] = await Promise.all([
leseOderErsatz('queue', nowMs, () => warteschlangeAlter(now), { wartet: null, aeltesteMin: null }),
leseOderErsatz('betriebe', nowMs, zaehleAktiveBetriebe, null),
])
const bis = pausiertBis()
const pauseAktiv = bis !== null && bis.getTime() > nowMs
return {
budget: { ...mailBudget(now), verworfeneG01: verworfeneG01() },
queue: { wartet: queue.wartet, aeltesteMin: queue.aeltesteMin },
pause: { aktiv: pauseAktiv, bis: pauseAktiv ? bis.toISOString() : null },
letzterTagesjob: letzterTagesjob(),
aktiveBetriebe,
}
}
/** Nur fuer Tests: den gemerkten Zustand (Zeit der letzten Warnung und Mail je Schluessel, letzter Tagesjob, Deckel-Folge) zuruecksetzen. */
export function resetBetriebForTests() {
gemerkt.clear()