From 664043e8e909e45d405fbe2b671e1ea98557ac11 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 16:28:15 +0000 Subject: [PATCH] =?UTF-8?q?Kundenansicht=20P6:=20Mailvorlagen=20K38=20bis?= =?UTF-8?q?=20K41=20und=20T24=20f=C3=BCr=20das=20Abo-Modell?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- .../K38 Funktion wird eingerichtet.html | 56 ++++++++++++++++++ .../mail-templates/K39 Funktion beendet.html | 54 ++++++++++++++++++ .../mail-templates/K40 Zeiten Abo-Ende.html | 54 ++++++++++++++++++ .../K41 Zeiten abgeschaltet Abo.html | 52 +++++++++++++++++ .../T24 Funktion eingeschaltet.html | 57 +++++++++++++++++++ test/aboFunktionenVorlagen.test.js | 26 +++++++++ test/mailTemplates.test.js | 6 +- 7 files changed, 302 insertions(+), 3 deletions(-) create mode 100644 server/mail-templates/K38 Funktion wird eingerichtet.html create mode 100644 server/mail-templates/K39 Funktion beendet.html create mode 100644 server/mail-templates/K40 Zeiten Abo-Ende.html create mode 100644 server/mail-templates/K41 Zeiten abgeschaltet Abo.html create mode 100644 server/mail-templates/T24 Funktion eingeschaltet.html create mode 100644 test/aboFunktionenVorlagen.test.js diff --git a/server/mail-templates/K38 Funktion wird eingerichtet.html b/server/mail-templates/K38 Funktion wird eingerichtet.html new file mode 100644 index 0000000..7ff6d96 --- /dev/null +++ b/server/mail-templates/K38 Funktion wird eingerichtet.html @@ -0,0 +1,56 @@ + + + + + + + + +Deine Funktion wird eingerichtet: {{funktion}} + + + + +{{funktion}} wird für dein Projekt eingerichtet. ͏ ͏ ͏ + + + + + diff --git a/server/mail-templates/K39 Funktion beendet.html b/server/mail-templates/K39 Funktion beendet.html new file mode 100644 index 0000000..f180dc9 --- /dev/null +++ b/server/mail-templates/K39 Funktion beendet.html @@ -0,0 +1,54 @@ + + + + + + + + +Deine Funktion wurde beendet: {{funktion}} + + + + +{{funktion}} wurde beendet. ͏ ͏ ͏ + + + + + diff --git a/server/mail-templates/K40 Zeiten Abo-Ende.html b/server/mail-templates/K40 Zeiten Abo-Ende.html new file mode 100644 index 0000000..234b49c --- /dev/null +++ b/server/mail-templates/K40 Zeiten Abo-Ende.html @@ -0,0 +1,54 @@ + + + + + + + + +Dein Abo ist beendet – Öffnungszeiten live läuft noch bis {{frist}} + + + + +Öffnungszeiten live läuft noch bis {{frist}}. ͏ ͏ ͏ + + + + + diff --git a/server/mail-templates/K41 Zeiten abgeschaltet Abo.html b/server/mail-templates/K41 Zeiten abgeschaltet Abo.html new file mode 100644 index 0000000..647aa26 --- /dev/null +++ b/server/mail-templates/K41 Zeiten abgeschaltet Abo.html @@ -0,0 +1,52 @@ + + + + + + + + +Öffnungszeiten live ist abgeschaltet + + + + +Öffnungszeiten live ist abgeschaltet. ͏ ͏ ͏ + + + + + diff --git a/server/mail-templates/T24 Funktion eingeschaltet.html b/server/mail-templates/T24 Funktion eingeschaltet.html new file mode 100644 index 0000000..f63017a --- /dev/null +++ b/server/mail-templates/T24 Funktion eingeschaltet.html @@ -0,0 +1,57 @@ + + + + + + + + +[Abo] Funktion eingeschaltet: {{funktion}} – {{kunde_name}} + + + + +{{kunde_name}}: {{funktion}}, Projekt {{projekt}}. ͏ ͏ ͏ + + + + + diff --git a/test/aboFunktionenVorlagen.test.js b/test/aboFunktionenVorlagen.test.js new file mode 100644 index 0000000..decf2fe --- /dev/null +++ b/test/aboFunktionenVorlagen.test.js @@ -0,0 +1,26 @@ +import assert from 'node:assert/strict' +import { test } from 'node:test' +import { loadTemplate, renderMail, templateIds } from '../server/services/mailTemplates.js' + +// P6 Task 3: Mailvorlagen K38 bis K41 und T24 des Abo-Modells (Kopien von K16 bzw. T19, du-Form, ohne Euro-Zeichen) +const SOLL = { // [Datei, Betreff, Platzhalter, Blöcke] + K38: ['K38 Funktion wird eingerichtet.html', 'Deine Funktion wird eingerichtet: {{funktion}}', ['kunde_name', 'projekt', 'funktion', 'funktion_akk', 'einrichtung', 'ticket', 'fassung', 'portal_url'], ['stundenkonto', 'projekt_kosten', 'anhang']], + K39: ['K39 Funktion beendet.html', 'Deine Funktion wurde beendet: {{funktion}}', ['kunde_name', 'projekt', 'funktion', 'loeschtag', 'portal_url'], ['abo_ende', 'hosting', 'termine', 'offene']], + K40: ['K40 Zeiten Abo-Ende.html', 'Dein Abo ist beendet – Öffnungszeiten live läuft noch bis {{frist}}', ['kunde_name', 'projekt', 'frist', 'portal_url'], ['abo_ende', 'hosting', 'wechsel_widerrufen']], + K41: ['K41 Zeiten abgeschaltet Abo.html', 'Öffnungszeiten live ist abgeschaltet', ['kunde_name', 'projekt', 'loeschdatum', 'portal_url'], []], + T24: ['T24 Funktion eingeschaltet.html', '[Abo] Funktion eingeschaltet: {{funktion}} – {{kunde_name}}', ['kunde_name', 'email', 'projekt', 'funktion', 'woid', 'kontostand', 'woms_url'], ['vorschuss', 'anhang_fehlt']], +} +const mail = (id, on) => { const t = loadTemplate(id); return renderMail(id, Object.fromEntries(t.placeholders.map((p) => [p, `W-${p}`])), Object.fromEntries(t.blocks.map((b) => [b, on]))) } +for (const [id, [file, subject, placeholders, blocks]] of Object.entries(SOLL)) test(`${id}: Datei, Betreff, Platzhalter, Blöcke; rendert ohne Reste; ohne € und ohne Sie-Form (K)`, () => { + const t = loadTemplate(id); assert.deepEqual([t.file, t.subject, t.placeholders, t.blocks], [file, subject, placeholders, blocks]) + for (const on of [true, false]) { const m = mail(id, on); assert.deepEqual(m.problems, []); assert.doesNotMatch(m.html + m.text, /€/); if (id.startsWith('K')) assert.doesNotMatch(m.text, /\bSie\b|\bIhr/) } +}) +test('Festtexte', () => { + assert.match(mail('K38', true).text, /Die Einrichtung geht von deinem Stundenkonto ab, auch wenn das Guthaben noch nicht reicht\. Extrakosten entstehen keine\./) + assert.match(mail('K38', true).text, /Von deinem Stundenkonto geht nichts ab\. Die Funktion läuft, solange dein Wartungs-Abo läuft\./) + assert.doesNotMatch(mail('K38', false).text, /Stundenkonto|Wartungs-Abo läuft/) + assert.match(mail('K39', true).text, /Läuft bis dahin wieder ein passendes Abo, schalten wir W-funktion automatisch und ohne Gebühr wieder ein\./) + assert.match(mail('K39', true).text, /Bitte informiere deine Gäste selbst\./); assert.doesNotMatch(mail('K39', false).text, /CSV|Offene Bestellungen/) + assert.match(mail('T24', true).text, /Das Guthaben reicht voraussichtlich nicht \(W-kontostand\)\./); assert.doesNotMatch(mail('T24', false).text, /Vorschuss/) +}) +test('templateIds enthält K38 bis K41 und T24', () => { for (const id of Object.keys(SOLL)) assert.ok(templateIds().includes(id)) }) diff --git a/test/mailTemplates.test.js b/test/mailTemplates.test.js index e41fc60..f96f35e 100644 --- a/test/mailTemplates.test.js +++ b/test/mailTemplates.test.js @@ -6,9 +6,9 @@ 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); T18 Bankzugang erneuern (Finanzen); T19 Erstattung fällig und T20 Terminplaner Betriebsmeldung (Terminplaner TP2, Team-Hinweise; T16 gilt je Funktion, T19 aus B9b); T21 Neue Webshop-Anfrage (Webshop B1, Team-Hinweis); K24 Webshop-Anfrage entschieden (Webshop B2, Kunde); G09–G14 Käufermails des Webshops (Bestellbestätigung, versendet, abholbereit, Eingang Widerruf, Erstattung, Zahlung fehlgeschlagen; Webshop E4, eigener Fuß statt Terminplaner-Fuß, Sie-Form) und K25, K26 an den Händler (neue Bestellung, Widerruf; Webshop E4); K27–K30 Öffnungszeiten live (eingeschaltet, Feiertage prüfen, Abo beendet, abgeschaltet; Öffnungszeiten live Task 13); K04 und T09 gelten beim Widerruf einer Funktion je Funktion (produkt, T09 zusätzlich preis; Terminplaner TP2 F9); K33–K37 und T23 Buchungskalender (K36 und K37 Neue Anfrage und Neue Buchung an den Vermieter, T23 Team-Hinweis; zuvor K31, K32, T22, die den Aufträgen gehören); K31, K32 und T22 Auftragsbestätigung (Aufträge und Rechnungen, Strang L5; K31 und K32 in Sie-Form, T22 mit [Aufträge]) +// 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); T18 Bankzugang erneuern (Finanzen); T19 Erstattung fällig und T20 Terminplaner Betriebsmeldung (Terminplaner TP2, Team-Hinweise; T16 gilt je Funktion, T19 aus B9b); T21 Neue Webshop-Anfrage (Webshop B1, Team-Hinweis); K24 Webshop-Anfrage entschieden (Webshop B2, Kunde); G09–G14 Käufermails des Webshops (Bestellbestätigung, versendet, abholbereit, Eingang Widerruf, Erstattung, Zahlung fehlgeschlagen; Webshop E4, eigener Fuß statt Terminplaner-Fuß, Sie-Form) und K25, K26 an den Händler (neue Bestellung, Widerruf; Webshop E4); K27–K30 Öffnungszeiten live (eingeschaltet, Feiertage prüfen, Abo beendet, abgeschaltet; Öffnungszeiten live Task 13); K04 und T09 gelten beim Widerruf einer Funktion je Funktion (produkt, T09 zusätzlich preis; Terminplaner TP2 F9); K33–K37 und T23 Buchungskalender (K36 und K37 Neue Anfrage und Neue Buchung an den Vermieter, T23 Team-Hinweis; zuvor K31, K32, T22, die den Aufträgen gehören); K31, K32 und T22 Auftragsbestätigung (Aufträge und Rechnungen, Strang L5; K31 und K32 in Sie-Form, T22 mit [Aufträge]); K38–K41 und T24 Funktionen im Abo-Modell (Kundenansicht P6: K38 wird eingerichtet, K39 beendet, K40 und K41 Öffnungszeiten live Abo-Ende und abgeschaltet ohne Preis, T24 Team-Hinweis; Du-Form, Test in aboFunktionenVorlagen.test.js) const ids = (prefix, count) => Array.from({ length: count }, (_, i) => `${prefix}${String(i + 1).padStart(2, '0')}`) -const EXPECTED = [...ids('G', 22), ...ids('K', 37), ...ids('T', 23)] +const EXPECTED = [...ids('G', 22), ...ids('K', 41), ...ids('T', 24)] // GUEST = Gästemails des Terminplaners (gemeinsamer Fuß, Anrede-Blöcke); SHOP_GUEST = Käufermails des Webshops (G09–G14, siehe unten) const GUEST = ids('G', 8) const SHOP_GUEST = ['G09', 'G10', 'G11', 'G12', 'G13', 'G14', 'G15'] @@ -715,7 +715,7 @@ test('T07: Hinweis auf nicht genutzte Arbeitszeit nur im Block wartung', () => { }) // Etappe 4: das Admin-Panel ist abgeschaltet, Team-Mails fuehren ins Ticketsystem -const TEAM_FOOTER = ['T01', 'T02', 'T03', 'T04', 'T05', 'T06', 'T07', 'T08', 'T09', 'T10', 'T11', 'T12', 'T13', 'T14', 'T16', 'T19', 'T20', 'T21', 'T22'] +const TEAM_FOOTER = ['T01', 'T02', 'T03', 'T04', 'T05', 'T06', 'T07', 'T08', 'T09', 'T10', 'T11', 'T12', 'T13', 'T14', 'T16', 'T19', 'T20', 'T21', 'T22', 'T24'] test('keine Vorlage verlinkt admin.html oder nennt den Admin-Bereich', () => { for (const id of EXPECTED) {