fix(checkout): use full email template with PDF attachment for checkout confirmations
Some checks failed
Staging Build / build (push) Failing after 24s
Some checks failed
Staging Build / build (push) Failing after 24s
This commit is contained in:
@@ -8,6 +8,8 @@ import { sendMail } from '@/utils/mail';
|
|||||||
import { renderToBuffer } from '@react-pdf/renderer';
|
import { renderToBuffer } from '@react-pdf/renderer';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { InvoicePDF } from '@/components/invoice-pdf';
|
import { InvoicePDF } from '@/components/invoice-pdf';
|
||||||
|
import { buildEmailItemsSection } from '@/lib/actions/orders';
|
||||||
|
import { getOrderEmailTemplate } from '@/lib/actions/email-templates';
|
||||||
|
|
||||||
function generateOrderNumber(prefix: 'BE' | 'AE' = 'AE'): string {
|
function generateOrderNumber(prefix: 'BE' | 'AE' = 'AE'): string {
|
||||||
const year = new Date().getFullYear();
|
const year = new Date().getFullYear();
|
||||||
@@ -57,11 +59,119 @@ async function triggerPostProcessing(orderId: string, supabase: any, customerSna
|
|||||||
|
|
||||||
// E-Mail versenden
|
// E-Mail versenden
|
||||||
if (email) {
|
if (email) {
|
||||||
|
const formattedDate = new Date(order.created_at).toLocaleDateString('de-DE');
|
||||||
|
const taxRate = orderSnapshot.tax_rate ?? 19;
|
||||||
|
const items = orderSnapshot.items ?? [];
|
||||||
|
const oneTimeItems = items.filter((item: any) => item.billing_interval === 'one_time');
|
||||||
|
const monthlyItems = items.filter((item: any) => item.billing_interval === 'monthly');
|
||||||
|
|
||||||
|
const oneTimeNet = oneTimeItems.reduce((sum: number, item: any) => sum + (item.item_total || 0), 0);
|
||||||
|
const oneTimeTax = Math.round(oneTimeNet * (taxRate / 100) * 100) / 100;
|
||||||
|
const oneTimeGross = Math.round((oneTimeNet + oneTimeTax) * 100) / 100;
|
||||||
|
|
||||||
|
const monthlyNet = monthlyItems.reduce((sum: number, item: any) => sum + (item.item_total || 0), 0);
|
||||||
|
const monthlyTax = Math.round(monthlyNet * (taxRate / 100) * 100) / 100;
|
||||||
|
const monthlyGross = Math.round((monthlyNet + monthlyTax) * 100) / 100;
|
||||||
|
|
||||||
|
let totalDetailsText = '';
|
||||||
|
if (oneTimeNet > 0 && monthlyNet > 0) {
|
||||||
|
totalDetailsText = `\nEinmaliger Betrag:\n- Netto: ${oneTimeNet.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}\n- zzgl. ${taxRate}% MwSt: ${oneTimeTax.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}\n- Brutto Gesamtbetrag: ${oneTimeGross.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}\n\nMonatlicher Betrag:\n- Netto: ${monthlyNet.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat\n- zzgl. ${taxRate}% MwSt: ${monthlyTax.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat\n- Brutto Gesamtbetrag: ${monthlyGross.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat`;
|
||||||
|
} else if (oneTimeNet > 0) {
|
||||||
|
totalDetailsText = `\nGesamtsumme:\n- Netto: ${oneTimeNet.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}\n- zzgl. ${taxRate}% MwSt: ${oneTimeTax.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}\n- Brutto Gesamtbetrag: ${oneTimeGross.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}`;
|
||||||
|
} else {
|
||||||
|
totalDetailsText = `\nGesamtsumme:\n- Netto: ${monthlyNet.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat\n- zzgl. ${taxRate}% MwSt: ${monthlyTax.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat\n- Brutto Gesamtbetrag: ${monthlyGross.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalDetailsHtml = '';
|
||||||
|
if (oneTimeNet > 0 && monthlyNet > 0) {
|
||||||
|
totalDetailsHtml = `
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" style="padding: 8px 0; border-top: 1px solid #e2e8f0; font-weight: bold; color: #0f172a;">Einmaliger Betrag:</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; padding-left: 10px; color: #475569;">Netto:</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; color: #475569;">${oneTimeNet.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; padding-left: 10px; color: #475569;">zzgl. ${taxRate}% MwSt:</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; color: #475569;">${oneTimeTax.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; padding-left: 10px; font-weight: bold; color: #0f172a;">Brutto Gesamtbetrag:</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; font-weight: bold; color: #0f172a;">${oneTimeGross.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" style="padding: 8px 0; border-top: 1px solid #e2e8f0; font-weight: bold; color: #0f172a;">Monatlicher Betrag:</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; padding-left: 10px; color: #475569;">Netto:</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; color: #475569;">${monthlyNet.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; padding-left: 10px; color: #475569;">zzgl. ${taxRate}% MwSt:</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; color: #475569;">${monthlyTax.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; padding-left: 10px; font-weight: bold; color: #0f172a;">Brutto Gesamtbetrag:</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; font-weight: bold; color: #0f172a;">${monthlyGross.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
} else if (oneTimeNet > 0) {
|
||||||
|
totalDetailsHtml = `
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; color: #475569;">Netto-Gesamtbetrag:</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; color: #475569;">${oneTimeNet.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; color: #475569;">zzgl. ${taxRate}% MwSt:</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; color: #475569;">${oneTimeTax.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; font-weight: bold; color: #0f172a; border-top: 1px solid #e2e8f0; padding-top: 6px;">Gesamtbetrag (brutto):</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; font-weight: bold; color: #0f172a; border-top: 1px solid #e2e8f0; padding-top: 6px;">${oneTimeGross.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
totalDetailsHtml = `
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; color: #475569;">Netto-Gesamtbetrag:</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; color: #475569;">${monthlyNet.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; color: #475569;">zzgl. ${taxRate}% MwSt:</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; color: #475569;">${monthlyTax.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; font-weight: bold; color: #0f172a; border-top: 1px solid #e2e8f0; padding-top: 6px;">Gesamtbetrag (brutto):</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; font-weight: bold; color: #0f172a; border-top: 1px solid #e2e8f0; padding-top: 6px;">${monthlyGross.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemsSection = buildEmailItemsSection(items);
|
||||||
|
|
||||||
|
const emailTemplate = getOrderEmailTemplate({
|
||||||
|
orderNumber: order.order_number,
|
||||||
|
formattedDate,
|
||||||
|
customerCompanyName: customerSnapshot.company_name,
|
||||||
|
totalDetailsText,
|
||||||
|
totalDetailsHtml,
|
||||||
|
itemsDetailsText: itemsSection.text,
|
||||||
|
itemsDetailsHtml: itemsSection.html
|
||||||
|
}, `${process.env.NEXT_PUBLIC_SITE_URL || 'https://staging.hephex.de'}`, false);
|
||||||
|
|
||||||
await sendMail({
|
await sendMail({
|
||||||
to: email,
|
to: email,
|
||||||
subject: `Bestätigung Ihrer CASPOS-Anfrage ${order.order_number}`,
|
subject: `Anfragebestätigung ${order.order_number}`,
|
||||||
text: `Vielen Dank für Ihre Anfrage ${order.order_number}.`,
|
text: emailTemplate.text,
|
||||||
html: `<p>Vielen Dank für Ihre Anfrage <strong>${order.order_number}</strong>.</p>`
|
html: emailTemplate.html,
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
filename: `Anfragebestaetigung_${order.order_number}.pdf`,
|
||||||
|
content: buffer,
|
||||||
|
contentType: 'application/pdf',
|
||||||
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { getOrderEmailTemplate, getStatusEmailTemplate } from '@/lib/actions/ema
|
|||||||
|
|
||||||
// ─── Hilfsfunktionen ─────────────────────────────────────────────────────────
|
// ─── Hilfsfunktionen ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function buildEmailItemsSection(items: any[]) {
|
export function buildEmailItemsSection(items: any[]) {
|
||||||
const groupedItems: { [key: string]: any[] } = {}
|
const groupedItems: { [key: string]: any[] } = {}
|
||||||
items.forEach((item: any) => {
|
items.forEach((item: any) => {
|
||||||
const devName = item.device_name || 'Kasse 1'
|
const devName = item.device_name || 'Kasse 1'
|
||||||
|
|||||||
Reference in New Issue
Block a user