Kundenansicht P5: Katalogfelder, consentAbo mit Hinweis und Unterlagen-Quelle im Funktionen-Register
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8e0d2b2736
commit
4bf1b67e67
4 files changed
+268
-1
No files matched your search
@@ -435,6 +435,13 @@ function unterkunftConsent() {
|
|||||||
const features = {
|
const features = {
|
||||||
news: {
|
news: {
|
||||||
key: 'news',
|
key: 'news',
|
||||||
|
// Katalog der Kundenansicht neu (P5, V5): Beschreibung (hoechstens zwei Saetze, du-Form, kein Preis), Bild, Ablauf
|
||||||
|
// (direkt | fragebogen | anfrage), Dauer der Einrichtung und ob die Funktion nur Betriebe bekommen
|
||||||
|
beschreibung: 'Zeig Neuigkeiten und Aktionen direkt auf deiner Website. Du schreibst sie im Portal, sie erscheinen sofort.',
|
||||||
|
bild: '/neu/bilder/news.webp',
|
||||||
|
ablauf: 'direkt',
|
||||||
|
einrichtung: 'meist in 1–2 Wochen',
|
||||||
|
nurBetrieb: false,
|
||||||
product: 'newsSetup',
|
product: 'newsSetup',
|
||||||
label: { name: 'News-Pinnwand', kurz: 'News', keine: 'keine News-Pinnwand' },
|
label: { name: 'News-Pinnwand', kurz: 'News', keine: 'keine News-Pinnwand' },
|
||||||
priceLabel: '30 €',
|
priceLabel: '30 €',
|
||||||
@@ -500,6 +507,13 @@ const features = {
|
|||||||
},
|
},
|
||||||
termine: {
|
termine: {
|
||||||
key: 'termine',
|
key: 'termine',
|
||||||
|
// Katalog der Kundenansicht neu (P5, V5): Beschreibung (hoechstens zwei Saetze, du-Form, kein Preis), Bild, Ablauf
|
||||||
|
// (direkt | fragebogen | anfrage), Dauer der Einrichtung und ob die Funktion nur Betriebe bekommen
|
||||||
|
beschreibung: 'Deine Kunden buchen Termine online, rund um die Uhr. Zeiten, Team und Buchungen verwaltest du im Portal.',
|
||||||
|
bild: '/neu/bilder/termine.webp',
|
||||||
|
ablauf: 'fragebogen',
|
||||||
|
einrichtung: 'meist in 1–2 Wochen',
|
||||||
|
nurBetrieb: false,
|
||||||
product: 'termineSetup',
|
product: 'termineSetup',
|
||||||
label: { name: 'Terminplaner', kurz: 'Termine', keine: 'keinen Terminplaner' },
|
label: { name: 'Terminplaner', kurz: 'Termine', keine: 'keinen Terminplaner' },
|
||||||
priceLabel: TERMINE_PREIS,
|
priceLabel: TERMINE_PREIS,
|
||||||
@@ -553,6 +567,13 @@ const features = {
|
|||||||
},
|
},
|
||||||
shop: {
|
shop: {
|
||||||
key: 'shop',
|
key: 'shop',
|
||||||
|
// Katalog der Kundenansicht neu (P5, V5): Beschreibung (hoechstens zwei Saetze, du-Form, kein Preis), Bild, Ablauf
|
||||||
|
// (direkt | fragebogen | anfrage), Dauer der Einrichtung und ob die Funktion nur Betriebe bekommen
|
||||||
|
beschreibung: 'Verkaufe Artikel direkt über deine Website. Die Zahlung läuft über dein eigenes Stripe-Konto.',
|
||||||
|
bild: '/neu/bilder/shop.webp',
|
||||||
|
ablauf: 'anfrage',
|
||||||
|
einrichtung: 'meist in 2–3 Wochen',
|
||||||
|
nurBetrieb: true,
|
||||||
product: 'shopSetup',
|
product: 'shopSetup',
|
||||||
label: { name: 'Webshop', kurz: 'Shop', keine: 'keinen Webshop' },
|
label: { name: 'Webshop', kurz: 'Shop', keine: 'keinen Webshop' },
|
||||||
priceLabel: SHOP_PREIS,
|
priceLabel: SHOP_PREIS,
|
||||||
@@ -605,6 +626,13 @@ const features = {
|
|||||||
},
|
},
|
||||||
unterkunft: {
|
unterkunft: {
|
||||||
key: 'unterkunft',
|
key: 'unterkunft',
|
||||||
|
// Katalog der Kundenansicht neu (P5, V5): Beschreibung (hoechstens zwei Saetze, du-Form, kein Preis), Bild, Ablauf
|
||||||
|
// (direkt | fragebogen | anfrage), Dauer der Einrichtung und ob die Funktion nur Betriebe bekommen
|
||||||
|
beschreibung: 'Gäste fragen deine Unterkunft online an, du gibst frei und behältst Belegung und Preise im Blick. Den Kalender baut WEBklar in deine Website ein.',
|
||||||
|
bild: '/neu/bilder/unterkunft.webp',
|
||||||
|
ablauf: 'anfrage',
|
||||||
|
einrichtung: 'meist in 1–2 Wochen',
|
||||||
|
nurBetrieb: false,
|
||||||
product: 'unterkunftSetup',
|
product: 'unterkunftSetup',
|
||||||
label: { name: 'Buchungskalender', kurz: 'Buchungskalender', keine: 'keinen Buchungskalender' },
|
label: { name: 'Buchungskalender', kurz: 'Buchungskalender', keine: 'keinen Buchungskalender' },
|
||||||
priceLabel: UNTERKUNFT_PREIS,
|
priceLabel: UNTERKUNFT_PREIS,
|
||||||
@@ -656,6 +684,13 @@ const features = {
|
|||||||
// Oeffnungszeiten live (Spec 2026-10-04): 30 EUR einmalig, im Wartungs-Abo ohne Aufpreis (aboInklusive, consentFrei)
|
// Oeffnungszeiten live (Spec 2026-10-04): 30 EUR einmalig, im Wartungs-Abo ohne Aufpreis (aboInklusive, consentFrei)
|
||||||
zeiten: {
|
zeiten: {
|
||||||
key: 'zeiten',
|
key: 'zeiten',
|
||||||
|
// Katalog der Kundenansicht neu (P5, V5): Beschreibung (hoechstens zwei Saetze, du-Form, kein Preis), Bild, Ablauf
|
||||||
|
// (direkt | fragebogen | anfrage), Dauer der Einrichtung und ob die Funktion nur Betriebe bekommen
|
||||||
|
beschreibung: 'Deine Öffnungszeiten stehen live auf deiner Website und sind immer aktuell. Du änderst sie im Portal, auch für Feiertage und Urlaub.',
|
||||||
|
bild: '/neu/bilder/zeiten.webp',
|
||||||
|
ablauf: 'direkt',
|
||||||
|
einrichtung: 'meist in wenigen Tagen',
|
||||||
|
nurBetrieb: false,
|
||||||
product: 'zeitenSetup',
|
product: 'zeitenSetup',
|
||||||
label: { name: 'Öffnungszeiten live', kurz: 'Zeiten', keine: 'keine Öffnungszeiten live' },
|
label: { name: 'Öffnungszeiten live', kurz: 'Zeiten', keine: 'keine Öffnungszeiten live' },
|
||||||
priceLabel: '30 €',
|
priceLabel: '30 €',
|
||||||
@@ -809,6 +844,37 @@ export const config = {
|
|||||||
ustHinweis,
|
ustHinweis,
|
||||||
// Register der kaufbaren Funktionen (siehe oben)
|
// Register der kaufbaren Funktionen (siehe oben)
|
||||||
features,
|
features,
|
||||||
|
// Abo-Modell der Funktionen (Kundenansicht neu, P5): Fassung der Einschalt-Erklaerung, Reihenfolge des Katalogs, Entwurfstexte
|
||||||
|
// (Task 15 / P7 Task 0 stellt fassung und texte.beginn auf Fassung 6; config.js importiert keine Dienste) und der Katalogeintrag der
|
||||||
|
// Analyse (sie steht nicht im Register der kaufbaren Funktionen). Die Items baut featureRegistry.consentAbo bei jedem Aufruf.
|
||||||
|
aboFunktionen: {
|
||||||
|
fassung: '2026-10-16',
|
||||||
|
reihenfolge: ['news', 'termine', 'shop', 'unterkunft', 'zeiten', 'analyse'],
|
||||||
|
texte: {
|
||||||
|
// Haken "Beginn" (nur Privatkunden innerhalb der Widerrufsfrist); Entwurf, wortgleich zu WORTLAUT_V6.beginn.einrichtung (P8)
|
||||||
|
beginn:
|
||||||
|
'Bitte beginnt sofort mit der Einrichtung. Mir ist klar: Mit der vollständigen Einrichtung erlischt mein Widerrufsrecht, und wenn ich vorher widerrufe, zahle ich den bis dahin erbrachten Teil.',
|
||||||
|
// Hinweis auf der Einschalt-Seite (ohne Haken): die Pflichten der alten Haken verantwortung, stripe und gesundheit;
|
||||||
|
// Entwurf, wortgleich zu WORTLAUT_V6.hinweis (P8)
|
||||||
|
hinweis: {
|
||||||
|
shop: 'Du verkaufst in eigenem Namen und bist für deine Produkte, Preise und Rechtstexte verantwortlich. Die Zahlungen deiner Käufer laufen über dein eigenes Stripe-Konto.',
|
||||||
|
unterkunft:
|
||||||
|
'Du vermietest in eigenem Namen und bist für Preise, Stornobedingungen, Rechnungen und Meldepflichten verantwortlich. Online-Zahlungen deiner Gäste laufen über dein eigenes Stripe-Konto.',
|
||||||
|
termine: 'Der Terminplaner ist nicht für den Gesundheitsbereich gedacht. Das fragen wir im Fragebogen ab.',
|
||||||
|
},
|
||||||
|
// Haken der Analyse (Kinder unter 16 Jahren, DSGVO Art. 8)
|
||||||
|
zielgruppe: 'Meine Website richtet sich nicht gezielt an Kinder unter 16 Jahren.',
|
||||||
|
analyseKnopf: 'Analyse einschalten',
|
||||||
|
},
|
||||||
|
analyse: {
|
||||||
|
name: 'Website-Analyse',
|
||||||
|
beschreibung: 'Sieh, wie viele Besucher auf deine Website kommen und welche Seiten sie ansehen. Die Auswertung findest du direkt im Portal.',
|
||||||
|
bild: '/neu/bilder/analyse.webp',
|
||||||
|
ablauf: 'direkt',
|
||||||
|
einrichtung: 'meist in wenigen Tagen',
|
||||||
|
nurBetrieb: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
// Funktionen-Tab (News-Pinnwand): Alias auf den News-Eintrag des Registers, Zeiteinschaetzung auf der
|
// Funktionen-Tab (News-Pinnwand): Alias auf den News-Eintrag des Registers, Zeiteinschaetzung auf der
|
||||||
// Buchungsseite und Wortlaut der Zustimmung (die Getter folgen config.features.news zur Laufzeit)
|
// Buchungsseite und Wortlaut der Zustimmung (die Getter folgen config.features.news zur Laufzeit)
|
||||||
funktionen: {
|
funktionen: {
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { config } from '../config.js'
|
||||||
|
import { legalPaths } from './analyseLegal.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Einzige Quelle der verlinkten Unterlagen im Abo-Modell (Katalog, Einschalt-Nachweis, Analyse). Blattmodul: importiert
|
||||||
|
* nur config und analyseLegal.js. Liest bei jedem Aufruf (Tests aendern config zur Laufzeit). Task 15 (Fassung 6) stellt
|
||||||
|
* nur diese Quelle um; die Aufrufer bleiben unveraendert.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fassung = (unterlage) => (unterlage ? { version: unterlage.version, url: unterlage.url ?? '' } : null)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Leistungsbeschreibung und AVV einer Funktion: { leistungsbeschreibung: { version, url } | null, avv: { version, url } | null }.
|
||||||
|
* Funktionen aus dem Register: deren Eintraege; 'analyse': die versionierten Seiten aus legalPaths(config.analyse.avvVersion).
|
||||||
|
* Ein unbekannter Schluessel ist ein Programmfehler (TypeError).
|
||||||
|
*/
|
||||||
|
export function unterlagenFuer(key) {
|
||||||
|
if (key === 'analyse') {
|
||||||
|
const version = config.analyse.avvVersion
|
||||||
|
const pfade = legalPaths(version)
|
||||||
|
return { leistungsbeschreibung: { version, url: pfade.lb }, avv: { version, url: pfade.avv } }
|
||||||
|
}
|
||||||
|
if (typeof key !== 'string' || !Object.hasOwn(config.features, key)) throw new TypeError(`Unbekannte Funktion: ${String(key)}`)
|
||||||
|
const eintrag = config.features[key]
|
||||||
|
return { leistungsbeschreibung: fassung(eintrag.leistungsbeschreibung), avv: fassung(eintrag.avv) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Unterlagen der Analyse fuer Nachweis und Mail: { avvVersion, legal: { lb, avv } } (Adressen der versionierten Seiten). */
|
||||||
|
export function analyseUnterlagen() {
|
||||||
|
const { leistungsbeschreibung, avv } = unterlagenFuer('analyse')
|
||||||
|
return { avvVersion: avv.version, legal: { lb: leistungsbeschreibung.url, avv: avv.url } }
|
||||||
|
}
|
||||||
@@ -3,10 +3,12 @@ import { termineFuerKunde } from './termine/schalter.js'
|
|||||||
import { shopFuerKunde } from './shop/schalter.js'
|
import { shopFuerKunde } from './shop/schalter.js'
|
||||||
import { unterkunftFuerKunde } from './unterkunft/schalter.js'
|
import { unterkunftFuerKunde } from './unterkunft/schalter.js'
|
||||||
import { zeitenFuerKunde } from './zeiten/schalter.js'
|
import { zeitenFuerKunde } from './zeiten/schalter.js'
|
||||||
|
import { analyseUnterlagen, unterlagenFuer } from './aboUnterlagen.js'
|
||||||
|
|
||||||
// Helfer fuer das Funktionen-Register config.features (News-Pinnwand, Terminplaner, Webshop, Oeffnungszeiten live; Daten in server/config.js).
|
// Helfer fuer das Funktionen-Register config.features (News-Pinnwand, Terminplaner, Webshop, Oeffnungszeiten live; Daten in server/config.js).
|
||||||
// Diese Datei wird von siteFeatures.js importiert und importiert selbst keine Dienste statisch (einzige Ausnahmen:
|
// Diese Datei wird von siteFeatures.js importiert und importiert selbst keine Dienste statisch (einzige Ausnahmen:
|
||||||
// termine/schalter.js, shop/schalter.js und zeiten/schalter.js, die nur config lesen): Standard-Haken laden ihre Dienste per dynamischem import()
|
// termine/schalter.js, shop/schalter.js und zeiten/schalter.js, die nur config lesen, und aboUnterlagen.js, ein Blattmodul,
|
||||||
|
// das nur config und analyseLegal.js importiert): Standard-Haken laden ihre Dienste per dynamischem import()
|
||||||
// innerhalb der Funktion (sonst entstuenden Importzyklen).
|
// innerhalb der Funktion (sonst entstuenden Importzyklen).
|
||||||
|
|
||||||
/** Namen der Haken, die ein Registereintrag hat (optional): Bestell-, Freischalt- und Unterlagenpruefungen, dazu nachFreischaltung. */
|
/** Namen der Haken, die ein Registereintrag hat (optional): Bestell-, Freischalt- und Unterlagenpruefungen, dazu nachFreischaltung. */
|
||||||
@@ -216,6 +218,77 @@ export function freiweg(feature) {
|
|||||||
return { ...feature, consent: feature.consentFrei }
|
return { ...feature, consent: feature.consentFrei }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------------------------------------------
|
||||||
|
// Abo-Modell der Funktionen (Kundenansicht neu, P5): Katalogfelder, Einschalt-Erklaerung ohne Preis und Unterlagen.
|
||||||
|
// Reine Lesefunktionen auf config.features und config.aboFunktionen; die Unterlagen kommen aus aboUnterlagen.js.
|
||||||
|
// ---------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Schluessel des Katalogs in Anzeigereihenfolge (Kopie), mit 'analyse' am Ende. */
|
||||||
|
export function katalogReihenfolge() {
|
||||||
|
return [...config.aboFunktionen.reihenfolge]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** { beschreibung, bild, ablauf, einrichtung, nurBetrieb } einer Funktion (auch 'analyse'); unbekannter Schluessel => null. */
|
||||||
|
export function katalogFelder(key) {
|
||||||
|
const quelle = key === 'analyse' ? config.aboFunktionen.analyse : featureConfig(key)
|
||||||
|
if (!quelle) return null
|
||||||
|
const { beschreibung, bild, ablauf, einrichtung, nurBetrieb } = quelle
|
||||||
|
return { beschreibung, bild, ablauf, einrichtung, nurBetrieb }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Einschalt-Erklaerung einer Funktion im Abo-Modell: { version, knopf: 'Einschalten', items } in Anzeigereihenfolge
|
||||||
|
* einschalten-zeile (Linkzeile mit Fassung, kein Haken), hinweis (nur shop, unterkunft, termine; kein Haken) und beginn
|
||||||
|
* (Haken, nur fuer Privatkunden innerhalb der Widerrufsfrist). Baut die Items bei jedem Aufruf aus aboUnterlagen.js und
|
||||||
|
* config.aboFunktionen.texte. Analyse und unbekannte Schluessel => null.
|
||||||
|
*/
|
||||||
|
export function consentAbo(key) {
|
||||||
|
if (!featureConfig(key)) return null
|
||||||
|
const { leistungsbeschreibung, avv } = unterlagenFuer(key)
|
||||||
|
const { fassung: version, texte } = config.aboFunktionen
|
||||||
|
const zeile = avv
|
||||||
|
? `Es gelten Leistungsbeschreibung und Auftragsverarbeitung (Fassung ${leistungsbeschreibung.version}).`
|
||||||
|
: `Es gilt die Leistungsbeschreibung (Fassung ${leistungsbeschreibung.version}).`
|
||||||
|
const items = [{ id: 'einschalten-zeile', text: zeile, haken: false }]
|
||||||
|
if (Object.hasOwn(texte.hinweis, key)) items.push({ id: 'hinweis', text: texte.hinweis[key], haken: false })
|
||||||
|
items.push({ id: 'beginn', text: texte.beginn, haken: true, nurPrivatInFrist: true })
|
||||||
|
return { version, knopf: 'Einschalten', items }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Die Erklaerung, wie sie angezeigt wird: ohne beginn, solange das Haekchen nicht verlangt ist. */
|
||||||
|
export function consentAboAktiv(key, { beginn = false } = {}) {
|
||||||
|
const consent = consentAbo(key)
|
||||||
|
if (!consent) return null
|
||||||
|
return { ...consent, items: consent.items.filter((item) => item.id !== 'beginn' || beginn) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Abo-Weg einer Funktion fuer den Nachweis: derselbe Eintrag mit der Abo-Erklaerung, den Unterlagen aus aboUnterlagen.js und ohne Widerrufsbelehrung. */
|
||||||
|
export function aboweg(feature, { beginn = false } = {}) {
|
||||||
|
const { leistungsbeschreibung, avv } = unterlagenFuer(feature.key)
|
||||||
|
return { ...feature, consent: consentAboAktiv(feature.key, { beginn }), leistungsbeschreibung, avv, widerruf: null }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Einschalt-Erklaerung der Analyse: Linkzeile (Fassung der AVV), Zielgruppen-Haken (Kinder unter 16) und Knopf. */
|
||||||
|
export function consentAboAnalyse() {
|
||||||
|
const { avvVersion } = analyseUnterlagen()
|
||||||
|
const { fassung: version, texte } = config.aboFunktionen
|
||||||
|
return {
|
||||||
|
version,
|
||||||
|
avvVersion,
|
||||||
|
knopf: texte.analyseKnopf,
|
||||||
|
items: [
|
||||||
|
{ id: 'einschalten-zeile', text: `Es gelten Leistungsbeschreibung und Auftragsverarbeitung (Fassung ${avvVersion}).`, haken: false },
|
||||||
|
{ id: 'zielgruppe', text: texte.zielgruppe, haken: true },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Abo-Weg der Analyse in der Form, die speichereNachweisAusAnfrage erwartet. */
|
||||||
|
export function abowegAnalyse() {
|
||||||
|
const { avvVersion } = analyseUnterlagen()
|
||||||
|
return { key: 'analyse', consent: consentAboAnalyse(), leistungsbeschreibung: { version: avvVersion }, avv: { version: avvVersion }, widerruf: null }
|
||||||
|
}
|
||||||
|
|
||||||
const fassung = (f) => (f ? { version: f.version, url: f.url ?? '' } : null)
|
const fassung = (f) => (f ? { version: f.version, url: f.url ?? '' } : null)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import { test, after } from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { startAppwriteMock } from './support/appwriteMock.js'
|
||||||
|
import { startFakeStripe } from './support/fakeStripe.js'
|
||||||
|
import { useTestEnv } from './support/testEnv.js'
|
||||||
|
|
||||||
|
// Katalogfelder, consentAbo (mit Hinweis) und die Unterlagen-Quelle im Funktionen-Register (P5, V5)
|
||||||
|
const aw = await startAppwriteMock()
|
||||||
|
const st = await startFakeStripe()
|
||||||
|
after(() => Promise.all([aw.close(), st.close()]))
|
||||||
|
useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url })
|
||||||
|
const { config } = await import('../server/config.js')
|
||||||
|
const reg = await import('../server/services/featureRegistry.js')
|
||||||
|
const { unterlagenFuer, analyseUnterlagen } = await import('../server/services/aboUnterlagen.js')
|
||||||
|
|
||||||
|
const KEYS = ['news', 'termine', 'shop', 'unterkunft', 'zeiten']
|
||||||
|
test('Reihenfolge und Felder je Funktion', () => {
|
||||||
|
assert.deepEqual(reg.katalogReihenfolge(), [...KEYS, 'analyse'])
|
||||||
|
const ablauf = { news: 'direkt', termine: 'fragebogen', shop: 'anfrage', unterkunft: 'anfrage', zeiten: 'direkt', analyse: 'direkt' }
|
||||||
|
for (const key of [...KEYS, 'analyse']) {
|
||||||
|
const f = reg.katalogFelder(key)
|
||||||
|
assert.deepEqual(Object.keys(f).sort(), ['ablauf', 'beschreibung', 'bild', 'einrichtung', 'nurBetrieb'])
|
||||||
|
assert.deepEqual([f.bild, f.ablauf, f.nurBetrieb], [`/neu/bilder/${key}.webp`, ablauf[key], key === 'shop'], key)
|
||||||
|
assert.ok(f.beschreibung.split(/(?<=[.!?])\s+/).length <= 2, 'höchstens zwei Sätze')
|
||||||
|
assert.doesNotMatch(f.beschreibung + f.einrichtung, /€|\bSie\b|\bIhre?[nmrs]?\b/, 'kein Preis, keine Sie-Form')
|
||||||
|
}
|
||||||
|
assert.equal(reg.katalogFelder('shop').einrichtung, 'meist in 2–3 Wochen')
|
||||||
|
assert.equal(reg.katalogFelder('gibtEsNicht'), null)
|
||||||
|
// die festen Werte der Tabelle
|
||||||
|
const einrichtung = { news: 'meist in 1–2 Wochen', termine: 'meist in 1–2 Wochen', shop: 'meist in 2–3 Wochen', unterkunft: 'meist in 1–2 Wochen', zeiten: 'meist in wenigen Tagen', analyse: 'meist in wenigen Tagen' }
|
||||||
|
for (const [key, text] of Object.entries(einrichtung)) assert.equal(reg.katalogFelder(key).einrichtung, text, key)
|
||||||
|
assert.equal(reg.katalogFelder('news').beschreibung, 'Zeig Neuigkeiten und Aktionen direkt auf deiner Website. Du schreibst sie im Portal, sie erscheinen sofort.')
|
||||||
|
assert.equal(reg.katalogFelder('analyse').beschreibung, 'Sieh, wie viele Besucher auf deine Website kommen und welche Seiten sie ansehen. Die Auswertung findest du direkt im Portal.')
|
||||||
|
})
|
||||||
|
|
||||||
|
const HINWEIS = { // Pflichten der alten Häkchen verantwortung, stripe, gesundheit; wörtlich wie WORTLAUT_V6.hinweis (P8)
|
||||||
|
termine: 'Der Terminplaner ist nicht für den Gesundheitsbereich gedacht. Das fragen wir im Fragebogen ab.',
|
||||||
|
shop: 'Du verkaufst in eigenem Namen und bist für deine Produkte, Preise und Rechtstexte verantwortlich. Die Zahlungen deiner Käufer laufen über dein eigenes Stripe-Konto.',
|
||||||
|
unterkunft: 'Du vermietest in eigenem Namen und bist für Preise, Stornobedingungen, Rechnungen und Meldepflichten verantwortlich. Online-Zahlungen deiner Gäste laufen über dein eigenes Stripe-Konto.',
|
||||||
|
}
|
||||||
|
test('consentAbo: Linkzeile mit Fassung, Hinweis (termine, shop, unterkunft), beginn als Entwurf, kein Häkchen gesundheit', () => {
|
||||||
|
const V = config.aboFunktionen.fassung
|
||||||
|
for (const key of KEYS) {
|
||||||
|
const c = reg.consentAbo(key)
|
||||||
|
const ids = key in HINWEIS ? ['einschalten-zeile', 'hinweis', 'beginn'] : ['einschalten-zeile', 'beginn']
|
||||||
|
assert.deepEqual([c.version, c.knopf, c.items.map((i) => i.id)], [V, 'Einschalten', ids], key)
|
||||||
|
assert.deepEqual(c.items.map((i) => i.haken), ids.map((id) => id === 'beginn'), `${key}: nur beginn ist ein Häkchen`)
|
||||||
|
assert.deepEqual([c.items.at(-1).text, c.items.at(-1).nurPrivatInFrist], [config.aboFunktionen.texte.beginn, true], key)
|
||||||
|
if (key in HINWEIS) assert.equal(c.items[1].text, HINWEIS[key], key)
|
||||||
|
}
|
||||||
|
assert.equal(config.aboFunktionen.texte.beginn, 'Bitte beginnt sofort mit der Einrichtung. Mir ist klar: Mit der vollständigen Einrichtung erlischt mein Widerrufsrecht, und wenn ich vorher widerrufe, zahle ich den bis dahin erbrachten Teil.')
|
||||||
|
assert.equal(V, '2026-10-16')
|
||||||
|
assert.equal(reg.consentAbo('termine').items[0].text, `Es gelten Leistungsbeschreibung und Auftragsverarbeitung (Fassung ${config.termine.avvVersion}).`)
|
||||||
|
assert.equal(reg.consentAbo('zeiten').items[0].text, 'Es gilt die Leistungsbeschreibung (Fassung 2026-10-04).')
|
||||||
|
assert.equal(reg.consentAbo('news').items[0].text, `Es gilt die Leistungsbeschreibung (Fassung ${config.termine.avvVersion}).`)
|
||||||
|
assert.ok(!reg.consentAbo('termine').items.some((i) => i.id === 'gesundheit'), 'das Häkchen gesundheit entfällt, der Satz steht als Hinweis')
|
||||||
|
assert.doesNotMatch(JSON.stringify(Object.values(HINWEIS)), /€|\bSie\b|\bIhre?[nmrs]?\b/)
|
||||||
|
assert.equal(reg.consentAbo('analyse'), null)
|
||||||
|
assert.equal(reg.consentAbo('gibtEsNicht'), null)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('consentAboAktiv und aboweg zeigen nur, was verlangt ist; das alte Register bleibt unverändert', () => {
|
||||||
|
assert.deepEqual(reg.consentAboAktiv('shop').items.map((i) => i.id), ['einschalten-zeile', 'hinweis'])
|
||||||
|
assert.deepEqual(reg.consentAboAktiv('news').items.map((i) => i.id), ['einschalten-zeile'])
|
||||||
|
assert.deepEqual(reg.consentAboAktiv('shop', { beginn: true }).items.map((i) => [i.id, i.haken]), [['einschalten-zeile', false], ['hinweis', false], ['beginn', true]])
|
||||||
|
const w = reg.aboweg(reg.featureConfig('termine'), { beginn: true })
|
||||||
|
assert.deepEqual([w.consent.version, w.widerruf, w.avv.version, w.leistungsbeschreibung.version, w.key], [config.aboFunktionen.fassung, null, config.termine.avvVersion, config.termine.avvVersion, 'termine'])
|
||||||
|
assert.deepEqual(reg.featureConfig('termine').consent.items.map((i) => i.id), ['unterlagen', 'gesundheit', 'beginn', 'information'])
|
||||||
|
assert.notEqual(reg.featureConfig('termine').widerruf, null, 'aboweg ist eine Kopie, das Register bleibt')
|
||||||
|
assert.equal(Object.keys(reg.oeffentlicheAnsicht('termine')).length, 11, 'oeffentlicheAnsicht ohne neue Felder')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('unterlagenFuer und analyseUnterlagen: eine Quelle für Katalog, Nachweis und Analyse', () => {
|
||||||
|
const t = unterlagenFuer('termine')
|
||||||
|
assert.deepEqual([t.leistungsbeschreibung, t.avv], [{ version: config.termine.avvVersion, url: config.features.termine.leistungsbeschreibung.url }, { version: config.termine.avvVersion, url: config.features.termine.avv.url }])
|
||||||
|
assert.deepEqual([unterlagenFuer('news').avv, unterlagenFuer('zeiten').avv], [null, null])
|
||||||
|
assert.ok(unterlagenFuer('news').leistungsbeschreibung.version)
|
||||||
|
const a = analyseUnterlagen()
|
||||||
|
assert.deepEqual(a, { avvVersion: config.analyse.avvVersion, legal: { lb: `/rechtliches/leistungsbeschreibung-website-analyse-${config.analyse.avvVersion}.html`, avv: `/rechtliches/avv-website-analyse-${config.analyse.avvVersion}.html` } })
|
||||||
|
assert.deepEqual(unterlagenFuer('analyse'), {
|
||||||
|
leistungsbeschreibung: { version: config.analyse.avvVersion, url: a.legal.lb },
|
||||||
|
avv: { version: config.analyse.avvVersion, url: a.legal.avv },
|
||||||
|
})
|
||||||
|
assert.throws(() => unterlagenFuer('gibtEsNicht'), TypeError)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('consentAboAnalyse und abowegAnalyse: Linkzeile, Zielgruppen-Häkchen und Knopf wörtlich wie in P4', () => {
|
||||||
|
const c = reg.consentAboAnalyse()
|
||||||
|
assert.deepEqual([c.version, c.avvVersion, c.knopf], [config.aboFunktionen.fassung, config.analyse.avvVersion, 'Analyse einschalten'])
|
||||||
|
assert.deepEqual(c.items, [
|
||||||
|
{ id: 'einschalten-zeile', text: `Es gelten Leistungsbeschreibung und Auftragsverarbeitung (Fassung ${config.analyse.avvVersion}).`, haken: false },
|
||||||
|
{ id: 'zielgruppe', text: 'Meine Website richtet sich nicht gezielt an Kinder unter 16 Jahren.', haken: true },
|
||||||
|
])
|
||||||
|
const w = reg.abowegAnalyse()
|
||||||
|
assert.deepEqual([w.key, w.leistungsbeschreibung.version, w.avv.version, w.widerruf, w.consent.knopf], ['analyse', config.analyse.avvVersion, config.analyse.avvVersion, null, 'Analyse einschalten'])
|
||||||
|
})
|
||||||
Reference in new issue
Block a user