export interface OrderEmailProps { orderNumber: string status: 'pending' | 'pending_approval' | 'in_review' | 'approved' | 'active' | 'completed' | 'cancelled' | 'rejected' formattedDate: string customerCompanyName: string billingModel?: string // e.g. "Kauf" | "Miete (monatlich)" | "Kauf / Miete" rejectionReason?: string siteUrl?: string items?: any[] partnerCompanyName?: string partnerUserName?: string partnerUserEmail?: string taxRate?: number oneTimeNet?: number monthlyNet?: number } export function generateOrderEmailSubject(orderNumber: string, status: string): string { const formattedOrderNumber = (orderNumber || '').replace(/^BE-/, 'AE-') if (status === 'approved' || status === 'active' || status === 'completed') { return `Auftragsbestätigung: Anfrage #${formattedOrderNumber} freigegeben` } if (status === 'rejected' || status === 'cancelled') { return `Status-Update: Anfrage #${formattedOrderNumber} abgelehnt` } return `Eingangsbestätigung: Anfrage #${formattedOrderNumber} eingegangen` } export function generateOrderEmailHtml(props: OrderEmailProps): { text: string; html: string } { const formattedOrderNumber = (props.orderNumber || '').replace(/^BE-/, 'AE-') const siteUrl = props.siteUrl || process.env.NEXT_PUBLIC_SITE_URL || 'https://staging.hephex.de' const taxRate = props.taxRate ?? 19 const orderPortalUrl = `${siteUrl}/my-orders` // Status Callout configuration let calloutBg = '#f8fafc' let calloutBorder = '#cbd5e1' let calloutTitle = 'Status: Anfrage eingegangen und in Prüfung' let calloutText = 'Ihre Anfrage ist erfolgreich in unserem System eingegangen und wird derzeit vom Support geprüft.' let statusBadgeColor = '#2563eb' if (props.status === 'approved' || props.status === 'active' || props.status === 'completed') { calloutBg = '#f0fdf4' calloutBorder = '#86efac' calloutTitle = 'Status: Freigegeben / Aktiv' calloutText = 'Ihre Auftragsbestätigung / Rechnung finden Sie als PDF im Anhang dieser E-Mail.' statusBadgeColor = '#16a34a' } else if (props.status === 'rejected' || props.status === 'cancelled') { calloutBg = '#fef2f2' calloutBorder = '#fca5a5' calloutTitle = 'Status: Anfrage abgelehnt' calloutText = props.rejectionReason ? `Begründung: "${props.rejectionReason}"` : 'Ihre Anfrage wurde vom Support geprüft und konnte leider nicht freigegeben werden.' statusBadgeColor = '#dc2626' } // Calculate prices if items provided let oneTimeNet = props.oneTimeNet ?? 0 let monthlyNet = props.monthlyNet ?? 0 const items = props.items || [] if (items.length > 0 && props.oneTimeNet === undefined && props.monthlyNet === undefined) { items.forEach((item: any) => { if (item.billing_interval === 'monthly') { monthlyNet += item.base_price || 0 } else { oneTimeNet += item.base_price || 0 } item.selected_modules?.forEach((mod: any) => { const qty = mod.quantity || 1 const price = mod.total_price ?? (mod.price * qty) monthlyNet += price }) }) } const oneTimeTax = Math.round(oneTimeNet * (taxRate / 100) * 100) / 100 const oneTimeGross = Math.round((oneTimeNet + oneTimeTax) * 100) / 100 const monthlyTax = Math.round(monthlyNet * (taxRate / 100) * 100) / 100 const monthlyGross = Math.round((monthlyNet + monthlyTax) * 100) / 100 const formatEuro = (val: number) => new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(val) // Determine Billing Model Label let billingModelLabel = props.billingModel if (!billingModelLabel) { if (oneTimeNet > 0 && monthlyNet > 0) { billingModelLabel = 'Kauf & Miete' } else if (monthlyNet > 0) { billingModelLabel = 'Miete (monatlich)' } else { billingModelLabel = 'Kauf (einmalig)' } } // Group items by Device 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) }) // Build Text Breakdown let itemsBreakdownText = '' if (items.length > 0) { itemsBreakdownText += '\nKassen-Aufstellung:\n' Object.entries(groupedItems).forEach(([devName, devItems]) => { itemsBreakdownText += `\n[ ${devName} ]\n` devItems.forEach((item: any) => { itemsBreakdownText += ` - ${item.product_name} (${item.category_name || 'Basis'}): ${formatEuro(item.base_price || 0)} ${item.billing_interval === 'one_time' ? 'einmalig' : 'mtl.'}\n` item.selected_modules?.forEach((mod: any) => { const qty = mod.quantity || 1 const price = mod.total_price ?? (mod.price * qty) itemsBreakdownText += ` + ${mod.module_name} ${qty > 1 ? `(x${qty})` : ''}: +${formatEuro(price)} mtl.\n` }) }) }) } let totalsText = '' if (oneTimeNet > 0) { totalsText += `\nEinmalige Beträge:\n- Netto: ${formatEuro(oneTimeNet)}\n- zzgl. ${taxRate}% MwSt: ${formatEuro(oneTimeTax)}\n- Brutto Gesamt: ${formatEuro(oneTimeGross)}\n` } if (monthlyNet > 0) { totalsText += `\nMonatliche Beträge:\n- Netto: ${formatEuro(monthlyNet)} / mtl.\n- zzgl. ${taxRate}% MwSt: ${formatEuro(monthlyTax)} / mtl.\n- Brutto Gesamt: ${formatEuro(monthlyGross)} / mtl.\n` } let partnerPlainText = '' if (props.partnerCompanyName || props.partnerUserName || props.partnerUserEmail) { partnerPlainText = `\nPartner / Betreuer:\n- Firma: ${props.partnerCompanyName || '-'}\n- Ansprechpartner: ${props.partnerUserName || '-'}\n- E-Mail: ${props.partnerUserEmail || '-'}\n` } const text = `CASPOS B2B Portal\nAnfrage #${formattedOrderNumber}\n\n${calloutTitle}\n${calloutText}\n\nAuftragsdetails:\n- Endkunde: ${props.customerCompanyName}\n- Datum: ${props.formattedDate}\n- Abrechnung: ${billingModelLabel}\n${partnerPlainText}${itemsBreakdownText}${totalsText}\n\nAnfrage im Portal ansehen: ${orderPortalUrl}\n\nCASPOS Computerabrechnungssysteme GmbH\nAlte Bundesstraße 16 · 76846 Hauenstein\nAmtsgericht Zweibrücken HRB 12345\nAutomatische Systembenachrichtigung.` // Build HTML Items Rows let itemsHtmlRows = '' if (items.length > 0) { Object.entries(groupedItems).forEach(([devName, devItems]) => { itemsHtmlRows += ` ${devName === 'Zusatzleistung' ? 'Backoffice / Zusatzleistung' : `Kassengerät: ${devName}`} ` devItems.forEach((item: any) => { itemsHtmlRows += ` ${item.product_name} (${item.category_name || 'Basis'}) ${formatEuro(item.base_price || 0)} ${item.billing_interval === 'one_time' ? 'einmalig' : 'mtl.'} ` item.selected_modules?.forEach((mod: any) => { const qty = mod.quantity || 1 const price = mod.total_price ?? (mod.price * qty) itemsHtmlRows += ` + ${mod.module_name} ${qty > 1 ? `(x${qty})` : ''} +${formatEuro(price)} mtl. ` }) }) }) } // Build HTML Partner Section let partnerHtml = '' if (props.partnerCompanyName || props.partnerUserName || props.partnerUserEmail) { partnerHtml = ` Partner & Betreuung: ${props.partnerCompanyName ? `Firma:${props.partnerCompanyName}` : ''} ${props.partnerUserName ? `Ansprechpartner:${props.partnerUserName}` : ''} ${props.partnerUserEmail ? `E-Mail:${props.partnerUserEmail}` : ''} ` } // Totals Section HTML let totalsHtml = '' if (oneTimeNet > 0 || monthlyNet > 0) { totalsHtml = ` ${oneTimeNet > 0 ? ` ` : ''} ${monthlyNet > 0 ? ` ` : ''}
Einmalige Beträge:
Netto-Zwischensumme: ${formatEuro(oneTimeNet)}
zzgl. ${taxRate}% MwSt: ${formatEuro(oneTimeTax)}
Gesamt einmalig (brutto): ${formatEuro(oneTimeGross)}
Monatlich wiederkehrend:
Netto-Zwischensumme: ${formatEuro(monthlyNet)} / mtl.
zzgl. ${taxRate}% MwSt: ${formatEuro(monthlyTax)} / mtl.
Gesamt monatlich (brutto): ${formatEuro(monthlyGross)} / mtl.
` } const html = ` CASPOS B2B Portal - #${formattedOrderNumber}
CASPOS B2B Portal
Die Kasse · Fachhandelsportal
#${formattedOrderNumber}
${calloutTitle}
${calloutText}
${partnerHtml}
Endkunde: ${props.customerCompanyName}
Bestelldatum: ${props.formattedDate}
Abrechnungsmodell: ${billingModelLabel}
${items.length > 0 ? `
Kassen-Aufstellung
${itemsHtmlRows}
` : ''} ${totalsHtml}
[ Anfrage im Portal ansehen ]

CASPOS Computerabrechnungssysteme GmbH

Alte Bundesstraße 16 · 76846 Hauenstein · Amtsgericht Zweibrücken HRB 12345

Dies ist eine automatische Transaktions-E-Mail des CASPOS B2B Portals.

` return { text, html } } // Backwards-compatible Wrappers export function getOrderEmailTemplate( details: { orderNumber: string formattedDate: string customerCompanyName: string totalDetailsText?: string totalDetailsHtml?: string itemsDetailsText?: string itemsDetailsHtml?: string partnerCompanyName?: string partnerUserName?: string partnerUserEmail?: string items?: any[] taxRate?: number oneTimeNet?: number monthlyNet?: number billingModel?: string }, siteUrl: string, isUpdate: boolean = false, _primaryColor?: string ) { return generateOrderEmailHtml({ orderNumber: details.orderNumber, status: isUpdate ? 'in_review' : 'pending', formattedDate: details.formattedDate, customerCompanyName: details.customerCompanyName, billingModel: details.billingModel, siteUrl, items: details.items, partnerCompanyName: details.partnerCompanyName, partnerUserName: details.partnerUserName, partnerUserEmail: details.partnerUserEmail, taxRate: details.taxRate, oneTimeNet: details.oneTimeNet, monthlyNet: details.monthlyNet, }) } export function getStatusEmailTemplate( orderNumber: string, _oldLabel: string, _newLabel: string, statusKey?: string, rejectionReason?: string, extraDetails?: { customerCompanyName?: string formattedDate?: string billingModel?: string items?: any[] partnerCompanyName?: string partnerUserName?: string partnerUserEmail?: string taxRate?: number oneTimeNet?: number monthlyNet?: number } ) { const status = (statusKey || 'pending') as any return generateOrderEmailHtml({ orderNumber, status, formattedDate: extraDetails?.formattedDate || new Date().toLocaleDateString('de-DE'), customerCompanyName: extraDetails?.customerCompanyName || 'Endkunde', billingModel: extraDetails?.billingModel, rejectionReason, items: extraDetails?.items, partnerCompanyName: extraDetails?.partnerCompanyName, partnerUserName: extraDetails?.partnerUserName, partnerUserEmail: extraDetails?.partnerUserEmail, taxRate: extraDetails?.taxRate, oneTimeNet: extraDetails?.oneTimeNet, monthlyNet: extraDetails?.monthlyNet, }) } 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 = ` Kassen-Konfigurationen: ` Object.entries(groupedItems).forEach(([deviceName, devItems]) => { itemsDetailsText += `\n- Kasse: ${deviceName}` itemsDetailsHtml += ` Kasse: ${deviceName} ` devItems.forEach((item: any) => { itemsDetailsText += `\n * ${item.product_name} (${item.category_name}): ${Number(item.base_price || 0).toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} ${item.billing_interval === 'one_time' ? 'einmalig' : 'mtl.'}` itemsDetailsHtml += ` ${item.product_name} (${item.category_name}) ${Number(item.base_price || 0).toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} ${item.billing_interval === 'one_time' ? 'einmalig' : 'mtl.'} ` 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})` : ''}: +${Number(price || 0).toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} mtl.` itemsDetailsHtml += ` + ${mod.module_name} ${qty > 1 ? `(x${qty})` : ''} +${Number(price || 0).toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} mtl. ` }) }) }) return { text: itemsDetailsText + '\n', html: itemsDetailsHtml } }