Files
Webklar-Kundenbereich/test/auftraegeVorlagen.test.js
T

157 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { config } from '../server/config.js'
import { loadTemplate, renderMail } from '../server/services/mailTemplates.js'
import { euroDe } from '../server/services/finance/format.js'
// K31, K32, T22 (Aufträge und Rechnungen, Strang L5) und der Block projekt in K04 und T09 (Task 11). Die allgemeinen
// Vorlagenregeln (Kopf, Platzhalter, Blöcke, Logo, Text ohne Entities, Team-Fuß) prüft test/mailTemplates.test.js.
const eur = (cent) => euroDe(cent).replace(/\u{a0}/gu, ' ') // die Textfassung macht geschützte Leerzeichen zu normalen
const DU_FORM = /(^|[^\p{L}])(du|dein\p{L}*|dir|dich)(?![\p{L}-])/iu
const SIE_FORM = /(^|[^\p{L}])(Sie|Ihr\p{L}*|Ihnen)(?![\p{L}-])/u
const alle = (id) => Object.fromEntries(loadTemplate(id).blocks.map((name) => [name, true]))
const pruefeZeilen = (mail, erwartet) => {
const zeilen = mail.text.split('\n')
for (const zeile of erwartet) assert.ok(zeilen.includes(zeile), `Zeile fehlt: ${zeile}\n${mail.text}`)
}
const K31 = { name: 'Erika Muster', nummer: 'AB-2026-0001', titel: 'Website für Musterbäckerei', datum: '06.10.2026', betrag: eur(80000), link: 'https://project.webklar.com/auftrag.html#t=TOKEN', gueltig_bis: '05.12.2026' }
const K32 = { name: 'Erika Muster', nummer: 'AB-2026-0001', titel: 'Website für Musterbäckerei', zeitpunkt: '07.10.2026, 10:30', fruehester_beginn: '21.10.2026', fruehbeginn_text: 'Ich verlange ausdrücklich den Beginn vor Ablauf der Widerrufsfrist.' }
const T22 = { kunde: 'Muster GmbH', nummer: 'AB-2026-0001', titel: 'Website für Musterbäckerei', zeitpunkt: '07.10.2026, 10:30', ueber: 'Bestätigungslink', woms_link: 'https://ticket.webklar.com/customers/c1?tab=invoices', fruehester_beginn: '21.10.2026' }
test('K31, K32, T22: Datei, Betreff, Platzhalter und Blöcke wie im Vertrag V6 (K31 mit dem Gegenblock kundenmail)', () => {
const erwartet = {
K31: { file: 'K31 Auftragsbestaetigung.html', subject: 'Ihre Auftragsbestätigung {{nummer}} – {{titel}}', placeholders: ['name', 'nummer', 'titel', 'datum', 'betrag', 'link', 'gueltig_bis'], blocks: ['erinnerung', 'privat', 'festpreis', 'aufwand', 'teamkopie', 'kundenmail'] },
K32: { file: 'K32 Auftrag bestaetigt.html', subject: 'Ihre Bestätigung zu {{nummer}} ist eingegangen', placeholders: ['name', 'nummer', 'titel', 'zeitpunkt', 'fruehester_beginn', 'fruehbeginn_text'], blocks: ['fruehbeginn', 'ohne_fruehbeginn'] },
T22: { file: 'T22 Auftrag bestaetigt.html', subject: '[Aufträge] Auftrag {{nummer}} bestätigt: {{kunde}}', placeholders: ['kunde', 'nummer', 'titel', 'zeitpunkt', 'ueber', 'woms_link', 'fruehester_beginn'], blocks: ['privat_fruehbeginn', 'privat_ohne_fruehbeginn'] },
}
for (const [id, soll] of Object.entries(erwartet)) {
const vorlage = loadTemplate(id)
assert.equal(vorlage.file, soll.file, id)
assert.equal(vorlage.subject, soll.subject, id)
assert.deepEqual(vorlage.placeholders, soll.placeholders, id)
assert.deepEqual(vorlage.blocks, soll.blocks, id)
}
})
test('K31 Festpreis an eine Privatperson: Angaben, Knopf mit Link im Fragment, Anhänge, Widerrufshinweis; keine Erinnerung, kein Teamhinweis', () => {
const mail = renderMail('K31', K31, { festpreis: true, privat: true, kundenmail: true })
assert.deepEqual(mail.problems, [])
assert.equal(mail.subject, 'Ihre Auftragsbestätigung AB-2026-0001 – Website für Musterbäckerei')
pruefeZeilen(mail, [
'Guten Tag Erika Muster,',
'Auftrag: AB-2026-0001 vom 06.10.2026',
'Projekt: Website für Musterbäckerei',
'Gesamtpreis: 800,00 € (Festpreis, gemäß § 19 UStG ohne Umsatzsteuer)',
'Bestätigung möglich bis: 05.12.2026',
'Auftrag bestätigen: https://project.webklar.com/auftrag.html#t=TOKEN',
'Viele Grüße',
'Ihr WEBklar-Team',
])
assert.ok(mail.text.includes('Die Auftragsbestätigung (AB-2026-0001.pdf) und unsere Allgemeinen Geschäftsbedingungen hängen als PDF an dieser E-Mail.'))
assert.ok(mail.text.includes('Sie sind Verbraucher: Ihr Widerrufsrecht und das Muster-Widerrufsformular finden Sie in der angehängten Auftragsbestätigung;'))
assert.ok(mail.text.includes(`Widerrufsformular auf unserer Website (${config.publicBaseUrl}/widerrufen.html)`))
assert.doesNotMatch(mail.text, /Erinnerung|Vergütung:|Kopie für das Team/)
})
test('K31 Aufwand an ein Unternehmen als Erinnerung: Stundensatz, Hinweis auf den früheren Link, kein Widerrufshinweis', () => {
const mail = renderMail('K31', { ...K31, betrag: `${eur(10000)} pro Stunde` }, { aufwand: true, erinnerung: true, kundenmail: true })
assert.deepEqual(mail.problems, [])
pruefeZeilen(mail, ['Vergütung: 100,00 € pro Stunde (nach Aufwand in Schritten von 15 Minuten, gemäß § 19 UStG ohne Umsatzsteuer)', 'Auftrag bestätigen: https://project.webklar.com/auftrag.html#t=TOKEN'])
assert.ok(mail.text.includes('Wir haben Ihnen diese Auftragsbestätigung am 06.10.2026 schon einmal geschickt und noch keine Bestätigung von Ihnen erhalten. Der Link in der früheren E-Mail gilt nicht mehr.'))
assert.doesNotMatch(mail.text, /Gesamtpreis|Widerruf|Verbraucher/)
})
test('K31 als Kopie für das Team: Hinweis statt Knopf, kein Link, keine Anhänge, kein Widerrufshinweis; die Erinnerung bleibt erkennbar', () => {
const mail = renderMail('K31', { ...K31, link: '' }, { aufwand: true, erinnerung: true, teamkopie: true })
assert.deepEqual(mail.problems, [])
pruefeZeilen(mail, ['Kopie für das Team – der Bestätigungslink steht nur in der Mail an den Kunden.', 'Auftrag: AB-2026-0001 vom 06.10.2026', 'Bestätigung möglich bis: 05.12.2026'])
assert.ok(mail.text.includes('Der Link in der früheren E-Mail gilt nicht mehr.'))
assert.doesNotMatch(mail.text, /Auftrag bestätigen|auftrag\.html|hängen als PDF|Anhänge|Verbraucher/)
assert.doesNotMatch(mail.html, /auftrag\.html|#t=|class="btn"/)
})
test('K32: Eingangsbestätigung mit Datum und Uhrzeit; Frühbeginn ja (wörtliche Erklärung), nein und Unternehmen', () => {
const ja = renderMail('K32', K32, { fruehbeginn: true })
assert.deepEqual(ja.problems, [])
assert.equal(ja.subject, 'Ihre Bestätigung zu AB-2026-0001 ist eingegangen')
pruefeZeilen(ja, ['Guten Tag Erika Muster,', 'Auftrag: AB-2026-0001', 'Projekt: Website für Musterbäckerei', 'Bestätigt am: 07.10.2026, 10:30 Uhr', 'Ihre Erklärung zum früheren Beginn', K32.fruehbeginn_text, 'Viele Grüße', 'Ihr WEBklar-Team'])
assert.ok(ja.text.includes('wir haben Ihre Bestätigung zu AB-2026-0001 am 07.10.2026, 10:30 Uhr erhalten.'))
assert.doesNotMatch(ja.text, /21\.10\.2026|nicht verlangt|Widerrufsrecht bleibt/)
const nein = renderMail('K32', K32, { ohne_fruehbeginn: true })
assert.ok(nein.text.includes('Sie haben nicht verlangt, dass wir vor Ablauf der Widerrufsfrist beginnen. Wir beginnen daher frühestens am 21.10.2026 mit der Arbeit.'))
assert.doesNotMatch(nein.text, /Erklärung zum früheren Beginn/)
assert.doesNotMatch(renderMail('K32', K32, {}).text, /Beginn der Arbeiten|21\.10\.2026|Widerruf|Erklärung/)
})
test('T22: Betreff mit Präfix, Angaben, Knopf ins Ticketsystem; Hinweise nur bei Privatpersonen', () => {
const ja = renderMail('T22', T22, { privat_fruehbeginn: true })
assert.deepEqual(ja.problems, [])
assert.equal(ja.subject, '[Aufträge] Auftrag AB-2026-0001 bestätigt: Muster GmbH')
pruefeZeilen(ja, ['Kunde: Muster GmbH', 'Auftrag: AB-2026-0001', 'Projekt: Website für Musterbäckerei', 'Bestätigt am: 07.10.2026, 10:30 Uhr', 'Weg: Bestätigungslink', 'Im Ticketsystem öffnen: https://ticket.webklar.com/customers/c1?tab=invoices'])
assert.ok(ja.text.includes('Der Kunde hat ausdrücklich verlangt, dass wir vor Ablauf der Widerrufsfrist beginnen. Die Arbeit darf sofort starten. Bei einem Widerruf ist Wertersatz zu prüfen (§ 357a Abs. 2 BGB).'))
assert.doesNotMatch(ja.text, /keinen früheren Beginn|21\.10\.2026/)
const nein = renderMail('T22', T22, { privat_ohne_fruehbeginn: true })
assert.ok(nein.text.includes('Der Kunde hat keinen früheren Beginn verlangt. Arbeitsbeginn frühestens am 21.10.2026, dem Tag nach Ablauf der Widerrufsfrist.'))
assert.doesNotMatch(renderMail('T22', T22, {}).text, /Privatperson|Wertersatz|frühestens/)
})
test('K31 und K32 stehen in der Sie-Form; Werte werden HTML-sicher eingesetzt', () => {
for (const [id, werte] of [['K31', K31], ['K32', K32]]) {
const mail = renderMail(id, werte, alle(id))
assert.doesNotMatch(mail.text, DU_FORM, `${id}: Du-Form\n${mail.text}`)
assert.match(mail.text, SIE_FORM, `${id}: spricht den Kunden mit Sie an`)
}
const mail = renderMail('K31', { ...K31, titel: 'Café <b>&"' }, { festpreis: true })
assert.match(mail.html, /Café &lt;b&gt;&amp;&quot;/)
assert.doesNotMatch(mail.html, /<b>&/)
})
const K04 = { name: 'Erika Muster', produkt: 'Projektauftrag', bestellung: 'Projektauftrag (Auftragsbestätigung AB-2026-0001)', eingang_zeitpunkt: '07.10.2026, 10:30', email: 'k@example.invalid', kundennummer: 'KD-9' }
const T09 = { email: 'k@example.invalid', name: 'Erika Muster', produkt: 'Projektauftrag', preis: '', kundennummer: 'KD-9', bestellung: 'Projektauftrag (Auftragsbestätigung AB-2026-0001)', stripe_abo_id: '', abo_beginn: '–', fristende: '20.10.2026, 24 Uhr', rechtzeitig: 'RECHTZEITIG', ereignisse: 'Auftragsbestätigung auf „widerrufen“ gesetzt', pruefhinweis: '' }
test('K04 und T09: Block projekt ist deklariert (K04 mit projekt_unternehmen), die Platzhalter bleiben, die anderen Blöcke auch', () => {
assert.deepEqual(loadTemplate('K04').blocks, ['kundennummer', 'abschluss', 'wechsel', 'zusatzspeicher', 'funktion', 'pruefen', 'projekt', 'projekt_unternehmen'])
assert.deepEqual(loadTemplate('K04').placeholders, ['name', 'produkt', 'bestellung', 'eingang_zeitpunkt', 'email', 'kundennummer'])
assert.deepEqual(loadTemplate('T09').blocks.slice(-2), ['bereits_widerrufen', 'projekt'])
assert.deepEqual(loadTemplate('T09').placeholders, ['email', 'name', 'produkt', 'preis', 'kundennummer', 'bestellung', 'stripe_abo_id', 'abo_beginn', 'fristende', 'rechtzeitig', 'ereignisse', 'pruefhinweis'])
})
test('K04 Block projekt: Vertragsart, „wir prüfen“; bei Unternehmen zusätzlich der Hinweis ohne gesetzliches Widerrufsrecht; keine Zusagen', () => {
const privat = renderMail('K04', K04, { kundennummer: true, projekt: true })
assert.deepEqual(privat.problems, [])
assert.equal(privat.subject, 'Eingangsbestätigung Ihres Widerrufs – WEBklar Projektauftrag')
pruefeZeilen(privat, [
'wir haben Ihren Widerruf der Bestellung „Projektauftrag (Auftragsbestätigung AB-2026-0001)“ am 07.10.2026, 10:30 Uhr erhalten.',
'Bestellung: Projektauftrag (Auftragsbestätigung AB-2026-0001)',
'Kundennummer: KD-9',
'Wie es weitergeht',
'Wir prüfen Ihren Widerruf des Projektauftrags und melden uns umgehend bei Ihnen.',
])
assert.doesNotMatch(privat.text, /Unternehmen|gesetzliches Widerrufsrecht|Abbuchungen|erstatt|Wertersatz|Widerrufsfrist läuft/)
const firma = renderMail('K04', K04, { projekt: true, projekt_unternehmen: true })
assert.deepEqual(firma.problems, [])
pruefeZeilen(firma, ['Wir prüfen Ihren Widerruf des Projektauftrags und melden uns umgehend bei Ihnen. Für Verträge mit Unternehmen besteht kein gesetzliches Widerrufsrecht.'])
// der Zusatz gehört zum Block projekt: allein erscheint er nie, und ohne projekt gibt es keinen Kasten
assert.doesNotMatch(renderMail('K04', K04, { projekt_unternehmen: true }).text, /Unternehmen|Wie es weitergeht/)
assert.doesNotMatch(renderMail('K04', K04, {}).text, /Wie es weitergeht|Projektauftrags/)
})
test('T09 Block projekt: Ergebnis, Widerrufsfrist und Eingang als eigene Zeilen, Hinweis auf die Handarbeit; ohne den Block nichts davon', () => {
const mail = renderMail('T09', T09, { kundennummer: true, projekt: true })
assert.deepEqual(mail.problems, [])
assert.equal(mail.subject, '[Abo] WIDERRUF eingegangen: k@example.invalid')
pruefeZeilen(mail, [
'Bestellung: Projektauftrag (Auftragsbestätigung AB-2026-0001)',
'Abo: Projektauftrag',
'Kundennummer: KD-9',
'Zu tun',
'Ergebnis: Auftragsbestätigung auf „widerrufen“ gesetzt',
'Widerrufsfrist bis: 20.10.2026, 24 Uhr',
'Eingang: RECHTZEITIG',
])
assert.ok(mail.text.includes('Der Kunde hat „wir prüfen“ bekommen. Erstattung, Wertersatz und das Ende der Arbeit sind Handarbeit (Ticketsystem: Kunde → Aufträge und Rechnungen).'))
assert.doesNotMatch(mail.text, /KEIN aktives Abo|Stripe-Abo|Abo in Stripe/)
assert.doesNotMatch(renderMail('T09', T09, { kundennummer: true }).text, /Ergebnis:|Eingang:|Handarbeit|Zu tun/)
})