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:
1 parent
d66dd60a76
commit
36b36bcc4d
7 files changed
+268
-26
No files matched your search
@@ -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',
|
||||
|
||||
@@ -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()
|
||||
},
|
||||
|
||||
|
||||
Reference in new issue
Block a user