import { NextResponse } from 'next/server'; import { createClient } from '@/lib/supabase/server'; import { getProducts, getCategories } from '@/lib/actions/products'; import { buildCustomerSnapshot, buildOrderSnapshot } from '@/lib/license-transform'; import { sendMail } from '@/utils/mail'; import { createHash } from 'crypto'; import { renderToBuffer } from '@react-pdf/renderer'; import React from 'react'; import { InvoicePDF } from '@/components/invoice-pdf'; import { getOrderEmailTemplate, buildEmailItemsSection } from '@/lib/actions/email-templates'; import { validateWizardSelections } from '@/lib/actions/validation'; function generateOrderNumber(prefix: 'BE' | 'AE' = 'AE'): string { const year = new Date().getFullYear(); const rand = Math.floor(10000 + Math.random() * 90000); return `${prefix}-${year}-${rand}`; } function hashOrderSnapshot(snapshot: object): string { return createHash('sha256').update(JSON.stringify(snapshot)).digest('hex'); } export async function POST(request: Request) { try { const supabase = await createClient(); const { data: { user } } = await supabase.auth.getUser(); if (!user) { return NextResponse.json({ error: 'Not authenticated' }, { status: 401 }); } const { data: dbUser } = await supabase .from('users') .select('role, company_id') .eq('id', user.id) .single(); const isAdminUser = dbUser?.role === 'admin'; if (!isAdminUser && !dbUser?.company_id) { return NextResponse.json({ error: 'Firma erforderlich' }, { status: 403 }); } const body = await request.json(); const { items, customerProfile, endCustomerId, endCustomer } = body; if (!items || !Array.isArray(items) || items.length === 0) { return NextResponse.json({ error: 'Warenkorb leer' }, { status: 400 }); } const products = await getProducts(); const categories = await getCategories(); // --- Backend-Validierung: alle Kategorie-Flags für jede Kasse prüfen --- for (const item of items) { if (item.selections) { validateWizardSelections(item.selections, products, categories); } } // Split items into purchase and subscription const purchaseItems = items.filter( (item: any) => item.billingInterval === 'one_time' || item.billingType === 'purchase' ); const subscriptionItems = items.filter( (item: any) => item.billingInterval === 'monthly' || item.billingType === 'subscription' ); const createdOrders: any[] = []; const processOrderGroup = async (groupItems: any[], type: 'purchase' | 'subscription', paymentMethod: string) => { if (groupItems.length === 0) return; // Build consolidated snapshots // For the multi-item support, we combine the order snapshots of each item in the group const customerSnapshot = buildCustomerSnapshot(customerProfile, endCustomer); const orderItemsList: any[] = []; let total = 0; for (const item of groupItems) { const itemSnapshot = buildOrderSnapshot( item.selections, products, categories, type === 'purchase' ? 'one_time' : 'monthly', item.moduleQuantities ); const itemsWithDevice = itemSnapshot.items.map(i => ({ ...i, device_name: item.deviceName })); orderItemsList.push(...itemsWithDevice); total += itemSnapshot.total; } const orderSnapshot = { schema_version: 1, billing_cycle: type === 'purchase' ? 'one_time' : 'monthly', items: orderItemsList, total: total, tax_rate: 19, // default tax_amount: Math.round(total * 0.19 * 100) / 100, subtotal: Math.round((total / 1.19) * 100) / 100 }; const orderHash = hashOrderSnapshot(orderSnapshot); const orderNumber = generateOrderNumber(type === 'purchase' ? 'AE' : 'BE'); const { data: order, error: orderError } = await supabase .from('orders') .insert([{ user_id: user.id, company_id: dbUser?.company_id || null, order_number: orderNumber, order_hash: orderHash, end_customer_id: endCustomerId || null, total_price: total, customer_data: customerSnapshot, order_data: orderSnapshot, type: type, payment_method: paymentMethod, status: 'pending' }]) .select() .single(); if (orderError) throw orderError; const fileName = `ab_${order.id}.pdf`; // Trigger PDF generation & storage upload try { const buffer = await renderToBuffer( React.createElement(InvoicePDF, { order, customer: customerSnapshot, orderSnapshot, }) ); const { error: uploadError } = await supabase .storage .from('invoices') .upload(fileName, buffer, { contentType: 'application/pdf', upsert: true }); if (!uploadError) { await supabase.from('orders').update({ pdf_url: fileName }).eq('id', order.id); order.pdf_url = fileName; } } catch (pdfErr) { console.error('PDF error for order ' + order.id, pdfErr); } // Trigger E-Mail notifications if (user.email) { try { 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 = `