Merge feature/terminplaner-tp2 (a629b25) in feature/oeffnungszeiten
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
1d5a565e9f
95 files changed
+12879
-546
No files matched your search
@@ -6,7 +6,7 @@ import { contractAttachmentFor, termsDocFor } from '../server/services/products.
|
||||
|
||||
const DIR = new URL('../server/mail-templates/', import.meta.url)
|
||||
// G01–G08 Gästemails des Terminplaners (Task 9, neutrales Layout im Auftrag eines Betriebs), K20 und K21 Hinweise an den Betrieb;
|
||||
// K12, K14 und T12 (Wechsel ins Wartungs-Abo) aus Task 9, K13 und T13 (Zusatzspeicher) aus Task 8 (Fix 1); Endfassung der Texte in Task 12; T14 „Löschfristen“ aus Task 21 (F39); T15 Push-Zeitmail; K15, K16, T16 Funktionen (News-Pinnwand, Terminplaner; K15, K16 und T16 gelten je Funktion, Terminplaner TP2 B9a und B9b); T17 Repo zuordnen; K17 Chat-Hinweis an Kunden; K18, K19 Website-Analyse; K22, K23 Terminplaner beendet/läuft wieder (Terminplaner TP2 E3); K24–K27 Öffnungszeiten live (eingeschaltet, Feiertage prüfen, Abo beendet, abgeschaltet; Öffnungszeiten live Task 13); T18 Bankzugang erneuern (Finanzen); T19 Erstattung fällig und T20 Terminplaner Betriebsmeldung (Terminplaner TP2, Team-Hinweise; T16 gilt je Funktion, T19 aus B9b)
|
||||
// K12, K14 und T12 (Wechsel ins Wartungs-Abo) aus Task 9, K13 und T13 (Zusatzspeicher) aus Task 8 (Fix 1); Endfassung der Texte in Task 12; T14 „Löschfristen“ aus Task 21 (F39); T15 Push-Zeitmail; K15, K16, T16 Funktionen (News-Pinnwand, Terminplaner; K15, K16 und T16 gelten je Funktion, Terminplaner TP2 B9a und B9b); T17 Repo zuordnen; K17 Chat-Hinweis an Kunden; K18, K19 Website-Analyse; K22, K23 Terminplaner beendet/läuft wieder (Terminplaner TP2 E3); K24–K27 Öffnungszeiten live (eingeschaltet, Feiertage prüfen, Abo beendet, abgeschaltet; Öffnungszeiten live Task 13); T18 Bankzugang erneuern (Finanzen); T19 Erstattung fällig und T20 Terminplaner Betriebsmeldung (Terminplaner TP2, Team-Hinweise; T16 gilt je Funktion, T19 aus B9b); K04 und T09 gelten beim Widerruf einer Funktion je Funktion (produkt, T09 zusätzlich preis; Terminplaner TP2 F9)
|
||||
const ids = (prefix, count) => Array.from({ length: count }, (_, i) => `${prefix}${String(i + 1).padStart(2, '0')}`)
|
||||
const EXPECTED = [...ids('G', 8), ...ids('K', 27), ...ids('T', 19), 'T20']
|
||||
const GUEST = ids('G', 8)
|
||||
@@ -74,8 +74,9 @@ test('T20: Betreff [Terminplaner], Platzhalter vorfall/details/zeit, keine Blöc
|
||||
test('K15, K16: Platzhalter und Blöcke je Funktion, keine Funktion fest im Text', () => {
|
||||
const k15 = loadTemplate('K15')
|
||||
assert.equal(k15.subject, 'Deine Bestellung: {{funktion}}')
|
||||
assert.deepEqual(k15.placeholders, ['kunde_name', 'projekt', 'funktion', 'funktion_gen', 'funktion_akk', 'funktion_nom', 'funktion_pron', 'preis', 'preis_hinweis', 'zeit', 'danach', 'portal_url', 'rechnung_url'])
|
||||
assert.deepEqual(k15.blocks, ['rechnung', 'behalten', 'neu'])
|
||||
// F8: fassung, lb_url, avv_url, widerruf_url, zustimmungen_text und die Blöcke anlagen (mit anhang) und zustimmungen für Funktionen mit Rechtsdokumenten
|
||||
assert.deepEqual(k15.placeholders, ['kunde_name', 'projekt', 'funktion', 'funktion_gen', 'funktion_akk', 'funktion_nom', 'funktion_pron', 'preis', 'preis_hinweis', 'zeit', 'danach', 'portal_url', 'rechnung_url', 'fassung', 'lb_url', 'avv_url', 'widerruf_url', 'zustimmungen_text'])
|
||||
assert.deepEqual(k15.blocks, ['rechnung', 'behalten', 'neu', 'anlagen', 'anhang', 'zustimmungen'])
|
||||
const k16 = loadTemplate('K16')
|
||||
assert.equal(k16.subject, '{{funktion_dein}} ist bereit')
|
||||
assert.deepEqual(k16.placeholders, ['kunde_name', 'projekt', 'funktion', 'funktion_dein', 'funktion_nom', 'bereit', 'knopf', 'portal_url'])
|
||||
@@ -89,6 +90,21 @@ test('K15, K16: Platzhalter und Blöcke je Funktion, keine Funktion fest im Text
|
||||
assert.ok(mail.text.includes(part), `K15 enthält: ${part}\n---\n${mail.text}`)
|
||||
}
|
||||
assert.doesNotMatch(renderMail('K15', sampleValues(k15), { rechnung: false }).text, /Rechnung ansehen/)
|
||||
// Vertragsunterlagen (F8): anlagen mit den drei versionierten Adressen, der PDF-Satz nur mit anhang, die Zustimmungen mehrzeilig
|
||||
const werte = { ...sampleValues(k15), fassung: '2026-10-05', lb_url: 'https://p.example/rechtliches/lb-2026-10-05.html', avv_url: 'https://p.example/rechtliches/avv-2026-10-05.html', widerruf_url: 'https://p.example/rechtliches/w-2026-10-05.html', zustimmungen_text: 'Du hast zugestimmt:\n– Erstens\n– Zweitens' }
|
||||
const aus = renderMail('K15', werte, { rechnung: false, anlagen: false, anhang: false, zustimmungen: false })
|
||||
assert.doesNotMatch(aus.text, /Vertragsunterlagen|Zustimmungen|Fassung 2026|hängen als PDF/)
|
||||
const ohneAnhang = renderMail('K15', werte, { anlagen: true, anhang: false, zustimmungen: false })
|
||||
assert.deepEqual(ohneAnhang.problems, [])
|
||||
for (const part of ['Deine Vertragsunterlagen', 'Fassung 2026-10-05: Leistungsbeschreibung (https://p.example/rechtliches/lb-2026-10-05.html)', 'Auftragsverarbeitungsvertrag (https://p.example/rechtliches/avv-2026-10-05.html)', 'Widerrufsbelehrung mit Muster-Widerrufsformular (https://p.example/rechtliches/w-2026-10-05.html)']) {
|
||||
assert.ok(ohneAnhang.text.includes(part), `K15 anlagen enthält: ${part}\n---\n${ohneAnhang.text}`)
|
||||
}
|
||||
assert.doesNotMatch(ohneAnhang.text, /hängen als PDF|Deine Zustimmungen/)
|
||||
const mitAnhang = renderMail('K15', werte, { anlagen: true, anhang: true, zustimmungen: true })
|
||||
assert.match(mitAnhang.text, /Alle drei hängen als PDF an dieser E-Mail\. Bitte bewahre sie auf\./)
|
||||
assert.ok(mitAnhang.text.includes('Deine Zustimmungen\nDu hast zugestimmt:\n– Erstens\n– Zweitens'), mitAnhang.text)
|
||||
// anhang steht nur innerhalb von anlagen
|
||||
assert.doesNotMatch(renderMail('K15', werte, { anlagen: false, anhang: true }).text, /hängen als PDF/)
|
||||
const bereit = renderMail('K16', sampleValues(k16), {})
|
||||
assert.deepEqual(bereit.problems, [])
|
||||
assert.equal(bereit.subject, 'Wert-funktion_dein ist bereit')
|
||||
@@ -101,7 +117,7 @@ test('K15, K16: Platzhalter und Blöcke je Funktion, keine Funktion fest im Text
|
||||
test('T16, T19: Betreff, Platzhalter und Blöcke stehen in der Vorlage', () => {
|
||||
const t16 = loadTemplate('T16')
|
||||
assert.equal(t16.subject, 'Neue Funktion bestellt: {{funktion}}')
|
||||
assert.deepEqual(t16.blocks, ['zahlung_fehlgeschlagen', 'ungeklaert', 'kein_abo', 'bereits_aktiv', 'anleitung', 'gesundheit', 'abo', 'team'])
|
||||
assert.deepEqual(t16.blocks, ['zahlung_fehlgeschlagen', 'ungeklaert', 'kein_abo', 'bereits_aktiv', 'anleitung', 'gesundheit', 'abo', 'team', 'anhang_fehlt'])
|
||||
const t19 = loadTemplate('T19')
|
||||
assert.equal(t19.file, 'T19 Erstattung faellig.html')
|
||||
assert.match(t19.subject, /^\[Abo\] Erstattung fällig: \{\{funktion\}\}-Einrichtung$/)
|
||||
@@ -239,7 +255,7 @@ test('optionale Blöcke: nur eingeschaltete erscheinen', () => {
|
||||
})
|
||||
|
||||
test('verschachtelte Blöcke (T09) werden aufgelöst', () => {
|
||||
const values = { email: 'a@example.com', name: 'A', kundennummer: '', stripe_abo_id: 'sub_1', abo_beginn: '01.10.2026', fristende: '15.10.2026', rechtzeitig: 'RECHTZEITIG' }
|
||||
const values = { email: 'a@example.com', name: 'A', kundennummer: '', produkt: 'Terminplaner', preis: '149 €', stripe_abo_id: 'sub_1', abo_beginn: '01.10.2026', fristende: '15.10.2026', rechtzeitig: 'RECHTZEITIG' }
|
||||
const ok = renderMail('T09', values, { abo_gefunden: true, auf_kuendigung_gesetzt: true })
|
||||
assert.match(ok.html, /sub_1 <span[^>]*>\(auf Kündigung gesetzt\)<\/span>/)
|
||||
assert.doesNotMatch(ok.html, /KEIN aktives Abo|NICHT automatisch/)
|
||||
@@ -580,6 +596,40 @@ test('K04: Folgen des Widerrufs je Bestellung (Abschluss, Wechsel, Zusatzspeiche
|
||||
assert.match(renderMail('K04', values, { pruefen: true }).text, /prüfen, ob für diese Bestellung eine Widerrufsfrist läuft/)
|
||||
})
|
||||
|
||||
// F9: Widerruf je Funktion - T09 kennt preis, die Blöcke bleiben unverändert; K04/T09 nennen Produkt und Folgen der Funktion
|
||||
test('F9: T09 deklariert preis, Blöcke von K04 und T09 unverändert', () => {
|
||||
assert.ok(loadTemplate('T09').placeholders.includes('preis'))
|
||||
assert.ok(loadTemplate('T09').placeholders.includes('produkt'))
|
||||
assert.deepEqual(loadTemplate('K04').blocks, ['kundennummer', 'abschluss', 'wechsel', 'zusatzspeicher', 'funktion', 'pruefen'])
|
||||
assert.deepEqual(loadTemplate('T09').blocks, [
|
||||
'kundennummer', 'abo_gefunden', 'auf_kuendigung_gesetzt', 'kein_abo_gefunden', 'kuendigung_fehlgeschlagen', 'abschluss', 'wechsel',
|
||||
'zusatzspeicher', 'funktion', 'keine_frist', 'nachweis_fehlt', 'wechsel_offen', 'abschluss_verspaetet', 'nicht_pruefbar', 'bereits_widerrufen',
|
||||
])
|
||||
})
|
||||
|
||||
test('F9: K04 und T09 beim Widerruf einer Funktion (begonnene Einrichtung, Wertersatz, Preis, Handgriffe)', () => {
|
||||
const k04 = { name: 'K', produkt: 'Terminplaner', bestellung: 'Terminplaner (Einrichtung)', eingang_zeitpunkt: '01.10.2026, 10:00', email: 'k@example.invalid', kundennummer: '' }
|
||||
const kunde = renderMail('K04', k04, { funktion: true })
|
||||
assert.deepEqual(kunde.problems, [])
|
||||
assert.equal(kunde.subject, 'Eingangsbestätigung Ihres Widerrufs – WEBklar Terminplaner')
|
||||
assert.match(kunde.text, /Widerruf der Bestellung „Terminplaner \(Einrichtung\)“/)
|
||||
assert.match(kunde.text, /Einrichtung auf Ihren ausdrücklichen Wunsch schon vor Ablauf der Widerrufsfrist begonnen/)
|
||||
assert.match(kunde.text, /Wertersatz[^.]*\(§ 357a Abs\. 2 BGB\)/)
|
||||
assert.doesNotMatch(kunde.text, /abgeschlossen/)
|
||||
assert.match(kunde.text, /Sie erhalten dazu eine gesonderte Abrechnung/)
|
||||
|
||||
const t09 = { ...sampleValues(loadTemplate('T09')), produkt: 'Terminplaner', preis: '149 €', ereignisse: 'Studio Anker: in Einrichtung' }
|
||||
const team = renderMail('T09', t09, { funktion: true })
|
||||
assert.deepEqual(team.problems, [])
|
||||
assert.match(team.text, /Widerruf einer Funktion \(Terminplaner, 149 € einmalig\)/)
|
||||
assert.match(team.text, /NICHTS automatisch geändert/)
|
||||
assert.match(team.text, /innerhalb von 14 Tagen erstatten/)
|
||||
assert.match(team.text, /abzüglich Wertersatz für den begonnenen Einbau \(Aufwand im Arbeitsblatt, ⚖ Q5\)/)
|
||||
assert.match(team.text, /Reiter „Funktionen“/)
|
||||
assert.match(team.text, /Beim Terminplaner bleibt das Wartungs-Abo unberührt/)
|
||||
assert.match(team.text, /Studio Anker: in Einrichtung/)
|
||||
})
|
||||
|
||||
test('K12: Monatspreis mit gebuchtem Zusatzspeicher (Block), Widerruf des Wechsels wie in Fassung 4', () => {
|
||||
const values = sampleValues(loadTemplate('K12'))
|
||||
const mit = renderMail('K12', values, { zusatzspeicher: true })
|
||||
|
||||
Reference in new issue
Block a user