]*>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/)
assert.ok(du.text.includes(WEG), du.text)
assert.doesNotMatch(du.text, /jederzeit/)
assert.doesNotMatch(du.text, SIE_FORM)
})
test('G03, G04, G05, G06, G07: Inhalt je Mail', () => {
const g03 = renderMail('G03', { ...sampleValues(loadTemplate('G03')), gast_name: 'Erika', betrieb_name: 'Salon Muster', link: 'https://x.example/m' }, { sie: true })
assert.match(g03.text, /Salon Muster prüft sie und meldet sich bei Ihnen/)
assert.match(g03.text, /Anfrage absagen: https:\/\/x\.example\/m/)
const t04 = loadTemplate('G04')
const g04 = renderMail('G04', { ...sampleValues(t04), nachricht: 'Am Montag sind wir leider im Urlaub.' }, { sie: true })
assert.doesNotMatch(g04.text, /Urlaub/)
assert.match(g04.text, /leider kann Wert-betrieb_name Ihre Terminanfrage nicht annehmen/)
assert.match(renderMail('G04', { ...sampleValues(t04), nachricht: 'Am Montag sind wir leider im Urlaub.' }, { sie: true, nachricht: true }).text, /Am Montag sind wir leider im Urlaub\./)
const t05 = loadTemplate('G05')
const g05 = renderMail('G05', { ...sampleValues(t05), person: 'Anna', link: 'https://x.example/m' }, { du: true, person: true })
assert.match(g05.text, /Anna/)
assert.match(g05.text, /Termin verwalten: https:\/\/x\.example\/m/)
assert.match(g05.text, /Erinnerung/)
assert.doesNotMatch(renderMail('G05', { ...sampleValues(t05), person: 'Anna' }, { du: true }).text, /Anna/)
const t06 = loadTemplate('G06')
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|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 })
assert.match(g07.text, /Bisher: 12\.10\.2026, 10:00 Uhr/)
assert.match(g07.text, /Neu: 13\.10\.2026, 11:30 Uhr/)
})
test('G08: Eingangsbestätigung mit Datum und Uhrzeit, Blöcke zugeordnet und nach_frist, nichts vom Gast', () => {
const tpl = loadTemplate('G08')
const values = { ...sampleValues(tpl), betrieb_name: 'Salon Muster', eingang: '02.10.2026, 10:07 Uhr', nummer: 'AB12CD34' }
const ohne = renderMail('G08', values, { sie: true })
assert.deepEqual(ohne.problems, [])
assert.match(ohne.text, /Eingegangen am: 02\.10\.2026, 10:07 Uhr/)
assert.match(ohne.text, /Widerruf/)
assert.doesNotMatch(ohne.text, /AB12CD34|bereits abgelaufen/)
const zugeordnet = renderMail('G08', values, { sie: true, zugeordnet: true })
assert.match(zugeordnet.text, /Buchung AB12CD34/)
assert.match(zugeordnet.text, /Der Termin ist abgesagt\./)
assert.doesNotMatch(zugeordnet.text, /bereits abgelaufen/)
assert.doesNotMatch(zugeordnet.text, /keiner Buchung zuordnen/, 'zugeordnet widerspricht sich nicht selbst')
const spaet = renderMail('G08', values, { sie: true, nach_frist: true })
assert.match(spaet.text, /Nach unseren Unterlagen war die Widerrufsfrist bei Eingang Ihrer Erklärung bereits abgelaufen\./)
assert.doesNotMatch(spaet.text, /Der Termin ist abgesagt|AB12CD34|keiner Buchung zuordnen/)
// der Satz für alle Fälle ist in jedem Zustand wahr, in beiden Anreden
for (const blocks of [{}, { zugeordnet: true }, { nach_frist: true }]) {
assert.match(renderMail('G08', values, { sie: true, ...blocks }).text, /Salon Muster prüft Ihre Erklärung und meldet sich bei Ihnen, falls noch etwas zu klären ist\./)
assert.match(renderMail('G08', values, { du: true, ...blocks }).text, /Salon Muster prüft deine Erklärung und meldet sich bei dir, falls noch etwas zu klären ist\./)
}
assert.match(renderMail('G08', values, { du: true, nach_frist: true }).text, /Nach unseren Unterlagen war die Widerrufsfrist bei Eingang deiner Erklärung bereits abgelaufen\./)
// Hinweis für Dritte: wer nichts erklärt hat, kann die Mail ignorieren
assert.match(ohne.text, /Waren Sie das nicht, können Sie diese Nachricht ignorieren\./)
})
// Terminplaner TP2 (E3): K22 und K23 stehen in Du-Form (K02/K03 bleiben Sie-Form wie ihr Rest); Öffnungszeiten live: K27–K30 ebenso
test('K22, K23, K27–K30: Du-Form fest, kein Sie und kein Ihr, auch mit allen Blöcken', () => {
for (const id of ['K22', 'K23', 'K27', 'K28', 'K29', 'K30']) {
const tpl = loadTemplate(id)
assert.ok(!tpl.blocks.includes('sie') && !tpl.blocks.includes('du'), `${id}: Du-Form fest, keine Anredeblöcke`)
const mail = renderMail(id, sampleValues(tpl), allBlocks(tpl, true))
assert.doesNotMatch(mail.text, SIE_FORM, `${id}: Du-Form\n${mail.text}`)
assert.match(mail.text, /Hallo Wert-kunde_name,/, id)
assert.match(mail.text, DU_FORM, `${id}: spricht den Kunden mit Du an`)
}
})
test('K20 und K21: WEBklar-Layout in Du-Form, Gastdaten maskiert, Blöcke je Fall', () => {
for (const id of ['K20', 'K21']) {
const tpl = loadTemplate(id)
assert.equal(tpl.subject, '{{betreff}}', id)
for (const p of ['betreff', 'kunde_name', 'leistung', 'person', 'datum', 'zeit', 'gast_name', 'gast_email', 'gast_telefon', 'gast_nachricht', 'portal_link']) assert.ok(tpl.placeholders.includes(p), `${id}: ${p}`)
assert.ok(!tpl.blocks.includes('sie') && !tpl.blocks.includes('du'), `${id}: Du-Form fest, keine Anredeblöcke`)
const mail = renderMail(id, sampleValues(tpl), allBlocks(tpl, true))
assert.doesNotMatch(mail.text, SIE_FORM, `${id}: Du-Form\n${mail.text}`)
assert.match(mail.text, /Hallo Wert-kunde_name,/, id)
assert.match(mail.html, /Muster', gast_email: 'max@example.com', gast_telefon: '0170 1', gast_nachricht: 'Bitte\nFenster', portal_link: 'https://project.webklar.com/dashboard.html' }
const buchung = renderMail('K20', werte20, {})
assert.deepEqual(buchung.problems, [])
assert.match(buchung.html, /Max <b>Muster<\/b>/)
assert.match(buchung.html, /Bitte Fenster/)
assert.match(buchung.text, /E-Mail: max@example\.com/)
assert.match(buchung.text, /Im Portal ansehen: https:\/\/project\.webklar\.com\/dashboard\.html/)
assert.doesNotMatch(buchung.text, /noch nicht bestätigt/)
assert.match(renderMail('K20', werte20, { anfrage: true }).text, /Diese Anfrage ist noch nicht bestätigt/)
const t21 = loadTemplate('K21')
const werte21 = { ...sampleValues(t21), alt_datum: '11.10.2026', alt_zeit: '09:00', datum: '12.10.2026', zeit: '10:00', eingang: '02.10.2026, 10:07 Uhr' }
const fall = (block) => renderMail('K21', werte21, { [block]: true })
assert.match(fall('abgesagt').text, /hat den Termin abgesagt/)
assert.match(fall('verschoben').text, /Bisher: 11\.10\.2026, 09:00 Uhr/)
assert.match(fall('verschoben').text, /Neu: 12\.10\.2026, 10:00 Uhr/)
assert.match(fall('widerrufen').text, /Eingegangen am: 02\.10\.2026, 10:07 Uhr/)
assert.match(fall('widerrufen').text, /widerrufen/)
assert.match(fall('nach_frist').text, /Nach unseren Unterlagen war die Widerrufsfrist bei Eingang bereits abgelaufen/)
assert.match(fall('begonnen').text, /Ein Widerruf ist eingegangen, nachdem der Termin bereits begonnen hatte\. Bitte prüfe, ob das Widerrufsrecht schon erloschen ist \(vollständige Leistung nach Zustimmung zum vorzeitigen Beginn\) oder ob Wertersatz in Frage kommt\./)
assert.match(fall('begonnen').text, /Eingegangen am: 02\.10\.2026, 10:07 Uhr/)
assert.match(fall('nicht_zugeordnet').text, /keiner Buchung zuordnen/)
// je Fall erscheint nur der eigene Abschnitt
const SATZ = { abgesagt: 'hat den Termin abgesagt', verschoben: 'hat den Termin verschoben', widerrufen: 'hat den Vertrag widerrufen', nach_frist: 'Nach unseren Unterlagen war die Widerrufsfrist bei Eingang bereits abgelaufen', begonnen: 'nachdem der Termin bereits begonnen hatte', nicht_zugeordnet: 'keiner Buchung zuordnen' }
for (const block of Object.keys(SATZ)) {
const mail = fall(block)
assert.deepEqual(mail.problems, [], block)
for (const [anderer, satz] of Object.entries(SATZ)) assert.equal(mail.text.includes(satz), anderer === block, `${block}: ${satz}`)
}
})
|