diff --git a/docs/rechtliches/termine-widerrufsbelehrung-muster.md b/docs/rechtliches/termine-widerrufsbelehrung-muster.md index fc49219..4d2472c 100644 --- a/docs/rechtliches/termine-widerrufsbelehrung-muster.md +++ b/docs/rechtliches/termine-widerrufsbelehrung-muster.md @@ -11,8 +11,8 @@ Der Text unten ist die Fassung, die `widerrufsbelehrung(betrieb)` in `server/ser | Gestaltungshinweis in Anlage 1 | Umsetzung | |---|---| | Frist: Textbaustein a) für Dienstleistungsverträge | „des Vertragsabschlusses.“ (Fristbeginn ist der Vertragsschluss: bei einer sofort verbindlichen Buchung die Bestätigung per Link, bei einer Anfrage die Annahme durch den Betrieb) | -| „Fügen Sie Ihren Namen, Ihre Anschrift, Ihre Telefonnummer und Ihre E-Mail-Adresse ein.“ | Name, Anschrift, Telefon, E-Mail aus den Betriebsangaben der Einstellung (`betrieb.name`, `adresse`, `telefon`, `email`); fehlen Anschrift oder Telefon, entfallen sie aus dem Text | -| Widerrufsfunktion bei online geschlossenen Verträgen (§ 356a BGB) | „anderer geeigneter Hinweis“ statt Internetadresse: Schaltfläche „Vertrag widerrufen“ im Terminbereich der Website und Link „Termin verwalten“ in der Bestätigungs-E-Mail; danach der Gesetzeswortlaut zur Eingangsbestätigung unverändert | +| „Fügen Sie Ihren Namen, Ihre Anschrift, Ihre Telefonnummer und Ihre E-Mail-Adresse ein.“ | Name, Anschrift, Telefon, E-Mail aus den Betriebsangaben der Einstellung (`betrieb.name`, `adresse`, `telefon`, `email`); alle vier sind Pflicht, fehlt oder ist eine Angabe leer, wirft `widerrufsbelehrung()` einen TypeError | +| Widerrufsfunktion bei online geschlossenen Verträgen (§ 356a BGB) | Satzrahmen des Gesetzes wörtlich („Sie können Ihr Widerrufsrecht auch online unter … ausüben. Wenn Sie diese Online-Funktion nutzen, …“); in die Lücke „…“ kommt statt einer Internetadresse ein „anderer geeigneter Hinweis“: „Vertrag widerrufen“ (Schaltfläche im Terminbereich auf unserer Website sowie Link „Termin verwalten“ in Ihrer Bestätigungs-E-Mail); der Gesetzeswortlaut zur Eingangsbestätigung bleibt unverändert | | Absatz zum elektronisch ausfüllbaren Webformular | ausgelassen: Es gibt keine eigene Webformular-Funktion außer der Widerrufsfunktion | | Textbausteine zu Waren (Besitz, Rückzahlungsverweigerung, Abholung oder Rücksendung, Rücksendekosten, Wertverlust) | ausgelassen: Ein Termin hat keine Waren | | Dienstleistungen: Wertersatz („Haben Sie verlangt, dass die Dienstleistungen …“) | enthalten; „oder Lieferung von Wasser/Gas/Strom/Fernwärme [Unzutreffendes streichen]“ ist gestrichen, wie der Gestaltungshinweis es vorsieht | @@ -33,7 +33,7 @@ Die Widerrufsfrist beträgt vierzehn Tage ab dem Tag des Vertragsabschlusses. Um Ihr Widerrufsrecht auszuüben, müssen Sie uns ({{Name des Betriebs}}, {{Anschrift}}, Telefon: {{Telefon}}, E-Mail: {{E-Mail}}) mittels einer eindeutigen Erklärung (z. B. ein mit der Post versandter Brief oder eine E-Mail) über Ihren Entschluss, diesen Vertrag zu widerrufen, informieren. Sie können dafür das beigefügte Muster-Widerrufsformular verwenden, das jedoch nicht vorgeschrieben ist. -Sie können Ihr Widerrufsrecht auch online über die Schaltfläche „Vertrag widerrufen“ ausüben, die Sie im Terminbereich auf unserer Website und über den Link „Termin verwalten“ in Ihrer Bestätigungs-E-Mail finden. Wenn Sie diese Online-Funktion nutzen, übermitteln wir Ihnen auf einem dauerhaften Datenträger (z. B. durch eine E-Mail) unverzüglich eine Eingangsbestätigung mit Informationen zum Inhalt der Widerrufserklärung sowie dem Datum und der Uhrzeit ihres Eingangs. +Sie können Ihr Widerrufsrecht auch online unter „Vertrag widerrufen“ (Schaltfläche im Terminbereich auf unserer Website sowie Link „Termin verwalten“ in Ihrer Bestätigungs-E-Mail) ausüben. Wenn Sie diese Online-Funktion nutzen, übermitteln wir Ihnen auf einem dauerhaften Datenträger (z. B. durch eine E-Mail) unverzüglich eine Eingangsbestätigung mit Informationen zum Inhalt der Widerrufserklärung sowie dem Datum und der Uhrzeit ihres Eingangs. Zur Wahrung der Widerrufsfrist reicht es aus, dass Sie die Mitteilung über die Ausübung des Widerrufsrechts vor Ablauf der Widerrufsfrist absenden. @@ -67,7 +67,7 @@ Muster-Widerrufsformular 1. **Lieferkosten-Klammer** im Absatz „Folgen des Widerrufs“ („einschließlich der Lieferkosten (mit Ausnahme …)“) steht unverändert im Text. Anlage 1 enthält keinen Gestaltungshinweis, der sie bei reinen Dienstleistungen zu streichen erlaubt; sie ist deshalb nicht gekürzt. Prüfen, ob sie für Dienstleistungen entfallen darf. 2. **„… beginnen soll“**: Nach dem Streichen von „oder Lieferung von Wasser/Gas/Strom/Fernwärme“ steht „die Dienstleistungen … beginnen soll“ (Singular im Gesetzestext). Der Wortlaut ist bewusst nicht geglättet, damit das Muster unverändert bleibt. Prüfen, ob „sollen“ zulässig ist. -3. **Anschrift und Telefon**: Das Muster verlangt Anschrift und Telefonnummer. Die Einstellung verlangt nur Name und E-Mail. Das Portal sollte die Anschrift (und besser auch die Telefonnummer) verlangen, bevor eine verbindliche Verbraucher-Buchung möglich ist; sonst fehlt sie in der Belehrung. +3. **Anschrift und Telefon**: Das Muster verlangt Anschrift und Telefonnummer. `widerrufsbelehrung()` wirft deshalb einen TypeError, wenn eine der vier Angaben fehlt. Die Einstellungsprüfung (späterer Task) soll Anschrift und Telefon verlangen, sobald Gäste ein Widerrufsrecht haben können (öffentlich `widerruf: true`). 4. **Wertersatz-Absatz** gilt nur, wenn der Gast den vorzeitigen Beginn verlangt hat (Zustimmung `ZUSTIMMUNG_VORZEITIG`, nur bei Terminbeginn innerhalb von 14 Tagen). Der Absatz steht trotzdem immer in der Belehrung, wie das Muster es für Dienstleistungen vorsieht. -5. **„Terminbereich auf unserer Website“** ist als geeigneter Hinweis gewählt (Entscheidung R12). Prüfen, ob ein Hinweis ohne Internetadresse genügt oder die Adresse der Seite genannt werden muss. +5. **Hinweis statt Internetadresse**: „Vertrag widerrufen“ (Schaltfläche im Terminbereich auf unserer Website sowie Link „Termin verwalten“ in Ihrer Bestätigungs-E-Mail) ist als geeigneter Hinweis gewählt (Entscheidung R12). Prüfen, ob ein Hinweis ohne Internetadresse genügt oder die Adresse der Seite genannt werden muss. 6. **Gültigkeit der Gesetzlichkeitsfiktion**: Sie gilt nur, wenn das Muster inhaltlich und gestalterisch dem Gesetz entspricht. Jede Abweichung (auch Zeilenumbrüche, Überschriften, die „–“-Aufzählung im Formular) ist hier Darstellung, nicht Inhalt. Vor dem Einsatz bei fremden Betrieben durch eine Kanzlei prüfen lassen. diff --git a/server/services/termine/rechtstexte.js b/server/services/termine/rechtstexte.js index b9675c4..f51e1f7 100644 --- a/server/services/termine/rechtstexte.js +++ b/server/services/termine/rechtstexte.js @@ -7,8 +7,8 @@ * * widerrufsbelehrung: Muster der Anlage 1 und Muster-Widerrufsformular der Anlage 2 zu Art. 246a § 1 Abs. 2 Satz 2 EGBGB * (docs/rechtliches/quellen/, abgerufen 2026-10-02), Gestaltungshinweise fuer einen Dienstleistungsvertrag angewendet: - * Fristbeginn „des Vertragsabschlusses.“, Betriebsdaten, Hinweis auf die Widerrufsfunktion als „anderer geeigneter - * Hinweis“ (ohne Internetadresse), Wertersatz fuer Dienstleistungen. Alles Uebrige ist der Gesetzestext, unveraendert. + * Fristbeginn „des Vertragsabschlusses.“, Betriebsdaten (Name, Anschrift, Telefon, E-Mail, alle Pflicht), Hinweis auf die + * Widerrufsfunktion als „anderer geeigneter Hinweis“ (ohne Internetadresse) im woertlichen Satzrahmen, Wertersatz fuer Dienstleistungen. Alles Uebrige ist der Gesetzestext, unveraendert. * Ausgelassen: Textbausteine zu Waren (Besitz, Rueckzahlungsverweigerung, Rueckgabe, Rueckkosten, Wertverlust) und der * Absatz zum elektronischen Webformular (es gibt nur die Widerrufsfunktion). test/termineRechtstexte.test.js prueft * den Wortlaut gegen die Quelldateien. @@ -24,12 +24,21 @@ const ABSATZ = '\n\n' /** Einzeiliger Text: jeder Leerraum (auch Umbrueche) wird zu einem Leerzeichen, Rand getrimmt; Nicht-Text = ''. */ const einzeilig = (wert) => (typeof wert === 'string' ? wert.replace(/\s+/g, ' ').trim() : '') -/** Betriebsdaten fuer die Texte. Name und E-Mail sind Pflicht (die Einstellung prueft sie), Anschrift und Telefon entfallen, wenn leer. */ -function betriebsdaten(betrieb) { +/** + * Betriebsdaten fuer die Texte. Name und E-Mail sind immer Pflicht (die Einstellung prueft sie). Mit vollstaendig: true + * auch Anschrift und Telefonnummer (das Muster der Anlage 1 verlangt alle vier): fehlt eine Angabe oder ist sie leer, + * entsteht ein TypeError, nie ein unvollstaendiger Text. + */ +function betriebsdaten(betrieb, { vollstaendig = false } = {}) { const name = einzeilig(betrieb?.name) const email = einzeilig(betrieb?.email) if (!name || !email) throw new TypeError('Betriebsname und E-Mail-Adresse sind für die Rechtstexte erforderlich.') - return { name, email, adresse: einzeilig(betrieb.adresse), telefon: einzeilig(betrieb.telefon) } + const adresse = einzeilig(betrieb.adresse) + const telefon = einzeilig(betrieb.telefon) + if (vollstaendig && (!adresse || !telefon)) { + throw new TypeError('Anschrift und Telefonnummer des Betriebs sind für die Widerrufsbelehrung erforderlich.') + } + return { name, email, adresse, telefon } } const verbinde = (teile) => teile.filter(Boolean).join(', ') @@ -38,10 +47,11 @@ const verbinde = (teile) => teile.filter(Boolean).join(', ') const FRIST = 'des Vertragsabschlusses.' // Gestaltungshinweis a): Dienstleistungsvertrag -// Widerrufsfunktion: „anderer geeigneter Hinweis“ statt Internetadresse, danach der Gesetzeswortlaut. +// Widerrufsfunktion: Satzrahmen des Gesetzes wörtlich („Sie können Ihr Widerrufsrecht auch online unter … ausüben.“); in die +// Lücke „… [Internetadresse oder anderen geeigneten Hinweis …]“ kommt ein geeigneter Hinweis statt einer Internetadresse. const WIDERRUFSFUNKTION = - 'Sie können Ihr Widerrufsrecht auch online über die Schaltfläche „Vertrag widerrufen“ ausüben, ' - + 'die Sie im Terminbereich auf unserer Website und über den Link „Termin verwalten“ in Ihrer Bestätigungs-E-Mail finden. ' + 'Sie können Ihr Widerrufsrecht auch online unter „Vertrag widerrufen“ (Schaltfläche im Terminbereich auf unserer Website ' + + 'sowie Link „Termin verwalten“ in Ihrer Bestätigungs-E-Mail) ausüben. ' + 'Wenn Sie diese Online-Funktion nutzen, übermitteln wir Ihnen auf einem dauerhaften Datenträger (z. B. durch eine E-Mail) ' + 'unverzüglich eine Eingangsbestätigung mit Informationen zum Inhalt der Widerrufserklärung sowie dem Datum und der Uhrzeit ihres Eingangs.' @@ -53,7 +63,7 @@ const WERTERSATZ = /** Kontaktangaben fuer „Um Ihr Widerrufsrecht auszuüben, müssen Sie uns (…)“: Name, Anschrift, Telefonnummer, E-Mail-Adresse. */ function kontakt({ name, adresse, telefon, email }) { - return verbinde([name, adresse, telefon && `Telefon: ${telefon}`, `E-Mail: ${email}`]) + return verbinde([name, adresse, `Telefon: ${telefon}`, `E-Mail: ${email}`]) } function belehrungsAbsaetze(daten) { @@ -98,11 +108,12 @@ function formularAbsaetze({ name, adresse, email }) { /** * Widerrufsbelehrung und Muster-Widerrufsformular fuer einen Dienstleistungs-Termin, Sie-Form, als Text mit Zeilenumbruechen. - * betrieb: { name, email, adresse?, telefon? } aus der Einstellung. Fehlen Name oder E-Mail: TypeError. Fehlende Anschrift - * oder Telefonnummer entfallen aus dem Text (die Einstellung verlangt sie nicht; das Portal sollte die Anschrift verlangen). + * betrieb: { name, email, adresse, telefon } aus der Einstellung, alle vier Pflicht. Fehlt eine Angabe oder ist sie leer: TypeError + * (eine unvollstaendige gesetzliche Belehrung wird nie stillschweigend erzeugt). Die Einstellungspruefung soll Anschrift und + * Telefon verlangen, sobald Gaeste ein Widerrufsrecht haben koennen (spaeterer Task); bis dahin faengt diese Funktion es ab. */ export function widerrufsbelehrung(betrieb) { - const daten = betriebsdaten(betrieb) + const daten = betriebsdaten(betrieb, { vollstaendig: true }) return [...belehrungsAbsaetze(daten), ...formularAbsaetze(daten)].join(ABSATZ) } diff --git a/test/termineRechtstexte.test.js b/test/termineRechtstexte.test.js index 4c1efbd..88355bb 100644 --- a/test/termineRechtstexte.test.js +++ b/test/termineRechtstexte.test.js @@ -64,18 +64,19 @@ test('Belehrung: alle Muster-Absaetze der Anlage 1 stehen wörtlich darin, nur d } }) -test('Belehrung: Widerrufsfunktion als „anderer geeigneter Hinweis“, Rest des Gesetzeswortlauts unverändert', () => { +test('Belehrung: Satzrahmen der Widerrufsfunktion wörtlich aus der Anlage, nur die Lücke „…“ ist mit einem geeigneten Hinweis gefüllt', () => { const text = belehrung() - const m = /Wenn Sie diese Online-Funktion nutzen, ([^“]+?)\s*“/.exec(ANLAGE_1) - assert.ok(m, 'Gestaltungshinweis zur Widerrufsfunktion fehlt in anlage-1.txt') - assert.ok(m[1].includes('Eingangsbestätigung') && m[1].includes('Uhrzeit ihres Eingangs')) - const absatz = 'Sie können Ihr Widerrufsrecht auch online über die Schaltfläche „Vertrag widerrufen“ ausüben, ' - + 'die Sie im Terminbereich auf unserer Website und über den Link „Termin verwalten“ in Ihrer Bestätigungs-E-Mail finden. ' - + `Wenn Sie diese Online-Funktion nutzen, ${m[1]}` - assert.ok(text.includes(absatz), 'Absatz zur Widerrufsfunktion fehlt oder weicht ab') - assert.ok(text.includes('Vertrag widerrufen')) - assert.ok(!text.includes('online unter'), 'keine Internetadresse, kein Parameter') - assert.ok(!/https?:\/\//.test(text)) + // Rahmen und Gesetzesteil kommen aus anlage-1.txt, nichts davon ist hier abgeschrieben: + // „Sie können Ihr Widerrufsrecht auch online unter … [Internetadresse oder anderen geeigneten Hinweis …] ausüben. Wenn Sie diese Online-Funktion nutzen, …“ + const m = /„(Sie können Ihr Widerrufsrecht auch online unter) … \[[^\]]+\] (ausüben\.) (Wenn Sie diese Online-Funktion nutzen, [^“]+?)\s*“/.exec(ANLAGE_1) + assert.ok(m, 'Gestaltungshinweis zur Widerrufsfunktion fehlt oder hat sich geändert in anlage-1.txt') + assert.ok(m[3].includes('Eingangsbestätigung') && m[3].includes('Uhrzeit ihres Eingangs')) + const hinweis = '„Vertrag widerrufen“ (Schaltfläche im Terminbereich auf unserer Website sowie Link „Termin verwalten“ in Ihrer Bestätigungs-E-Mail)' + const absatz = `${m[1]} ${hinweis} ${m[2]} ${m[3]}` + assert.ok(text.includes(absatz), 'Absatz zur Widerrufsfunktion fehlt oder weicht vom Satzrahmen der Anlage ab') + assert.ok(text.includes('Vertrag widerrufen') && text.includes('Termin verwalten')) + assert.ok(!/https?:\/\/|www\./.test(text), 'keine Internetadresse, kein Parameter') + assert.ok(!text.includes('über die Schaltfläche'), 'kein umformulierter Satzrahmen') }) test('Belehrung: Wertersatz für Dienstleistungen, Unzutreffendes (Wasser/Gas/Strom/Fernwärme) gestrichen', () => { @@ -130,23 +131,25 @@ test('Belehrung: reiner Text mit Zeilenumbrüchen, nicht HTML-maskiert (die Mail assert.equal(belehrung(), belehrung(), 'deterministisch') }) -test('Belehrung: fehlende Telefonnummer oder Anschrift hinterlässt keine leeren Klammern oder Kommas', () => { - const ohneTel = belehrung({ ...BETRIEB, telefon: '' }) - assert.ok(ohneTel.includes('uns (Salon Müller & Söhne, Hauptstraße 1, 67722 Winnweiler, E-Mail: info@salon-mueller.de)')) - assert.ok(!ohneTel.includes('Telefon:')) - const ohneAdresse = belehrung({ ...BETRIEB, adresse: '', telefon: undefined }) - assert.ok(ohneAdresse.includes('uns (Salon Müller & Söhne, E-Mail: info@salon-mueller.de)')) - assert.ok(ohneAdresse.includes('An Salon Müller & Söhne, info@salon-mueller.de:')) - assert.ok(!/,\s*,|\(\s*,|,\s*\)|:\s*,/.test(ohneAdresse)) -}) - -test('Belehrung: Betriebsname und E-Mail sind Pflicht, Leerraum in Angaben wird zu einem Leerzeichen', () => { - assert.throws(() => widerrufsbelehrung({ ...BETRIEB, name: '' }), TypeError) - assert.throws(() => widerrufsbelehrung({ ...BETRIEB, email: undefined }), TypeError) +test('Belehrung: Name, Anschrift, Telefon und E-Mail sind Pflicht, eine unvollständige Belehrung entsteht nie (TypeError)', () => { + for (const feld of ['name', 'adresse', 'telefon', 'email']) { + for (const leer of [undefined, null, '', ' ', ' \n\t ', 42, {}]) { + assert.throws(() => widerrufsbelehrung({ ...BETRIEB, [feld]: leer }), TypeError, `${feld}=${JSON.stringify(leer)}`) + } + const ohne = { ...BETRIEB } + delete ohne[feld] + assert.throws(() => widerrufsbelehrung(ohne), TypeError, `${feld} fehlt`) + } + assert.throws(() => widerrufsbelehrung({ name: 'Salon Müller', email: 'info@salon.de' }), TypeError) assert.throws(() => widerrufsbelehrung(undefined), TypeError) assert.throws(() => widerrufsbelehrung(null), TypeError) + assert.throws(() => widerrufsbelehrung('Salon'), TypeError) +}) + +test('Belehrung: Leerraum in den Angaben wird zu einem Leerzeichen, vollständige Angaben stehen in Klammer und „An“-Zeile', () => { const text = belehrung({ ...BETRIEB, name: ' Salon \n Müller ', adresse: 'Hauptstraße 1,\n67722 Winnweiler' }) - assert.ok(text.includes('uns (Salon Müller, Hauptstraße 1, 67722 Winnweiler, Telefon:')) + assert.ok(text.includes('uns (Salon Müller, Hauptstraße 1, 67722 Winnweiler, Telefon: 06301 1234, E-Mail: info@salon-mueller.de)')) + assert.ok(text.includes('An Salon Müller, Hauptstraße 1, 67722 Winnweiler, info@salon-mueller.de:')) }) test('Belehrung ist die Quelle von docs/rechtliches/termine-widerrufsbelehrung-muster.md (Platzhalter-Fassung steht dort wörtlich)', () => {