Merge remote-tracking branch 'origin/main' into feature/oeffnungszeiten

Zusammenführung von TP2-Abschluss und Webshop (origin/main cab9c33) mit Öffnungszeiten live. Beide
Funktionen bleiben vollständig: jeder Konflikt ist als Vereinigung gelöst, Reihenfolge der Funktionsschlüssel
überall news, termine, shop, zeiten.

Konfliktdateien (28): public/app.js, dashboard.html, funktionen.js, widerrufen.html; scripts/stripe-products.mjs;
server/config.js, index.js, middleware/kundenansichtGuard.js, routes/abo.js, routes/admin/funktionenAdmin.js,
routes/funktionen.js, services/customerAdmin.js, customerViewLog.js, featureRegistry.js, retention.js,
siteFeatures.js, stripeBilling.js; test/customerViewLog, featureFreischaltung, featureRegistry, funktionenRoutes,
funktionenTermine, kundenansichtGuard, mailTemplates, publicForms, siteFeatures, stripeProductsScript (je
.test.js) und test/support/schema.js.

Entscheidungen: Mailvorlagen G01–G15, K01–K30, T01–T21 lückenlos (K24–K26 Webshop, K27–K30 Öffnungszeiten);
Stripe-Katalog mit shopSetup und zeitenSetup; Widerruf kennt shop und zeiten; ABO_FUNKTIONEN bleibt analyse
und termine (kein zeiten); activate() schreibt die Zeiten-Felder nur über extra; kein Halter von
feature-activate:<id> ruft disable() auf; die Aufbewahrungs-Löschung löscht Zeiten-Daten vor dem Abschalten und
hält den Kunden bei einem Fehler (held); die Admin-Antwort trägt ersatzTicket und warnung; Hash-Routen webshop
und zeiten, Ansichten view-webshop und view-zeiten; assertOwnProject behält die Meldung von main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-06 06:12:23 +00:00
commit e87140e5ba
245 files changed
+38799 -1050

No files matched your search

