import { Elysia, t } from "elysia"; import prisma from "../lib/prisma"; export const ordersRoute = new Elysia({ prefix: "/orders" }) .get("/", async () => { return await prisma.order.findMany({ orderBy: { createdAt: 'desc' }, include: { items: true, customer: true } }); }) .get("/:id", async ({ params: { id }, set }) => { const order = await prisma.order.findUnique({ where: { id: Number(id) }, include: { items: true, customer: true } }); if (!order) { set.status = 404; return { error: "Order not found" }; } return order; }) .post("/", async ({ body, set }) => { try { const data = body as any; const settings = await prisma.storeSettings.findFirst() const earnRate = settings?.pointsEarnRate || 1.0 const netAmount = data.totalAmount - data.shippingFee const pointsEarned = Math.floor(Math.max(0, netAmount) * earnRate) if (data.customerId && data.pointsUsed && data.pointsUsed > 0) { // Verify customer has enough points const customer = await prisma.customer.findUnique({ where: { id: data.customerId } }) if (!customer || customer.points < data.pointsUsed) { set.status = 400 return { error: "Insufficient points" } } // Deduct points immediately await prisma.customer.update({ where: { id: data.customerId }, data: { points: { decrement: data.pointsUsed } } }) } const order = await prisma.order.create({ data: { customerId: data.customerId, customerName: data.customerName, customerEmail: data.customerEmail, customerPhone: data.customerPhone, shippingAddress: data.shippingAddress, subtotal: data.subtotal || 0, promoLevel1Name: data.promoLevel1Name, promoLevel1Disc: data.promoLevel1Disc || 0, promoLevel2Name: data.promoLevel2Name, promoLevel2Disc: data.promoLevel2Disc || 0, couponCode: data.couponCode || null, discountAmount: data.discountAmount || 0, pointsUsed: data.pointsUsed || 0, pointsDiscount: data.pointsDiscount || 0, pointsEarned: pointsEarned, shippingFee: data.shippingFee, totalAmount: data.totalAmount, items: { create: data.items.map((item: any) => ({ productId: item.productId, variantId: item.variantId, quantity: item.quantity, priceAtPurchase: item.priceAtPurchase, productName: item.productName, variantSize: item.variantSize, productImage: item.productImage })) } }, include: { items: true } }); // If a coupon was used, increment its usedCount if (data.couponCode) { try { await prisma.coupon.update({ where: { code: data.couponCode.toUpperCase() }, data: { usedCount: { increment: 1 } } }); } catch (e) { console.error("Failed to increment coupon usedCount:", e); } } return order; } catch (err) { console.error("[Orders] Error:", err); set.status = 500; return { error: "Failed to create order" }; } }, { body: t.Object({ customerId: t.Optional(t.Nullable(t.Number())), customerName: t.String(), customerEmail: t.String(), customerPhone: t.String(), shippingAddress: t.String(), totalAmount: t.Number(), shippingFee: t.Number(), subtotal: t.Optional(t.Number()), promoLevel1Name: t.Optional(t.Nullable(t.String())), promoLevel1Disc: t.Optional(t.Number()), promoLevel2Name: t.Optional(t.Nullable(t.String())), promoLevel2Disc: t.Optional(t.Number()), couponCode: t.Optional(t.Nullable(t.String())), discountAmount: t.Optional(t.Number()), pointsUsed: t.Optional(t.Number()), pointsDiscount: t.Optional(t.Number()), items: t.Array(t.Object({ productId: t.Number(), variantId: t.Optional(t.Number()), quantity: t.Number(), priceAtPurchase: t.Number(), productName: t.String(), variantSize: t.Optional(t.String()), productImage: t.Optional(t.String()) })) }) }) .patch("/:id/status", async ({ params: { id }, body, set }) => { try { const { status } = body as any; const order = await prisma.order.findUnique({ where: { id: Number(id) } }); const updatedOrder = await prisma.order.update({ where: { id: Number(id) }, data: { status }, include: { items: true } }); // Grant points if status becomes PAID and it wasn't PAID before if (status === 'PAID' && order?.status !== 'PAID' && order?.customerId && order?.pointsEarned > 0) { await prisma.customer.update({ where: { id: order.customerId }, data: { points: { increment: order.pointsEarned } } }); } // If status becomes CANCELLED, refund the points used if (status === 'CANCELLED' && order?.status !== 'CANCELLED' && order?.customerId && order?.pointsUsed > 0) { await prisma.customer.update({ where: { id: order.customerId }, data: { points: { increment: order.pointsUsed } } }); } return updatedOrder; } catch (err) { set.status = 500; return { error: "Failed to update order status" }; } }, { body: t.Object({ status: t.String() }) }) .patch("/:id/slip", async ({ params: { id }, body, set }) => { try { const { paymentSlipUrl } = body as any; return await prisma.order.update({ where: { id: Number(id) }, data: { paymentSlipUrl }, include: { items: true } }); } catch (err) { set.status = 500; return { error: "Failed to upload slip" }; } }, { body: t.Object({ paymentSlipUrl: t.String() }) }) .patch("/:id/tracking", async ({ params: { id }, body, set }) => { try { const { trackingNo } = body as any; return await prisma.order.update({ where: { id: Number(id) }, data: { trackingNo }, include: { items: true } }); } catch (err) { set.status = 500; return { error: "Failed to update tracking number" }; } }, { body: t.Object({ trackingNo: t.String() }) }) .get("/customer/:customerId", async ({ params: { customerId } }) => { return await prisma.order.findMany({ where: { customerId: Number(customerId) }, orderBy: { createdAt: 'desc' }, include: { items: true } }); }) .post("/track", async ({ body, set }) => { try { const { orderId, phone } = body as any; const order = await prisma.order.findUnique({ where: { id: Number(orderId) } }); if (!order) { set.status = 404; return { error: "Order not found" }; } const sanitizePhone = (p: string) => p.replace(/[^0-9]/g, ''); if (sanitizePhone(order.customerPhone) !== sanitizePhone(phone)) { set.status = 401; return { error: "Authentication failed. Phone number does not match." }; } return { success: true, orderId: order.id }; } catch (err) { set.status = 500; return { error: "Invalid tracking request" }; } }, { body: t.Object({ orderId: t.Number(), phone: t.String() }) });