Terminplaner-Banner und Meldung nach Abschaltgrund mit Löschdatum

GET /api/termine/einstellungen liefert bei abgeschalteter Funktion
abgeschaltet { grund, seit, loeschungAm }. Die 409-Meldung bei
Schreibversuchen (Code abgeschaltet) und der Banner im Portal
unterscheiden Wartungs-Abo beendet, Abo vor der Einrichtung beendet und
die übrigen Gründe und nennen das Löschdatum, wenn es eines gibt.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-04 18:41:03 +00:00
1 parent d66dd60a76
commit 36b36bcc4d
7 files changed
+268 -26

No files matched your search

+67
View File
@@ -87,6 +87,7 @@ function neueFixture() {
fx.daten = (state = fx.projekte[0].features.termine.state) => ({
state, aktivSeit: '2026-10-01T09:00:00.000Z', einstellung: fx.einstellung, version: fx.einstellung ? 3 : 0, vorlagen: fx.vorlagen, feeds: {}, siteKey: KEY,
einbau: { snippet: '' }, anfragenOffen: fx.buchungen.filter((b) => b.status === 'angefragt').length || fx.anfragenOffen, fehlt: fx.fehlt,
abgeschaltet: state === 'disabled' ? fx.abgeschaltet ?? { grund: null, seit: null, loeschungAm: null } : null,
})
return fx
}
@@ -735,6 +736,49 @@ async function pruefungen() {
})(),
})
// ------------------------------------------------------------ Abgeschaltet nach Grund (E11)
await check('fx_abgeschaltet_abo_ende', async (page) => {
await oeffne(page, '#termine')
await warteAnsicht(page)
const hinweis = page.locator('.tm-box-warn')
await hinweis.waitFor()
const text = await textVon(hinweis)
assert(text.includes('Wartungs-Abo') && text.includes('04.11.2026'), `Banner nennt Abo und Löschdatum: ${text}`)
assert(text.includes('nur noch ansehen'), `Banner: ${text}`)
eq(await page.locator('.tm-box-warn a').count(), 1, 'genau ein Link im Banner')
eq(await page.locator('.tm-box-warn a').innerText(), 'exportieren', 'Linktext')
eq(await page.locator('.tm-box-warn a').getAttribute('href'), '/api/termine/export.csv?projectId=p1', 'Export-Link')
eq(await page.locator('[data-werkzeug]').count(), 0, 'keine Schreib-Knöpfe')
await shot(page, 'fx-abgeschaltet-abo-ende')
}, {
fixture: (() => {
const fx = neueFixture()
fx.projekte[0].features.termine = { state: 'disabled' }
fx.abgeschaltet = { grund: 'abo_ende', seit: '2026-10-04T23:30:00.000Z', loeschungAm: '2026-11-04' }
return fx
})(),
})
await check('fx_abgeschaltet_vor_einrichtung', async (page) => {
await oeffne(page, '#termine')
await warteAnsicht(page)
const hinweis = page.locator('.tm-box-warn')
await hinweis.waitFor()
const text = await textVon(hinweis)
assert(text.includes('eingerichtet') && text.includes('Wartungs-Abo') && text.includes('04.11.2026'), `Banner: ${text}`)
assert(!text.includes('exportieren'), `Banner ohne Export-Satz: ${text}`)
eq(await hinweis.locator('a').count(), 0, 'kein Link: es gibt nichts zu exportieren')
assert((await textVon(page.locator('.tm-info'))).includes('Keine Termine eingerichtet'), 'Info-Karte')
}, {
fixture: (() => {
const fx = neueFixture()
fx.projekte[0].features.termine = { state: 'disabled' }
fx.einstellung = null
fx.abgeschaltet = { grund: 'abo_ende_vor_einrichtung', seit: '2026-10-04T23:30:00.000Z', loeschungAm: '2026-11-04' }
return fx
})(),
})
// ------------------------------------------------------------ Datumssprung mit Unsinn
await check('fx_datumssprung_unsinn', async (page) => {
await oeffne(page, '#termine')
@@ -1037,6 +1081,7 @@ async function pruefungen() {
await warteAnsicht(page)
const hinweis = await textVon(page.locator('.tm-box-warn'))
assert(hinweis.includes('abgeschaltet') && hinweis.includes('nur noch ansehen'), `Banner: ${hinweis}`)
assert(!hinweis.includes('Wartungs-Abo') && !hinweis.includes('löschen'), `ohne Grund und Zeitpunkt der bisherige Satz: ${hinweis}`)
eq(await page.locator('.tm-box-warn a').getAttribute('href'), '/api/termine/export.csv?projectId=p1', 'Export-Link')
eq(await page.locator('[data-werkzeug]').count(), 0, 'keine Schreib-Knoepfe oben')
await page.getByRole('button', { name: 'Tag', exact: true }).click()
@@ -1049,6 +1094,28 @@ async function pruefungen() {
eq(r, 409, 'Server: Schreiben bei abgeschalteter Funktion')
})
await check('echt_abgeschaltet_abo_ende', async (page) => {
await srv.setzeFunktion('disabled', { disabledReason: 'abo_ende', disabledAt: '2026-10-04T23:30:00.000Z' })
await oeffne(page, '#termine')
await warteAnsicht(page)
const hinweis = page.locator('.tm-box-warn')
await hinweis.waitFor()
const text = await textVon(hinweis)
assert(text.includes('Wartungs-Abo') && text.includes('04.11.2026'), `Banner nennt Abo und Löschdatum: ${text}`)
eq(await page.locator('.tm-box-warn a').count(), 1, 'genau ein Link im Banner')
eq(await page.locator('.tm-box-warn a').innerText(), 'exportieren', 'Linktext')
eq(await page.locator('.tm-box-warn a').getAttribute('href'), '/api/termine/export.csv?projectId=p1', 'Export-Link')
// die Schnittstelle nennt dasselbe beim Schreiben
const antwort = await page.evaluate(async () => {
const res = await fetch('/api/termine/buchungen/b_alt/aktion', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ projectId: 'p1', aktion: 'annehmen' }) })
return { status: res.status, body: await res.json() }
})
eq(antwort.status, 409, 'Server: Schreiben bei abgeschalteter Funktion')
eq(antwort.body.code, 'abgeschaltet', 'Code bleibt')
assert(antwort.body.error.includes('Wartungs-Abo') && antwort.body.error.includes('04.11.2026'), `Meldung: ${antwort.body.error}`)
await shot(page, 'echt-abgeschaltet-abo-ende')
})
await check('echt_390_liste_und_formular', async (page) => {
srv.aw.seed('termineBookings', {
$id: 'b_390', featureId: srv.FEATURE, customerId: srv.KUNDE, projectId: srv.PROJEKT, leistungId: S_DAMEN, leistungName: 'Damenschnitt', ressourceId: R_ANNA, ressourceName: 'Anna',
+5 -5
View File
@@ -188,13 +188,13 @@ export async function startTermineUiServer({ jetzt = JETZT, crossOrigin = false,
setzeUhr(datum) { versatz = new Date(datum).getTime() - Date.now() },
/** Portal-Funktion des Termin-Features (portalFeatures-Dokument). */
featureDoc(state = 'active') {
return { $id: FEATURE, customerId: KUNDE, projectId: PROJEKT, featureKey: 'termine', state, enabled: state === 'active', siteKey: SITE_KEY, activatedAt: '2026-10-01T09:00:00.000Z' }
featureDoc(state = 'active', extra = {}) {
return { $id: FEATURE, customerId: KUNDE, projectId: PROJEKT, featureKey: 'termine', state, enabled: state === 'active', siteKey: SITE_KEY, activatedAt: '2026-10-01T09:00:00.000Z', ...extra }
},
/** Zustand der Funktion aendern ('active' | 'disabled' | 'off'); der Kontext-Speicher des Servers wird geleert. */
async setzeFunktion(state) {
aw.seed('portalFeatures', srv.featureDoc(state))
/** Zustand der Funktion aendern ('active' | 'disabled' | 'off'); extra: weitere Felder des Dokuments (z. B. disabledReason, disabledAt). Der Kontext-Speicher des Servers wird geleert. */
async setzeFunktion(state, extra = {}) {
aw.seed('portalFeatures', srv.featureDoc(state, extra))
resetKontextForTests()
},