Automatische Mails im neuen Design (HTML mit Textfassung)
Alle 22 automatischen Mails gehen jetzt als HTML nach den Vorlagen aus
server/mail-templates raus, dazu eine aus dem HTML erzeugte Textfassung
(multipart/alternative). Werte werden HTML-sicher eingesetzt, optionale
Blöcke (WENN/ENDE) je nach Fall ein- oder ausgeblendet.
- Neu: services/mailTemplates.js (Vorlagen lesen, Platzhalter, Blöcke,
Textfassung); mailer.js nimmt { template, values, blocks }.
- K11 "Passwort geändert" war nicht im Design-Export: aus den Bausteinen
von K02 zusammengesetzt.
- T09 (Widerruf, intern): Fälle "kein Abo gefunden" und "Stripe-Kündigung
fehlgeschlagen" ergänzt; bisher stand dort immer "auf Kündigung gesetzt".
- Einheitlich: Datum TT.MM.JJJJ, Beträge deutsch (T04 "50,00 €"),
"WEBklar" statt "Webklar", Anrede mit Kundennamen, Team-Absender
"WEBklar Kundenportal", Betreff T05 "Kündigung (Stripe)".
- Logo für die Mails: public/webklar-logo.png (192 px, aus dem Design-SVG).
Tests: 52 neue (jede Vorlage: Kopf, Platzhalter, Blöcke, Rendern,
Escaping, Textfassung), npm test 97/97 auf Node 20.19.5; alle 40 Auslöser
einmal durch den Server geschickt (Appwrite-Mock, Stripe-Double,
SMTP-Falle mit STARTTLS).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e3daffffa3
commit
18c3cdf3e5
11 files changed
+657
-316
No files matched your search
@@ -48,7 +48,6 @@ export const REQUIRED_CHECKS = [
|
||||
{ key: 'replaced', label: 'Die Leistung wird durch das Wartungs-Abo ersetzt (Umzug zu WEBklar)' },
|
||||
]
|
||||
|
||||
const FOOTER = 'WEBklar GbR · Am Schwimmbad 10 · 67722 Winnweiler · Telefon 0176 23726355 · support@webklar.com'
|
||||
const DAY_MS = 24 * 60 * 60 * 1000
|
||||
|
||||
function httpError(status, message) {
|
||||
@@ -430,44 +429,35 @@ export async function createTakeoverRequest({ customer, input, uploads, consentI
|
||||
}
|
||||
|
||||
const name = customer.name || customer.companyName || input.email
|
||||
const summary = [
|
||||
` Antrag: ${shortRef(doc.$id)}`,
|
||||
` Leistung: ${servicesLabel(doc.services)}`,
|
||||
` Bisher bei: ${doc.provider}`,
|
||||
doc.domain ? ` Domain: ${doc.domain}` : null,
|
||||
` Rechnung: ${fmtEuro(doc.invoiceAmountCents)} für ${monthsLabel(doc.billingMonths)}`,
|
||||
` Bezahlt bis: ${fmtDate(doc.paidUntil)}`,
|
||||
` Nachweise: ${files.map((f) => f.name).join(', ')}`,
|
||||
].filter((l) => l !== null)
|
||||
const summary = {
|
||||
antrag_nr: shortRef(doc.$id),
|
||||
leistung: servicesLabel(doc.services),
|
||||
bisheriger_anbieter: doc.provider,
|
||||
domain: doc.domain || '–',
|
||||
rechnung_betrag: fmtEuro(doc.invoiceAmountCents),
|
||||
rechnung_zeitraum: monthsLabel(doc.billingMonths),
|
||||
bezahlt_bis: fmtDate(doc.paidUntil),
|
||||
nachweise: files.map((f) => f.name).join(', '),
|
||||
}
|
||||
|
||||
await sendCustomerMail(input.email, 'Eingangsbestätigung: Ihr Antrag auf Übernahmegutschrift – WEBklar', [
|
||||
`Guten Tag ${customer.name || ''}`.trim() + ',',
|
||||
'',
|
||||
`wir haben Ihren Antrag auf eine Übernahmegutschrift am ${fmtDateTime(now)} erhalten.`,
|
||||
'',
|
||||
...summary,
|
||||
'',
|
||||
'Wir prüfen Ihre Angaben und den Nachweis und antworten Ihnen per E-Mail an diese Adresse.',
|
||||
`Den Stand Ihres Antrags sehen Sie auch im Kundenportal: ${config.publicBaseUrl}/dashboard.html#gutschrift`,
|
||||
'',
|
||||
'Viele Grüße',
|
||||
'Ihr WEBklar-Team',
|
||||
'',
|
||||
FOOTER,
|
||||
]).catch((e) => console.error('[takeover] Eingangsbestaetigung fehlgeschlagen:', e.message))
|
||||
await sendCustomerMail(input.email, {
|
||||
template: 'K07',
|
||||
values: { name: customer.name || '', eingang_zeitpunkt: fmtDateTime(now), ...summary },
|
||||
}).catch((e) => console.error('[takeover] Eingangsbestaetigung fehlgeschlagen:', e.message))
|
||||
|
||||
// Nachweise bewusst nicht als Anhang: sie bleiben auf dem Server (Datenschutzerklaerung Abschnitt 7)
|
||||
await sendAdminNotice(`Übernahmegutschrift beantragt: ${name}`, [
|
||||
`${name}${customer.code ? ` (Kd.-Nr. ${customer.code})` : ''} hat eine Übernahmegutschrift beantragt.`,
|
||||
`Antwort an: ${input.email}`,
|
||||
'',
|
||||
...summary,
|
||||
'',
|
||||
'Grund / Nachricht des Kunden:',
|
||||
input.message,
|
||||
'',
|
||||
`Nachweise ansehen, prüfen und entscheiden: ${config.publicBaseUrl}/admin.html?gutschrift=${encodeURIComponent(doc.$id)}`,
|
||||
])
|
||||
await sendAdminNotice({
|
||||
template: 'T10',
|
||||
values: {
|
||||
kunde_name: name,
|
||||
kundennummer: customer.code || '–',
|
||||
email: input.email,
|
||||
...summary,
|
||||
nachricht: input.message,
|
||||
admin_link: `${config.publicBaseUrl}/admin.html?gutschrift=${encodeURIComponent(doc.$id)}`,
|
||||
},
|
||||
blocks: { nachricht: Boolean(input.message) },
|
||||
})
|
||||
|
||||
return doc
|
||||
}
|
||||
@@ -486,15 +476,17 @@ export async function answerQuestion({ customer, doc, message, uploads }) {
|
||||
history: withHistory(doc, { type: 'antwort', by: 'customer', text, files: added.map((f) => f.id) }),
|
||||
})
|
||||
|
||||
const name = customer.name || customer.companyName || doc.email
|
||||
await sendAdminNotice(`Übernahmegutschrift: Antwort von ${name}`, [
|
||||
`${name} hat auf die Rückfrage zum Antrag ${shortRef(doc.$id)} geantwortet:`,
|
||||
'',
|
||||
text || '(ohne Text)',
|
||||
...(added.length ? ['', `Neue Nachweise: ${added.map((f) => f.name).join(', ')}`] : []),
|
||||
'',
|
||||
`Prüfen und entscheiden: ${config.publicBaseUrl}/admin.html?gutschrift=${encodeURIComponent(doc.$id)}`,
|
||||
])
|
||||
await sendAdminNotice({
|
||||
template: 'T11',
|
||||
values: {
|
||||
kunde_name: customer.name || customer.companyName || doc.email,
|
||||
antrag_nr: shortRef(doc.$id),
|
||||
antwort: text || '(ohne Text)',
|
||||
neue_nachweise: added.map((f) => f.name).join(', '),
|
||||
admin_link: `${config.publicBaseUrl}/admin.html?gutschrift=${encodeURIComponent(doc.$id)}`,
|
||||
},
|
||||
blocks: { neue_nachweise: added.length > 0 },
|
||||
})
|
||||
return updated
|
||||
}
|
||||
|
||||
@@ -804,57 +796,28 @@ export async function applyTakeoverCredit(doc) {
|
||||
}
|
||||
}
|
||||
|
||||
function customerGreeting(doc, customer) {
|
||||
return `Guten Tag ${customer?.name || ''}`.trim() + ','
|
||||
}
|
||||
|
||||
async function mailDecision(doc, customer, action) {
|
||||
const ref = shortRef(doc.$id)
|
||||
const submitted = fmtDate(doc.$createdAt)
|
||||
const common = { name: customer?.name || '', antrag_datum: fmtDate(doc.$createdAt), antrag_nr: shortRef(doc.$id) }
|
||||
const note = String(doc.decisionNote || '').trim()
|
||||
const footer = ['', 'Viele Grüße', 'Ihr WEBklar-Team', '', FOOTER]
|
||||
if (action === 'approve') {
|
||||
return sendCustomerMail(doc.email, 'Ihre Übernahmegutschrift ist genehmigt – WEBklar', [
|
||||
customerGreeting(doc, customer),
|
||||
'',
|
||||
`wir haben Ihren Antrag auf Übernahmegutschrift vom ${submitted} geprüft und genehmigt.`,
|
||||
'',
|
||||
` Antrag: ${ref}`,
|
||||
` Leistung: ${servicesLabel(doc.services)} (bisher bei ${doc.provider})`,
|
||||
` Gutschrift: ${fmtEuro(doc.amountCents)}`,
|
||||
` Berechnung: ${doc.calculation}`,
|
||||
...(note ? ['', note] : []),
|
||||
'',
|
||||
'Die Gutschrift verrechnen wir automatisch mit den nächsten Monatsgebühren Ihres Wartungs-Abos.',
|
||||
'Auf Ihren Rechnungen erscheint sie als angerechnetes Guthaben. Eine Auszahlung ist nicht möglich;',
|
||||
'ein bei Vertragsende noch nicht verrechneter Rest entfällt.',
|
||||
...footer,
|
||||
])
|
||||
return sendCustomerMail(doc.email, {
|
||||
template: 'K09',
|
||||
values: {
|
||||
...common,
|
||||
gutschrift_betrag: fmtEuro(doc.amountCents),
|
||||
leistung: servicesLabel(doc.services),
|
||||
bisheriger_anbieter: doc.provider,
|
||||
berechnung: doc.calculation || '–',
|
||||
anmerkung: note,
|
||||
},
|
||||
blocks: { anmerkung: Boolean(note) },
|
||||
})
|
||||
}
|
||||
if (action === 'reject') {
|
||||
return sendCustomerMail(doc.email, 'Ihr Antrag auf Übernahmegutschrift – WEBklar', [
|
||||
customerGreeting(doc, customer),
|
||||
'',
|
||||
`wir haben Ihren Antrag auf Übernahmegutschrift vom ${submitted} (${ref}) geprüft und können ihn leider nicht genehmigen.`,
|
||||
'',
|
||||
note,
|
||||
'',
|
||||
'Bei Fragen antworten Sie einfach auf diese E-Mail.',
|
||||
...footer,
|
||||
])
|
||||
return sendCustomerMail(doc.email, { template: 'K10', values: { ...common, begruendung: note } })
|
||||
}
|
||||
const question = [...historyOf(doc)].reverse().find((h) => h.type === 'rueckfrage')?.text || ''
|
||||
return sendCustomerMail(doc.email, 'Rückfrage zu Ihrem Antrag auf Übernahmegutschrift – WEBklar', [
|
||||
customerGreeting(doc, customer),
|
||||
'',
|
||||
`zu Ihrem Antrag auf Übernahmegutschrift vom ${submitted} (${ref}) haben wir eine Rückfrage:`,
|
||||
'',
|
||||
question,
|
||||
'',
|
||||
`Bitte antworten Sie im Kundenportal unter ${config.publicBaseUrl}/dashboard.html#gutschrift – dort können Sie`,
|
||||
'auch weitere Nachweise hochladen. Alternativ antworten Sie einfach auf diese E-Mail.',
|
||||
...footer,
|
||||
])
|
||||
return sendCustomerMail(doc.email, { template: 'K08', values: { ...common, rueckfrage_text: question } })
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user