Terminplaner: G01 Block bestaetigen, G02 kein_widerruf, G06 grund/nachricht, Hinweise zu Blockpaaren

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-03 08:17:43 +00:00
1 parent 274192e88e
commit 6c25a76e28
5 files changed
+69 -24

No files matched your search

@@ -8,9 +8,10 @@
Absender: „<Betrieb>“ <Portal-Postfach> · 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).
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
@@ -30,11 +31,9 @@ a{color:#111111;}
<tr><td class="px" bgcolor="{{farbe}}" style="background:{{farbe}};border-radius:16px 16px 0 0;padding:18px 40px;"><table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr><td class="pill" bgcolor="#ffffff" style="background:#ffffff;border-radius:8px;padding:8px 14px;font-family:Arial,Helvetica,sans-serif;font-size:17px;line-height:22px;font-weight:bold;color:#111111;"><span style="color:#111111;">{{betrieb_name}}</span></td></tr></table></td></tr>
<tr><td class="card" style="background:#ffffff;border-radius:0 0 16px 16px;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<!-- WENN bestaetigen -->
<tr><td class="px ink" style="padding:36px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:26px;line-height:32px;mso-line-height-rule:exactly;font-weight:bold;letter-spacing:-0.5px;color:#111111;"><!-- WENN sie -->Bitte bestätigen Sie Ihren Termin<!-- ENDE sie --><!-- WENN du -->Bitte bestätige deinen Termin<!-- ENDE du --></td></tr>
<tr><td class="px mut" style="padding:14px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:25px;mso-line-height-rule:exactly;color:#4a4843;"><!-- WENN sie -->Guten Tag,<br>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.<!-- ENDE sie --><!-- WENN du -->Hallo,<br>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.<!-- ENDE du --></td></tr>
<!-- WENN zu_viele -->
<tr><td class="px" style="padding:20px 40px 0 40px;"><table role="presentation" class="box" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f7f6f3;border:1px solid #e8e6e0;border-radius:12px;"><tr><td class="ink" style="padding:16px 20px;font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:21px;color:#111111;"><!-- WENN sie --><strong>Wichtiger Hinweis:</strong> Zu dieser E-Mail-Adresse gibt es bei {{betrieb_name}} bereits die höchstmögliche Zahl offener Termine. Der Termin unten wurde deshalb nicht für Sie reserviert, den Knopf können Sie ignorieren. Bitte sagen Sie einen Ihrer offenen Termine ab (Link in der jeweiligen Bestätigungs-E-Mail) oder rufen Sie uns an: {{betrieb_telefon}}.<!-- ENDE sie --><!-- WENN du --><strong>Wichtiger Hinweis:</strong> Zu dieser E-Mail-Adresse gibt es bei {{betrieb_name}} bereits die höchstmögliche Zahl offener Termine. Der Termin unten wurde deshalb nicht für dich reserviert, den Knopf kannst du ignorieren. Bitte sage einen deiner offenen Termine ab (Link in der jeweiligen Bestätigungs-E-Mail) oder ruf uns an: {{betrieb_telefon}}.<!-- ENDE du --></td></tr></table></td></tr>
<!-- ENDE zu_viele -->
<tr><td class="px" style="padding:24px 40px 0 40px;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:21px;mso-line-height-rule:exactly;">
<tr><td class="lbl mut rowline" width="150" valign="top" style="width:150px;padding:11px 12px 11px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Leistung</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;"><strong>{{leistung}}</strong></td></tr>
<tr><td class="lbl mut rowline" width="150" valign="top" style="width:150px;padding:11px 12px 11px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Datum</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{datum}}</td></tr>
@@ -42,7 +41,12 @@ a{color:#111111;}
</table></td></tr>
<tr><td class="px" style="padding:26px 40px 0 40px;"><table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr><td class="btn" bgcolor="#111111" style="background:#111111;border-radius:10px;"><a href="{{link}}" style="display:block;padding:14px 26px;font-family:Arial,Helvetica,sans-serif;font-size:15px;font-weight:bold;color:#ffffff;text-decoration:none;">Termin bestätigen &rarr;</a></td></tr></table></td></tr>
<tr><td class="px mut" style="padding:10px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:20px;mso-line-height-rule:exactly;color:#6b6964;"><!-- WENN sie -->Der Knopf öffnet die Bestätigungsseite. Dort sehen Sie noch einmal alle Angaben.<!-- ENDE sie --><!-- WENN du -->Der Knopf öffnet die Bestätigungsseite. Dort siehst du noch einmal alle Angaben.<!-- ENDE du --></td></tr>
<tr><td class="px mut" style="padding:14px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:20px;mso-line-height-rule:exactly;color:#6b6964;"><!-- WENN sie -->Das waren Sie nicht? Dann ignorieren Sie diese E-Mail.<!-- ENDE sie --><!-- WENN du -->Das warst du nicht? Dann ignoriere diese E-Mail.<!-- ENDE du --></td></tr>
<!-- ENDE bestaetigen -->
<!-- WENN zu_viele -->
<tr><td class="px ink" style="padding:36px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:26px;line-height:32px;mso-line-height-rule:exactly;font-weight:bold;letter-spacing:-0.5px;color:#111111;"><!-- WENN sie -->Ihr Termin konnte nicht vorgemerkt werden<!-- ENDE sie --><!-- WENN du -->Dein Termin konnte nicht vorgemerkt werden<!-- ENDE du --></td></tr>
<tr><td class="px mut" style="padding:14px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:25px;mso-line-height-rule:exactly;color:#4a4843;"><!-- WENN sie -->Guten Tag,<br>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}}.<!-- ENDE sie --><!-- WENN du -->Hallo,<br>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}}.<!-- ENDE du --></td></tr>
<!-- ENDE zu_viele -->
<tr><td class="px mut" style="padding:20px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:20px;mso-line-height-rule:exactly;color:#6b6964;"><!-- WENN sie -->Das waren Sie nicht? Dann ignorieren Sie diese E-Mail.<!-- ENDE sie --><!-- WENN du -->Das warst du nicht? Dann ignoriere diese E-Mail.<!-- ENDE du --></td></tr>
<tr><td class="px ink" style="padding:28px 40px 40px 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:24px;color:#111111;">Viele Grüße<br><strong>{{betrieb_name}}</strong></td></tr>
</table>
</td></tr>
@@ -8,10 +8,12 @@
Absender: „<Betrieb>“ <Portal-Postfach> · 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).
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
@@ -56,6 +58,9 @@ a{color:#111111;}
<!-- ENDE storno -->
<tr><td class="px" style="padding:26px 40px 0 40px;"><table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr><td class="btn" bgcolor="#111111" style="background:#111111;border-radius:10px;"><a href="{{link}}" style="display:block;padding:14px 26px;font-family:Arial,Helvetica,sans-serif;font-size:15px;font-weight:bold;color:#ffffff;text-decoration:none;">Termin verwalten &rarr;</a></td></tr></table></td></tr>
<tr><td class="px mut" style="padding:10px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:20px;mso-line-height-rule:exactly;color:#6b6964;"><!-- WENN sie -->Hier können Sie Ihren Termin ansehen, absagen oder verschieben, solange es die Bedingungen des Betriebs erlauben. Ein Kalendereintrag liegt dieser E-Mail bei.<!-- ENDE sie --><!-- WENN du -->Hier kannst du deinen Termin ansehen, absagen oder verschieben, solange es die Bedingungen des Betriebs erlauben. Ein Kalendereintrag liegt dieser E-Mail bei.<!-- ENDE du --></td></tr>
<!-- WENN kein_widerruf -->
<tr><td class="px mut" style="padding:30px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:25px;mso-line-height-rule:exactly;color:#4a4843;">Für diese Buchung besteht kein Widerrufsrecht (§ 312g Abs. 2 Nr. 9 BGB). Absagen richten sich nach den Stornobedingungen.</td></tr>
<!-- ENDE kein_widerruf -->
<!-- WENN widerruf -->
<tr><td class="px mut" style="padding:30px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:25px;mso-line-height-rule:exactly;color:#4a4843;"><!-- WENN sie --><strong>Widerrufsrecht.</strong> 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.<!-- ENDE sie --><!-- WENN du --><strong>Widerrufsrecht.</strong> 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.<!-- ENDE du --></td></tr>
<tr><td class="px" style="padding:18px 40px 0 40px;"><table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr><td class="btn2" style="border:2px solid #111111;border-radius:10px;"><a href="{{widerruf_link}}" style="display:block;padding:12px 24px;font-family:Arial,Helvetica,sans-serif;font-size:15px;font-weight:bold;color:#111111;text-decoration:none;">Vertrag widerrufen</a></td></tr></table></td></tr>
@@ -7,9 +7,10 @@
Betreff: {{betreff}}
Absender: „<Betrieb>“ <Portal-Postfach> · 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.
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
@@ -36,10 +37,13 @@ a{color:#111111;}
<tr><td class="lbl mut rowline" width="150" valign="top" style="width:150px;padding:11px 12px 11px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Datum</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{datum}}</td></tr>
<tr><td class="lbl mut rowline" width="150" valign="top" style="width:150px;padding:11px 12px 11px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Uhrzeit</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{zeit}} Uhr</td></tr>
<tr><td class="lbl mut rowline" width="150" valign="top" style="width:150px;padding:11px 12px 11px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Abgesagt von</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{absage_von}}</td></tr>
<!-- WENN grund -->
<tr><td class="lbl mut rowline" width="150" valign="top" style="width:150px;padding:11px 12px 11px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Grund</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{grund}}</td></tr>
<!-- ENDE grund -->
</table></td></tr>
<!-- WENN nachricht -->
<tr><td class="px ink" style="padding:24px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:15px;line-height:22px;font-weight:bold;color:#111111;">Grund und Nachricht</td></tr>
<tr><td class="px" style="padding:8px 40px 0 40px;"><table role="presentation" class="box" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f7f6f3;border:1px solid #e8e6e0;border-radius:12px;"><tr><td class="ink" style="padding:16px 20px;font-family:Arial,Helvetica,sans-serif;font-size:15px;line-height:23px;color:#111111;">{{grund}}</td></tr></table></td></tr>
<tr><td class="px ink" style="padding:24px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:15px;line-height:22px;font-weight:bold;color:#111111;">Nachricht von {{betrieb_name}}</td></tr>
<tr><td class="px" style="padding:8px 40px 0 40px;"><table role="presentation" class="box" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f7f6f3;border:1px solid #e8e6e0;border-radius:12px;"><tr><td class="ink" style="padding:16px 20px;font-family:Arial,Helvetica,sans-serif;font-size:15px;line-height:23px;color:#111111;">{{nachricht}}</td></tr></table></td></tr>
<!-- ENDE nachricht -->
<tr><td class="px mut" style="padding:20px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:20px;mso-line-height-rule:exactly;color:#6b6964;"><!-- WENN sie -->Die beigefügte Kalenderdatei entfernt den Termin aus Ihrem Kalender.<!-- ENDE sie --><!-- WENN du -->Die beigefügte Kalenderdatei entfernt den Termin aus deinem Kalender.<!-- ENDE du --></td></tr>
<tr><td class="px ink" style="padding:28px 40px 40px 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:24px;color:#111111;">Viele Grüße<br><strong>{{betrieb_name}}</strong></td></tr>
@@ -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).
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
+42 -11
View File
@@ -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 &lt;b&gt;ohne&lt;\/b&gt; Gründe\.<br>Zweite Zeile &amp; mehr/)
assert.ok(voll.text.includes('Widerrufsbelehrung\n\nWiderrufsrecht: 14 Tage <b>ohne</b> 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"[^>]*><tr><td[^>]*>Widerrufsbelehrung<br><br>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 })