fix: generate missing migration for Menu and Newsletter tables
This commit is contained in:
1 parent
363be70e11
commit
f5029fc935
123 files changed
+8683
No files matched your search
@@ -0,0 +1,247 @@
|
||||
import { Elysia, t } from "elysia";
|
||||
import prisma from "../lib/prisma";
|
||||
import { sendOrderCreatedNotification, sendPaymentConfirmedNotification, sendOrderShippedNotification, sendOrderCancelledNotification, markOrderAsPaid } from "../lib/notifications";
|
||||
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;
|
||||
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) => ({
|
||||
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);
|
||||
}
|
||||
}
|
||||
// Trigger LINE notification asynchronously
|
||||
sendOrderCreatedNotification(order.id).catch(e => {
|
||||
console.error("Failed to trigger sendOrderCreatedNotification:", 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;
|
||||
const order = await prisma.order.findUnique({ where: { id: Number(id) } });
|
||||
if (!order) {
|
||||
set.status = 404;
|
||||
return { error: "Order not found" };
|
||||
}
|
||||
if (status === 'PAID') {
|
||||
await markOrderAsPaid(order.id);
|
||||
}
|
||||
else {
|
||||
await prisma.order.update({
|
||||
where: { id: order.id },
|
||||
data: { status }
|
||||
});
|
||||
// If status becomes CANCELLED, refund the points used and notify
|
||||
if (status === 'CANCELLED' && order.status !== 'CANCELLED') {
|
||||
if (order.customerId && order.pointsUsed > 0) {
|
||||
await prisma.customer.update({
|
||||
where: { id: order.customerId },
|
||||
data: { points: { increment: order.pointsUsed } }
|
||||
});
|
||||
}
|
||||
sendOrderCancelledNotification(order.id).catch(e => console.error(e));
|
||||
}
|
||||
// If status becomes SHIPPED, notify
|
||||
if (status === 'SHIPPED' && order.status !== 'SHIPPED') {
|
||||
sendOrderShippedNotification(order.id).catch(e => console.error(e));
|
||||
}
|
||||
}
|
||||
return await prisma.order.findUnique({
|
||||
where: { id: order.id },
|
||||
include: { items: true, customer: true }
|
||||
});
|
||||
}
|
||||
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;
|
||||
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;
|
||||
const order = await prisma.order.update({
|
||||
where: { id: Number(id) },
|
||||
data: { trackingNo, status: "SHIPPED" },
|
||||
include: { items: true }
|
||||
});
|
||||
// Trigger notification asynchronously
|
||||
sendOrderShippedNotification(order.id).catch(e => {
|
||||
console.error("Failed to trigger sendOrderShippedNotification:", e);
|
||||
});
|
||||
return order;
|
||||
}
|
||||
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;
|
||||
const order = await prisma.order.findUnique({
|
||||
where: { id: Number(orderId) }
|
||||
});
|
||||
if (!order) {
|
||||
set.status = 404;
|
||||
return { error: "Order not found" };
|
||||
}
|
||||
const sanitizePhone = (p) => 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()
|
||||
})
|
||||
});
|
||||
//# sourceMappingURL=orders.js.map
|
||||
Reference in new issue
Block a user