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[];
|
items: any[];
|
||||||
endCustomerId: string;
|
endCustomerId: string;
|
||||||
customerProfile?: any;
|
customerProfile?: any;
|
||||||
|
lastLicenseDate?: string | null;
|
||||||
}) {
|
}) {
|
||||||
const { items, endCustomerId, customerProfile } = params;
|
const { items, endCustomerId, customerProfile, lastLicenseDate } = params;
|
||||||
const supabase = await createClient();
|
const supabase = await createClient();
|
||||||
|
|
||||||
// 1. Session & Auth Check
|
// 1. Session & Auth Check
|
||||||
@@ -256,7 +257,8 @@ export async function checkoutAction(params: {
|
|||||||
products,
|
products,
|
||||||
categories,
|
categories,
|
||||||
type === 'purchase' ? 'one_time' : 'monthly',
|
type === 'purchase' ? 'one_time' : 'monthly',
|
||||||
item.moduleQuantities
|
item.moduleQuantities,
|
||||||
|
type === 'purchase' ? lastLicenseDate : null
|
||||||
);
|
);
|
||||||
const itemsWithDevice = itemSnapshot.items.map(i => ({
|
const itemsWithDevice = itemSnapshot.items.map(i => ({
|
||||||
...i,
|
...i,
|
||||||
@@ -273,7 +275,8 @@ export async function checkoutAction(params: {
|
|||||||
total: total,
|
total: total,
|
||||||
tax_rate: 19,
|
tax_rate: 19,
|
||||||
tax_amount: Math.round(total * 0.19 * 100) / 100,
|
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');
|
const orderNumber = generateOrderNumber(type === 'purchase' ? 'AE' : 'BE');
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export async function POST(request: Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const body = await request.json();
|
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) {
|
if (!items || !Array.isArray(items) || items.length === 0) {
|
||||||
return NextResponse.json({ error: 'Warenkorb leer' }, { status: 400 });
|
return NextResponse.json({ error: 'Warenkorb leer' }, { status: 400 });
|
||||||
@@ -84,7 +84,8 @@ export async function POST(request: Request) {
|
|||||||
products,
|
products,
|
||||||
categories,
|
categories,
|
||||||
type === 'purchase' ? 'one_time' : 'monthly',
|
type === 'purchase' ? 'one_time' : 'monthly',
|
||||||
item.moduleQuantities
|
item.moduleQuantities,
|
||||||
|
type === 'purchase' ? lastLicenseDate : null
|
||||||
);
|
);
|
||||||
const itemsWithDevice = itemSnapshot.items.map(i => ({
|
const itemsWithDevice = itemSnapshot.items.map(i => ({
|
||||||
...i,
|
...i,
|
||||||
@@ -101,7 +102,8 @@ export async function POST(request: Request) {
|
|||||||
total: total,
|
total: total,
|
||||||
tax_rate: 19, // default
|
tax_rate: 19, // default
|
||||||
tax_amount: Math.round(total * 0.19 * 100) / 100,
|
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);
|
const orderHash = hashOrderSnapshot(orderSnapshot);
|
||||||
|
|||||||
@@ -567,6 +567,7 @@ export function OrderWizard({
|
|||||||
customerProfile: customerData,
|
customerProfile: customerData,
|
||||||
endCustomerId: selectedEndCustomerId,
|
endCustomerId: selectedEndCustomerId,
|
||||||
endCustomer: selectedEndCustomer,
|
endCustomer: selectedEndCustomer,
|
||||||
|
lastLicenseDate: lastLicenseDate || null,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -332,9 +332,9 @@ export async function updateOrderStatus(
|
|||||||
throw new Error(`Fehler beim Aktualisieren des Status: ${updateError?.message || 'Unbekannt'}`)
|
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
|
let attachmentBuffer: Buffer | null = null
|
||||||
if (newStatus === 'active') {
|
if (newStatus === 'active' || newStatus === 'approved') {
|
||||||
try {
|
try {
|
||||||
const customerSnapshot = updatedOrder.customer_data
|
const customerSnapshot = updatedOrder.customer_data
|
||||||
const orderSnapshot = updatedOrder.order_data
|
const orderSnapshot = updatedOrder.order_data
|
||||||
@@ -362,7 +362,7 @@ export async function updateOrderStatus(
|
|||||||
updatedOrder.pdf_url = fileName
|
updatedOrder.pdf_url = fileName
|
||||||
}
|
}
|
||||||
} catch (pdfError) {
|
} 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,
|
html: statusEmail.html,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bei Aktivierung hänge die PDF an
|
// Bei Aktivierung oder Freigabe hänge die PDF an
|
||||||
if (newStatus === 'active' && attachmentBuffer) {
|
if ((newStatus === 'active' || newStatus === 'approved') && attachmentBuffer) {
|
||||||
mailOptions.attachments = [
|
mailOptions.attachments = [
|
||||||
{
|
{
|
||||||
filename: `Anfragebestaetigung_${orderNumber}.pdf`,
|
filename: `Anfragebestaetigung_${orderNumber}.pdf`,
|
||||||
@@ -418,6 +418,143 @@ export async function updateOrderStatus(
|
|||||||
return updatedOrder as Order
|
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(
|
export async function updateOrder(
|
||||||
orderId: string,
|
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