You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

145 lines
4.0 KiB

/**
* POST /api/orders/create
*
* Create a new order from the user's cart
*
* Request Body:
* {
* salutation: 'male' | 'female' | 'other'
* firstName: string
* lastName: string
* dateOfBirth: string (YYYY-MM-DD)
* street: string
* postCode: string
* city: string
* countryCode: string
* saveAddress: boolean (optional, default: false)
* }
*
* Behavior:
* - Creates order with status 'pending'
* - Copies cart items to order_items with price snapshot
* - Stores billing address snapshot in order
* - Generates unique order number (format: EXP-2025-00001)
* - Optionally saves address to user profile
* - Does NOT clear cart (cart is cleared after order confirmation)
*
* Response:
* {
* success: true
* orderId: string
* orderNumber: string
* message: string
* }
*/
import { checkoutSchema } from '../../utils/schemas/checkout'
import { orders, orderItems, users } from '../../database/schema'
import { eq, desc, sql } from 'drizzle-orm'
export default defineEventHandler(async (event) => {
// Require authentication
const { user } = await requireUserSession(event)
// Validate request body
const body = await readBody(event)
const checkoutData = await checkoutSchema.parseAsync(body)
const db = useDatabase()
// Get user's cart
const cart = await getOrCreateCart(event)
const cartSummary = await getCartWithItems(cart.id)
// Validate cart has items
if (cartSummary.items.length === 0) {
throw createError({
statusCode: 400,
statusMessage: 'Warenkorb ist leer',
})
}
// Generate unique order number
// Format: EXP-YYYY-NNNNN (e.g., EXP-2025-00001)
const year = new Date().getFullYear()
// Get the highest order number for this year
const lastOrder = await db.query.orders.findFirst({
where: sql`${orders.orderNumber} LIKE ${`EXP-${year}-%`}`,
orderBy: desc(orders.createdAt),
})
let sequenceNumber = 1
if (lastOrder) {
// Extract sequence number from last order number (EXP-2025-00123 -> 123)
const match = lastOrder.orderNumber.match(/EXP-\d{4}-(\d{5})/)
if (match) {
sequenceNumber = Number.parseInt(match[1], 10) + 1
}
}
const orderNumber = `EXP-${year}-${String(sequenceNumber).padStart(5, '0')}`
// Prepare billing address (exclude saveAddress flag)
const billingAddress = {
salutation: checkoutData.salutation,
firstName: checkoutData.firstName,
lastName: checkoutData.lastName,
dateOfBirth: checkoutData.dateOfBirth,
street: checkoutData.street,
postCode: checkoutData.postCode,
city: checkoutData.city,
countryCode: checkoutData.countryCode,
}
// Create order
const [order] = await db
.insert(orders)
.values({
orderNumber,
userId: user.id,
totalAmount: cartSummary.total.toFixed(2),
status: 'pending', // Order starts as pending (awaiting mock payment)
billingAddress,
})
.returning()
// Create order items with price snapshots
const orderItemsData = cartSummary.items.map((item) => ({
orderId: order.id,
productId: item.productId,
quantity: item.quantity,
priceSnapshot: item.product.price, // Snapshot price at time of order
productSnapshot: {
name: item.product.name,
description: item.product.description,
navProductId: item.product.navProductId,
category: item.product.category,
},
}))
await db.insert(orderItems).values(orderItemsData)
// Optionally save address to user profile
if (checkoutData.saveAddress) {
await db
.update(users)
.set({
salutation: checkoutData.salutation,
dateOfBirth: new Date(checkoutData.dateOfBirth),
street: checkoutData.street,
postCode: checkoutData.postCode,
city: checkoutData.city,
countryCode: checkoutData.countryCode,
updatedAt: new Date(),
})
.where(eq(users.id, user.id))
}
return {
success: true,
orderId: order.id,
orderNumber: order.orderNumber,
message: 'Bestellung erfolgreich erstellt',
}
})