diff --git a/server/mail-templates/G01 Termin bestaetigen.html b/server/mail-templates/G01 Termin bestaetigen.html index 3653085..7ef68c7 100644 --- a/server/mail-templates/G01 Termin bestaetigen.html +++ b/server/mail-templates/G01 Termin bestaetigen.html @@ -8,9 +8,10 @@ Absender: „“ · Antworten an die Betriebs-E-Mail Empfänger: Gast (die beim Buchen angegebene Adresse) · ohne BCC Platzhalter: {{leistung}}, {{datum}}, {{zeit}}, {{link}}, {{betreff}}, {{farbe}}, {{betrieb_name}}, {{betrieb_adresse}}, {{betrieb_telefon}}, {{betrieb_email}}, {{impressum_url}}, {{datenschutz_url}} - Optionale Blöcke (Anrede genau einer von sie und du, vom Server gesetzt): sie, du, zu_viele + Optionale Blöcke (Anrede genau einer von sie und du, vom Server gesetzt): sie, du, bestaetigen, zu_viele Kein vom Gast eingegebener Text (auch kein Name): die Mail geht an eine noch nicht bestätigte Adresse. - Block zu_viele: Grenze offener Termine erreicht, nichts wurde reserviert. + Block bestaetigen: Normalfall mit Terminangaben und Bestätigungsknopf. Block zu_viele: Grenze offener Termine erreicht, nichts wurde reserviert (kein Knopf). + Schließen sich aus: bestaetigen und zu_viele (der Server setzt genau einen von beiden). --> @@ -30,11 +31,9 @@ a{color:#111111;}
{{betrieb_name}}
+ - - - - + + + + + +
Bitte bestätigen Sie Ihren TerminBitte bestätige deinen Termin
Guten Tag,
bei {{betrieb_name}} wurde mit dieser E-Mail-Adresse ein Termin ausgewählt. Bitte bestätigen Sie ihn mit dem Knopf unten. Der Termin ist nur kurze Zeit für Sie reserviert.Hallo,
bei {{betrieb_name}} wurde mit dieser E-Mail-Adresse ein Termin ausgewählt. Bitte bestätige ihn mit dem Knopf unten. Der Termin ist nur kurze Zeit für dich reserviert.
@@ -42,7 +41,12 @@ a{color:#111111;}
Leistung{{leistung}}
Datum{{datum}}
Termin bestätigen →
Der Knopf öffnet die Bestätigungsseite. Dort sehen Sie noch einmal alle Angaben.Der Knopf öffnet die Bestätigungsseite. Dort siehst du noch einmal alle Angaben.
Das waren Sie nicht? Dann ignorieren Sie diese E-Mail.Das warst du nicht? Dann ignoriere diese E-Mail.
Ihr Termin konnte nicht vorgemerkt werdenDein Termin konnte nicht vorgemerkt werden
Guten Tag,
bei {{betrieb_name}} wurde mit dieser E-Mail-Adresse ein Termin ausgewählt. Zu dieser E-Mail-Adresse gibt es dort aber bereits die höchstmögliche Zahl offener Termine. Der gewünschte Termin wurde deshalb nicht für Sie reserviert. Bitte sagen Sie einen Ihrer offenen Termine ab (Link in der jeweiligen Bestätigungs-E-Mail) oder rufen Sie uns an: {{betrieb_telefon}}.Hallo,
bei {{betrieb_name}} wurde mit dieser E-Mail-Adresse ein Termin ausgewählt. Zu dieser E-Mail-Adresse gibt es dort aber bereits die höchstmögliche Zahl offener Termine. Der gewünschte Termin wurde deshalb nicht für dich reserviert. Bitte sage einen deiner offenen Termine ab (Link in der jeweiligen Bestätigungs-E-Mail) oder ruf uns an: {{betrieb_telefon}}.
Das waren Sie nicht? Dann ignorieren Sie diese E-Mail.Das warst du nicht? Dann ignoriere diese E-Mail.
Viele Grüße
{{betrieb_name}}
diff --git a/server/mail-templates/G02 Termin bestaetigt.html b/server/mail-templates/G02 Termin bestaetigt.html index 9ec8274..be95394 100644 --- a/server/mail-templates/G02 Termin bestaetigt.html +++ b/server/mail-templates/G02 Termin bestaetigt.html @@ -8,10 +8,12 @@ Absender: „“ · Antworten an die Betriebs-E-Mail Empfänger: Gast · ohne BCC Platzhalter: {{gast_name}}, {{leistung}}, {{person}}, {{datum}}, {{zeit}}, {{dauer}}, {{preis}}, {{zahlung}}, {{nummer}}, {{link}}, {{ort}}, {{storno_text}}, {{widerrufsbelehrung}}, {{widerruf_link}}, {{betreff}}, {{farbe}}, {{betrieb_name}}, {{betrieb_adresse}}, {{betrieb_telefon}}, {{betrieb_email}}, {{impressum_url}}, {{datenschutz_url}} - Optionale Blöcke (Anrede genau einer von sie und du, vom Server gesetzt): sie, du, preis, person, storno, widerruf, anfrage_angenommen + Optionale Blöcke (Anrede genau einer von sie und du, vom Server gesetzt): sie, du, preis, person, storno, widerruf, kein_widerruf, anfrage_angenommen Block preis: Preis und Zahlungsart. Block person: Person oder Platz. Block storno: Stornobedingungen im Wortlaut ({{storno_text}}). Block widerruf: Widerrufsrecht besteht ({{widerrufsbelehrung}} als Text mit Zeilenumbrüchen, {{widerruf_link}}). + Block kein_widerruf: Verbraucherbuchung einer Freizeitdienstleistung mit festem Termin (§ 312g Abs. 2 Nr. 9 BGB), gleicher Wortlaut in Sie- und Du-Fassung. Block anfrage_angenommen: der Betrieb hat eine Anfrage angenommen. Kalendereintrag (.ics) kommt als icalEvent. + Schließen sich aus: widerruf und kein_widerruf (der Server setzt höchstens einen von beiden). --> @@ -56,6 +58,9 @@ a{color:#111111;}
Termin verwalten →
Hier können Sie Ihren Termin ansehen, absagen oder verschieben, solange es die Bedingungen des Betriebs erlauben. Ein Kalendereintrag liegt dieser E-Mail bei.Hier kannst du deinen Termin ansehen, absagen oder verschieben, solange es die Bedingungen des Betriebs erlauben. Ein Kalendereintrag liegt dieser E-Mail bei. + +Für diese Buchung besteht kein Widerrufsrecht (§ 312g Abs. 2 Nr. 9 BGB). Absagen richten sich nach den Stornobedingungen. + Widerrufsrecht. Wenn Sie Verbraucher sind, können Sie diesen Vertrag innerhalb von 14 Tagen ab Vertragsschluss ohne Angabe von Gründen widerrufen. Dafür können Sie jederzeit den Knopf „Vertrag widerrufen“ nutzen. Die ausführliche Widerrufsbelehrung und das Muster-Widerrufsformular finden Sie weiter unten in dieser E-Mail.Widerrufsrecht. Wenn du Verbraucher bist, kannst du diesen Vertrag innerhalb von 14 Tagen ab Vertragsschluss ohne Angabe von Gründen widerrufen. Dafür kannst du jederzeit den Knopf „Vertrag widerrufen“ nutzen. Die ausführliche Widerrufsbelehrung und das Muster-Widerrufsformular findest du weiter unten in dieser E-Mail.
Vertrag widerrufen
diff --git a/server/mail-templates/G06 Termin abgesagt.html b/server/mail-templates/G06 Termin abgesagt.html index d7e6a2d..1caeb74 100644 --- a/server/mail-templates/G06 Termin abgesagt.html +++ b/server/mail-templates/G06 Termin abgesagt.html @@ -7,9 +7,10 @@ Betreff: {{betreff}} Absender: „“ · Antworten an die Betriebs-E-Mail Empfänger: Gast · ohne BCC - Platzhalter: {{gast_name}}, {{leistung}}, {{datum}}, {{zeit}}, {{absage_von}}, {{grund}}, {{betreff}}, {{farbe}}, {{betrieb_name}}, {{betrieb_adresse}}, {{betrieb_telefon}}, {{betrieb_email}}, {{impressum_url}}, {{datenschutz_url}} - Optionale Blöcke (Anrede genau einer von sie und du, vom Server gesetzt): sie, du, nachricht - Block nachricht: Grund und Nachricht ({{grund}}). Kalenderdatei (.ics, CANCEL) kommt als icalEvent. + Platzhalter: {{gast_name}}, {{leistung}}, {{datum}}, {{zeit}}, {{absage_von}}, {{grund}}, {{nachricht}}, {{betreff}}, {{farbe}}, {{betrieb_name}}, {{betrieb_adresse}}, {{betrieb_telefon}}, {{betrieb_email}}, {{impressum_url}}, {{datenschutz_url}} + Optionale Blöcke (Anrede genau einer von sie und du, vom Server gesetzt): sie, du, grund, nachricht + Block grund: gewählter Grund bei Absage durch den Betrieb ({{grund}}). Block nachricht: freie Nachricht des Betriebs ({{nachricht}}). Kalenderdatei (.ics, CANCEL) kommt als icalEvent. + Schließen sich nicht aus: grund und nachricht sind unabhängig und können zusammen gesetzt werden. --> @@ -36,10 +37,13 @@ a{color:#111111;} Datum{{datum}} Uhrzeit{{zeit}} Uhr Abgesagt von{{absage_von}} + +Grund{{grund}} + -Grund und Nachricht - +Nachricht von {{betrieb_name}} + Die beigefügte Kalenderdatei entfernt den Termin aus Ihrem Kalender.Die beigefügte Kalenderdatei entfernt den Termin aus deinem Kalender. Viele Grüße
{{betrieb_name}} diff --git a/server/mail-templates/G08 Eingang Widerruf.html b/server/mail-templates/G08 Eingang Widerruf.html index e0e800c..68cdacb 100644 --- a/server/mail-templates/G08 Eingang Widerruf.html +++ b/server/mail-templates/G08 Eingang Widerruf.html @@ -10,7 +10,8 @@ Platzhalter: {{eingang}}, {{nummer}}, {{betreff}}, {{farbe}}, {{betrieb_name}}, {{betrieb_adresse}}, {{betrieb_telefon}}, {{betrieb_email}}, {{impressum_url}}, {{datenschutz_url}} Optionale Blöcke (Anrede genau einer von sie und du, vom Server gesetzt): sie, du, zugeordnet, nach_frist Kein vom Gast eingegebener Text (weder Name noch Freitext): die Mail geht an eine ungeprüfte Adresse. - Höchstens einer der Blöcke zugeordnet (wirksamer Widerruf, Buchung abgesagt) und nach_frist (Frist bei Eingang abgelaufen). {{nummer}} nur im Block zugeordnet. + Block zugeordnet: wirksamer Widerruf, Buchung abgesagt ({{nummer}} nur hier). Block nach_frist: Frist bei Eingang abgelaufen. + Schließen sich aus: zugeordnet und nach_frist (der Server setzt höchstens einen von beiden). --> diff --git a/test/mailTemplates.test.js b/test/mailTemplates.test.js index e196491..a52f9be 100644 --- a/test/mailTemplates.test.js +++ b/test/mailTemplates.test.js @@ -590,12 +590,12 @@ test('G01 und G08 enthalten keinen vom Gast eingegebenen Text (kein Platzhalter test('G-Vorlagen: Platzhalter und Blöcke wie im Plan', () => { const common = ['betreff', 'farbe', 'betrieb_name', 'betrieb_adresse', 'betrieb_telefon', 'betrieb_email', 'impressum_url', 'datenschutz_url'] const expected = { - G01: { p: ['leistung', 'datum', 'zeit', 'link'], b: ['zu_viele'] }, - G02: { p: ['gast_name', 'leistung', 'person', 'datum', 'zeit', 'dauer', 'preis', 'zahlung', 'nummer', 'link', 'ort', 'storno_text', 'widerrufsbelehrung', 'widerruf_link'], b: ['preis', 'person', 'storno', 'widerruf', 'anfrage_angenommen'] }, + G01: { p: ['leistung', 'datum', 'zeit', 'link'], b: ['bestaetigen', 'zu_viele'] }, + G02: { p: ['gast_name', 'leistung', 'person', 'datum', 'zeit', 'dauer', 'preis', 'zahlung', 'nummer', 'link', 'ort', 'storno_text', 'widerrufsbelehrung', 'widerruf_link'], b: ['preis', 'person', 'storno', 'widerruf', 'kein_widerruf', 'anfrage_angenommen'] }, G03: { p: ['gast_name', 'leistung', 'datum', 'zeit', 'link'], b: [] }, G04: { p: ['gast_name', 'leistung', 'datum', 'zeit', 'nachricht'], b: ['nachricht'] }, G05: { p: ['gast_name', 'leistung', 'person', 'datum', 'zeit', 'link'], b: ['person'] }, - G06: { p: ['gast_name', 'leistung', 'datum', 'zeit', 'absage_von', 'grund'], b: ['nachricht'] }, + G06: { p: ['gast_name', 'leistung', 'datum', 'zeit', 'absage_von', 'grund', 'nachricht'], b: ['grund', 'nachricht'] }, G07: { p: ['gast_name', 'leistung', 'alt_datum', 'alt_zeit', 'datum', 'zeit', 'link'], b: [] }, G08: { p: ['eingang', 'nummer'], b: ['zugeordnet', 'nach_frist'] }, } @@ -606,22 +606,32 @@ test('G-Vorlagen: Platzhalter und Blöcke wie im Plan', () => { } }) -test('G01: Knopf zur Bestätigung, Hinweis für Dritte; Block zu_viele nennt Telefon und Absagen', () => { +test('G01: Block bestaetigen mit Angaben und Knopf, Block zu_viele nur mit eigenem Text und ohne Knopf', () => { const tpl = loadTemplate('G01') const values = { ...sampleValues(tpl), betrieb_name: 'Salon Muster', betrieb_telefon: '06301 1234', leistung: 'Herrenschnitt', datum: 'Montag, 12.10.2026', zeit: '10:00', link: 'https://project.webklar.com/termin.html#v=abc' } - const sie = renderMail('G01', values, { sie: true }) + const sie = renderMail('G01', values, { sie: true, bestaetigen: true }) assert.deepEqual(sie.problems, []) assert.match(sie.text, /Leistung: Herrenschnitt/) assert.match(sie.text, /Datum: Montag, 12\.10\.2026/) assert.match(sie.text, /Uhrzeit: 10:00 Uhr/) assert.match(sie.text, /Termin bestätigen: https:\/\/project\.webklar\.com\/termin\.html#v=abc/) assert.match(sie.text, /Das waren Sie nicht\? Dann ignorieren Sie diese E-Mail\./) - assert.doesNotMatch(sie.text, /höchstmögliche Zahl/) + assert.doesNotMatch(sie.text, /höchstmögliche Zahl|rufen Sie uns an/) + assert.match(renderMail('G01', values, { du: true, bestaetigen: true }).text, /Das warst du nicht\? Dann ignoriere diese E-Mail\./) + + // zu_viele: nichts reserviert, also weder Terminangaben noch Knopf noch Link const viele = renderMail('G01', values, { sie: true, zu_viele: true }) + assert.deepEqual(viele.problems, []) assert.match(viele.text, /bereits die höchstmögliche Zahl offener Termine/) assert.match(viele.text, /sagen Sie einen Ihrer offenen Termine ab/) - assert.match(viele.text, /rufen Sie uns an: 06301 1234/) - assert.match(renderMail('G01', values, { du: true }).text, /Das warst du nicht\? Dann ignoriere diese E-Mail\./) + assert.match(viele.text, /rufen Sie uns an: 06301 1234\./) + assert.doesNotMatch(viele.text, /Herrenschnitt|12\.10\.2026|Uhrzeit|Termin bestätigen|termin\.html|ignorieren Sie den Knopf|Knopf/) + assert.doesNotMatch(viele.html, /termin\.html|Herrenschnitt/) + assert.match(renderMail('G01', values, { du: true, zu_viele: true }).text, /ruf uns an: 06301 1234\./) + + // ohne gesetzten Block bleibt nur der gemeinsame Rahmen + const leer = renderMail('G01', values, { sie: true }) + assert.doesNotMatch(leer.text, /Herrenschnitt|höchstmögliche Zahl|Termin bestätigen/) }) test('G02: Zusammenfassung, Verwaltungslink, Preis, Person, Storno und Widerruf nur mit ihren Blöcken', () => { @@ -653,9 +663,20 @@ test('G02: Zusammenfassung, Verwaltungslink, Preis, Person, Storno und Widerruf // Belehrung wörtlich, Zeilenumbrüche bleiben, Sonderzeichen sind maskiert assert.match(voll.html, /Widerrufsrecht: 14 Tage <b>ohne<\/b> Gründe\.
Zweite Zeile & mehr/) assert.ok(voll.text.includes('Widerrufsbelehrung\n\nWiderrufsrecht: 14 Tage ohne Gründe.\nZweite Zeile & mehr'), voll.text) + assert.doesNotMatch(voll.text, /kein Widerrufsrecht/, 'widerruf und kein_widerruf schließen sich aus') // der Wortlaut der Belehrung steht in einem abgesetzten Kasten assert.match(voll.html, /class="box"[^>]*>]*>Widerrufsbelehrung

Widerrufsrecht/) + // Freizeitausnahme: Hinweis statt Widerrufsrecht + const kein = renderMail('G02', values, { sie: true, kein_widerruf: true }) + assert.deepEqual(kein.problems, []) + assert.ok(kein.text.includes('Für diese Buchung besteht kein Widerrufsrecht (§ 312g Abs. 2 Nr. 9 BGB). Absagen richten sich nach den Stornobedingungen.'), kein.text) + assert.doesNotMatch(kein.text, /Vertrag widerrufen|Widerrufsbelehrung|Muster-Widerrufsformular/) + const keinDu = renderMail('G02', values, { du: true, kein_widerruf: true }) + assert.ok(keinDu.text.includes('Für diese Buchung besteht kein Widerrufsrecht (§ 312g Abs. 2 Nr. 9 BGB). Absagen richten sich nach den Stornobedingungen.'), keinDu.text) + assert.doesNotMatch(keinDu.text, SIE_FORM) + assert.doesNotMatch(basis.text, /kein Widerrufsrecht/) + const du = renderMail('G02', values, { du: true, anfrage_angenommen: true, widerruf: true }) assert.match(du.text, /Dein Termin ist bestätigt/) assert.match(du.text, /hat deine Terminanfrage angenommen/) @@ -681,10 +702,20 @@ test('G03, G04, G05, G06, G07: Inhalt je Mail', () => { assert.doesNotMatch(renderMail('G05', { ...sampleValues(t05), person: 'Anna' }, { du: true }).text, /Anna/) const t06 = loadTemplate('G06') - const g06 = renderMail('G06', { ...sampleValues(t06), absage_von: 'dem Betrieb', grund: 'Krankheit im Team' }, { sie: true }) + const werte06 = { ...sampleValues(t06), absage_von: 'dem Betrieb', grund: 'Krankheit im Team', nachricht: 'Wir melden uns wegen eines neuen Termins.' } + const g06 = renderMail('G06', werte06, { sie: true }) assert.match(g06.text, /Abgesagt von: dem Betrieb/) - assert.doesNotMatch(g06.text, /Krankheit/) - assert.match(renderMail('G06', { ...sampleValues(t06), grund: 'Krankheit im Team' }, { sie: true, nachricht: true }).text, /Krankheit im Team/) + assert.doesNotMatch(g06.text, /Krankheit|Grund:|neuen Termins/) + const g06grund = renderMail('G06', werte06, { sie: true, grund: true }) + assert.match(g06grund.text, /Grund: Krankheit im Team/) + assert.doesNotMatch(g06grund.text, /neuen Termins/) + const g06nachricht = renderMail('G06', werte06, { sie: true, nachricht: true }) + assert.match(g06nachricht.text, /Nachricht von Wert-betrieb_name\s+Wir melden uns wegen eines neuen Termins\./) + assert.doesNotMatch(g06nachricht.text, /Krankheit/) + const beide = renderMail('G06', werte06, { sie: true, grund: true, nachricht: true }) + assert.deepEqual(beide.problems, []) + assert.match(beide.text, /Krankheit im Team/) + assert.match(beide.text, /neuen Termins/) const t07 = loadTemplate('G07') const g07 = renderMail('G07', { ...sampleValues(t07), alt_datum: '12.10.2026', alt_zeit: '10:00', datum: '13.10.2026', zeit: '11:30' }, { du: true })