feat: implement quote editing for admins and creators
This commit is contained in:
@@ -132,6 +132,13 @@ export default async function MyOrdersPage() {
|
||||
Details ansehen
|
||||
</Button>
|
||||
</Link>
|
||||
{o.status === 'pending' && (
|
||||
<Link href={`/order?id=${o.id}`}>
|
||||
<Button variant="outline" size="sm" className="border-amber-500/20 hover:bg-amber-500/10 hover:border-amber-500/40 text-amber-400 text-xs">
|
||||
Bearbeiten
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
{o.pdf_url && (
|
||||
<>
|
||||
<Button variant="ghost" size="sm" asChild className="text-xs text-slate-400 hover:text-white">
|
||||
|
||||
@@ -6,7 +6,12 @@ import { OrderWizard } from '@/components/order-wizard'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { Suspense } from 'react'
|
||||
|
||||
export default async function OrderPage() {
|
||||
interface PageProps {
|
||||
searchParams: Promise<{ id?: string }>
|
||||
}
|
||||
|
||||
export default async function OrderPage({ searchParams }: PageProps) {
|
||||
const params = await searchParams
|
||||
return (
|
||||
<div className="min-h-screen bg-[#0a0a0a] text-white selection:bg-primary/30">
|
||||
<div className="container mx-auto py-10">
|
||||
@@ -20,19 +25,19 @@ export default async function OrderPage() {
|
||||
</div>
|
||||
|
||||
<Suspense fallback={<div className="h-96 w-full animate-pulse bg-white/5 rounded-2xl" />}>
|
||||
<OrderDataWrapper />
|
||||
<OrderDataWrapper orderId={params.id} />
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function OrderDataWrapper() {
|
||||
async function OrderDataWrapper({ orderId }: { orderId?: string }) {
|
||||
const supabase = await createClient()
|
||||
const { data: { user } } = await supabase.auth.getUser()
|
||||
|
||||
if (!user) {
|
||||
redirect('/auth/login?next=/order')
|
||||
redirect(`/auth/login?next=/order${orderId ? `?id=${orderId}` : ''}`)
|
||||
}
|
||||
|
||||
// Check user role
|
||||
@@ -42,6 +47,32 @@ async function OrderDataWrapper() {
|
||||
.eq('id', user.id)
|
||||
.single()
|
||||
|
||||
let initialOrder = null
|
||||
if (orderId) {
|
||||
const adminClient = createAdminClient()
|
||||
const { data: orderData, error } = await adminClient
|
||||
.from('orders')
|
||||
.select('*')
|
||||
.eq('id', orderId)
|
||||
.single()
|
||||
|
||||
if (error || !orderData) {
|
||||
redirect('/order')
|
||||
}
|
||||
|
||||
const isAdmin = userData?.role === 'admin'
|
||||
const isOwner = orderData.user_id === user.id
|
||||
if (!isAdmin && !isOwner) {
|
||||
redirect('/order')
|
||||
}
|
||||
|
||||
if (orderData.status !== 'pending' && !isAdmin) {
|
||||
redirect('/order')
|
||||
}
|
||||
|
||||
initialOrder = orderData
|
||||
}
|
||||
|
||||
const products = await getProducts().catch(() => [])
|
||||
const categories = await getCategories().catch(() => [])
|
||||
|
||||
@@ -75,5 +106,5 @@ async function OrderDataWrapper() {
|
||||
console.error('Fehler beim Laden des Profils:', e)
|
||||
}
|
||||
|
||||
return <OrderWizard products={products} categories={categories} initialProfile={profile} initialEndCustomers={endCustomers} />
|
||||
return <OrderWizard products={products} categories={categories} initialProfile={profile} initialEndCustomers={endCustomers} initialOrder={initialOrder} />
|
||||
}
|
||||
|
||||
@@ -216,8 +216,14 @@ export function OrdersTable({ initialOrders }: OrdersTableProps) {
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{order.pdf_url ? (
|
||||
<div className="flex justify-end gap-1.5">
|
||||
<Button variant="outline" size="sm" asChild className="h-8 border-amber-500/20 hover:bg-amber-500/10 text-amber-400 text-xs">
|
||||
<a href={`/order?id=${order.id}`}>
|
||||
Bearbeiten
|
||||
</a>
|
||||
</Button>
|
||||
{order.pdf_url ? (
|
||||
<>
|
||||
<Button variant="outline" size="sm" asChild className="h-8 border-white/10 hover:bg-primary/20 text-xs">
|
||||
<a href={order.pdf_url} target="_blank" rel="noopener noreferrer">
|
||||
<ExternalLink className="w-3.5 h-3.5 mr-1" /> PDF
|
||||
@@ -228,10 +234,11 @@ export function OrdersTable({ initialOrders }: OrdersTableProps) {
|
||||
<Download className="w-3.5 h-3.5" />
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-xs text-slate-500 italic">Keine Bestätigung</span>
|
||||
<span className="text-xs text-slate-500 italic self-center px-2">Keine Bestätigung</span>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useState, useMemo } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
import { Product, ProductModule, Profile, EndCustomer } from '@/lib/types'
|
||||
import { Product, ProductModule, Profile, EndCustomer, Order } from '@/lib/types'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
@@ -12,7 +12,7 @@ import { Label } from '@/components/ui/label'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
|
||||
import { Check, ChevronRight, ChevronLeft, ShoppingCart, User, ShieldCheck, Cloud, Utensils, HardDrive, Package, AlertCircle, Loader2, UserPlus, Building2, CreditCard, Calendar, Search } from 'lucide-react'
|
||||
import { submitOrder } from '@/lib/actions/orders'
|
||||
import { submitOrder, updateOrder } from '@/lib/actions/orders'
|
||||
import { createEndCustomer } from '@/lib/actions/end-customers'
|
||||
import { Category } from '@/lib/types'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
@@ -80,11 +80,13 @@ export function OrderWizard({
|
||||
categories,
|
||||
initialProfile,
|
||||
initialEndCustomers,
|
||||
initialOrder,
|
||||
}: {
|
||||
products: Product[]
|
||||
categories: Category[]
|
||||
initialProfile: Profile | null
|
||||
initialEndCustomers: EndCustomer[]
|
||||
initialOrder?: Order | null
|
||||
}) {
|
||||
const router = useRouter()
|
||||
const [step, setStep] = useState(1)
|
||||
@@ -96,7 +98,9 @@ export function OrderWizard({
|
||||
// Endkunden-State
|
||||
const [endCustomers, setEndCustomers] = useState<EndCustomer[]>(initialEndCustomers)
|
||||
const [selectedEndCustomerId, setSelectedEndCustomerId] = useState<string | null>(
|
||||
initialEndCustomers.length > 0 ? initialEndCustomers[0].id : null
|
||||
initialOrder
|
||||
? initialOrder.end_customer_id
|
||||
: (initialEndCustomers.length > 0 ? initialEndCustomers[0].id : null)
|
||||
)
|
||||
const selectedEndCustomer = endCustomers.find(c => c.id === selectedEndCustomerId) ?? null
|
||||
|
||||
@@ -134,13 +138,26 @@ export function OrderWizard({
|
||||
})
|
||||
|
||||
// Abrechnungsmodell-Auswahl ('one_time' = Kauf, 'monthly' = Abo)
|
||||
const [selectedBillingInterval, setSelectedBillingInterval] = useState<'one_time' | 'monthly'>('monthly')
|
||||
const [selectedBillingInterval, setSelectedBillingInterval] = useState<'one_time' | 'monthly'>(
|
||||
initialOrder?.order_data?.billing_cycle ?? 'monthly'
|
||||
)
|
||||
|
||||
// Modulmengen (moduleId -> Menge)
|
||||
const [moduleQuantities, setModuleQuantities] = useState<Record<string, number>>({})
|
||||
const [moduleQuantities, setModuleQuantities] = useState<Record<string, number>>(() => {
|
||||
if (!initialOrder) return {}
|
||||
const quantities: Record<string, number> = {}
|
||||
initialOrder.order_data?.items?.forEach(item => {
|
||||
item.selected_modules?.forEach(mod => {
|
||||
quantities[mod.module_id] = mod.quantity ?? 1
|
||||
})
|
||||
})
|
||||
return quantities
|
||||
})
|
||||
|
||||
// Datum der letzten Lizenz (Bestandskunden-Update-Logik)
|
||||
const [lastLicenseDate, setLastLicenseDate] = useState<string>("")
|
||||
const [lastLicenseDate, setLastLicenseDate] = useState<string>(
|
||||
initialOrder?.order_data?.last_license_date ?? ""
|
||||
)
|
||||
|
||||
const lastLicenseMonths = useMemo(() => {
|
||||
if (!lastLicenseDate || customerMode !== 'select' || !selectedEndCustomerId) return null
|
||||
@@ -174,6 +191,16 @@ export function OrderWizard({
|
||||
const [selections, setSelections] = useState<Record<string, CategorySelection>>(() => {
|
||||
const init: Record<string, CategorySelection> = {}
|
||||
categories.forEach(cat => {
|
||||
if (initialOrder) {
|
||||
const item = initialOrder.order_data?.items?.find(i => i.category_id === cat.id)
|
||||
if (item) {
|
||||
init[cat.id] = {
|
||||
productId: item.product_id,
|
||||
moduleIds: item.selected_modules?.map(m => m.module_id) ?? []
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
const first = products.find(p => p.category_id === cat.id && p.show_in_abo !== false)
|
||||
init[cat.id] = { productId: first?.id ?? null, moduleIds: [] }
|
||||
})
|
||||
@@ -335,7 +362,19 @@ export function OrderWizard({
|
||||
if (isSubmitting) return // Doppelklick-Guard
|
||||
setIsSubmitting(true)
|
||||
try {
|
||||
const order = await submitOrder({
|
||||
let order
|
||||
if (initialOrder) {
|
||||
order = await updateOrder(initialOrder.id, {
|
||||
selections,
|
||||
moduleQuantities,
|
||||
customerProfile: customerData,
|
||||
endCustomerId: selectedEndCustomerId,
|
||||
endCustomer: selectedEndCustomer,
|
||||
billingInterval: selectedBillingInterval,
|
||||
lastLicenseDate: lastLicenseDate || null,
|
||||
})
|
||||
} else {
|
||||
order = await submitOrder({
|
||||
selections,
|
||||
moduleQuantities,
|
||||
products,
|
||||
@@ -346,10 +385,11 @@ export function OrderWizard({
|
||||
billingInterval: selectedBillingInterval,
|
||||
lastLicenseDate: lastLicenseDate || null,
|
||||
})
|
||||
}
|
||||
router.push(`/order/success?id=${order.id}`)
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error(error)
|
||||
alert('Fehler bei der Bestellung. Bitte versuchen Sie es erneut.')
|
||||
alert(error?.message || (initialOrder ? 'Fehler beim Aktualisieren des Angebots.' : 'Fehler bei der Bestellung. Bitte versuchen Sie es erneut.'))
|
||||
setIsSubmitting(false) // Nur bei Fehler wieder entsperren
|
||||
}
|
||||
}
|
||||
@@ -1106,9 +1146,13 @@ export function OrderWizard({
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<><Loader2 className="mr-2 h-5 w-5 animate-spin" /> Bestellung wird verarbeitet...</>
|
||||
initialOrder ? (
|
||||
<><Loader2 className="mr-2 h-5 w-5 animate-spin" /> Angebot wird aktualisiert...</>
|
||||
) : (
|
||||
'Kostenpflichtig bestellen'
|
||||
<><Loader2 className="mr-2 h-5 w-5 animate-spin" /> Bestellung wird verarbeitet...</>
|
||||
)
|
||||
) : (
|
||||
initialOrder ? 'Angebot aktualisieren' : 'Kostenpflichtig bestellen'
|
||||
)}
|
||||
</Button>
|
||||
<Button variant="ghost" className="w-full text-white" onClick={prevStep}>
|
||||
|
||||
@@ -374,3 +374,231 @@ export async function updateOrderStatus(
|
||||
revalidatePath('/my-orders')
|
||||
return updatedOrder as Order
|
||||
}
|
||||
|
||||
export async function updateOrder(
|
||||
orderId: string,
|
||||
params: {
|
||||
selections: WizardSelections
|
||||
moduleQuantities?: Record<string, number>
|
||||
customerProfile: Partial<Profile>
|
||||
endCustomerId?: string | null
|
||||
endCustomer?: EndCustomer | null
|
||||
billingInterval?: 'one_time' | 'monthly'
|
||||
lastLicenseDate?: string | null
|
||||
}
|
||||
): Promise<Order> {
|
||||
const { selections, moduleQuantities, customerProfile, endCustomerId, endCustomer, billingInterval, lastLicenseDate } = params
|
||||
const supabase = await createClient()
|
||||
const admin = createAdminClient()
|
||||
|
||||
const { data: { user } } = await supabase.auth.getUser()
|
||||
if (!user) throw new Error('Not authenticated')
|
||||
|
||||
// 1. Fetch current order & verify permissions
|
||||
const { data: existingOrder, error: fetchError } = await admin
|
||||
.from('orders')
|
||||
.select('*')
|
||||
.eq('id', orderId)
|
||||
.single()
|
||||
|
||||
if (fetchError || !existingOrder) {
|
||||
throw new Error(`Bestellung nicht gefunden: ${fetchError?.message || 'Unbekannt'}`)
|
||||
}
|
||||
|
||||
const { data: dbUser } = await admin
|
||||
.from('users')
|
||||
.select('role')
|
||||
.eq('id', user.id)
|
||||
.single()
|
||||
|
||||
const isAdmin = dbUser?.role === 'admin'
|
||||
const isOwner = existingOrder.user_id === user.id
|
||||
|
||||
if (!isAdmin && !isOwner) {
|
||||
throw new Error('Keine Berechtigung dieses Angebot zu bearbeiten.')
|
||||
}
|
||||
|
||||
if (existingOrder.status !== 'pending' && !isAdmin) {
|
||||
throw new Error('Nur Angebote im Status "Eingegangen" können bearbeitet werden.')
|
||||
}
|
||||
|
||||
// Fetch catalog directly from DB to prevent client tampering
|
||||
const dbProducts = await getProducts()
|
||||
const dbCategories = await getCategories()
|
||||
|
||||
// ─── Constraint Validation ──────────────────────────────────────────────────
|
||||
for (const cat of dbCategories) {
|
||||
const sel = selections[cat.id]
|
||||
if (cat.is_required && (!sel || !sel.productId)) {
|
||||
throw new Error(`Die Kategorie "${cat.name}" ist ein Pflichtfeld, wurde aber nicht ausgewählt.`)
|
||||
}
|
||||
if (sel?.productId) {
|
||||
const prod = dbProducts.find(p => p.id === sel.productId)
|
||||
if (!prod) {
|
||||
throw new Error(`Das ausgewählte Produkt für Kategorie "${cat.name}" wurde nicht gefunden.`)
|
||||
}
|
||||
|
||||
// Validate selected modules
|
||||
for (const mId of sel.moduleIds) {
|
||||
const mod = prod.modules?.find(m => m.id === mId)
|
||||
if (!mod) {
|
||||
throw new Error(`Das Modul mit ID "${mId}" gehört nicht zum Produkt "${prod.name}".`)
|
||||
}
|
||||
// Requirements check
|
||||
if (mod.requirements && mod.requirements.length > 0) {
|
||||
const missing = mod.requirements.filter(reqId => !sel.moduleIds.includes(reqId))
|
||||
if (missing.length > 0) {
|
||||
throw new Error(`Das Modul "${mod.name}" setzt die Aktivierung anderer Module voraus.`)
|
||||
}
|
||||
}
|
||||
// Exclusions check
|
||||
if (mod.exclusions && mod.exclusions.length > 0) {
|
||||
const conflicting = mod.exclusions.filter(exId => sel.moduleIds.includes(exId))
|
||||
if (conflicting.length > 0) {
|
||||
throw new Error(`Das Modul "${mod.name}" schließt die Kombination mit anderen gewählten Modulen aus.`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Product-level constraints validation
|
||||
const selectedProductIds = Object.values(selections)
|
||||
.map(s => s.productId)
|
||||
.filter((id): id is string => !!id)
|
||||
|
||||
for (const prodId of selectedProductIds) {
|
||||
const prod = dbProducts.find(p => p.id === prodId)
|
||||
if (!prod) continue
|
||||
|
||||
// Product requirements check
|
||||
if (prod.requirements && prod.requirements.length > 0) {
|
||||
const missing = prod.requirements.filter(reqId => !selectedProductIds.includes(reqId))
|
||||
if (missing.length > 0) {
|
||||
const missingNames = missing
|
||||
.map(reqId => dbProducts.find(p => p.id === reqId)?.name || reqId)
|
||||
.join(', ')
|
||||
throw new Error(`Das Produkt "${prod.name}" erfordert die Auswahl von: ${missingNames}.`)
|
||||
}
|
||||
}
|
||||
|
||||
// Product exclusions check
|
||||
if (prod.exclusions && prod.exclusions.length > 0) {
|
||||
const conflicting = prod.exclusions.filter(exId => selectedProductIds.includes(exId))
|
||||
if (conflicting.length > 0) {
|
||||
const conflictingNames = conflicting
|
||||
.map(exId => dbProducts.find(p => p.id === exId)?.name || exId)
|
||||
.join(', ')
|
||||
throw new Error(`Das Produkt "${prod.name}" schließt die gleichzeitige Auswahl von folgenden Produkten aus: ${conflictingNames}.`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Snapshots aufbauen
|
||||
const customerSnapshot = buildCustomerSnapshot(customerProfile, endCustomer ?? null)
|
||||
const orderSnapshot = buildOrderSnapshot(selections, dbProducts, dbCategories, billingInterval, moduleQuantities, lastLicenseDate)
|
||||
const orderHash = hashOrderSnapshot(orderSnapshot)
|
||||
|
||||
// 2. Bestellung in DB aktualisieren
|
||||
const { data: order, error: orderError } = await admin
|
||||
.from('orders')
|
||||
.update({
|
||||
end_customer_id: endCustomerId ?? null,
|
||||
total_price: orderSnapshot.total,
|
||||
customer_data: customerSnapshot,
|
||||
order_data: orderSnapshot,
|
||||
order_hash: orderHash,
|
||||
})
|
||||
.eq('id', orderId)
|
||||
.select()
|
||||
.single()
|
||||
|
||||
if (orderError || !order) throw orderError || new Error('Fehler beim Aktualisieren der Bestellung.')
|
||||
|
||||
// 3. PDF generieren und überschreiben
|
||||
try {
|
||||
const buffer = await renderToBuffer(
|
||||
React.createElement(InvoicePDF, {
|
||||
order,
|
||||
customer: customerSnapshot,
|
||||
orderSnapshot,
|
||||
})
|
||||
)
|
||||
|
||||
// PDF hochladen (upsert: true, um das alte zu ersetzen)
|
||||
const fileName = `ab_${order.id}.pdf`
|
||||
const { error: uploadError } = await supabase
|
||||
.storage
|
||||
.from('invoices')
|
||||
.upload(fileName, buffer, { contentType: 'application/pdf', upsert: true })
|
||||
|
||||
if (uploadError) {
|
||||
console.error('PDF Upload Error:', uploadError)
|
||||
} else {
|
||||
const { data: publicUrlData } = supabase.storage.from('invoices').getPublicUrl(fileName)
|
||||
await admin.from('orders').update({ pdf_url: publicUrlData.publicUrl }).eq('id', order.id)
|
||||
order.pdf_url = publicUrlData.publicUrl
|
||||
}
|
||||
|
||||
// 4. E-Mail mit PDF-Anhang an den Besteller senden (Info über Änderung)
|
||||
const orderUserEmail = user.email || (await admin.auth.admin.getUserById(existingOrder.user_id).then(res => res.data.user?.email))
|
||||
if (orderUserEmail) {
|
||||
try {
|
||||
const formattedDate = new Date(order.created_at).toLocaleDateString('de-DE')
|
||||
const formattedTotal = order.total_price.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })
|
||||
|
||||
await sendMail({
|
||||
to: orderUserEmail,
|
||||
subject: `Angebotsänderung ${order.order_number}`,
|
||||
text: `Hallo,\n\nihr Angebot wurde erfolgreich aktualisiert!\n\nNeue Angebotsdetails:\n- Anfrage-Nummer: ${order.order_number}\n- Datum: ${formattedDate}\n- Endkunde: ${customerSnapshot.company_name}\n- Gesamtsumme: ${formattedTotal}\n\nIm Anhang dieser E-Mail finden Sie Ihr aktualisiertes Angebot als PDF-Dokument.\n\nViele Grüße,\nIhr CASPOS Shop-Team`,
|
||||
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: #0f172a; margin-bottom: 16px;">Ihr Angebot wurde aktualisiert!</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;">Ihr Angebot mit der Nummer ${order.order_number} wurde erfolgreich aktualisiert.</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;">Neue Anfrage-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;">Anfrage-Nummer:</td>
|
||||
<td style="padding: 4px 0;">${order.order_number}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 4px 0; font-weight: bold;">Datum:</td>
|
||||
<td style="padding: 4px 0;">${formattedDate}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 4px 0; font-weight: bold;">Endkunde:</td>
|
||||
<td style="padding: 4px 0;">${customerSnapshot.company_name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 4px 0; font-weight: bold;">Gesamtsumme:</td>
|
||||
<td style="padding: 4px 0; font-weight: bold; color: #0f172a;">${formattedTotal}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<p style="color: #475569; font-size: 16px; line-height: 1.5;">Im Anhang dieser E-Mail finden Sie Ihre aktualisierte Anfragebestätigung als PDF-Dokument.</p>
|
||||
<p style="color: #475569; font-size: 16px; line-height: 1.5; margin-top: 24px;">Mit freundlichen Grüßen,<br>Ihr CASPOS Shop-Team</p>
|
||||
</div>
|
||||
`,
|
||||
attachments: [
|
||||
{
|
||||
filename: `Angebotsbestaetigung_${order.order_number}.pdf`,
|
||||
content: buffer,
|
||||
contentType: 'application/pdf',
|
||||
}
|
||||
]
|
||||
})
|
||||
} catch (mailError) {
|
||||
console.error('Failed to send order update confirmation mail:', mailError)
|
||||
}
|
||||
}
|
||||
} catch (pdfError) {
|
||||
console.error('PDF Re-generation Error:', pdfError)
|
||||
}
|
||||
|
||||
revalidatePath('/my-customers')
|
||||
revalidatePath('/my-orders')
|
||||
revalidatePath('/admin/orders')
|
||||
return order as Order
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user