Merge feature/terminplaner-tp2 (a629b25) in feature/oeffnungszeiten

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-05 04:42:31 +00:00
commit 1d5a565e9f
95 files changed
+12879 -546

No files matched your search

+118 -28
View File
@@ -165,6 +165,82 @@ const ustHinweis = 'ohne Umsatzsteuer gemäß § 19 UStG'
const ticketProjektName = (project) => project?.projectName || project?.subdomain || ''
/**
* Rechtsunterlage des Terminplaners mit Fassung und Adresse aus config.termine.avvVersion (bei jedem Zugriff gelesen).
* Tests duerfen sie ueberschreiben (Zuweisung); wird der aktuelle Standardwert zurueckgeschrieben (Wiederherstellen nach
* dem Test) oder undefined zugewiesen, gilt wieder der Standard und die Unterlage folgt der Konstante. null gilt ebenso,
* ausser bei nullIstWert (Widerrufsbelehrung): dort heisst null "keine Unterlage" (so setzen Tests eine Bestellung ohne sie).
*/
function termineRechtsunterlage(slug, { nullIstWert = false } = {}) {
let ueberschrieben // undefined = Standard
const standard = () => ({ version: config.termine.avvVersion, url: `/rechtliches/${slug}-${config.termine.avvVersion}.html` })
return {
get: () => (ueberschrieben === undefined ? standard() : ueberschrieben),
set(wert) {
const std = standard()
const zurueck = wert === undefined || (wert === null && !nullIstWert) || (wert !== null && wert.version === std.version && wert.url === std.url)
ueberschrieben = zurueck ? undefined : wert
},
}
}
const termineLb = termineRechtsunterlage('leistungsbeschreibung-terminplaner')
const termineAvv = termineRechtsunterlage('avv-terminplaner')
const termineWiderruf = termineRechtsunterlage('widerrufsbelehrung-funktionen', { nullIstWert: true })
const TERMINE_PREIS = '149 €'
const TERMINE_DAUER = 'meist innerhalb von 1–2 Wochen'
/**
* Bestelltexte des Terminplaners (Task F7): vier Haken, alle Besteller (D1), ohne Adressen im Text (die versionierten Links
* zeigt die Bestellseite daneben aus bestellung.dokumente). Die Fassung (consent.version) ist config.termine.avvVersion; die
* dynamischen Teile (Fassung, Preis, Umsatzsteuer-Hinweis, Dauer) stehen fertig im Text, gespeichert wird immer dieser Text.
* Die Haken werden je Fassung einmal gebaut und danach unveraendert herausgegeben (Tests aendern einzelne Texte und stellen sie
* zurueck); aendert sich die Fassung zur Laufzeit, entstehen sie neu. Bei Textaenderung neues Fassungsdatum und neuen Hash im
* Pin von test/featureConsentTexte.test.js. Zitat und Wertersatz-Satz von "beginn" und die Pflichtinformationen sind Entwurf bis
* zur Kanzlei (Kanzlei-Paket, Fragen Q3, Q5, Q19, Q20).
*/
function termineConsent() {
let versionGesetzt // undefined = config.termine.avvVersion
let gebaut = null // { version, items }
const version = () => versionGesetzt ?? config.termine.avvVersion
const items = (v) => [
{
id: 'unterlagen',
haken: true,
text: `Ich stimme der Leistungsbeschreibung Terminplaner und dem Auftragsverarbeitungsvertrag Terminplaner in der Fassung ${v} zu.`,
},
{
id: 'gesundheit',
haken: true,
text: 'Mein Angebot liegt nicht im Gesundheitsbereich (zum Beispiel keine Arzt-, Zahnarzt-, Psychotherapie-, Heilpraktiker- oder Physiotherapiepraxis).',
},
{
id: 'beginn',
haken: true,
text: 'Ich verlange ausdrücklich, dass WEBklar vor Ablauf der Widerrufsfrist mit der Einrichtung des Terminplaners beginnt. Mir ist bekannt, dass mein Widerrufsrecht mit der vollständigen Erbringung der Einrichtung erlischt (§ 356 Abs. 5 Nr. 2 BGB). Widerrufe ich vor der vollständigen Erbringung, schulde ich einen angemessenen Betrag für die bis dahin erbrachte Leistung (§ 357a Abs. 2 BGB).',
},
{
id: 'information',
haken: true,
text: `Ich habe die Pflichtinformationen zu dieser Bestellung gelesen: Leistung (Einrichtung des Terminplaners für diese Website: ein Widget an einer Stelle, gestaltet nach dem Fragebogen, mit Einbau und Datenschutz-Absatz), Gesamtpreis ${TERMINE_PREIS} einmalig (${ustHinweis}), voraussichtliche Dauer ${TERMINE_DAUER}, der Terminplaner endet mit dem Wartungs-Abo. Die Widerrufsbelehrung mit Muster-Widerrufsformular habe ich gelesen.`,
},
]
return {
get version() {
return version()
},
set version(wert) {
versionGesetzt = wert === undefined || wert === null || wert === config.termine.avvVersion ? undefined : wert
},
knopf: `Zahlungspflichtig bestellen · ${TERMINE_PREIS}`,
get items() {
const v = version()
if (gebaut?.version !== v) gebaut = { version: v, items: items(v) }
return gebaut.items
},
}
}
/**
* Register der kaufbaren Funktionen (News-Pinnwand, Terminplaner): Preis, Zustimmungen, Rechtsunterlagen,
* Ticket- und Mail-Texte je Funktion; reine Daten, config.js importiert keine Dienste. Die Helfer (featureConfig,
@@ -181,19 +257,30 @@ const features = {
label: { name: 'News-Pinnwand', kurz: 'News', keine: 'keine News-Pinnwand' },
priceLabel: '30 €',
setupEstimate: 'meist innerhalb von 1–2 Wochen',
// Wortlaut der Zustimmung; wird mit Zeitstempel am Feature-Dokument gespeichert (Widerruf, § 356 Abs. 4 BGB)
// Wortlaut der Zustimmung; wird mit Zeitstempel am Feature-Dokument gespeichert (Widerruf, § 356 Abs. 5 Nr. 2 BGB,
// Wertersatz § 357a Abs. 2 BGB). Aufbau wie der Gaestetext ZUSTIMMUNG_VORZEITIG; Zitat und Wertersatz-Satz sind
// Entwurf bis zur Kanzlei (Kanzlei-Paket, Frage Q3). Die Fassung folgt config.termine.avvVersion (<F>); bestehende
// Bestellungen behalten Text und Fassung, die am Dokument und im Nachweis gespeichert sind.
consent: {
version: '2026-09-30',
get version() {
return config.termine.avvVersion
},
knopf: 'Zahlungspflichtig bestellen · 30 €',
items: [
{
id: 'beginn',
haken: true,
text: 'Ich verlange ausdrücklich, dass WEBklar vor Ablauf der Widerrufsfrist mit dem Einbau beginnt. Mir ist bekannt, dass mein Widerrufsrecht erlischt, sobald der Einbau vollständig erbracht ist (§ 356 Abs. 4 BGB).',
text: 'Ich verlange ausdrücklich, dass WEBklar vor Ablauf der Widerrufsfrist mit dem Einbau beginnt. Mir ist bekannt, dass mein Widerrufsrecht mit der vollständigen Erbringung des Einbaus erlischt (§ 356 Abs. 5 Nr. 2 BGB). Widerrufe ich vor der vollständigen Erbringung, schulde ich einen angemessenen Betrag für die bis dahin erbrachte Leistung (§ 357a Abs. 2 BGB).',
},
],
},
leistungsbeschreibung: { version: '2026-09-30', url: '/vertragsunterlagen/funktionen-leistungsbeschreibung.html' },
// Stand der Leistungsbeschreibung (public/vertragsunterlagen/funktionen-leistungsbeschreibung.html) = <F>
leistungsbeschreibung: {
get version() {
return config.termine.avvVersion
},
url: '/vertragsunterlagen/funktionen-leistungsbeschreibung.html',
},
avv: null,
widerruf: null,
returnHash: 'news',
@@ -220,7 +307,7 @@ const features = {
knopf: 'Beitrag schreiben',
danach: 'Danach kannst Du Beiträge im Portal schreiben.',
bereit: 'Du kannst ab sofort Beiträge schreiben, sie erscheinen direkt bei Dir auf der Seite.',
preisHinweis: 'brutto, einmalig; die Nutzung ist kostenlos, es läuft kein Abo',
preisHinweis: `einmalig, ${ustHinweis}; die Nutzung ist kostenlos, es läuft kein Abo`,
anleitung: false,
},
requiresWartungsAbo: false,
@@ -233,30 +320,29 @@ const features = {
key: 'termine',
product: 'termineSetup',
label: { name: 'Terminplaner', kurz: 'Termine', keine: 'keinen Terminplaner' },
priceLabel: '149 €',
setupEstimate: 'meist innerhalb von 1–2 Wochen',
// Platzhalter bis Block F (Rechtsrunde): Struktur und IDs stehen, Texte und Fassung ersetzt Block F
consent: {
version: 'ENTWURF-2026-10-03',
knopf: 'Zahlungspflichtig bestellen · 149 €',
items: [
{ id: 'unterlagen', haken: true, text: 'ENTWURF (Block F): Zustimmung zu Leistungsbeschreibung und Auftragsverarbeitungsvertrag (AVV) Terminplaner.' },
{ id: 'gesundheit', haken: true, text: 'ENTWURF (Block F): Bestätigung, dass der Terminplaner nicht im Gesundheitsbereich eingesetzt wird.' },
{
id: 'beginn',
haken: true,
text: 'ENTWURF (Block F): Ausdrückliches Verlangen, dass vor Ablauf der Widerrufsfrist mit dem Einbau begonnen wird, mit Hinweis auf das Erlöschen des Widerrufsrechts und den Wertersatz.',
},
{
id: 'information',
haken: true,
text: 'ENTWURF (Block F): Pflichtinformationen zu Leistung, Gesamtpreis, Ende mit dem Wartungs-Abo und Einrichtungsdauer.',
},
],
priceLabel: TERMINE_PREIS,
setupEstimate: TERMINE_DAUER,
consent: termineConsent(),
// Fassung und Adresse folgen config.termine.avvVersion bei jedem Zugriff (Slugs wie in services/legalDocs.js; config.js
// importiert keine Dienste, test/legalDocs.test.js gleicht beide ab)
get leistungsbeschreibung() {
return termineLb.get()
},
set leistungsbeschreibung(wert) {
termineLb.set(wert)
},
get avv() {
return termineAvv.get()
},
set avv(wert) {
termineAvv.set(wert)
},
get widerruf() {
return termineWiderruf.get()
},
set widerruf(wert) {
termineWiderruf.set(wert)
},
leistungsbeschreibung: { version: 'ENTWURF', url: '' },
avv: { version: 'ENTWURF', url: '' },
widerruf: null,
returnHash: 'termine',
returnProjekt: true,
ticket: {
@@ -617,6 +703,10 @@ export const config = {
.split(',')
.map((s) => s.trim().toLowerCase())
.filter(Boolean),
// Fassung der Terminplaner-Bestellunterlagen (Leistungsbeschreibung, AVV, Widerrufsbelehrung, Bestelltexte, News-
// Zustimmung): Berliner Kalendertag des Fassungs-Commits (JJJJ-MM-TT). Eine ausgelieferte Fassung wird nie
// ueberschrieben: nach Uebergabe an die Kanzlei oder Livegang heisst jede Textaenderung neues Datum und neue Dateien.
avvVersion: '2026-10-05',
holdMinutes: 30,
zelleMin: 5,
mailsProStunde: 200,