feat(mail): HTML-Mails mit Link zum Ticket/zur Rechnung als Schaltfläche

Mails wurden bisher nur als Klartext verschickt. sendMail/sendTemplateMail
verschicken jetzt zusätzlich eine einfache, mandantenfarbige HTML-Version
(renderTemplateHtml in @kc/platform/mail): ein Platzhalter, dessen Name auf
"Link" endet, wird darin zu einer Schaltfläche statt einer nackten URL; der
Klartext-Teil behält den Link weiterhin sichtbar (Fallback für Mailprogramme
ohne HTML-Ansicht).

ticket_created/ticket_message bekommen dafür eine neue Variable
{{ticketLink}}, invoice_issued {{invoiceLink}} (Migration 025 – nur
Vorlagen mit noch unverändertem Text werden angepasst, damit von Personal
bereits bearbeitete Texte nicht überschrieben werden).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Kundencenter 2026-09-28 23:00:31 +02:00
parent c0289317bf
commit 8616e51adc
6 changed files with 59 additions and 10 deletions

View file

@ -10,6 +10,7 @@ import { clientIp, requireAuth, requirePermission } from '../../core/auth.js';
import { badRequest, conflict, forbidden, notFound } from '../../core/errors.js';
import { can, canInOrg } from '../../core/policy.js';
import type { KcModule } from '../../core/module.js';
import { config } from '../../core/config.js';
import { renderInvoicePdf, type CompanySettings, type InvoiceForPdf } from './pdf.js';
/** Kaufmännisches Runden (halb auf), wie in @kc/platform/pricing – hier lokal, weil Rechnungspositionen
@ -223,7 +224,7 @@ export const invoicesModule: KcModule = {
});
await audit({ actorType: 'user', actorId: a.user.id, orgId: v.org_id, action: 'invoice.issue', resourceType: 'invoice', resourceId: id, correlationId: req.correlationId, ip: clientIp(req), after: { number } });
await notify('invoice.issued', { number, gross: v.total_gross_cents }, `invoice.issued:${id}`, req.correlationId);
if (org?.billing_email) await mailTo(org.billing_email, 'invoice_issued', { name: org.contact_name || org.company || org.name, number, amount: deMoney(v.total_gross_cents), dueDate: deDate(due) }, `mail:invoice.issued:${id}`, req.correlationId);
if (org?.billing_email) await mailTo(org.billing_email, 'invoice_issued', { name: org.contact_name || org.company || org.name, number, amount: deMoney(v.total_gross_cents), dueDate: deDate(due), invoiceLink: `${config.baseUrl}/rechnungen/${id}` }, `mail:invoice.issued:${id}`, req.correlationId);
return { ok: true, number };
});