Kundenansicht P6: Zusage-Mails K42 und K43 für Webshop- und Buchungskalender-Anfrage ohne Preis

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-16 16:56:52 +00:00
1 parent bb1efa5c9e
commit 9a3ce7c8bf
10 files changed
+205 -7

No files matched your search

@@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="de" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<!--
K42 · Webshop-Anfrage entschieden, Abo-Modell (Annahme oder Ablehnung)
Betreff: Deine Webshop-Anfrage für {{projekt}}
Absender: „WEBklar“ <Portal-Postfach>
Empfänger: Kunde (E-Mail des Kundenkontos) · BCC intern
Platzhalter: {{kunde_name}}, {{projekt}}, {{begruendung}}, {{portal_url}}
Optionale Blöcke (zwischen WENN-/ENDE-Kommentaren; bei Nichtzutreffen samt Inhalt entfernen): angenommen, abgelehnt
Genau einer der Blöcke ist an. angenommen = Anfrage angenommen, der Kunde kann den Webshop jetzt im Kundenportal einschalten; abgelehnt = Anfrage abgelehnt, mit Begründung des Teams.
Fassung von K24 für Kunden der neuen Ansicht (Abo-Modell, Kundenansicht P6): ohne Preis, ohne Umsatzsteuerhinweis und ohne das Wort „bestellen“. K24 bleibt für die alte Ansicht unverändert.
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>Deine Webshop-Anfrage für {{projekt}}</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;">Deine Webshop-Anfrage wurde bearbeitet.&#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;">Webshop</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;"><!-- WENN angenommen -->Deine Webshop-Anfrage wurde angenommen<!-- ENDE angenommen --><!-- WENN abgelehnt -->Zu Deiner Webshop-Anfrage<!-- ENDE abgelehnt --></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><!-- WENN angenommen -->wir haben Deine Webshop-Anfrage für {{projekt}} angenommen. Du kannst den Webshop jetzt im Kundenportal einschalten; danach richten wir ihn ein.<!-- ENDE angenommen --><!-- WENN abgelehnt -->leider können wir Deine Webshop-Anfrage für {{projekt}} nicht annehmen. Den Grund findest Du unten. Wenn Du Fragen hast, antworte einfach auf diese E-Mail.<!-- ENDE abgelehnt --></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;color:#6b6964;">Projekt</td><td class="val ink rowline" valign="top" style="padding:12px 0;color:#111111;"><strong>{{projekt}}</strong></td></tr><!-- WENN abgelehnt --><tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;border-top:1px solid #ecebe6;color:#6b6964;">Begründung</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-top:1px solid #ecebe6;color:#111111;">{{begruendung}}</td></tr><!-- ENDE abgelehnt --></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;"><!-- WENN angenommen -->Webshop einschalten<!-- ENDE angenommen --><!-- WENN abgelehnt -->Zum Kundenportal<!-- ENDE abgelehnt --> &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,55 @@
<!DOCTYPE html>
<html lang="de" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<!--
K43 · Anfrage Buchungskalender entschieden, Abo-Modell (Annahme oder Ablehnung)
Betreff: Deine Anfrage zum Buchungskalender für {{projekt}}
Absender: „WEBklar“ <Portal-Postfach>
Empfänger: Kunde (E-Mail des Kundenkontos) · BCC intern
Platzhalter: {{kunde_name}}, {{projekt}}, {{begruendung}}, {{portal_url}}
Optionale Blöcke (zwischen WENN-/ENDE-Kommentaren; bei Nichtzutreffen samt Inhalt entfernen): angenommen, abgelehnt
Genau einer der Blöcke ist an. angenommen = Anfrage angenommen, der Kunde kann den Buchungskalender jetzt im Kundenportal einschalten; abgelehnt = mit Begründung des Teams.
Fassung von K34 für Kunden der neuen Ansicht (Abo-Modell, Kundenansicht P6): ohne Preis, ohne Umsatzsteuerhinweis und ohne das Wort „bestellen“. K34 bleibt für die alte Ansicht unverändert.
portal_url = 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>Deine Anfrage zum Buchungskalender für {{projekt}}</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;">
<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;">Buchungskalender</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;">Buchungskalender</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;"><!-- WENN angenommen -->Deine Anfrage wurde angenommen<!-- ENDE angenommen --><!-- WENN abgelehnt -->Zu Deiner Anfrage<!-- ENDE abgelehnt --></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><!-- WENN angenommen -->wir haben Deine Anfrage zum Buchungskalender für {{projekt}} angenommen. Du kannst ihn jetzt im Kundenportal einschalten; danach richten wir ihn ein.<!-- ENDE angenommen --><!-- WENN abgelehnt -->leider können wir Deine Anfrage zum Buchungskalender für {{projekt}} nicht annehmen. Den Grund findest Du unten. Wenn Du Fragen hast, antworte einfach auf diese E-Mail.<!-- ENDE abgelehnt --></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;">Projekt</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;"><strong>{{projekt}}</strong></td></tr>
<!-- WENN abgelehnt --><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;">Begründung</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{begruendung}}</td></tr>
<!-- ENDE abgelehnt --></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;">Zum Kundenportal &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>
+23 -1
View File
@@ -9,7 +9,8 @@ import { datumKurz } from './termine/zeit.js'
* K38 Deine Funktion wird eingerichtet (Einschalten aus dem Abo oder durch das Team; nicht für Öffnungszeiten live, dort bleibt K27)
* K39 Deine Funktion wurde beendet (der Tagesabgleich beendet sie mit dem Abo)
* T24 Team-Hinweis: Funktion eingeschaltet (Einrichtung geht aufs Stundenkonto, Block vorschuss)
* (K42 und K43, die Zusage-Mails der Webshop- und der Buchungskalender-Anfrage, folgen in Task 15.)
* K42, K43 Webshop- und Buchungskalender-Anfrage entschieden (Zusage oder Absage), Ersatz fuer K24 und K34 im Abo-Modell: ohne Preis und ohne
* das Wort "bestellen" (anfrageEntschiedenMail)
* Die Builder sind rein (kein Zugriff auf Appwrite, kein Versand) und liefern { template, values, blocks }, so wie
* zeiten/mails.js: die Werte decken genau die Platzhalter der Vorlage ab. Keine Preise, kein Wort „bestellen“.
*
@@ -110,6 +111,27 @@ export function t24Mail({ kunde, email, projekt, key, customerId, projectId, woi
}
}
/**
* K42 (Webshop) oder K43 (Buchungskalender): die Anfrage wurde vom Team entschieden. Im Abo-Modell ohne Preiszeile und ohne "bestellen":
* bei Zusage kann der Kunde die Funktion im Kundenportal einschalten. Werte und Adresse wie die alten Mails K24 und K34 (sendeEntscheidung).
* @param {{ kunde: string | { name?: string }, projekt: string, key: 'shop' | 'unterkunft', projectId: string, angenommen: boolean, begruendung?: string }} p
* kunde_name ist der getrimmte Name, leer 'Kunde'; ein anderer key ist ein TypeError.
*/
export function anfrageEntschiedenMail({ kunde, projekt, key, projectId, angenommen, begruendung = '' }) {
if (key !== 'shop' && key !== 'unterkunft') throw new TypeError(`Keine Anfrage-Mail fuer die Funktion: ${String(key)}`)
const eintrag = funktion(key)
return {
template: key === 'shop' ? 'K42' : 'K43',
values: {
kunde_name: kundenName(kunde) || 'Kunde',
projekt,
begruendung,
portal_url: `${config.publicBaseUrl}/dashboard.html#${eintrag.returnHash}?projekt=${encodeURIComponent(projectId)}`,
},
blocks: { angenommen: Boolean(angenommen), abgelehnt: !angenommen },
}
}
/** Tage zwischen dem Abschalten und der Löschung der Eingaben: news 30, termine 30, shop und unterkunft aus der Konfiguration; sonst TypeError. */
export function loeschFristTage(key) {
if (key === 'news') return 30
+11 -1
View File
@@ -3,6 +3,8 @@ import { createDocument, getDocument, updateDocument } from '../appwriteAdmin.js
import { withLocks } from '../keyedLock.js'
import { sendAdminNotice, sendCustomerMail } from '../mailer.js'
import { assertOwnProject, customerOf, ensureFeatureDoc, getFeatureDoc } from '../siteFeatures.js'
import { aboModellFuerKunde } from '../aboFunktionen.js'
import { anfrageEntschiedenMail } from '../aboFunktionenMails.js'
import { shopFuerKunde } from './schalter.js'
import { NUR_BETRIEB_TEXT } from '../kundenart.js'
@@ -286,7 +288,11 @@ export async function entscheideAnfrage({ customerId, projectId, entscheidung, b
return { anfrage: anfrageAnsicht(gespeichert), mailFehler }
}
/** K24 an den Kunden; true = gesendet. Jeder Fehler wird nur geloggt (die Entscheidung ist gespeichert). */
/**
* K24 an den Kunden; true = gesendet. Jeder Fehler wird nur geloggt (die Entscheidung ist gespeichert). Fuer Kunden der neuen Ansicht
* (aboModellFuerKunde, Kundenansicht P6) geht stattdessen K42 ohne Preis und ohne "bestellen"; ist das nicht lesbar, geht keine Mail raus
* (lieber keine als eine mit Preis, die Antwort traegt mailFehler).
*/
async function sendeEntscheidung({ customerId, projectId, project, angenommen, begruendung }) {
try {
const kunde = await customerOf(customerId)
@@ -294,6 +300,10 @@ async function sendeEntscheidung({ customerId, projectId, project, angenommen, b
console.warn(`[shop] K24 nicht gesendet: Kunde ${customerId} ohne E-Mail-Adresse`)
return false
}
if (await aboModellFuerKunde(customerId)) {
await sendCustomerMail(kunde.email, anfrageEntschiedenMail({ kunde, projekt: project?.projectName || projectId, key: 'shop', projectId, angenommen, begruendung }))
return true
}
await sendCustomerMail(kunde.email, {
template: 'K24',
values: {
+11 -1
View File
@@ -2,6 +2,8 @@ import { config } from '../../config.js'
import { withLocks } from '../keyedLock.js'
import { sendAdminNotice, sendCustomerMail } from '../mailer.js'
import { assertOwnProject, customerOf, ensureFeatureDoc, getFeatureDoc } from '../siteFeatures.js'
import { aboModellFuerKunde } from '../aboFunktionen.js'
import { anfrageEntschiedenMail } from '../aboFunktionenMails.js'
import { unterkunftFuerKunde } from './schalter.js'
import { ladeEinstellungen, speichereEinstellungen } from './speicher.js'
@@ -268,7 +270,11 @@ export async function entscheideAnfrage({ customerId, projectId, entscheidung, b
return { anfrage: anfrageAnsicht(gespeichert), mailFehler }
}
/** K34 an den Kunden; true = gesendet. Jeder Fehler wird nur geloggt (die Entscheidung ist gespeichert). */
/**
* K34 an den Kunden; true = gesendet. Jeder Fehler wird nur geloggt (die Entscheidung ist gespeichert). Fuer Kunden der neuen Ansicht
* (aboModellFuerKunde, Kundenansicht P6) geht stattdessen K43 ohne Preis und ohne "bestellen"; ist das nicht lesbar, geht keine Mail raus
* (lieber keine als eine mit Preis, die Antwort traegt mailFehler).
*/
async function sendeEntscheidung({ customerId, projectId, project, angenommen, begruendung }) {
try {
const kunde = await customerOf(customerId)
@@ -276,6 +282,10 @@ async function sendeEntscheidung({ customerId, projectId, project, angenommen, b
console.warn(`[unterkunft] K34 nicht gesendet: Kunde ${customerId} ohne E-Mail-Adresse`)
return false
}
if (await aboModellFuerKunde(customerId)) {
await _versand.kunde(kunde.email, anfrageEntschiedenMail({ kunde, projekt: project?.projectName || projectId, key: 'unterkunft', projectId, angenommen, begruendung }))
return true
}
await _versand.kunde(kunde.email, {
template: 'K34',
values: {