feat(checkout): implement wizard and admin actions
All checks were successful
Staging Build / build (push) Successful in 3m11s
All checks were successful
Staging Build / build (push) Successful in 3m11s
This commit is contained in:
@@ -182,8 +182,9 @@ export async function checkoutAction(params: {
|
||||
items: any[];
|
||||
endCustomerId: string;
|
||||
customerProfile?: any;
|
||||
lastLicenseDate?: string | null;
|
||||
}) {
|
||||
const { items, endCustomerId, customerProfile } = params;
|
||||
const { items, endCustomerId, customerProfile, lastLicenseDate } = params;
|
||||
const supabase = await createClient();
|
||||
|
||||
// 1. Session & Auth Check
|
||||
@@ -256,7 +257,8 @@ export async function checkoutAction(params: {
|
||||
products,
|
||||
categories,
|
||||
type === 'purchase' ? 'one_time' : 'monthly',
|
||||
item.moduleQuantities
|
||||
item.moduleQuantities,
|
||||
type === 'purchase' ? lastLicenseDate : null
|
||||
);
|
||||
const itemsWithDevice = itemSnapshot.items.map(i => ({
|
||||
...i,
|
||||
@@ -273,7 +275,8 @@ export async function checkoutAction(params: {
|
||||
total: total,
|
||||
tax_rate: 19,
|
||||
tax_amount: Math.round(total * 0.19 * 100) / 100,
|
||||
subtotal: Math.round((total / 1.19) * 100) / 100
|
||||
subtotal: Math.round((total / 1.19) * 100) / 100,
|
||||
last_license_date: type === 'purchase' ? lastLicenseDate : null
|
||||
};
|
||||
|
||||
const orderNumber = generateOrderNumber(type === 'purchase' ? 'AE' : 'BE');
|
||||
|
||||
@@ -42,7 +42,7 @@ export async function POST(request: Request) {
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const { items, customerProfile, endCustomerId, endCustomer } = body;
|
||||
const { items, customerProfile, endCustomerId, endCustomer, lastLicenseDate } = body;
|
||||
|
||||
if (!items || !Array.isArray(items) || items.length === 0) {
|
||||
return NextResponse.json({ error: 'Warenkorb leer' }, { status: 400 });
|
||||
@@ -84,7 +84,8 @@ export async function POST(request: Request) {
|
||||
products,
|
||||
categories,
|
||||
type === 'purchase' ? 'one_time' : 'monthly',
|
||||
item.moduleQuantities
|
||||
item.moduleQuantities,
|
||||
type === 'purchase' ? lastLicenseDate : null
|
||||
);
|
||||
const itemsWithDevice = itemSnapshot.items.map(i => ({
|
||||
...i,
|
||||
@@ -101,7 +102,8 @@ export async function POST(request: Request) {
|
||||
total: total,
|
||||
tax_rate: 19, // default
|
||||
tax_amount: Math.round(total * 0.19 * 100) / 100,
|
||||
subtotal: Math.round((total / 1.19) * 100) / 100
|
||||
subtotal: Math.round((total / 1.19) * 100) / 100,
|
||||
last_license_date: type === 'purchase' ? lastLicenseDate : null
|
||||
};
|
||||
|
||||
const orderHash = hashOrderSnapshot(orderSnapshot);
|
||||
|
||||
@@ -567,6 +567,7 @@ export function OrderWizard({
|
||||
customerProfile: customerData,
|
||||
endCustomerId: selectedEndCustomerId,
|
||||
endCustomer: selectedEndCustomer,
|
||||
lastLicenseDate: lastLicenseDate || null,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -332,9 +332,9 @@ export async function updateOrderStatus(
|
||||
throw new Error(`Fehler beim Aktualisieren des Status: ${updateError?.message || 'Unbekannt'}`)
|
||||
}
|
||||
|
||||
// 3. Wenn Status auf 'active' wechselt, generiere PDF und lade es hoch (pdf-invoice-generator)
|
||||
// 3. Wenn Status auf 'active' oder 'approved' wechselt, generiere PDF und lade es hoch (pdf-invoice-generator)
|
||||
let attachmentBuffer: Buffer | null = null
|
||||
if (newStatus === 'active') {
|
||||
if (newStatus === 'active' || newStatus === 'approved') {
|
||||
try {
|
||||
const customerSnapshot = updatedOrder.customer_data
|
||||
const orderSnapshot = updatedOrder.order_data
|
||||
@@ -362,7 +362,7 @@ export async function updateOrderStatus(
|
||||
updatedOrder.pdf_url = fileName
|
||||
}
|
||||
} catch (pdfError) {
|
||||
console.error('PDF Generation Error on active status transition:', pdfError)
|
||||
console.error('PDF Generation Error on status transition:', pdfError)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,8 +395,8 @@ export async function updateOrderStatus(
|
||||
html: statusEmail.html,
|
||||
}
|
||||
|
||||
// Bei Aktivierung hänge die PDF an
|
||||
if (newStatus === 'active' && attachmentBuffer) {
|
||||
// Bei Aktivierung oder Freigabe hänge die PDF an
|
||||
if ((newStatus === 'active' || newStatus === 'approved') && attachmentBuffer) {
|
||||
mailOptions.attachments = [
|
||||
{
|
||||
filename: `Anfragebestaetigung_${orderNumber}.pdf`,
|
||||
@@ -418,6 +418,143 @@ export async function updateOrderStatus(
|
||||
return updatedOrder as Order
|
||||
}
|
||||
|
||||
export async function updateOrderPayload(
|
||||
orderId: string,
|
||||
params: {
|
||||
orderSnapshot: Partial<any>;
|
||||
}
|
||||
) {
|
||||
const admin = createAdminClient()
|
||||
const supabase = await createClient()
|
||||
|
||||
// Authentifizierung und Admin-Prüfung
|
||||
const { data: { user } } = await supabase.auth.getUser()
|
||||
if (!user) throw new Error('Not authenticated')
|
||||
|
||||
const { data: dbUser } = await admin
|
||||
.from('users')
|
||||
.select('role')
|
||||
.eq('id', user.id)
|
||||
.single()
|
||||
|
||||
if (dbUser?.role !== 'admin') {
|
||||
throw new Error('Nur Administratoren können Bestellanfragen anpassen.')
|
||||
}
|
||||
|
||||
// Aktuelle Bestellung abrufen
|
||||
const { data: order, error: fetchError } = await admin
|
||||
.from('orders')
|
||||
.select('*')
|
||||
.eq('id', orderId)
|
||||
.single()
|
||||
|
||||
if (fetchError || !order) {
|
||||
throw new Error(`Bestellung nicht gefunden: ${fetchError?.message || 'Unbekannt'}`)
|
||||
}
|
||||
|
||||
if (order.status !== 'in_review') {
|
||||
throw new Error('Bestellungen können nur im Status "in_review" angepasst werden.')
|
||||
}
|
||||
|
||||
// Neue Snapshot-Daten mergen
|
||||
const updatedSnapshot = {
|
||||
...order.order_data,
|
||||
...params.orderSnapshot
|
||||
}
|
||||
|
||||
const { data: updatedOrder, error: updateError } = await admin
|
||||
.from('orders')
|
||||
.update({
|
||||
order_data: updatedSnapshot,
|
||||
total_price: updatedSnapshot.total || order.total_price
|
||||
})
|
||||
.eq('id', orderId)
|
||||
.select()
|
||||
.single()
|
||||
|
||||
if (updateError || !updatedOrder) {
|
||||
throw new Error(`Fehler beim Aktualisieren der Bestelldaten: ${updateError?.message || 'Unbekannt'}`)
|
||||
}
|
||||
|
||||
revalidatePath('/admin/orders')
|
||||
revalidatePath('/my-orders')
|
||||
return updatedOrder as Order
|
||||
}
|
||||
|
||||
export async function rejectOrder(
|
||||
orderId: string,
|
||||
reason: string
|
||||
) {
|
||||
const admin = createAdminClient()
|
||||
const supabase = await createClient()
|
||||
|
||||
// Authentifizierung und Admin-Prüfung
|
||||
const { data: { user } } = await supabase.auth.getUser()
|
||||
if (!user) throw new Error('Not authenticated')
|
||||
|
||||
const { data: dbUser } = await admin
|
||||
.from('users')
|
||||
.select('role')
|
||||
.eq('id', user.id)
|
||||
.single()
|
||||
|
||||
if (dbUser?.role !== 'admin') {
|
||||
throw new Error('Nur Administratoren können Bestellungen ablehnen.')
|
||||
}
|
||||
|
||||
const { data: order, error: fetchError } = await admin
|
||||
.from('orders')
|
||||
.select('*')
|
||||
.eq('id', orderId)
|
||||
.single()
|
||||
|
||||
if (fetchError || !order) {
|
||||
throw new Error(`Bestellung nicht gefunden: ${fetchError?.message || 'Unbekannt'}`)
|
||||
}
|
||||
|
||||
const updatedSnapshot = {
|
||||
...order.order_data,
|
||||
rejection_reason: reason
|
||||
}
|
||||
|
||||
const { data: updatedOrder, error: updateError } = await admin
|
||||
.from('orders')
|
||||
.update({
|
||||
status: 'rejected',
|
||||
order_data: updatedSnapshot
|
||||
})
|
||||
.eq('id', orderId)
|
||||
.select()
|
||||
.single()
|
||||
|
||||
if (updateError || !updatedOrder) {
|
||||
throw new Error(`Fehler beim Ablehnen der Bestellung: ${updateError?.message || 'Unbekannt'}`)
|
||||
}
|
||||
|
||||
// Ablehnungs-Mail senden
|
||||
if (order.user_id) {
|
||||
try {
|
||||
const { data: { user: orderUser }, error: userError } = await admin.auth.admin.getUserById(order.user_id)
|
||||
if (!userError && orderUser && orderUser.email) {
|
||||
const orderNumber = order.order_number || order.id.slice(0, 8)
|
||||
const statusEmail = getStatusEmailTemplate(orderNumber, 'Wartet auf Freigabe', `Abgelehnt (Grund: ${reason})`)
|
||||
|
||||
await sendMail({
|
||||
to: orderUser.email,
|
||||
subject: `Anfrage abgelehnt: ${orderNumber}`,
|
||||
text: statusEmail.text,
|
||||
html: statusEmail.html
|
||||
})
|
||||
}
|
||||
} catch (mailError) {
|
||||
console.error('Failed to send rejection email:', mailError)
|
||||
}
|
||||
}
|
||||
|
||||
revalidatePath('/admin/orders')
|
||||
revalidatePath('/my-orders')
|
||||
return updatedOrder as Order
|
||||
}
|
||||
|
||||
export async function updateOrder(
|
||||
orderId: string,
|
||||
|
||||
36
shop/lib/actions/queries.ts
Normal file
36
shop/lib/actions/queries.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
'use server'
|
||||
|
||||
import { createClient } from '@/lib/supabase/server'
|
||||
import type { Order, EndCustomer } from '@/lib/types'
|
||||
|
||||
/**
|
||||
* Holt alle Bestellanfragen.
|
||||
* RLS filtert automatisch auf die eigene Firma (bzw. lässt Admins alle sehen).
|
||||
*/
|
||||
export async function getCompanyOrders(): Promise<Order[]> {
|
||||
const supabase = await createClient()
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('orders')
|
||||
.select('*')
|
||||
.order('created_at', { ascending: false })
|
||||
|
||||
if (error) throw error
|
||||
return data as Order[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt alle Endkunden.
|
||||
* RLS filtert automatisch auf die eigene Firma (bzw. lässt Admins alle sehen).
|
||||
*/
|
||||
export async function getCompanyEndCustomers(): Promise<EndCustomer[]> {
|
||||
const supabase = await createClient()
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('end_customers')
|
||||
.select('*')
|
||||
.order('company_name', { ascending: true })
|
||||
|
||||
if (error) throw error
|
||||
return data as EndCustomer[]
|
||||
}
|
||||
Reference in New Issue
Block a user