Webshop E4: Mails (G09-G14 an Käufer, K25/K26 an Händler), shop/mails.js, Mailversand der Warteschlange auch bei Terminplaner off

Käufermails laufen über die Warteschlange des Terminplaners (G09/G12 mit Vorrang),
G09 wird bei Überlänge um den AGB-Text gekürzt (Link statt Text). Der Mailteil des
Minutenjobs und die Aufräumschritte des Tagesjobs laufen auch bei TERMINE_ENABLED=off,
solange SHOP_ENABLED nicht off ist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-05 08:56:40 +00:00
1 parent 38bd56fe8b
commit 0ebd2e6663
15 files changed
+1570 -26

No files matched your search

@@ -0,0 +1,84 @@
<!DOCTYPE html>
<html lang="de" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<!--
G09 · Bestellbestätigung (Webshop, dauerhafter Datenträger nach § 312f Abs. 2 BGB)
Betreff: Ihre Bestellung {{bestellnummer}} bei {{betrieb_name}}
Absender: „<Shopname>“ <Portal-Postfach> · Antworten an die E-Mail-Adresse des Händlers
Empfänger: Käufer (Adresse aus der Bestellung bzw. der Widerrufserklärung) · ohne BCC
Platzhalter: {{bestellnummer}}, {{datum}}, {{positionen}}, {{zwischensumme}}, {{versand}}, {{gesamt}}, {{steuer_hinweis}}, {{lieferart_text}}, {{lieferadresse}}, {{abholort}}, {{lieferzeit}}, {{zahlungsart}}, {{ausschluss_text}}, {{agb}}, {{widerrufsbelehrung}}, {{widerrufsformular}}, {{gewaehrleistung}}, {{farbe}}, {{betrieb_name}}, {{anbieter_zeile}}, {{impressum_url}}, {{datenschutz_url}}, {{widerruf_url}}
Optionale Blöcke (vom Server gesetzt, versand und abholung schließen sich aus): versand, abholung, ausschluss
Sie-Form, keine Anrede-Blöcke. Footer: Anbieter-Zeile mit Name, Anschrift und E-Mail, Impressum, Datenschutz, Link zur Widerrufsfunktion.
positionen = eine Zeile je Position „Menge × Name (Variante) – Summe“; lieferadresse mehrzeilig; agb, widerrufsbelehrung, widerrufsformular und gewaehrleistung sind die vollen Texte (Zeilenumbrüche werden zu <br>).
Block versand: Lieferadresse. Block abholung: Abholort. Block ausschluss: Hinweis auf Artikel ohne Widerrufsrecht (ausschluss_text).
Ist die Mail zu lang für die Warteschlange, ersetzt der Server den AGB-Text durch einen Link auf die AGB im Shop.
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<title>Ihre Bestellung {{bestellnummer}} bei {{betrieb_name}}</title>
<!--[if mso]><style>table,td{font-family:Arial,Helvetica,sans-serif!important;}</style><![endif]-->
<style>
a{color:#111111;}
@media (max-width:620px){.w{width:100%!important;}.px{padding-left:24px!important;padding-right:24px!important;}.lbl{display:block!important;width:100%!important;padding-bottom:2px!important;border-bottom:0!important;}.val{display:block!important;width:100%!important;padding-top:0!important;}}
@media (prefers-color-scheme:dark){.bg{background:#161615!important;}.card{background:#1f1f1e!important;}.ink{color:#f2f1ee!important;}.mut{color:#a8a6a0!important;}.box{background:#2a2a28!important;border-color:#3a3a37!important;}.btn{background:#f2f1ee!important;}.btn a{color:#161615!important;}.btn2{border-color:#f2f1ee!important;}.btn2 a{color:#f2f1ee!important;}.rowline{border-color:#3a3a37!important;}.pill{background:#1f1f1e!important;}.pill span{color:#f2f1ee!important;}}
</style>
</head>
<body style="margin:0;padding:0;background:#f1f0ec;">
<table role="presentation" class="bg" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f1f0ec;">
<tr><td align="center" style="padding:32px 12px;">
<table role="presentation" class="w" width="600" cellpadding="0" cellspacing="0" border="0" style="width:600px;max-width:600px;">
<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">
<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;">Vielen Dank für Ihre Bestellung</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;">Guten Tag,<br>wir haben Ihre Bestellung bei {{betrieb_name}} erhalten und Ihre Zahlung ist eingegangen. Bitte bewahren Sie diese E-Mail auf: Sie enthält alle Angaben zu Ihrem Vertrag.</td></tr>
<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;">Bestellnummer</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;"><strong>{{bestellnummer}}</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;">Bestelldatum</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;">Anbieter</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{anbieter_zeile}}</td></tr>
</table></td></tr>
<tr><td class="px ink" style="padding:28px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:17px;line-height:24px;font-weight:bold;color:#111111;">Ihre Artikel</td></tr>
<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;">{{positionen}}</td></tr></table></td></tr>
<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;">Zwischensumme</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{zwischensumme}}</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;">Versand</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{versand}}</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;">Gesamt</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;"><strong>{{gesamt}}</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;">Steuer</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{steuer_hinweis}}</td></tr>
</table></td></tr>
<tr><td class="px ink" style="padding:28px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:17px;line-height:24px;font-weight:bold;color:#111111;">Lieferung und Zahlung</td></tr>
<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;">Lieferart</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{lieferart_text}}</td></tr>
<!-- WENN versand -->
<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;">Lieferadresse</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{lieferadresse}}</td></tr>
<!-- ENDE versand -->
<!-- WENN abholung -->
<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;">Abholort</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{abholort}}</td></tr>
<!-- ENDE abholung -->
<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;">Lieferzeit</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{lieferzeit}}</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;">Zahlungsart</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{zahlungsart}}</td></tr>
</table></td></tr>
<!-- WENN ausschluss -->
<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;"><strong>Hinweis zum Widerrufsrecht:</strong><br>{{ausschluss_text}}</td></tr></table></td></tr>
<!-- ENDE ausschluss -->
<tr><td class="px ink" style="padding:30px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:17px;line-height:24px;font-weight:bold;color:#111111;">Hinweis zur gesetzlichen Gewährleistung</td></tr>
<tr><td class="px" style="padding:10px 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="mut" style="padding:14px 18px;font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:20px;color:#4a4843;">{{gewaehrleistung}}</td></tr></table></td></tr>
<tr><td class="px ink" style="padding:30px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:17px;line-height:24px;font-weight:bold;color:#111111;">Widerrufsbelehrung</td></tr>
<tr><td class="px" style="padding:10px 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="mut" style="padding:14px 18px;font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:20px;color:#4a4843;">{{widerrufsbelehrung}}</td></tr></table></td></tr>
<tr><td class="px ink" style="padding:30px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:17px;line-height:24px;font-weight:bold;color:#111111;">Muster-Widerrufsformular</td></tr>
<tr><td class="px" style="padding:10px 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="mut" style="padding:14px 18px;font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:20px;color:#4a4843;">{{widerrufsformular}}</td></tr></table></td></tr>
<tr><td class="px ink" style="padding:30px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:17px;line-height:24px;font-weight:bold;color:#111111;">Allgemeine Geschäftsbedingungen</td></tr>
<tr><td class="px" style="padding:10px 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="mut" style="padding:14px 18px;font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:20px;color:#4a4843;">{{agb}}</td></tr></table></td></tr>
<tr><td class="px mut" style="padding:26px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:20px;mso-line-height-rule:exactly;color:#6b6964;">Sie können Ihren Vertrag jederzeit über die Schaltfläche „Vertrag widerrufen“ am Ende dieser E-Mail widerrufen, solange Ihnen ein Widerrufsrecht zusteht.</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>
<tr><td class="px mut" align="center" style="padding:24px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;"><div><strong>{{betrieb_name}}</strong></div><div>{{anbieter_zeile}}</div></td></tr>
<tr><td class="px mut" align="center" style="padding:12px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;"><a class="mut" href="{{impressum_url}}" style="color:#4a4843;text-decoration:underline;">Impressum</a> · <a class="mut" href="{{datenschutz_url}}" style="color:#4a4843;text-decoration:underline;">Datenschutz</a> · <a class="mut" href="{{widerruf_url}}" style="color:#4a4843;text-decoration:underline;">Vertrag widerrufen</a></td></tr>
<tr><td class="px mut" align="center" style="padding:12px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;">Diese E-Mail wurde im Auftrag von {{betrieb_name}} über den Webshop von WEBklar versendet. Antworten gehen direkt an {{betrieb_name}}.</td></tr>
</table>
</td></tr>
</table>
</body>
</html>
+53
View File
@@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="de" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<!--
G10 · Bestellung versendet (Webshop)
Betreff: Ihre Bestellung {{bestellnummer}} wurde versendet
Absender: „<Shopname>“ <Portal-Postfach> · Antworten an die E-Mail-Adresse des Händlers
Empfänger: Käufer (Adresse aus der Bestellung bzw. der Widerrufserklärung) · ohne BCC
Platzhalter: {{bestellnummer}}, {{sendungsnummer}}, {{versanddienst}}, {{farbe}}, {{betrieb_name}}, {{anbieter_zeile}}, {{impressum_url}}, {{datenschutz_url}}, {{widerruf_url}}
Optionale Blöcke (vom Server gesetzt, nur wenn Versanddienst oder Sendungsnummer bekannt ist): sendung
Sie-Form, keine Anrede-Blöcke. Footer: Anbieter-Zeile mit Name, Anschrift und E-Mail, Impressum, Datenschutz, Link zur Widerrufsfunktion.
Block sendung: Zeilen Versanddienst und Sendungsnummer (der Server setzt beide Werte, fehlende als „–“).
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<title>Ihre Bestellung {{bestellnummer}} wurde versendet</title>
<!--[if mso]><style>table,td{font-family:Arial,Helvetica,sans-serif!important;}</style><![endif]-->
<style>
a{color:#111111;}
@media (max-width:620px){.w{width:100%!important;}.px{padding-left:24px!important;padding-right:24px!important;}.lbl{display:block!important;width:100%!important;padding-bottom:2px!important;border-bottom:0!important;}.val{display:block!important;width:100%!important;padding-top:0!important;}}
@media (prefers-color-scheme:dark){.bg{background:#161615!important;}.card{background:#1f1f1e!important;}.ink{color:#f2f1ee!important;}.mut{color:#a8a6a0!important;}.box{background:#2a2a28!important;border-color:#3a3a37!important;}.btn{background:#f2f1ee!important;}.btn a{color:#161615!important;}.btn2{border-color:#f2f1ee!important;}.btn2 a{color:#f2f1ee!important;}.rowline{border-color:#3a3a37!important;}.pill{background:#1f1f1e!important;}.pill span{color:#f2f1ee!important;}}
</style>
</head>
<body style="margin:0;padding:0;background:#f1f0ec;">
<table role="presentation" class="bg" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f1f0ec;">
<tr><td align="center" style="padding:32px 12px;">
<table role="presentation" class="w" width="600" cellpadding="0" cellspacing="0" border="0" style="width:600px;max-width:600px;">
<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">
<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;">Ihre Bestellung ist unterwegs</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;">Guten Tag,<br>{{betrieb_name}} hat Ihre Bestellung versendet.</td></tr>
<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;">Bestellnummer</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;"><strong>{{bestellnummer}}</strong></td></tr>
<!-- WENN sendung -->
<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;">Versanddienst</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{versanddienst}}</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;">Sendungsnummer</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{sendungsnummer}}</td></tr>
<!-- ENDE sendung -->
</table></td></tr>
<tr><td class="px mut" style="padding:20px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:25px;mso-line-height-rule:exactly;color:#4a4843;">Bei Fragen zu Ihrer Bestellung antworten Sie einfach auf diese E-Mail.</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>
<tr><td class="px mut" align="center" style="padding:24px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;"><div><strong>{{betrieb_name}}</strong></div><div>{{anbieter_zeile}}</div></td></tr>
<tr><td class="px mut" align="center" style="padding:12px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;"><a class="mut" href="{{impressum_url}}" style="color:#4a4843;text-decoration:underline;">Impressum</a> · <a class="mut" href="{{datenschutz_url}}" style="color:#4a4843;text-decoration:underline;">Datenschutz</a> · <a class="mut" href="{{widerruf_url}}" style="color:#4a4843;text-decoration:underline;">Vertrag widerrufen</a></td></tr>
<tr><td class="px mut" align="center" style="padding:12px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;">Diese E-Mail wurde im Auftrag von {{betrieb_name}} über den Webshop von WEBklar versendet. Antworten gehen direkt an {{betrieb_name}}.</td></tr>
</table>
</td></tr>
</table>
</body>
</html>
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="de" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<!--
G11 · Bestellung abholbereit (Webshop)
Betreff: Ihre Bestellung {{bestellnummer}} ist abholbereit
Absender: „<Shopname>“ <Portal-Postfach> · Antworten an die E-Mail-Adresse des Händlers
Empfänger: Käufer (Adresse aus der Bestellung bzw. der Widerrufserklärung) · ohne BCC
Platzhalter: {{bestellnummer}}, {{abholort}}, {{hinweis}}, {{farbe}}, {{betrieb_name}}, {{anbieter_zeile}}, {{impressum_url}}, {{datenschutz_url}}, {{widerruf_url}}
Sie-Form, keine Anrede-Blöcke. Footer: Anbieter-Zeile mit Name, Anschrift und E-Mail, Impressum, Datenschutz, Link zur Widerrufsfunktion.
abholort = Adresse der Abholung; hinweis = Hinweis des Händlers zur Abholung (der Server setzt einen Standardtext, wenn der Händler keinen hinterlegt hat).
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<title>Ihre Bestellung {{bestellnummer}} ist abholbereit</title>
<!--[if mso]><style>table,td{font-family:Arial,Helvetica,sans-serif!important;}</style><![endif]-->
<style>
a{color:#111111;}
@media (max-width:620px){.w{width:100%!important;}.px{padding-left:24px!important;padding-right:24px!important;}.lbl{display:block!important;width:100%!important;padding-bottom:2px!important;border-bottom:0!important;}.val{display:block!important;width:100%!important;padding-top:0!important;}}
@media (prefers-color-scheme:dark){.bg{background:#161615!important;}.card{background:#1f1f1e!important;}.ink{color:#f2f1ee!important;}.mut{color:#a8a6a0!important;}.box{background:#2a2a28!important;border-color:#3a3a37!important;}.btn{background:#f2f1ee!important;}.btn a{color:#161615!important;}.btn2{border-color:#f2f1ee!important;}.btn2 a{color:#f2f1ee!important;}.rowline{border-color:#3a3a37!important;}.pill{background:#1f1f1e!important;}.pill span{color:#f2f1ee!important;}}
</style>
</head>
<body style="margin:0;padding:0;background:#f1f0ec;">
<table role="presentation" class="bg" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f1f0ec;">
<tr><td align="center" style="padding:32px 12px;">
<table role="presentation" class="w" width="600" cellpadding="0" cellspacing="0" border="0" style="width:600px;max-width:600px;">
<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">
<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;">Ihre Bestellung ist abholbereit</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;">Guten Tag,<br>Ihre Bestellung liegt bei {{betrieb_name}} zur Abholung bereit.</td></tr>
<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;">Bestellnummer</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;"><strong>{{bestellnummer}}</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;">Abholort</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{abholort}}</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;">Hinweis</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{hinweis}}</td></tr>
</table></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>
<tr><td class="px mut" align="center" style="padding:24px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;"><div><strong>{{betrieb_name}}</strong></div><div>{{anbieter_zeile}}</div></td></tr>
<tr><td class="px mut" align="center" style="padding:12px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;"><a class="mut" href="{{impressum_url}}" style="color:#4a4843;text-decoration:underline;">Impressum</a> · <a class="mut" href="{{datenschutz_url}}" style="color:#4a4843;text-decoration:underline;">Datenschutz</a> · <a class="mut" href="{{widerruf_url}}" style="color:#4a4843;text-decoration:underline;">Vertrag widerrufen</a></td></tr>
<tr><td class="px mut" align="center" style="padding:12px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;">Diese E-Mail wurde im Auftrag von {{betrieb_name}} über den Webshop von WEBklar versendet. Antworten gehen direkt an {{betrieb_name}}.</td></tr>
</table>
</td></tr>
</table>
</body>
</html>
@@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="de" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<!--
G12 · Eingang Widerruf (Eingangsbestätigung nach § 356a BGB, Webshop)
Betreff: Eingangsbestätigung Ihres Widerrufs bei {{betrieb_name}}
Absender: „<Shopname>“ <Portal-Postfach> · Antworten an die E-Mail-Adresse des Händlers
Empfänger: Käufer (Adresse aus der Bestellung bzw. der Widerrufserklärung) · ohne BCC
Platzhalter: {{name}}, {{bestellnummer}}, {{eingang}}, {{nachricht}}, {{farbe}}, {{betrieb_name}}, {{anbieter_zeile}}, {{impressum_url}}, {{datenschutz_url}}, {{widerruf_url}}
Optionale Blöcke (vom Server gesetzt, nur wenn der Käufer eine Erklärung eingegeben hat): nachricht
Sie-Form, keine Anrede-Blöcke. Footer: Anbieter-Zeile mit Name, Anschrift und E-Mail, Impressum, Datenschutz, Link zur Widerrufsfunktion.
Empfänger ist die bei der Erklärung angegebene Adresse (ungeprüft). name, bestellnummer und nachricht sind Eingaben des Käufers. eingang = „TT.MM.JJJJ, HH:MM Uhr“ (Berliner Zeit).
Die Mail enthält den Inhalt der Erklärung, Datum und Uhrzeit des Eingangs und gilt als Eingangsbestätigung.
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<title>Eingangsbestätigung Ihres Widerrufs bei {{betrieb_name}}</title>
<!--[if mso]><style>table,td{font-family:Arial,Helvetica,sans-serif!important;}</style><![endif]-->
<style>
a{color:#111111;}
@media (max-width:620px){.w{width:100%!important;}.px{padding-left:24px!important;padding-right:24px!important;}.lbl{display:block!important;width:100%!important;padding-bottom:2px!important;border-bottom:0!important;}.val{display:block!important;width:100%!important;padding-top:0!important;}}
@media (prefers-color-scheme:dark){.bg{background:#161615!important;}.card{background:#1f1f1e!important;}.ink{color:#f2f1ee!important;}.mut{color:#a8a6a0!important;}.box{background:#2a2a28!important;border-color:#3a3a37!important;}.btn{background:#f2f1ee!important;}.btn a{color:#161615!important;}.btn2{border-color:#f2f1ee!important;}.btn2 a{color:#f2f1ee!important;}.rowline{border-color:#3a3a37!important;}.pill{background:#1f1f1e!important;}.pill span{color:#f2f1ee!important;}}
</style>
</head>
<body style="margin:0;padding:0;background:#f1f0ec;">
<table role="presentation" class="bg" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f1f0ec;">
<tr><td align="center" style="padding:32px 12px;">
<table role="presentation" class="w" width="600" cellpadding="0" cellspacing="0" border="0" style="width:600px;max-width:600px;">
<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">
<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;">Eingangsbestätigung Ihres Widerrufs</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;">Guten Tag {{name}},<br>wir bestätigen den Eingang Ihrer Widerrufserklärung bei {{betrieb_name}}.</td></tr>
<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;">Eingegangen am</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;"><strong>{{eingang}}</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;">Bestellnummer</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{bestellnummer}}</td></tr>
<!-- WENN nachricht -->
<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;">Ihre Erklärung</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{nachricht}}</td></tr>
<!-- ENDE nachricht -->
</table></td></tr>
<tr><td class="px mut" style="padding:20px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:25px;mso-line-height-rule:exactly;color:#4a4843;">{{betrieb_name}} prüft Ihre Erklärung und meldet sich bei Ihnen.</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;">Sie haben diese E-Mail erhalten, weil mit Ihrer E-Mail-Adresse ein Widerruf erklärt wurde. Waren Sie das nicht, können Sie diese Nachricht ignorieren.</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>
<tr><td class="px mut" align="center" style="padding:24px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;"><div><strong>{{betrieb_name}}</strong></div><div>{{anbieter_zeile}}</div></td></tr>
<tr><td class="px mut" align="center" style="padding:12px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;"><a class="mut" href="{{impressum_url}}" style="color:#4a4843;text-decoration:underline;">Impressum</a> · <a class="mut" href="{{datenschutz_url}}" style="color:#4a4843;text-decoration:underline;">Datenschutz</a> · <a class="mut" href="{{widerruf_url}}" style="color:#4a4843;text-decoration:underline;">Vertrag widerrufen</a></td></tr>
<tr><td class="px mut" align="center" style="padding:12px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;">Diese E-Mail wurde im Auftrag von {{betrieb_name}} über den Webshop von WEBklar versendet. Antworten gehen direkt an {{betrieb_name}}.</td></tr>
</table>
</td></tr>
</table>
</body>
</html>
+53
View File
@@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="de" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<!--
G13 · Erstattung (Webshop)
Betreff: Erstattung zu Ihrer Bestellung {{bestellnummer}}
Absender: „<Shopname>“ <Portal-Postfach> · Antworten an die E-Mail-Adresse des Händlers
Empfänger: Käufer (Adresse aus der Bestellung bzw. der Widerrufserklärung) · ohne BCC
Platzhalter: {{bestellnummer}}, {{betrag}}, {{grund}}, {{farbe}}, {{betrieb_name}}, {{anbieter_zeile}}, {{impressum_url}}, {{datenschutz_url}}, {{widerruf_url}}
Optionale Blöcke (vom Server gesetzt, nur wenn der Händler einen Grund angegeben hat): grund
Sie-Form, keine Anrede-Blöcke. Footer: Anbieter-Zeile mit Name, Anschrift und E-Mail, Impressum, Datenschutz, Link zur Widerrufsfunktion.
betrag = erstatteter Betrag, z. B. „12,50 €“.
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<title>Erstattung zu Ihrer Bestellung {{bestellnummer}}</title>
<!--[if mso]><style>table,td{font-family:Arial,Helvetica,sans-serif!important;}</style><![endif]-->
<style>
a{color:#111111;}
@media (max-width:620px){.w{width:100%!important;}.px{padding-left:24px!important;padding-right:24px!important;}.lbl{display:block!important;width:100%!important;padding-bottom:2px!important;border-bottom:0!important;}.val{display:block!important;width:100%!important;padding-top:0!important;}}
@media (prefers-color-scheme:dark){.bg{background:#161615!important;}.card{background:#1f1f1e!important;}.ink{color:#f2f1ee!important;}.mut{color:#a8a6a0!important;}.box{background:#2a2a28!important;border-color:#3a3a37!important;}.btn{background:#f2f1ee!important;}.btn a{color:#161615!important;}.btn2{border-color:#f2f1ee!important;}.btn2 a{color:#f2f1ee!important;}.rowline{border-color:#3a3a37!important;}.pill{background:#1f1f1e!important;}.pill span{color:#f2f1ee!important;}}
</style>
</head>
<body style="margin:0;padding:0;background:#f1f0ec;">
<table role="presentation" class="bg" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f1f0ec;">
<tr><td align="center" style="padding:32px 12px;">
<table role="presentation" class="w" width="600" cellpadding="0" cellspacing="0" border="0" style="width:600px;max-width:600px;">
<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">
<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;">Ihre Erstattung</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;">Guten Tag,<br>{{betrieb_name}} hat Ihnen einen Betrag zu Ihrer Bestellung erstattet.</td></tr>
<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;">Bestellnummer</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;"><strong>{{bestellnummer}}</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;">Erstatteter Betrag</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;"><strong>{{betrag}}</strong></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>
<tr><td class="px mut" style="padding:20px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:25px;mso-line-height-rule:exactly;color:#4a4843;">Die Gutschrift erfolgt über die ursprüngliche Zahlungsart. Je nach Zahlungsart kann es einige Tage dauern, bis der Betrag bei Ihnen sichtbar ist.</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>
<tr><td class="px mut" align="center" style="padding:24px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;"><div><strong>{{betrieb_name}}</strong></div><div>{{anbieter_zeile}}</div></td></tr>
<tr><td class="px mut" align="center" style="padding:12px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;"><a class="mut" href="{{impressum_url}}" style="color:#4a4843;text-decoration:underline;">Impressum</a> · <a class="mut" href="{{datenschutz_url}}" style="color:#4a4843;text-decoration:underline;">Datenschutz</a> · <a class="mut" href="{{widerruf_url}}" style="color:#4a4843;text-decoration:underline;">Vertrag widerrufen</a></td></tr>
<tr><td class="px mut" align="center" style="padding:12px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;">Diese E-Mail wurde im Auftrag von {{betrieb_name}} über den Webshop von WEBklar versendet. Antworten gehen direkt an {{betrieb_name}}.</td></tr>
</table>
</td></tr>
</table>
</body>
</html>
@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="de" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<!--
G14 · Zahlung fehlgeschlagen (Webshop)
Betreff: Ihre Bestellung {{bestellnummer}} wurde nicht abgeschlossen
Absender: „<Shopname>“ <Portal-Postfach> · Antworten an die E-Mail-Adresse des Händlers
Empfänger: Käufer (Adresse aus der Bestellung bzw. der Widerrufserklärung) · ohne BCC
Platzhalter: {{bestellnummer}}, {{farbe}}, {{betrieb_name}}, {{anbieter_zeile}}, {{impressum_url}}, {{datenschutz_url}}, {{widerruf_url}}
Sie-Form, keine Anrede-Blöcke. Footer: Anbieter-Zeile mit Name, Anschrift und E-Mail, Impressum, Datenschutz, Link zur Widerrufsfunktion.
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<title>Ihre Bestellung {{bestellnummer}} wurde nicht abgeschlossen</title>
<!--[if mso]><style>table,td{font-family:Arial,Helvetica,sans-serif!important;}</style><![endif]-->
<style>
a{color:#111111;}
@media (max-width:620px){.w{width:100%!important;}.px{padding-left:24px!important;padding-right:24px!important;}.lbl{display:block!important;width:100%!important;padding-bottom:2px!important;border-bottom:0!important;}.val{display:block!important;width:100%!important;padding-top:0!important;}}
@media (prefers-color-scheme:dark){.bg{background:#161615!important;}.card{background:#1f1f1e!important;}.ink{color:#f2f1ee!important;}.mut{color:#a8a6a0!important;}.box{background:#2a2a28!important;border-color:#3a3a37!important;}.btn{background:#f2f1ee!important;}.btn a{color:#161615!important;}.btn2{border-color:#f2f1ee!important;}.btn2 a{color:#f2f1ee!important;}.rowline{border-color:#3a3a37!important;}.pill{background:#1f1f1e!important;}.pill span{color:#f2f1ee!important;}}
</style>
</head>
<body style="margin:0;padding:0;background:#f1f0ec;">
<table role="presentation" class="bg" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f1f0ec;">
<tr><td align="center" style="padding:32px 12px;">
<table role="presentation" class="w" width="600" cellpadding="0" cellspacing="0" border="0" style="width:600px;max-width:600px;">
<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">
<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;">Ihre Bestellung wurde nicht abgeschlossen</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;">Guten Tag,<br>die Zahlung für Ihre Bestellung bei {{betrieb_name}} ist fehlgeschlagen. Ihre Bestellung wurde nicht abgeschlossen; es wurde nichts berechnet.</td></tr>
<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;">Bestellnummer</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;"><strong>{{bestellnummer}}</strong></td></tr>
</table></td></tr>
<tr><td class="px mut" style="padding:20px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:25px;mso-line-height-rule:exactly;color:#4a4843;">Sie können jederzeit erneut bestellen. Bei Fragen antworten Sie einfach auf diese E-Mail.</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>
<tr><td class="px mut" align="center" style="padding:24px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;"><div><strong>{{betrieb_name}}</strong></div><div>{{anbieter_zeile}}</div></td></tr>
<tr><td class="px mut" align="center" style="padding:12px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;"><a class="mut" href="{{impressum_url}}" style="color:#4a4843;text-decoration:underline;">Impressum</a> · <a class="mut" href="{{datenschutz_url}}" style="color:#4a4843;text-decoration:underline;">Datenschutz</a> · <a class="mut" href="{{widerruf_url}}" style="color:#4a4843;text-decoration:underline;">Vertrag widerrufen</a></td></tr>
<tr><td class="px mut" align="center" style="padding:12px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;">Diese E-Mail wurde im Auftrag von {{betrieb_name}} über den Webshop von WEBklar versendet. Antworten gehen direkt an {{betrieb_name}}.</td></tr>
</table>
</td></tr>
</table>
</body>
</html>
@@ -0,0 +1,64 @@
<!DOCTYPE html>
<html lang="de" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<!--
K25 · Neue Bestellung im Webshop (an den Händler)
Betreff: Neue Bestellung {{bestellnummer}} in Deinem Webshop
Absender: „WEBklar“ <Portal-Postfach>
Empfänger: Kunde (E-Mail des Kundenkontos) · OHNE BCC (enthält Angaben von Käufern)
Platzhalter: {{kunde_name}}, {{shopname}}, {{bestellnummer}}, {{gesamt}}, {{positionen}}, {{lieferart_text}}, {{kaeufer_name}}, {{portal_url}}
Optionale Blöcke (zwischen WENN-/ENDE-Kommentaren; bei Nichtzutreffen samt Inhalt entfernen): ueberverkauft, sepa
Block ueberverkauft: der Bestand reichte nicht (die Bestellung war schon bezahlt). Block sepa: Zahlung per Lastschrift.
positionen = eine Zeile je Position „Menge × Name (Variante) – Summe“; portal_url = Webshop-Seite des Projekts im Kundenportal.
Texte sind Entwurf für die Mail-Überarbeitung des Inhabers; verbindlich sind nur Platzhalter und Blöcke.
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<title>Neue Bestellung {{bestellnummer}} in Deinem Webshop</title>
<!--[if mso]><style>table,td{font-family:Arial,Helvetica,sans-serif!important;}</style><![endif]-->
<style>
a{color:#111111;}
@media (max-width:620px){.w{width:100%!important;}.px{padding-left:24px!important;padding-right:24px!important;}.lbl{display:block!important;width:100%!important;padding-bottom:2px!important;border-bottom:0!important;}.val{display:block!important;width:100%!important;padding-top:0!important;}}
@media (prefers-color-scheme:dark){.bg{background:#161615!important;}.card{background:#1f1f1e!important;}.ink{color:#f2f1ee!important;}.mut{color:#a8a6a0!important;}.box{background:#2a2a28!important;border-color:#3a3a37!important;}.btn{background:#f2f1ee!important;}.btn a{color:#161615!important;}.rowline{border-color:#3a3a37!important;}}
</style>
</head>
<body style="margin:0;padding:0;background:#f1f0ec;">
<span style="display:none;max-height:0;overflow:hidden;opacity:0;mso-hide:all;font-size:1px;line-height:1px;color:#f1f0ec;">In Deinem Webshop ist eine neue Bestellung eingegangen.&#8199;&#847;&#8199;&#847;&#8199;&#847;</span>
<table role="presentation" class="bg" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f1f0ec;">
<tr><td align="center" style="padding:32px 12px;">
<table role="presentation" class="w" width="600" cellpadding="0" cellspacing="0" border="0" style="width:600px;max-width:600px;">
<tr><td class="px" style="padding:0 40px 20px 40px;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0"><tr>
<td><table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr><td valign="middle" style="padding-right:10px;"><img src="https://project.webklar.com/webklar-logo.png" width="40" height="40" alt="WEBklar" style="display:block;width:40px;height:40px;border:0;outline:none;text-decoration:none;border-radius:10px;"></td><td valign="middle" class="ink" style="font-family:Arial,Helvetica,sans-serif;font-size:26px;font-weight:bold;letter-spacing:-1px;color:#111111;">WEBklar</td></tr></table></td>
<td align="right" class="mut" style="font-family:Arial,Helvetica,sans-serif;font-size:12px;color:#6b6964;">Webshop</td>
</tr></table></td></tr>
<tr><td class="card" style="background:#ffffff;border-radius:16px;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<tr><td class="px" style="padding:44px 40px 8px 40px;"><table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr><td style="background:#e4f2e6;border-radius:100px;padding:6px 12px;font-family:Arial,Helvetica,sans-serif;font-size:12px;font-weight:bold;color:#1d5b2c;">Neue Bestellung</td></tr></table></td></tr>
<tr><td class="px ink" style="padding:16px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:28px;line-height:34px;mso-line-height-rule:exactly;font-weight:bold;letter-spacing:-0.5px;color:#111111;">Neue Bestellung {{bestellnummer}}</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;">Hallo {{kunde_name}},<br>in Deinem Webshop „{{shopname}}“ ist eine bezahlte Bestellung eingegangen.</td></tr>
<!-- WENN ueberverkauft -->
<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;"><strong>Achtung: Bestand reichte nicht.</strong> Die Bestellung ist bezahlt, aber von mindestens einem Artikel war nicht mehr genug da. Bitte melde Dich beim Käufer oder erstatte die Bestellung im Portal.</td></tr></table></td></tr>
<!-- ENDE ueberverkauft -->
<!-- WENN sepa -->
<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;"><strong>Zahlung per Lastschrift:</strong> Stripe hat die Zahlung bestätigt. Eine Lastschrift kann aber noch einige Wochen zurückgebucht werden. Bedenke das vor dem Versand teurer Ware.</td></tr></table></td></tr>
<!-- ENDE sepa -->
<tr><td class="px" style="padding:28px 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="170" valign="top" style="width:170px;padding:12px 12px 12px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Bestellnummer</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;"><strong>{{bestellnummer}}</strong></td></tr>
<tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Käufer</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{kaeufer_name}}</td></tr>
<tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Lieferart</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{lieferart_text}}</td></tr>
<tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Gesamt</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;"><strong>{{gesamt}}</strong></td></tr>
</table></td></tr>
<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;">{{positionen}}</td></tr></table></td></tr>
<tr><td class="px" style="padding:28px 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="{{portal_url}}" style="display:block;padding:14px 26px;font-family:Arial,Helvetica,sans-serif;font-size:15px;font-weight:bold;color:#ffffff;text-decoration:none;">Bestellung ansehen &rarr;</a></td></tr></table></td></tr>
<tr><td class="px ink" style="padding:28px 40px 44px 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:24px;color:#111111;">Viele Grüße<br><strong>Dein WEBklar-Team</strong></td></tr>
</table>
</td></tr>
<tr><td class="px" align="center" style="padding:24px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;"><a class="mut" href="https://project.webklar.com/dashboard.html" style="color:#4a4843;text-decoration:underline;">Kundenportal</a></td></tr>
<tr><td class="px mut" align="center" style="padding:14px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;">WEBklar GbR · Am Schwimmbad 10 · 67722 Winnweiler<br>Telefon <a class="mut" href="tel:+4917623726355" style="color:#6b6964;text-decoration:none;">0176 23726355</a> · <a class="mut" href="mailto:support@webklar.com" style="color:#6b6964;text-decoration:none;">support@webklar.com</a><br>Vertreten durch die Gesellschafter Kenso Grimm und Justin Klein</td></tr>
</table>
</td></tr>
</table>
</body>
</html>
@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="de" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<!--
K26 · Widerruf im Webshop eingegangen (an den Händler)
Betreff: Widerruf in Deinem Webshop eingegangen
Absender: „WEBklar“ <Portal-Postfach>
Empfänger: Kunde (E-Mail des Kundenkontos) · OHNE BCC (enthält Angaben von Käufern)
Platzhalter: {{kunde_name}}, {{shopname}}, {{bestellnummer}}, {{kaeufer_name}}, {{kaeufer_email}}, {{eingang}}, {{nachricht}}, {{portal_url}}
Optionale Blöcke (zwischen WENN-/ENDE-Kommentaren; bei Nichtzutreffen samt Inhalt entfernen): nachricht
Block nachricht: der Käufer hat eine Erklärung eingegeben. bestellnummer, kaeufer_name, kaeufer_email und nachricht sind Eingaben des Käufers (ungeprüft). eingang = „TT.MM.JJJJ, HH:MM Uhr“ (Berliner Zeit).
Der Käufer hat die Eingangsbestätigung (G12) schon automatisch bekommen.
Texte sind Entwurf für die Mail-Überarbeitung des Inhabers; verbindlich sind nur Platzhalter und Blöcke.
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<title>Widerruf in Deinem Webshop eingegangen</title>
<!--[if mso]><style>table,td{font-family:Arial,Helvetica,sans-serif!important;}</style><![endif]-->
<style>
a{color:#111111;}
@media (max-width:620px){.w{width:100%!important;}.px{padding-left:24px!important;padding-right:24px!important;}.lbl{display:block!important;width:100%!important;padding-bottom:2px!important;border-bottom:0!important;}.val{display:block!important;width:100%!important;padding-top:0!important;}}
@media (prefers-color-scheme:dark){.bg{background:#161615!important;}.card{background:#1f1f1e!important;}.ink{color:#f2f1ee!important;}.mut{color:#a8a6a0!important;}.box{background:#2a2a28!important;border-color:#3a3a37!important;}.btn{background:#f2f1ee!important;}.btn a{color:#161615!important;}.rowline{border-color:#3a3a37!important;}}
</style>
</head>
<body style="margin:0;padding:0;background:#f1f0ec;">
<span style="display:none;max-height:0;overflow:hidden;opacity:0;mso-hide:all;font-size:1px;line-height:1px;color:#f1f0ec;">Ein Käufer hat in Deinem Webshop einen Vertrag widerrufen.&#8199;&#847;&#8199;&#847;&#8199;&#847;</span>
<table role="presentation" class="bg" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f1f0ec;">
<tr><td align="center" style="padding:32px 12px;">
<table role="presentation" class="w" width="600" cellpadding="0" cellspacing="0" border="0" style="width:600px;max-width:600px;">
<tr><td class="px" style="padding:0 40px 20px 40px;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0"><tr>
<td><table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr><td valign="middle" style="padding-right:10px;"><img src="https://project.webklar.com/webklar-logo.png" width="40" height="40" alt="WEBklar" style="display:block;width:40px;height:40px;border:0;outline:none;text-decoration:none;border-radius:10px;"></td><td valign="middle" class="ink" style="font-family:Arial,Helvetica,sans-serif;font-size:26px;font-weight:bold;letter-spacing:-1px;color:#111111;">WEBklar</td></tr></table></td>
<td align="right" class="mut" style="font-family:Arial,Helvetica,sans-serif;font-size:12px;color:#6b6964;">Webshop</td>
</tr></table></td></tr>
<tr><td class="card" style="background:#ffffff;border-radius:16px;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<tr><td class="px" style="padding:44px 40px 8px 40px;"><table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr><td style="background:#e4f2e6;border-radius:100px;padding:6px 12px;font-family:Arial,Helvetica,sans-serif;font-size:12px;font-weight:bold;color:#1d5b2c;">Widerruf</td></tr></table></td></tr>
<tr><td class="px ink" style="padding:16px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:28px;line-height:34px;mso-line-height-rule:exactly;font-weight:bold;letter-spacing:-0.5px;color:#111111;">Widerruf in Deinem Webshop eingegangen</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;">Hallo {{kunde_name}},<br>ein Käufer hat in Deinem Webshop „{{shopname}}“ einen Vertrag widerrufen. Den Eingang haben wir ihm schon automatisch bestätigt.</td></tr>
<tr><td class="px" style="padding:28px 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="170" valign="top" style="width:170px;padding:12px 12px 12px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Eingegangen am</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;"><strong>{{eingang}}</strong></td></tr>
<tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Bestellnummer</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{bestellnummer}}</td></tr>
<tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Käufer</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{kaeufer_name}}</td></tr>
<tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">E-Mail</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{kaeufer_email}}</td></tr>
<!-- WENN nachricht -->
<tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Erklärung</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{nachricht}}</td></tr>
<!-- ENDE nachricht -->
</table></td></tr>
<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;"><strong>Was jetzt zu tun ist:</strong> Bitte bestätige dem Käufer den Widerruf persönlich und erstatte den Kaufpreis innerhalb von 14 Tagen nach Eingang des Widerrufs, wenn der Widerruf wirksam ist. Die Erstattung machst Du im Portal bei der Bestellung. Die Rücksendung der Ware klärst Du mit dem Käufer.</td></tr></table></td></tr>
<tr><td class="px" style="padding:28px 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="{{portal_url}}" style="display:block;padding:14px 26px;font-family:Arial,Helvetica,sans-serif;font-size:15px;font-weight:bold;color:#ffffff;text-decoration:none;">Widerrufe ansehen &rarr;</a></td></tr></table></td></tr>
<tr><td class="px ink" style="padding:28px 40px 44px 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:24px;color:#111111;">Viele Grüße<br><strong>Dein WEBklar-Team</strong></td></tr>
</table>
</td></tr>
<tr><td class="px" align="center" style="padding:24px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;"><a class="mut" href="https://project.webklar.com/dashboard.html" style="color:#4a4843;text-decoration:underline;">Kundenportal</a></td></tr>
<tr><td class="px mut" align="center" style="padding:14px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;">WEBklar GbR · Am Schwimmbad 10 · 67722 Winnweiler<br>Telefon <a class="mut" href="tel:+4917623726355" style="color:#6b6964;text-decoration:none;">0176 23726355</a> · <a class="mut" href="mailto:support@webklar.com" style="color:#6b6964;text-decoration:none;">support@webklar.com</a><br>Vertreten durch die Gesellschafter Kenso Grimm und Justin Klein</td></tr>
</table>
</td></tr>
</table>
</body>
</html>
+390
View File
@@ -0,0 +1,390 @@
import { config } from '../../config.js'
import { renderMail, loadTemplate } from '../mailTemplates.js'
import { sendCustomerMail } from '../mailer.js'
import { customerOf } from '../siteFeatures.js'
import { einreihen } from '../termine/outbox.js'
import { pruefeNow, zeitMs } from '../termine/util.js'
import { datumKurz, uhrzeit } from '../termine/zeit.js'
import { euro } from './geld.js'
import { oeffentlicheRechtstexte } from './kontext.js'
import { gewaehrleistungsHinweis } from './rechtstexte.js'
/**
* Mails des Webshops (Spezifikation 2026-10-05-webshop-design.md, Abschnitte 5.3, 5.4 und 8; Plan Task E4).
*
* Kaeufer (G09 Bestellbestaetigung, G10 versendet, G11 abholbereit, G12 Eingang Widerruf, G13 Erstattung, G14 Zahlung
* fehlgeschlagen) laufen durch die Mail-Warteschlange des Terminplaners (termine/outbox.js einreihen): Absendername = Shopname,
* Absenderadresse bleibt SMTP_FROM, Antworten an die E-Mail des Haendlers, nie BCC. featureId = ID des portalFeatures-Dokuments des
* Shops, bookingId = Bestell-ID. Der Betreff steht in der Vorlage (G-Vorlagen des Shops haben keinen {{betreff}}-Wert). Die
* Empfaenger-Kappe der Terminplaner-Mails (limits.darfEmpfaenger) gilt hier nicht: G09 ist Vertragspflicht.
* Haendler (K25 neue Bestellung, K26 Widerruf) gehen direkt per sendCustomerMail an die Kunden-E-Mail, ohne BCC ans Team
* (die Mails enthalten Kaeuferdaten).
*
* Beide Wege werfen nie wegen eines Mailfehlers: Rueckgabe { ok: true, ... } | { ok: false, fehler }; der Fehlertext steht im Log (ohne
* Adresse), die Bestellung bleibt unberuehrt.
*
* Erwartete Form der Bestellung (`order` = shopOrders-Dokument; der JSON-Schnappschuss steht in `bestellung`, ersatzweise `daten`,
* als Text oder Objekt; alle Felder sind optional, Fehlendes erscheint als "–"):
* order.bestellnummer, order.kaeuferEmail, order.bezahltAt | order.erstelltAt
* snap.positionen[] { name, varianteText, menge, summeCent, summeText?, widerruf ('gesetzlich'|'verderblich'|'individuell'|'hygiene') }
* snap.summen | snap { zwischensummeCent, versandCent, gesamtCent, texte?: { zwischensumme, versand, gesamt, steuerHinweis } }
* snap.lieferart ('versand'|'abholung'), snap.land ('DE'|'AT')
* snap.kaeufer { name, email, telefon } (oder snap.name)
* snap.lieferadresse { name?, line1|strasse, line2?, postal_code|plz, city|ort, country|land }
* snap.zahlungsart 'card' | 'klarna' | 'sepa_debit' | Text
* snap.einstellungenAuszug { anbieter, lieferzeit, abholung } (sonst die aktuellen Einstellungen des Shops)
*/
const LEER = '–'
const GAST_ARTEN = Object.freeze(['G09', 'G10', 'G11', 'G12', 'G13', 'G14'])
/** Grenzen des Outbox-Schemas (html 100000, text 50000) mit etwas Luft. */
const MAX_HTML = 95000
const MAX_TEXT = 47000
const LANDNAMEN = { DE: 'Deutschland', AT: 'Österreich' }
const ZAHLUNGSARTEN = { card: 'Kreditkarte, Apple Pay, Google Pay', klarna: 'Klarna', sepa_debit: 'SEPA-Lastschrift' }
// Wie WIDERRUF_TEXTE in produkte.js (dort nicht exportiert)
const WIDERRUF_AUSSCHLUSS = {
verderblich: 'Vom Widerruf ausgeschlossen: schnell verderbliche Ware',
individuell: 'Vom Widerruf ausgeschlossen: nach Kundenwunsch angefertigt',
hygiene: 'Vom Widerruf ausgeschlossen: versiegelte Ware aus Hygienegründen, wenn die Versiegelung entfernt wurde',
}
const ABHOLUNG_HINWEIS = 'Bitte bringen Sie Ihre Bestellnummer mit.'
const istObjekt = (wert) => wert !== null && typeof wert === 'object' && !Array.isArray(wert)
const hatText = (wert) => typeof wert === 'string' && wert.trim() !== ''
const text = (wert) => (typeof wert === 'string' ? wert.trim() : '')
const oderLeer = (wert) => (hatText(wert) ? wert.trim() : LEER)
const einzeilig = (wert) => text(wert).replace(/\s+/g, ' ')
// ---------------------------------------------------------------- Bausteine
/** Schnappschuss der Bestellung als Objekt ({} wenn es keinen oder keinen lesbaren gibt). */
function schnappschuss(order) {
for (const feld of ['bestellung', 'daten']) {
const roh = order?.[feld]
if (istObjekt(roh)) return roh
if (typeof roh === 'string' && roh !== '') {
try {
const geparst = JSON.parse(roh)
if (istObjekt(geparst)) return geparst
} catch {
// unlesbar: naechstes Feld
}
}
}
return {}
}
const einstellungenVon = (ctx) => ctx?.shop?.einstellungen ?? {}
const anbieterVon = (ctx, snap) => ({ ...(einstellungenVon(ctx).anbieter ?? {}), ...(istObjekt(snap?.einstellungenAuszug?.anbieter) ? snap.einstellungenAuszug.anbieter : {}) })
/** Name des Shops in Betreff, Kopf und Absender: Shopname, sonst Name des Anbieters. */
function shopName(ctx, snap = {}) {
return einzeilig(snap?.einstellungenAuszug?.shopname) || einzeilig(einstellungenVon(ctx).shopname) || einzeilig(anbieterVon(ctx, snap).name) || 'Webshop'
}
function anbieterZeile(a) {
const ort = [text(a.plz), text(a.ort)].filter(Boolean).join(' ')
const land = text(a.land) && a.land !== 'DE' ? (LANDNAMEN[a.land] ?? a.land) : ''
const anschrift = [text(a.strasse), ort, land].filter(Boolean).join(', ')
return [text(a.name), anschrift, text(a.email)].filter(Boolean).join(', ') || LEER
}
const siteKeyVon = (ctx) => ctx?.shop?.siteKey || ctx?.doc?.siteKey || ''
const shopSeite = (ctx) => `${config.publicBaseUrl}/shop/${siteKeyVon(ctx)}`
/** Akzentfarbe als #rrggbb (nur geprueft, damit nichts in das style-Attribut gelangt). */
const farbeVon = (e) => (/^#[0-9a-fA-F]{6}$/.test(e?.akzent ?? '') ? e.akzent : '#111111')
/** Menge × Name (Variante) – Summe, eine Zeile je Position. */
function positionenText(positionen) {
const zeilen = (Array.isArray(positionen) ? positionen : [])
.filter(istObjekt)
.map((p) => {
const variante = text(p.varianteText) ? ` (${einzeilig(p.varianteText)})` : ''
const summe = hatText(p.summeText) ? p.summeText.trim() : Number.isInteger(p.summeCent) ? euro(p.summeCent) : LEER
return `${Number.isInteger(p.menge) ? p.menge : LEER} × ${einzeilig(p.name) || LEER}${variante} – ${summe}`
})
return zeilen.length ? zeilen.join('\n') : LEER
}
function adresseText(a, fallbackName) {
if (!istObjekt(a)) return LEER
const feld = (...namen) => text(namen.map((n) => a[n]).find(hatText))
const land = feld('country', 'land')
const zeilen = [
feld('name') || text(fallbackName),
feld('line1', 'strasse'),
feld('line2'),
[feld('postal_code', 'plz'), feld('city', 'ort')].filter(Boolean).join(' '),
land && land !== 'DE' ? (LANDNAMEN[land] ?? land) : '',
].filter(Boolean)
return zeilen.length ? zeilen.join('\n') : LEER
}
function zahlungsartText(snap, e) {
const roh = text(snap.zahlungsart)
if (roh) return ZAHLUNGSARTEN[roh] ?? roh
return Array.isArray(e.zahlungsarten) && e.zahlungsarten.length === 1 ? (ZAHLUNGSARTEN[e.zahlungsarten[0]] ?? LEER) : 'Online-Zahlung'
}
function ausschlussText(positionen) {
return (Array.isArray(positionen) ? positionen : [])
.filter((p) => istObjekt(p) && Object.hasOwn(WIDERRUF_AUSSCHLUSS, p.widerruf))
.map((p) => `${einzeilig(p.name) || LEER}: ${WIDERRUF_AUSSCHLUSS[p.widerruf]}`)
.join('\n')
}
/** "TT.MM.JJJJ, HH:MM Uhr" (Berliner Zeit); ungueltige Zeitpunkte werfen. */
function eingangText(eingang) {
const ms = zeitMs(eingang)
if (!Number.isFinite(ms)) throw new TypeError('Eingang ist kein Zeitpunkt')
const d = new Date(ms)
return `${datumKurz(d)}, ${uhrzeit(d)} Uhr`
}
const datumText = (order, now) => {
const ms = zeitMs(order?.bezahltAt || order?.erstelltAt)
return datumKurz(Number.isFinite(ms) ? new Date(ms) : now)
}
// ---------------------------------------------------------------- Werte
/**
* Gemeinsame Werte der Kaeufermails aus dem Schnappschuss der Bestellung und dem Shop-Kontext: Kopf und Fuss (farbe, betrieb_name,
* anbieter_zeile, widerruf_url, impressum_url, datenschutz_url) und alle Werte von G09 (bestellnummer, datum, positionen, Summen,
* Lieferart, Adresse, Zahlungsart, Rechtstexte). `order` darf null sein (G12 ohne passende Bestellung): dann sind die
* Bestellwerte "–". Es sind mehr Werte als eine einzelne Vorlage braucht; anKaeufer reicht nur die deklarierten weiter.
* @param {{ doc?: object, shop: object }} ctx
* @param {object|null} order
* @param {Date} [now] Ersatzdatum, wenn die Bestellung keinen Zeitpunkt hat
* @returns {Record<string, string>}
*/
export function kaeuferWerte(ctx, order, now = new Date()) {
if (!istObjekt(ctx?.shop)) throw new TypeError('kaeuferWerte: ctx.shop fehlt')
const snap = schnappschuss(order)
const e = einstellungenVon(ctx)
const auszug = istObjekt(snap.einstellungenAuszug) ? snap.einstellungenAuszug : {}
const anbieter = anbieterVon(ctx, snap)
const abholung = { ...(e.abholung ?? {}), ...(istObjekt(auszug.abholung) ? auszug.abholung : {}) }
const rechtstexte = oeffentlicheRechtstexte(ctx) ?? {}
const summen = istObjekt(snap.summen) ? snap.summen : snap
const texte = istObjekt(summen.texte) ? summen.texte : {}
const cent = (wert) => (Number.isInteger(wert) ? euro(wert) : LEER)
const lieferart = snap.lieferart === 'abholung' ? 'abholung' : snap.lieferart === 'versand' ? 'versand' : ''
const kaeufer = istObjekt(snap.kaeufer) ? snap.kaeufer : {}
const land = text(snap.land)
const shop = shopSeite(ctx)
return {
farbe: farbeVon(e),
betrieb_name: shopName(ctx, snap),
anbieter_zeile: anbieterZeile(anbieter),
widerruf_url: `${shop}/widerruf`,
impressum_url: text(rechtstexte.impressumUrl) || shop,
datenschutz_url: text(rechtstexte.datenschutzUrl) || shop,
bestellnummer: oderLeer(order?.bestellnummer),
datum: datumText(order, now),
positionen: positionenText(snap.positionen),
zwischensumme: text(texte.zwischensumme) || cent(summen.zwischensummeCent),
versand: text(texte.versand) || (lieferart === 'abholung' ? 'Abholung – kostenlos' : cent(summen.versandCent)),
gesamt: text(texte.gesamt) || cent(summen.gesamtCent ?? order?.summeCent),
steuer_hinweis: text(texte.steuerHinweis) || (e.kleinunternehmer === true ? 'Gemäß § 19 UStG wird keine Umsatzsteuer berechnet.' : LEER),
lieferart_text: lieferart === 'abholung' ? 'Abholung' : lieferart === 'versand' ? `Versand${land ? ` nach ${LANDNAMEN[land] ?? land}` : ''}` : LEER,
lieferadresse: adresseText(snap.lieferadresse ?? kaeufer.lieferadresse ?? kaeufer.adresse, kaeufer.name ?? snap.name),
abholort: oderLeer(abholung.adresse),
lieferzeit: oderLeer(auszug.lieferzeit ?? e.lieferzeit),
zahlungsart: zahlungsartText(snap, e),
ausschluss_text: ausschlussText(snap.positionen),
agb: oderLeer(rechtstexte.agb),
widerrufsbelehrung: oderLeer(rechtstexte.widerrufsbelehrung),
widerrufsformular: oderLeer(rechtstexte.widerrufsformular),
gewaehrleistung: gewaehrleistungsHinweis(),
// fuer G11 (Standard, wenn der Haendler keinen Hinweis hinterlegt hat)
hinweis: text(abholung.hinweis) || ABHOLUNG_HINWEIS,
}
}
/**
* Werte und Bloecke einer Kaeufermail (alle Werte von kaeuferWerte plus die Werte der Aktion aus `extra`, gefiltert auf die
* Platzhalter der Vorlage).
* extra je Art:
* G10 versanddienst, sendungsnummer (beide optional; Block sendung, wenn eines da ist)
* G11 hinweis (ersetzt den Hinweis des Haendlers)
* G12 name, bestellnummer (Eingaben des Kaeufers; ohne Angabe die der Bestellung), eingang (Date | ISO, Pflicht), nachricht
* G13 betragCent (Pflicht, ganze Cent), grund
*/
function kaeuferMail(art, ctx, order, extra, now) {
const alle = kaeuferWerte(ctx, order, now)
const blocks = {}
switch (art) {
case 'G09':
blocks.versand = schnappschuss(order).lieferart === 'versand'
blocks.abholung = schnappschuss(order).lieferart === 'abholung'
blocks.ausschluss = alle.ausschluss_text !== ''
break
case 'G10':
alle.versanddienst = oderLeer(extra.versanddienst)
alle.sendungsnummer = oderLeer(extra.sendungsnummer)
blocks.sendung = hatText(extra.versanddienst) || hatText(extra.sendungsnummer)
break
case 'G11':
if (hatText(extra.hinweis)) alle.hinweis = extra.hinweis.trim()
break
case 'G12':
alle.name = text(extra.name)
if (hatText(extra.bestellnummer)) alle.bestellnummer = extra.bestellnummer.trim()
alle.eingang = eingangText(extra.eingang)
alle.nachricht = text(extra.nachricht)
blocks.nachricht = alle.nachricht !== ''
break
case 'G13':
if (!Number.isInteger(extra.betragCent) || extra.betragCent <= 0) throw new TypeError('extra.betragCent muss eine positive ganze Zahl sein')
alle.betrag = euro(extra.betragCent)
alle.grund = text(extra.grund)
blocks.grund = alle.grund !== ''
break
default:
break
}
const vorlage = loadTemplate(art)
const values = Object.fromEntries(Object.entries(alle).filter(([schluessel]) => vorlage.placeholders.includes(schluessel)))
return { values, blocks: Object.fromEntries(Object.entries(blocks).filter(([name]) => vorlage.blocks.includes(name))) }
}
const zuLang = (mail) => mail.html.length > MAX_HTML || mail.text.length > MAX_TEXT
// ---------------------------------------------------------------- Einreihen
/**
* Reiht eine Kaeufermail (G09-G14) in die Warteschlange ein. Empfaenger ist extra.an, sonst order.kaeuferEmail. bookingId ist
* order.$id, sonst extra.bookingId (z. B. die ID des Widerrufs bei G12), sonst "-". Ist G09 zu lang fuer die Warteschlange
* (AGB, Widerrufsbelehrung und Formular koennen zusammen sehr lang sein), ersetzt der Server den AGB-Text durch einen Satz mit
* Link auf die AGB im Shop; Widerrufsbelehrung, Formular und Gewaehrleistungshinweis bleiben immer vollstaendig.
* @param {'G09'|'G10'|'G11'|'G12'|'G13'|'G14'} art
* @param {{ doc: object, shop: object }} ctx Shop-Kontext (kontext.js)
* @param {object|null} order shopOrders-Dokument (bei G12 ohne passende Bestellung null)
* @param {object} [extra] siehe kaeuferMail
* @param {Date} [now]
* @returns {Promise<{ ok: true, eintrag: object, gekuerzt: boolean } | { ok: false, fehler: string }>}
*/
export async function anKaeufer(art, ctx, order, extra = {}, now = new Date()) {
try {
if (!GAST_ARTEN.includes(art)) throw new TypeError(`${String(art).slice(0, 20)} ist keine Käufermail`)
pruefeNow(now, 'anKaeufer')
extra = extra ?? {}
const { values, blocks } = kaeuferMail(art, ctx, order, extra, now)
const an = hatText(extra.an) ? extra.an.trim() : text(order?.kaeuferEmail)
if (an === '') throw new TypeError('Empfänger fehlt')
let gekuerzt = false
let mail = renderMail(art, values, blocks)
if (art === 'G09' && zuLang(mail)) {
values.agb = `Die vollständigen AGB finden Sie unter ${shopSeite(ctx)}?ansicht=agb`
mail = renderMail(art, values, blocks)
gekuerzt = true
}
if (zuLang(mail)) throw new TypeError('Die Mail ist zu lang für die Warteschlange')
const e = einstellungenVon(ctx)
const eintrag = await einreihen(
{
featureId: ctx.doc.$id,
bookingId: order?.$id || (hatText(extra.bookingId) ? extra.bookingId : '-'),
art,
an,
fromName: shopName(ctx, schnappschuss(order)).slice(0, 120),
replyTo: text(anbieterVon(ctx, schnappschuss(order)).email) || text(e.anbieter?.email) || null,
template: art,
values,
blocks,
},
now,
)
return { ok: true, eintrag, gekuerzt }
} catch (err) {
console.warn(`[shop-mails] ${String(art).slice(0, 20)} nicht eingereiht: ${err?.name}: ${String(err?.message).slice(0, 120)}`)
return { ok: false, fehler: 'Die E-Mail an den Käufer konnte nicht eingereiht werden.' }
}
}
// ---------------------------------------------------------------- Haendler
/** Portal-Adresse der Webshop-Seite des Projekts. */
const portalUrl = (ctx) => `${config.publicBaseUrl}/dashboard.html#webshop?projekt=${encodeURIComponent(ctx?.shop?.projectId || ctx?.doc?.projectId || '')}`
/**
* Werte und Bloecke von K25 "Neue Bestellung" (an den Haendler) aus der bezahlten Bestellung.
* @param {{ ueberverkauft?: boolean }} [optionen] ueberverkauft: der Bestand reichte nicht (Block ueberverkauft)
* @returns {{ values: Record<string, string>, blocks: Record<string, boolean> }}
*/
export function haendlerK25(ctx, order, { ueberverkauft = false } = {}) {
const snap = schnappschuss(order)
const alle = kaeuferWerte(ctx, order)
const kaeufer = istObjekt(snap.kaeufer) ? snap.kaeufer : {}
return {
values: {
shopname: alle.betrieb_name,
bestellnummer: alle.bestellnummer,
gesamt: alle.gesamt,
positionen: alle.positionen,
lieferart_text: alle.lieferart_text,
kaeufer_name: oderLeer(kaeufer.name ?? snap.name),
portal_url: portalUrl(ctx),
},
blocks: { ueberverkauft: ueberverkauft === true, sepa: text(snap.zahlungsart) === 'sepa_debit' },
}
}
/**
* Werte und Bloecke von K26 "Widerruf eingegangen" (an den Haendler) aus dem Widerruf (shopWiderrufe-Dokument oder
* { name, email, bestellnummer, nachricht, eingang }); eingang = Date | ISO.
*/
export function haendlerK26(ctx, widerruf, now = new Date()) {
const nachricht = text(widerruf?.nachricht)
return {
values: {
shopname: shopName(ctx),
bestellnummer: oderLeer(widerruf?.bestellnummer),
kaeufer_name: oderLeer(widerruf?.name),
kaeufer_email: oderLeer(widerruf?.email),
eingang: eingangText(widerruf?.eingang ?? widerruf?.eingangAt ?? now),
nachricht,
portal_url: portalUrl(ctx),
},
blocks: { nachricht: nachricht !== '' },
}
}
/**
* Schickt eine Haendlermail (K25, K26) an die E-Mail des Kundenkontos, ohne BCC ans Team. kunde_name, shopname und portal_url
* ergaenzt die Funktion, wenn die Vorlage sie kennt und values sie nicht setzt.
* @param {string} template 'K25' | 'K26'
* @param {{ doc: object, shop: object }} ctx
* @param {Record<string, string>} values
* @param {Record<string, boolean>} [blocks]
* @returns {Promise<{ ok: true } | { ok: false, fehler: string }>}
*/
export async function anHaendler(template, ctx, values = {}, blocks = {}) {
try {
const kunde = await customerOf(ctx?.shop?.customerId)
if (!hatText(kunde?.email)) {
console.warn(`[shop-mails] ${String(template).slice(0, 20)} nicht gesendet: Kunde ohne E-Mail-Adresse`)
return { ok: false, fehler: 'Der Kunde hat keine E-Mail-Adresse.' }
}
const erlaubt = loadTemplate(template).placeholders
const standard = { kunde_name: text(kunde.name) || 'Kunde', shopname: shopName(ctx), portal_url: portalUrl(ctx) }
const alle = { ...standard, ...values }
await sendCustomerMail(kunde.email, {
template,
values: Object.fromEntries(Object.entries(alle).filter(([schluessel]) => erlaubt.includes(schluessel))),
blocks,
bccTeam: false,
})
return { ok: true }
} catch (err) {
// Nur Art und Code des Fehlers: Meldungen des Mailservers koennen die Adresse enthalten
console.warn(`[shop-mails] ${String(template).slice(0, 20)} nicht gesendet: ${err?.name} ${String(err?.code ?? err?.responseCode ?? err?.status ?? '').slice(0, 40)}`.trim())
return { ok: false, fehler: 'Die E-Mail an den Händler konnte nicht gesendet werden.' }
}
}
+63 -16
View File
@@ -23,6 +23,7 @@ import { LOESCHFRIST_TAGE } from './frist.js'
import { kontextPerFeature, ladeEinstellung } from './kontext.js'
import { anGast } from './mails.js'
import { beendeFestgefahrene, raeumeAuf, verarbeite } from './outbox.js'
import { shopModus } from '../shop/schalter.js'
import { termineAus } from './schalter.js'
import { buchungsNummer } from './token.js'
import { pruefeNow, zeitMs } from './util.js'
@@ -57,6 +58,12 @@ import { gibZellenFrei, raeumeVerwaisteZellen } from './zellen.js'
* (`eingeschraenkt: true`): nur der Abo-Abgleich und die Loeschung abgeschalteter Funktionen, damit ein beendetes Abo auch bei
* TERMINE_ENABLED=off zum Abschalten und zur Loeschung nach 30 Tagen fuehrt. Loeschfristen der Buchungen, Warteschlange und Zellen
* bleiben aus.
* - Webshop (SHOP_ENABLED != off): die Mail-Warteschlange gehoert auch den Kaeufermails des Shops (G09-G14, shop/mails.js). Deshalb
* laufen bei Modus off des Terminplaners, aber aktivem Shop, der Mailversand-Teil des Minutenjobs (Warteschlange senden und
* Betriebspruefung) und im Tagesjob das Beenden festgefahrener und das Loeschen alter Warteschlangen-Eintraege weiter; alles
* andere bleibt an den Terminplaner-Modus gebunden. Der Tagesjob ist dann eingeschraenkt (`eingeschraenkt: true`, mit
* `festgefahren` und `aufgeraeumt` im Ergebnis); gibt es keine bezahlten Terminplaner, laufen Abo-Abgleich und Loeschung
* abgeschalteter Funktionen nicht.
* - Keine ueberlappenden Laeufe: laeuft ein Job noch, wird der naechste Takt uebersprungen ({ uebersprungen: true }, einmal im
* Log je Ueberlappung). Die Sperre gilt je `job:teil` (fuehreAus mit { teil }): haengt beim Minutenjob der Verfall, versendet der
* naechste Takt trotzdem die Warteschlange und umgekehrt; uebersprungen gilt der Takt erst, wenn beide Teile uebersprungen sind
@@ -127,18 +134,21 @@ async function schritt(lauf, was, fn, ersatz) {
* Rahmen jedes Jobs: Schalter, Ueberlappung, Laufdaten. => Ergebnis von arbeit(lauf) | { aus: true } | { uebersprungen: true }
* `teil` (Name eines Teiljobs, z. B. „Verfall“) gibt dem Teil eine eigene Ueberlappungssperre `job:teil` und nennt ihn in der
* Warnung (`job (teil)`); `lauf.job` bleibt der Jobname (Fehlerlog, Warnung bei fehlender Collection).
* `trotzAus` (async, => wahr/falsch): im Modus off laeuft der Job nur, wenn es wahr liefert; dann steht `lauf.eingeschraenkt` auf true
* und der Job beschraenkt sich auf das, was auch bei abgeschaltetem Terminplaner gelten muss. Wirft es, wird gewarnt (ohne Kennungen)
* `trotzAus` (async, => falsch/wahr/Objekt): im Modus off laeuft der Job nur, wenn es etwas Wahres liefert; dann steht
* `lauf.eingeschraenkt` auf true, `lauf.freigabe` traegt die Antwort (Tagesjob: { termine, mails }) und der Job beschraenkt sich auf
* das, was auch bei abgeschaltetem Terminplaner gelten muss. Wirft es, wird gewarnt (ohne Kennungen)
* und { aus: true } geliefert; ohne `trotzAus` gilt Modus off wie bisher (sofort { aus: true }, ohne Abfrage). Die Ueberlappungssperre
* gilt unveraendert je Job (bzw. `job:teil`), der eingeschraenkte Lauf teilt sie mit dem vollen.
*/
async function fuehreAus(job, now, arbeit, { teil = null, trotzAus = null } = {}) {
const nowMs = pruefeNow(now, job)
let eingeschraenkt = false
let freigabe = null
if (termineAus()) {
if (trotzAus === null) return { aus: true }
try {
if (!(await trotzAus())) return { aus: true }
freigabe = await trotzAus()
if (!freigabe) return { aus: true }
} catch (err) {
console.warn(`[termine-jobs] ${job}: Modus off, die Prüfung auf bezahlte Terminplaner ist fehlgeschlagen, der Job bleibt aus: ${fehlerText(err)}`)
return { aus: true }
@@ -155,13 +165,16 @@ async function fuehreAus(job, now, arbeit, { teil = null, trotzAus = null } = {}
}
laufend.add(sperre)
try {
return await arbeit({ job, nowMs, fehler: 0, gedeckelt: 0, eingeschraenkt })
return await arbeit({ job, nowMs, fehler: 0, gedeckelt: 0, eingeschraenkt, freigabe })
} finally {
laufend.delete(sperre)
ueberlappGemeldet.delete(sperre)
}
}
/** Guard des Mailversand-Teils (fuehreAus: trotzAus): bei Modus off des Terminplaners laeuft er, solange der Webshop aktiv ist. */
const shopMailsLaufen = async () => shopModus() !== 'off'
/** Laufdaten fuer einen Baustein, der ohne Job-Rahmen aufgerufen wird (Tests). */
const einzelLauf = (job, now) => ({ job, nowMs: pruefeNow(now, job), fehler: 0, gedeckelt: 0 })
@@ -285,10 +298,15 @@ export async function minutenJob(now) {
await schritt(lauf, 'Betriebsprüfung', () => pruefeBetrieb(now), [])
return { faellig, gesendet, fehler: lauf.fehler }
},
{ teil: 'Mailversand' },
{ teil: 'Mailversand', trotzAus: shopMailsLaufen },
),
])
if (verfall.aus || mail.aus) return { aus: true }
if (verfall.aus && mail.aus) return { aus: true }
// Terminplaner aus, Webshop aktiv: nur der Mailversand-Teil lief
if (verfall.aus) {
if (mail.uebersprungen) return { uebersprungen: true }
return { eingeschraenkt: true, verfallen: 0, faellig: mail.faellig ?? 0, gesendet: mail.gesendet ?? 0, fehler: mail.fehler ?? 0 }
}
if (verfall.uebersprungen && mail.uebersprungen) return { uebersprungen: true }
return {
verfallen: verfall.verfallen ?? 0,
@@ -792,6 +810,23 @@ async function gibtBezahlteTerminplaner() {
return treffer.length > 0
}
/**
* Freigabe des Tagesjobs im Modus off (fuehreAus: trotzAus): false (aus) oder { termine, mails }. termine = es gibt ein bezahltes
* Termine-Dokument (Abo-Abgleich und Loeschung abgeschalteter Funktionen), mails = der Webshop ist aktiv (Warteschlange aufraeumen).
* Eine einzige Abfrage; scheitert sie bei aktivem Shop, laeuft nur der Mail-Teil (Warnung), sonst wirft sie wie bisher.
*/
async function tagesjobFreigabe() {
const mails = shopModus() !== 'off'
let termine = false
try {
termine = await gibtBezahlteTerminplaner()
} catch (err) {
if (!mails) throw err
console.warn(`[termine-jobs] taeglicherJob: die Prüfung auf bezahlte Terminplaner ist fehlgeschlagen, es läuft nur die Mail-Warteschlange: ${fehlerText(err)}`)
}
return termine || mails ? { termine, mails } : false
}
/**
* Abo-Abgleich (aboAbgleich.js) als Schritt des Tagesjobs: ein Fehler stoppt weder die Loeschung noch den Rest. Die Fehler des
* Abgleichs (Lese- und Versandfehler, schon im Log) kommen in lauf.fehler. => Zaehler des Abgleichs
@@ -864,7 +899,7 @@ export async function taeglicherJob(now) {
await schritt(lauf, 'Tagesjob-Meldung', () => meldeTagesjob(ergebnis, now), [])
return ergebnis
},
{ trotzAus: gibtBezahlteTerminplaner },
{ trotzAus: tagesjobFreigabe },
)
} catch (err) {
// Abbruch ausserhalb der Schritte: das Team erfaehrt es; der Fehler (Text im Log des Aufrufers) geht unveraendert weiter
@@ -880,19 +915,28 @@ export async function taeglicherJob(now) {
* und wird wie im vollen Lauf gemeldet (Fehler gehen als Vorfall ans Team).
*/
async function eingeschraenkterTagesjob(lauf, now) {
const aboAbgleich = await aboAbgleichSchritt(lauf, now)
const { gedeckelt: abgeschaltetGedeckelt, ...abgeschaltet } = await schritt(lauf, 'Abgeschaltete Funktionen', () => loescheAbgeschaltete(lauf), {
funktionen: 0,
buchungen: 0,
gedeckelt: 0,
})
// Ohne Freigabe-Angabe (Aufruf ohne tagesjobFreigabe) gilt das bisherige Verhalten: nur Terminplaner-Teile
const termine = lauf.freigabe?.termine !== false
const mails = lauf.freigabe?.mails === true
const aboAbgleich = termine ? await aboAbgleichSchritt(lauf, now) : aboErsatz()
const { gedeckelt: abgeschaltetGedeckelt, ...abgeschaltet } = termine
? await schritt(lauf, 'Abgeschaltete Funktionen', () => loescheAbgeschaltete(lauf), { funktionen: 0, buchungen: 0, gedeckelt: 0 })
: { funktionen: 0, buchungen: 0, gedeckelt: 0 }
// Webshop aktiv: festgefahrene Mails beenden (sie bleiben 48 Stunden als Zustellfehler sichtbar), dann alte Eintraege loeschen
const mailErgebnis = mails
? {
festgefahren: await schritt(lauf, 'Festgefahrene Mails', () => beendeFestgefahrene(now), 0),
aufgeraeumt: await schritt(lauf, 'Warteschlange aufräumen', () => raeumeAuf(now), 0),
}
: {}
const ergebnis = {
eingeschraenkt: true,
aboAbgleich,
abgeschaltet,
gedeckelt: { loeschfristen: 0, abgeschaltet: abgeschaltetGedeckelt, zellen: 0 },
gedeckelt: { loeschfristen: 0, abgeschaltet: abgeschaltetGedeckelt ?? 0, zellen: 0 },
geloescht: abgeschaltet.buchungen,
aktiveBetriebe: null,
...mailErgebnis,
fehler: lauf.fehler,
}
await schritt(lauf, 'Tagesjob-Meldung', () => meldeTagesjob(ergebnis, now), [])
@@ -926,9 +970,12 @@ export function tagesbericht(ergebnis) {
? ''
: `Abo-Abgleich ${zahl(abo.geprueft)} geprüft / ${zahl(abo.abgeschaltet)} abgeschaltet / ${zahl(abo.wiederEin)} wieder ein / ${zahl(abo.mails)} Mails / ${zahl(abo.ausgenommen)} ausgenommen, `
if (ergebnis.eingeschraenkt) {
// Webshop aktiv: dazu die Warteschlange (festgefahrene Mails beenden, alte Eintraege loeschen)
const mitMails = Number.isInteger(ergebnis.aufgeraeumt)
const mailSegment = mitMails ? `${ergebnis.aufgeraeumt} Outbox, ${zahl(ergebnis.festgefahren)} Mails nicht zugestellt, ` : ''
return (
`[termine] Tagesjob (Modus off, nur Abo-Abgleich und Löschung): ${a.buchungen} Buchungen gelöscht (abgeschaltete Funktionen ${a.buchungen}), ` +
`${a.funktionen} Funktionen bereinigt, ${deckel}${aboSegment}${ergebnis.fehler} Fehler`
`[termine] Tagesjob (Modus off, nur Abo-Abgleich und Löschung${mitMails ? ' sowie Mail-Warteschlange' : ''}): ${a.buchungen} Buchungen gelöscht (abgeschaltete Funktionen ${a.buchungen}), ` +
`${a.funktionen} Funktionen bereinigt, ${mailSegment}${deckel}${aboSegment}${ergebnis.fehler} Fehler`
)
}
const l = ergebnis.loeschfristen
+9 -5
View File
@@ -39,7 +39,7 @@ import { pruefeNow, pruefeText, zeitMs } from './util.js'
* pause Anmeldepause laeuft
* budget Plaetze im Fenster >= mailsProStunde
* reserve nachrangige Mails (G05, K20, K21) im Fenster >= max(1, floor(mailsProStunde x (1 - mailsReserve))): so bleibt
* ein Teil der Stunde den Vorrang-Mails (G01, G02, G08) und den normalen Mails vorbehalten
* ein Teil der Stunde den Vorrang-Mails (G01, G02, G08, Webshop G09, G12) und den normalen Mails vorbehalten
* kontingent Mails dieser Funktion im Fenster >= mailsProFunktion: eine Funktion kann die Stunde nicht allein fuellen
*
* Minutenjob (verarbeite): liest nie alle wartenden Eintraege. Bei Pause oder leerem Gesamtbudget endet er ohne Abfrage.
@@ -71,8 +71,12 @@ const ID_BLOCK = 50
const iso = (wertMs) => new Date(wertMs).toISOString()
/** Mails, die vor allen anderen rausgehen duerfen (Bestaetigung, Termin, Eingangsbestaetigung des Widerrufs). */
export const MAIL_VORRANG = Object.freeze(['G01', 'G02', 'G08'])
/**
* Mails, die vor allen anderen rausgehen duerfen (Bestaetigung, Termin, Eingangsbestaetigung des Widerrufs). Dazu die beiden
* Pflichtmails des Webshops: G09 Bestellbestaetigung (dauerhafter Datentraeger) und G12 Eingangsbestaetigung des Widerrufs. Die
* uebrigen Shop-Mails G10, G11, G13, G14 sind 'normal'.
*/
export const MAIL_VORRANG = Object.freeze(['G01', 'G02', 'G08', 'G09', 'G12'])
/** Mails, die zuerst warten (Erinnerung, Hinweise an den Betrieb): sie nehmen nie die ganze Stunde. */
export const MAIL_NACHRANGIG = Object.freeze(['G05', 'K20', 'K21'])
@@ -115,7 +119,7 @@ function gesamtBremse(nowMs) {
* 'pause' | 'budget' | 'reserve' | 'kontingent'. Nur lesen: belegt keinen Platz.
* @param {number} nowMs
* @param {string} featureId
* @param {string} art Vorlagen-ID der Mail ('G01' ... 'K21')
* @param {string} art Vorlagen-ID der Mail ('G01' ... 'K21', Shop: 'G09' ... 'G14')
* @returns {null|'pause'|'budget'|'reserve'|'kontingent'}
*/
export function pruefePlatz(nowMs, featureId, art) {
@@ -438,7 +442,7 @@ async function verarbeiteKlasse(klasse, nowMs, now, lauf) {
}
/**
* Minutenjob: sendet faellige wartende Eintraege, Vorrang-Mails (G01, G02, G08) vor normalen vor nachrangigen (G05, K20, K21),
* Minutenjob: sendet faellige wartende Eintraege, Vorrang-Mails (G01, G02, G08, G09, G12) vor normalen vor nachrangigen (G05, K20, K21),
* in jeder Klasse die faelligsten zuerst, bis Pause oder Stundenbudget bremsen. Er liest begrenzte Listen statt aller Eintraege
* (siehe Kopfkommentar); Eintraege, die Vorrang-Reserve oder Kontingent ihrer Funktion bremsen, bleiben `wartet`, und die uebrigen
* kommen trotzdem dran. Fehler bei einzelnen Eintraegen (z. B. Appwrite) stehen im Log und stoppen den Lauf nicht.