+328 -2
View File
@@ -8,7 +8,7 @@ import { schemaKeys } from './support/schema.js'
// Terminplaner als zweite Funktion (Aufgabe 17): Team-Route /:customerId/:key, GET /api/funktionen mit Schalter,
// Kunde loeschen schaltet den Terminplaner ab und entfernt seine Entwuerfe (C6). News verhaelt sich wie bisher (test/funktionenRoutes.test.js).
const aw = await startAppwriteMock({
schema: { portalFeatures: schemaKeys('portalFeatures'), subscriptions: schemaKeys('subscriptions'), termineConfig: schemaKeys('termineConfig'), termineBookings: schemaKeys('termineBookings') },
schema: { portalFeatures: schemaKeys('portalFeatures'), subscriptions: schemaKeys('subscriptions'), termineConfig: schemaKeys('termineConfig'), termineBookings: schemaKeys('termineBookings'), featureConsents: schemaKeys('featureConsents') },
})
const st = await startFakeStripe()
useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url })
@@ -295,7 +295,7 @@ test('GET /api/funktionen: Projekte tragen features.termine und News wie bisher'
assert.equal(pt1.features.news.state, 'disabled')
const pt2 = body.projects.find((p) => p.projectId === 'pt2')
assert.deepEqual([pt2.features.termine.state, pt2.features.news.state], ['off', 'off'])
assert.deepEqual(Object.keys(pt2.features), ['news', 'termine', 'zeiten'])
assert.deepEqual(Object.keys(pt2.features), ['news', 'termine', 'shop', 'zeiten'])
// Abschaltgrund und Loeschfrist (E2, R105): News ist hier vom Team abgeschaltet: Grund und Zeitpunkt ja, ein Loeschdatum nein
// (der 30-Tage-Loeschlauf loescht nur Terminplaner-Daten); der aktive Terminplaner und das leere Projekt haben nichts
const aus = pt1.features.news
@@ -1363,3 +1363,329 @@ test('Teamansicht: hindernis je Terminplaner in setup, null in jedem anderen Zus
assert.equal(view.state, 'setup')
assert.equal(view.ticketMissing, false)
})
// ------------------------------------------------------------- Staff-Aktivierung mit Zustimmung in Textform (TP2 F13a) ---
const { zustimmungFuerSitzung, leseZustimmung } = await import('../server/services/featureConsents.js')
const { setLegalDirForTests } = await import('../server/services/legalDocs.js')
const berlinTagF13 = (ms = Date.now()) => new Intl.DateTimeFormat('sv-SE', { timeZone: 'Europe/Berlin' }).format(ms)
const tagMitVersatzF13 = (tag, tage) => new Date(Date.parse(`${tag}T12:00:00Z`) + tage * 86400000).toISOString().slice(0, 10)
const ttmmjjjjF13 = (tag) => tag.split('-').reverse().join('.')
const consentsF13 = () => [...aw.docs('featureConsents').values()]
const consentsDesF13 = (projectId) => consentsF13().filter((c) => c.projectId === projectId)
let f13Nr = 0
/** Kunde mit eigenem Projekt (ohne Funktionsdokument); E-Mail frei waehlbar, das Wartungs-Abo laeuft. */
function legeKundeF13({ email, abo = true } = {}) {
f13Nr += 1
const customerId = `c_f13_${f13Nr}`
const projectId = `p_f13_${f13Nr}`
aw.seed('customers', { $id: customerId, email: email ?? `f13-${f13Nr}@example.invalid`, name: `Kunde F13 ${f13Nr}`, status: 'active', portalAccessEnabled: true })
aw.seed('websiteProjects', { $id: projectId, customerId, projectName: `Projekt F13 ${f13Nr}` })
if (abo) aw.seed('subscriptions', subscriptionDoc(`sub_f13_${f13Nr}`, { ...wartungDoc, customerId }))
return { customerId, projectId, email: aw.docs('customers').get(customerId).email }
}
const aktivierenF13 = (k, body = {}, opts = STAFF) => team(k.customerId, 'termine', { projectId: k.projectId, action: 'activate', ...body }, opts)
const feldF13 = (k, over = {}) => ({ textformDate: berlinTagF13(), textformChannel: 'chat', ...over })
const k16AnF13 = (email) => mails.sent.filter((m) => m.subject === 'Dein Terminplaner ist bereit' && m.to.some((t) => t.address === email))
test('textform_pflicht_modus_on: ohne Angaben 400, mit gültigen Angaben 200, Nachweis mit Datum, Weg, Fassung, Mitarbeiter und leerer Sitzung, K16 mit LB und AVV als PDF', async (t) => {
modusMerken(t)
termineMit('on', ['pilot@example.invalid'])
const k = legeKundeF13()
const v = config.termine.avvVersion
const mailsVorher = mails.sent.length
const ohne = await aktivierenF13(k)
assert.equal(ohne.status, 400)
assert.match(ohne.body.error, /Datum der Zustimmung/)
assert.equal(await sf.getFeatureDoc(k.customerId, k.projectId, 'termine'), null, 'nichts angelegt')
assert.equal(consentsDesF13(k.projectId).length, 0)
assert.equal(mails.sent.length, mailsVorher)
// nur Datum ohne Weg
const nurDatum = await aktivierenF13(k, { textformDate: berlinTagF13() })
assert.equal(nurDatum.status, 400)
assert.match(nurDatum.body.error, /Weg der Zustimmung/)
const tag = berlinTagF13()
const ok = await aktivierenF13(k, { textformDate: tag, textformChannel: 'email' })
assert.equal(ok.status, 200, JSON.stringify(ok.body))
assert.equal(featuresOf(ok, k.projectId).termine.state, 'active')
const doc = await sf.getFeatureDoc(k.customerId, k.projectId, 'termine')
assert.deepEqual([doc.state, doc.enabled, doc.unlockedByPurchase], ['active', true, false], 'ohne Bestellung freigeschaltet')
const [nachweis] = consentsDesF13(k.projectId)
assert.equal(consentsDesF13(k.projectId).length, 1)
assert.deepEqual([nachweis.customerId, nachweis.projectId, nachweis.featureKey], [k.customerId, k.projectId, 'termine'])
assert.equal(nachweis.stripeSessionId, '', 'keine Stripe-Sitzung')
assert.equal(nachweis.ip, '')
assert.equal(nachweis.userAgent, '')
assert.equal(nachweis.konto, 'Mitarbeiter: Andrej')
assert.ok(Math.abs(Date.parse(nachweis.acceptedAt) - Date.now()) < 10000, 'Zeitpunkt der Erfassung')
const gelesen = leseZustimmung(nachweis)
assert.deepEqual(gelesen.fassungen, { leistungsbeschreibung: v, avv: v })
assert.deepEqual(gelesen.texte, [{
id: 'textform',
text: `Der Kunde hat der Leistungsbeschreibung Terminplaner und dem Auftragsverarbeitungsvertrag Terminplaner (Fassung ${v}) am ${ttmmjjjjF13(tag)} per E-Mail in Textform zugestimmt und bestätigt, dass sein Angebot nicht im Gesundheitsbereich liegt.`,
}])
assert.match(nachweis.textHash, /^[0-9a-f]{64}$/)
// der Weg Chat heißt „Chat im Kundenportal“
const chat = legeKundeF13()
assert.equal((await aktivierenF13(chat, { textformDate: tag, textformChannel: 'chat' })).status, 200)
assert.ok(leseZustimmung(consentsDesF13(chat.projectId)[0]).texte[0].text.includes(`am ${ttmmjjjjF13(tag)} per Chat im Kundenportal in Textform`))
// K16 an den Kunden mit genau den beiden PDFs (Leistungsbeschreibung, AVV), keine Widerrufsbelehrung
const k16 = k16AnF13(k.email)
assert.equal(k16.length, 1, mails.sent.map((m) => m.subject).join(' / '))
assert.deepEqual(k16[0].attachments.map((a) => a.filename), [`Leistungsbeschreibung-Terminplaner-${v}.pdf`, `AVV-Terminplaner-${v}.pdf`])
for (const a of k16[0].attachments) {
assert.equal(a.contentType, 'application/pdf')
assert.equal(Buffer.from(a.content, 'base64').subarray(0, 4).toString(), '%PDF')
}
assert.equal(mails.sent.length, mailsVorher + 2, 'je eine K16 für den Kunden und den Chat-Kunden, sonst nichts')
})
const K16_NICHT_GESENDET = 'K16 mit Unterlagen NICHT gesendet – von Hand nachsenden'
test('textform A2: scheitert der Versand von K16 mit den Unterlagen, bleibt die Funktion freigeschaltet, adminNote und warnung:true sagen es; Erfolg und Kunde ohne Adresse', async (t) => {
modusMerken(t)
termineMit('on', [])
t.mock.method(console, 'error', () => {})
// Erfolg: keine Warnung, keine Notiz
const gut = legeKundeF13()
const ok = await aktivierenF13(gut, feldF13(gut))
assert.equal(ok.status, 200, JSON.stringify(ok.body))
assert.equal(ok.body.warnung, undefined, 'ohne Fehler kein Feld warnung')
assert.doesNotMatch((await sf.getFeatureDoc(gut.customerId, gut.projectId, 'termine')).adminNote, /NICHT gesendet/)
// Versandfehler: Freischaltung und Nachweis stehen, die Warnung kommt zurueck
const k = legeKundeF13()
setMailTransportForTests({ sendMail: async () => { throw new Error('SMTP weg') } })
t.after(() => setMailTransportForTests(mails))
const res = await aktivierenF13(k, feldF13(k))
assert.equal(res.status, 200, JSON.stringify(res.body))
assert.equal(res.body.warnung, true)
const doc = await sf.getFeatureDoc(k.customerId, k.projectId, 'termine')
assert.equal(doc.state, 'active', 'die Freischaltung bleibt')
assert.equal(consentsDesF13(k.projectId).length, 1, 'der Nachweis bleibt')
assert.ok(doc.adminNote.includes(K16_NICHT_GESENDET), doc.adminNote)
assert.match(doc.adminNote, /^\d{4}-\d\d-\d\dT[\d:.]+Z System: K16 mit Unterlagen NICHT gesendet – von Hand nachsenden \| .*Andrej: activate/)
assert.equal(featuresOf(res, k.projectId).termine.adminNote, doc.adminNote, 'das Ticketsystem liest die Notiz mit der Antwort')
// Kunde ohne E-Mail-Adresse: K16 kann nicht raus, auch das ist eine Warnung
setMailTransportForTests(mails)
const leer = legeKundeF13({ email: '' })
aw.docs('customers').get(leer.customerId).email = ''
const ohneAdresse = await aktivierenF13(leer, feldF13(leer))
assert.equal(ohneAdresse.status, 200, JSON.stringify(ohneAdresse.body))
assert.equal(ohneAdresse.body.warnung, true)
assert.ok((await sf.getFeatureDoc(leer.customerId, leer.projectId, 'termine')).adminNote.includes(K16_NICHT_GESENDET))
})
test('textform_entfaellt: Modus off und pilot, Modus on mit Pilotadresse, News und Abschalten brauchen keine Angaben und schreiben keinen Nachweis', async (t) => {
modusMerken(t)
const mailsVorher = mails.sent.length
for (const [mode, pilot] of [['off', []], ['pilot', ['*']], ['pilot', []]]) {
termineMit(mode, pilot)
const k = legeKundeF13()
const res = await aktivierenF13(k)
assert.equal(res.status, 200, `${mode}: ${JSON.stringify(res.body)}`)
assert.equal(featuresOf(res, k.projectId).termine.state, 'active')
assert.equal(consentsDesF13(k.projectId).length, 0, mode)
}
// Modus on, aber die Adresse steht in der Pilotliste
const pilotKunde = legeKundeF13({ email: 'Pilot-F13@example.invalid' })
termineMit('on', ['pilot-f13@example.invalid'])
assert.equal((await aktivierenF13(pilotKunde)).status, 200)
assert.equal(consentsDesF13(pilotKunde.projectId).length, 0)
// Modus on, andere Adresse: Pflicht; News und Abschalten bleiben frei
const k = legeKundeF13()
assert.equal((await aktivierenF13(k)).status, 400)
const news = await team(k.customerId, 'news', { projectId: k.projectId, action: 'activate' })
assert.equal(news.status, 200)
assert.equal(featuresOf(news, k.projectId).news.state, 'active')
assert.equal((await team(k.customerId, 'news', { projectId: k.projectId, action: 'disable' })).status, 200)
assert.equal(await sf.getFeatureDoc(k.customerId, k.projectId, 'termine'), null)
const aktiv = legeKundeF13()
termineMit('off')
assert.equal((await aktivierenF13(aktiv)).status, 200)
termineMit('on', [])
const aus = await team(aktiv.customerId, 'termine', { projectId: aktiv.projectId, action: 'disable' })
assert.equal(aus.status, 200, 'Abschalten braucht keine Angaben')
assert.equal(featuresOf(aus, aktiv.projectId).termine.state, 'disabled')
assert.equal(consentsF13().filter((c) => [k.projectId, aktiv.projectId, pilotKunde.projectId].includes(c.projectId)).length, 0)
assert.equal(k16AnF13('k@example.invalid').length, 0)
assert.equal(mails.sent.slice(mailsVorher).filter((m) => m.subject === 'Dein Terminplaner ist bereit').length, 0, 'ohne Textform keine K16 (wie bisher)')
})
test('textform_entfaellt: ein bezahlter oder schon aktiver Terminplaner braucht keine neue Zustimmung in Textform (der Nachweis liegt schon vor)', async (t) => {
modusMerken(t)
termineMit('on', [])
// bezahlt (Zustimmung beim Checkout), Zustand setup, Wartungs-Abo läuft
const bezahlt = legeKundeF13()
aw.seed('portalFeatures', { $id: `f_${bezahlt.projectId}_termine`, customerId: bezahlt.customerId, projectId: bezahlt.projectId, featureKey: 'termine', siteKey: 'qrstuvwxyzabcdef', state: 'setup', enabled: false, unlockedByPurchase: true, purchaseStatus: 'paid' })
const res = await aktivierenF13(bezahlt)
assert.equal(res.status, 200, JSON.stringify(res.body))
assert.equal(featuresOf(res, bezahlt.projectId).termine.state, 'active')
assert.equal(consentsDesF13(bezahlt.projectId).length, 0)
// schon aktiv (Doppelklick im WOMS): kein zweiter Nachweis, keine zweite Mail
const k = legeKundeF13()
termineMit('off')
assert.equal((await aktivierenF13(k)).status, 200)
termineMit('on', [])
const vorher = mails.sent.length
assert.equal((await aktivierenF13(k)).status, 200)
assert.equal(consentsDesF13(k.projectId).length, 0)
assert.equal(mails.sent.length, vorher)
})
test('textform_datum: Datum vor der Fassung, in der Zukunft, ungültig oder fehlend und ungültiger Weg ergeben 400 ohne Nachweis, ohne Dokument, ohne Mail', async (t) => {
modusMerken(t)
termineMit('on', [])
const k = legeKundeF13()
const v = config.termine.avvVersion
const heute = berlinTagF13()
const mailsVorher = mails.sent.length
const faelle = [
[{ textformDate: tagMitVersatzF13(v, -1), textformChannel: 'chat' }, /liegt vor der Fassung/],
[{ textformDate: tagMitVersatzF13(heute, 1), textformChannel: 'chat' }, /in der Zukunft/],
[{ textformDate: '2026-02-30', textformChannel: 'chat' }, /fehlt oder ist ungültig/],
[{ textformDate: '05.10.2026', textformChannel: 'chat' }, /fehlt oder ist ungültig/],
[{ textformDate: 20261005, textformChannel: 'chat' }, /fehlt oder ist ungültig/],
[{ textformDate: heute }, /Weg der Zustimmung/],
[{ textformDate: heute, textformChannel: 'telefon' }, /Weg der Zustimmung/],
[{ textformDate: heute, textformChannel: '' }, /Weg der Zustimmung/],
[{ textformDate: heute, textformChannel: ['chat'] }, /Weg der Zustimmung/],
]
for (const [body, meldung] of faelle) {
const res = await aktivierenF13(k, body)
assert.equal(res.status, 400, JSON.stringify(body))
assert.match(res.body.error, meldung, JSON.stringify(body))
}
assert.equal(await sf.getFeatureDoc(k.customerId, k.projectId, 'termine'), null, 'nichts angelegt')
assert.equal(consentsDesF13(k.projectId).length, 0)
assert.equal(mails.sent.length, mailsVorher)
// genau am Tag der Fassung und heute ist erlaubt
assert.equal((await aktivierenF13(k, { textformDate: v, textformChannel: 'email' })).status, 200)
assert.equal(consentsDesF13(k.projectId).length, 1)
})
test('textform_pflicht_modus_on: fremdes Projekt, Kundensitzung und nicht lesbare Rechtsunterlagen ändern nichts (die Berechtigung des Team-Wegs bleibt)', async (t) => {
modusMerken(t)
termineMit('on', [])
const k = legeKundeF13()
const fremd = legeKundeF13()
// Projekt eines anderen Kunden: 403 vor jeder Prüfung der Zustimmung
const res = await team(k.customerId, 'termine', { projectId: fremd.projectId, action: 'activate', ...feldF13(k) })
assert.equal(res.status, 403)
const alsKunde = await aktivierenF13(k, feldF13(k), { cookie: customerCookie(k.customerId) })
assert.ok(alsKunde.status === 401 || alsKunde.status === 403)
assert.equal(consentsF13().filter((c) => [k.projectId, fremd.projectId].includes(c.projectId)).length, 0)
assert.equal(await sf.getFeatureDoc(k.customerId, k.projectId, 'termine'), null)
// PDFs nicht lesbar: nichts wird freigeschaltet, nichts gespeichert, keine Mail
const leer = await (await import('node:fs/promises')).mkdtemp((await import('node:path')).join((await import('node:os')).tmpdir(), 'f13-leer-'))
setLegalDirForTests(leer)
t.after(() => setLegalDirForTests(null))
const mailsVorher = mails.sent.length
const fehler = await aktivierenF13(k, feldF13(k))
assert.ok(fehler.status >= 500, `Status ${fehler.status}`)
assert.match(fehler.body.error, /Rechtsunterlagen/)
assert.equal(await sf.getFeatureDoc(k.customerId, k.projectId, 'termine'), null)
assert.equal(consentsDesF13(k.projectId).length, 0)
assert.equal(mails.sent.length, mailsVorher)
setLegalDirForTests(null)
assert.equal((await aktivierenF13(k, feldF13(k))).status, 200)
})
test('textform_pflicht_modus_on: eine leere oder keine PDF-Datei als Unterlage ergibt 503 vor jeder Änderung (R121), danach mit gültigen Dateien 200', async (t) => {
modusMerken(t)
termineMit('on', [])
const fs = await import('node:fs')
const os = await import('node:os')
const path = await import('node:path')
const { legalPdfFiles } = await import('../server/services/legalDocs.js')
const echt = legalPdfFiles('termine', config.termine.avvVersion)
const kopien = fs.mkdtempSync(path.join(os.tmpdir(), 'f13-pdf-'))
fs.mkdirSync(path.join(kopien, 'rechtliches'))
const kopiere = () => {
for (const f of Object.values(echt)) fs.copyFileSync(f.path, path.join(kopien, 'rechtliches', path.basename(f.path)))
}
t.after(() => {
setLegalDirForTests(null)
fs.rmSync(kopien, { recursive: true, force: true })
})
setLegalDirForTests(kopien)
const k = legeKundeF13()
const mailsVorher = mails.sent.length
for (const [doc, inhalt] of [['lb', ''], ['avv', '<html>kein PDF</html>'], ['lb', '%PDF']]) {
kopiere()
fs.writeFileSync(path.join(kopien, 'rechtliches', path.basename(echt[doc].path)), inhalt)
const res = await aktivierenF13(k, feldF13(k))
assert.equal(res.status, 503, `${doc}: ${JSON.stringify(inhalt)}`)
assert.match(res.body.error, /Rechtsunterlagen/)
assert.equal(await sf.getFeatureDoc(k.customerId, k.projectId, 'termine'), null, 'nichts angelegt')
assert.equal(consentsDesF13(k.projectId).length, 0, 'kein Nachweis')
assert.equal(mails.sent.length, mailsVorher)
}
kopiere()
const ok = await aktivierenF13(k, feldF13(k))
assert.equal(ok.status, 200, JSON.stringify(ok.body))
assert.equal(consentsDesF13(k.projectId).length, 1)
assert.equal(k16AnF13(k.email).length, 1)
})
test('get_admin_termine_feld: GET /api/team/funktionen/:kunde liefert termine { textformNoetig, avvVersion, links } je Modus', async (t) => {
modusMerken(t)
const k = legeKundeF13({ email: 'get-f13@example.invalid' })
const v = config.termine.avvVersion
const feldVon = async () => (await get(`/api/team/funktionen/${k.customerId}`, STAFF)).termine
termineMit('off')
assert.equal((await feldVon()).textformNoetig, false)
termineMit('pilot', ['*'])
assert.equal((await feldVon()).textformNoetig, false)
termineMit('on', ['get-f13@example.invalid'])
assert.equal((await feldVon()).textformNoetig, false, 'Pilotadresse')
termineMit('on', [])
const f = await feldVon()
assert.deepEqual(Object.keys(f).sort(), ['avvVersion', 'links', 'textformNoetig'])
assert.equal(f.textformNoetig, true)
assert.equal(f.avvVersion, v)
assert.deepEqual(Object.keys(f.links).sort(), ['avv', 'lb'])
// bisherige Antwort bleibt: projects
const ganz = await get(`/api/team/funktionen/${k.customerId}`, STAFF)
assert.ok(Array.isArray(ganz.projects))
// unbekannter Kunde: Modus on => Textform nötig (ohne Adresse), keine Ausnahme
termineMit('on', ['*'])
assert.equal((await get('/api/team/funktionen/c_gibtsnicht', STAFF)).termine.textformNoetig, true)
})
test('chat_vorlage_versioniert: die Adressen für die Chat-Vorlage verweisen nur auf Seiten mit Fassung (-<Fassung>.html), absolut, folgen der aktuellen Fassung', async (t) => {
modusMerken(t)
termineMit('on', [])
const k = legeKundeF13()
const v = config.termine.avvVersion
const { links } = (await get(`/api/team/funktionen/${k.customerId}`, STAFF)).termine
assert.equal(links.lb, `${config.publicBaseUrl}/rechtliches/leistungsbeschreibung-terminplaner-${v}.html`)
assert.equal(links.avv, `${config.publicBaseUrl}/rechtliches/avv-terminplaner-${v}.html`)
for (const adresse of Object.values(links)) {
assert.ok(adresse.endsWith(`-${v}.html`), adresse)
assert.match(adresse, /^https:\/\//)
}
// eine neue Fassung ändert die Adressen mit
const vorher = config.termine.avvVersion
t.after(() => { config.termine.avvVersion = vorher })
config.termine.avvVersion = '2099-01-02'
const neu = (await get(`/api/team/funktionen/${k.customerId}`, STAFF)).termine
assert.equal(neu.avvVersion, '2099-01-02')
for (const adresse of Object.values(neu.links)) assert.ok(adresse.endsWith('-2099-01-02.html'), adresse)
})
test('zustimmungFuerSitzung(""): der Nachweis der Staff-Aktivierung (leere Sitzung) wird über keine Sitzung gefunden', async (t) => {
modusMerken(t)
termineMit('on', [])
const k = legeKundeF13()
assert.equal((await aktivierenF13(k, feldF13(k))).status, 200)
assert.equal(consentsDesF13(k.projectId).length, 1)
assert.equal(consentsDesF13(k.projectId)[0].stripeSessionId, '')
for (const leer of ['', ' ', undefined, null]) assert.equal(await zustimmungFuerSitzung(leer), null, JSON.stringify(leer))
})