Files
webshop/shop/lib/actions/email-templates.ts
DanielS b692e6d4cf Merge branch 'staging' of https://gitea.hephex.de/admin/webshop into staging
# Conflicts:
#	shop/lib/actions/email-templates.ts
2026-08-27 00:34:39 +02:00

257 lines
12 KiB
TypeScript

interface EmailDetails {
orderNumber: string
formattedDate: string
customerCompanyName: string
totalDetailsText: string
totalDetailsHtml: string
itemsDetailsText?: string
itemsDetailsHtml?: string
partnerCompanyName?: string
partnerUserName?: string
partnerUserEmail?: string
}
export function getOrderEmailTemplate(
details: EmailDetails,
siteUrl: string,
isUpdate: boolean = false,
primaryColor: string = '#2563eb'
) {
const title = isUpdate ? 'Anfrage geändert' : 'Anfrage erhalten'
const intro = isUpdate
? `Deine Anfrage ${details.orderNumber} wurde aktualisiert.`
: `Deine Anfrage ${details.orderNumber} ist bei uns eingegangen und wird bearbeitet.`
let partnerText = ''
let partnerHtml = ''
if (details.partnerCompanyName || details.partnerUserName || details.partnerUserEmail) {
partnerText = `\nPartner-Informationen:`
if (details.partnerCompanyName) partnerText += `\n- Firma: ${details.partnerCompanyName}`
if (details.partnerUserName) partnerText += `\n- Benutzer: ${details.partnerUserName}`
if (details.partnerUserEmail) partnerText += `\n- E-Mail: ${details.partnerUserEmail}`
partnerText += `\n`
partnerHtml = `
<tr>
<td colspan="2" style="padding: 8px 0; border-top: 1px solid #e2e8f0; font-weight: bold; color: #0f172a;">Partner-Informationen:</td>
</tr>
`
if (details.partnerCompanyName) {
partnerHtml += `
<tr>
<td style="padding: 4px 0; padding-left: 10px; color: #475569; font-weight: bold;">Firma:</td>
<td style="padding: 4px 0; color: #475569;">${details.partnerCompanyName}</td>
</tr>
`
}
if (details.partnerUserName) {
partnerHtml += `
<tr>
<td style="padding: 4px 0; padding-left: 10px; color: #475569; font-weight: bold;">Benutzer:</td>
<td style="padding: 4px 0; color: #475569;">${details.partnerUserName}</td>
</tr>
`
}
if (details.partnerUserEmail) {
partnerHtml += `
<tr>
<td style="padding: 4px 0; padding-left: 10px; color: #475569; font-weight: bold;">E-Mail:</td>
<td style="padding: 4px 0; color: #475569;">${details.partnerUserEmail}</td>
</tr>
`
}
}
const text = `Hallo,\n\n${isUpdate ? 'Deine Anfrage wurde aktualisiert.' : 'Deine Anfrage ist bei uns eingegangen.'}\n\nDetails:\n- Nummer: ${details.orderNumber}\n- Datum: ${details.formattedDate}\n- Kunde: ${details.customerCompanyName}\n${partnerText}${details.itemsDetailsText || ''}\n${details.totalDetailsText}\n\nDeine Anfragebestätigung findest du im Anhang als PDF.\n\nPDF-Link: ${siteUrl}/api/orders/${details.orderNumber}/download\n\nViele Grüße,\nDein CASPOS Team`
const html = `
<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #e2e8f0; border-radius: 8px;">
<h2 style="color: ${primaryColor}; margin-bottom: 16px;">${title}</h2>
<p style="color: #475569; font-size: 16px; line-height: 1.5;">Hallo,</p>
<p style="color: #475569; font-size: 16px; line-height: 1.5;">${intro}</p>
<div style="background-color: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; padding: 16px; margin: 24px 0;">
<h3 style="color: #0f172a; margin-top: 0; margin-bottom: 12px;">Details</h3>
<table style="width: 100%; border-collapse: collapse; font-size: 14px; color: #475569;">
<tr>
<td style="padding: 4px 0; font-weight: bold; width: 140px;">Nummer:</td>
<td style="padding: 4px 0;">${details.orderNumber}</td>
</tr>
<tr>
<td style="padding: 4px 0; font-weight: bold;">Datum:</td>
<td style="padding: 4px 0;">${details.formattedDate}</td>
</tr>
<tr>
<td style="padding: 4px 0; font-weight: bold;">Kunde:</td>
<td style="padding: 4px 0;">${details.customerCompanyName}</td>
</tr>
${partnerHtml}
${details.itemsDetailsHtml || ''}
${details.totalDetailsHtml}
</table>
</div>
<p style="color: #475569; font-size: 16px; line-height: 1.5;">Die Bestätigung liegt als PDF im Anhang.</p>
<p style="color: #475569; font-size: 16px; line-height: 1.5;">
<a href="${siteUrl}/api/orders/${details.orderNumber}/download" style="color: ${primaryColor}; text-decoration: underline; font-weight: 500;">Anfragebestätigung PDF herunterladen</a>
</p>
<p style="color: #475569; font-size: 16px; line-height: 1.5; margin-top: 24px;">Viele Grüße,<br>Dein CASPOS Team</p>
</div>
`
return { text, html }
}
export function getStatusEmailTemplate(
orderNumber: string,
oldLabel: string,
newLabel: string,
statusKey?: string,
rejectionReason?: string
) {
const formattedOrderNumber = orderNumber.replace(/^BE-/, 'AE-')
let title = 'Statusänderung'
let intro = `der Status deiner Anfrage <strong>#${formattedOrderNumber}</strong> wurde aktualisiert.`
let statusColor = '#f59e0b' // Amber default
let showAttachmentBadge = false
if (statusKey === 'approved' || statusKey === 'active' || statusKey === 'completed') {
title = 'Freigabebestätigung'
intro = `Ihre Anfrage <strong>#${formattedOrderNumber}</strong> wurde erfolgreich freigegeben und ist nun aktiv. Die unterzeichnete Anfragebestätigung finden Sie im Anhang dieses Schreibens.`
statusColor = '#10b981' // Emerald
showAttachmentBadge = true
} else if (statusKey === 'rejected') {
title = 'Anfrage abgelehnt'
intro = `Ihre Anfrage <strong>#${formattedOrderNumber}</strong> wurde vom Support geprüft und abgelehnt.`
statusColor = '#ef4444' // Red
} else if (statusKey === 'pending_approval' || statusKey === 'in_review' || statusKey === 'pending') {
title = 'Eingangsbestätigung'
intro = `Ihre Anfrage <strong>#${formattedOrderNumber}</strong> ist eingegangen und wird derzeit von unserem Support geprüft.`
statusColor = '#f59e0b'
}
const text = `Hallo,\n\n${title}\n\n${intro.replace(/<[^>]*>/g, '')}\n\nStatus: ${newLabel} (vorher: ${oldLabel})\n${rejectionReason ? `Grund: ${rejectionReason}\n` : ''}\nViele Grüße,\nDein CASPOS Team`
const reasonSection = rejectionReason
? `
<div style="margin-top: 20px; padding: 12px 16px; background-color: rgba(239, 68, 68, 0.1); border-left: 4px solid #ef4444; border-radius: 4px;">
<p style="margin: 0; font-size: 11px; color: #ef4444; font-weight: bold; text-transform: uppercase; tracking-spacing: 0.05em;">Begründung:</p>
<p style="margin: 4px 0 0 0; font-size: 14px; color: #f8fafc; font-style: italic;">"${rejectionReason}"</p>
</div>
`
: ''
const footerActions = showAttachmentBadge
? `
<p style="text-align: center; margin: 32px 0 0 0;">
<span style="display: inline-block; background-color: #1e293b; border: 1px solid #334155; border-radius: 6px; padding: 10px 20px; color: #3b82f6; font-size: 13px; font-weight: bold; letter-spacing: 0.025em;">
📎 PDF im Anhang verfügbar
</span>
</p>
`
: ''
const html = `
<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 32px; border: 1px solid #1e293b; border-radius: 12px; background-color: #0b0f19; color: #f8fafc;">
<div style="text-align: center; margin-bottom: 32px;">
<h1 style="color: #3b82f6; font-size: 26px; margin: 0; font-weight: 900; letter-spacing: -0.025em; text-transform: uppercase;">CASPOS</h1>
<p style="color: #64748b; font-size: 10px; margin: 2px 0 0 0; text-transform: uppercase; letter-spacing: 0.2em; font-weight: bold;">Die Kasse</p>
</div>
<div style="background-color: #111827; border: 1px solid #1e293b; border-radius: 8px; padding: 24px; margin-bottom: 24px;">
<h2 style="color: #ffffff; font-size: 18px; margin-top: 0; margin-bottom: 12px; font-weight: 700;">${title}</h2>
<p style="color: #cbd5e1; font-size: 14px; line-height: 1.6; margin-top: 0;">Hallo,</p>
<p style="color: #cbd5e1; font-size: 14px; line-height: 1.6;">${intro}</p>
<table style="width: 100%; border-collapse: collapse; font-size: 13px; color: #cbd5e1; margin-top: 20px;">
<tr>
<td style="padding: 8px 0; border-bottom: 1px solid #1e293b; color: #64748b; width: 140px; font-weight: 600; text-transform: uppercase; font-size: 11px;">Anfragenummer:</td>
<td style="padding: 8px 0; border-bottom: 1px solid #1e293b; font-family: monospace; font-weight: bold; color: #3b82f6; font-size: 14px;">#${formattedOrderNumber}</td>
</tr>
<tr>
<td style="padding: 8px 0; border-bottom: 1px solid #1e293b; color: #64748b; font-weight: 600; text-transform: uppercase; font-size: 11px;">Vorher:</td>
<td style="padding: 8px 0; border-bottom: 1px solid #1e293b; text-decoration: line-through; color: #475569;">${oldLabel}</td>
</tr>
<tr>
<td style="padding: 8px 0; border-bottom: 1px solid #1e293b; color: #64748b; font-weight: 600; text-transform: uppercase; font-size: 11px;">Aktuell:</td>
<td style="padding: 8px 0; border-bottom: 1px solid #1e293b; font-weight: bold; color: ${statusColor}; font-size: 14px;">${newLabel}</td>
</tr>
</table>
${reasonSection}
</div>
${footerActions}
<div style="text-align: center; margin-top: 32px; border-top: 1px solid #1e293b; padding-top: 20px; color: #475569; font-size: 11px; line-height: 1.5;">
<p style="margin: 0; font-weight: 600;">CASPOS Computerabrechnungssysteme GmbH · Alte Bundesstraße 16 · 76846 Hauenstein</p>
<p style="margin: 4px 0 0 0;">Dies ist eine automatisch generierte Systembenachrichtigung.</p>
</div>
</div>
`
return { text, html }
}
export function buildEmailItemsSection(items: any[]) {
const groupedItems: { [key: string]: any[] } = {}
items.forEach((item: any) => {
const devName = item.device_name || 'Kasse 1'
if (!groupedItems[devName]) {
groupedItems[devName] = []
}
groupedItems[devName].push(item)
})
let itemsDetailsText = '\nKassen-Konfigurationen:'
let itemsDetailsHtml = `
<tr>
<td colspan="2" style="padding: 12px 0 6px 0; border-top: 1px solid #e2e8f0; font-weight: bold; color: #0f172a;">Kassen-Konfigurationen:</td>
</tr>
`
Object.entries(groupedItems).forEach(([deviceName, devItems]) => {
itemsDetailsText += `\n- Kasse: ${deviceName}`
itemsDetailsHtml += `
<tr style="background-color: #f1f5f9;">
<td colspan="2" style="padding: 6px 8px; font-weight: bold; color: #1e3a8a; font-size: 13px;">Kasse: ${deviceName}</td>
</tr>
`
devItems.forEach((item: any) => {
itemsDetailsText += `\n * ${item.product_name} (${item.category_name}): ${item.base_price.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} ${item.billing_interval === 'one_time' ? 'einmalig' : 'mtl.'}`
itemsDetailsHtml += `
<tr>
<td style="padding: 6px 8px; font-size: 13px; color: #334155; font-weight: 500;">
${item.product_name} <span style="font-size: 11px; color: #64748b;">(${item.category_name})</span>
</td>
<td style="padding: 6px 8px; text-align: right; font-size: 13px; font-weight: bold; color: #0f172a;">
${item.base_price.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} ${item.billing_interval === 'one_time' ? 'einmalig' : 'mtl.'}
</td>
</tr>
`
item.selected_modules?.forEach((mod: any) => {
const qty = mod.quantity || 1
const price = mod.total_price ?? (mod.price * qty)
itemsDetailsText += `\n + ${mod.module_name} ${qty > 1 ? `(x${qty})` : ''}: +${price.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} mtl.`
itemsDetailsHtml += `
<tr>
<td style="padding: 4px 8px 4px 20px; color: #64748b; font-size: 12px;">
+ ${mod.module_name} ${qty > 1 ? `<span style="font-size: 10px; font-weight: 600;">(x${qty})</span>` : ''}
</td>
<td style="padding: 4px 8px; text-align: right; color: #64748b; font-size: 12px;">
+${price.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} mtl.
</td>
</tr>
`
})
})
})
return {
text: itemsDetailsText + '\n',
html: itemsDetailsHtml
}
}