feat(pos): implement integrated full-screen POS point-of-sale system with inventory deduction and thermal receipt

This commit is contained in:
Your Name committed 2026-09-24 19:35:56 +07:00
1 parent b585277214
commit 5796e4d868
10 files changed
+1549 -23

No files matched your search

+51 -10
View File
@@ -48,13 +48,17 @@ export const ordersRoute = new Elysia({ prefix: "/orders" })
})
}
const isPos = data.channel === "POS";
const initialStatus = isPos ? "COMPLETED" : "PENDING_PAYMENT";
const order = await prisma.order.create({
data: {
channel: data.channel || "ONLINE",
customerId: data.customerId,
customerName: data.customerName,
customerEmail: data.customerEmail,
customerPhone: data.customerPhone,
shippingAddress: data.shippingAddress,
customerName: data.customerName || (isPos ? "ลูกค้าหน้าร้าน (Walk-in)" : null),
customerEmail: data.customerEmail || null,
customerPhone: data.customerPhone || null,
shippingAddress: data.shippingAddress || (isPos ? "หน้าร้าน (Takeaway)" : null),
subtotal: data.subtotal || 0,
promoLevel1Name: data.promoLevel1Name,
promoLevel1Disc: data.promoLevel1Disc || 0,
@@ -65,8 +69,13 @@ export const ordersRoute = new Elysia({ prefix: "/orders" })
pointsUsed: data.pointsUsed || 0,
pointsDiscount: data.pointsDiscount || 0,
pointsEarned: pointsEarned,
shippingFee: data.shippingFee,
shippingFee: data.shippingFee || 0,
totalAmount: data.totalAmount,
status: initialStatus,
paymentMethod: data.paymentMethod || (isPos ? "CASH" : null),
receivedAmount: data.receivedAmount,
changeAmount: data.changeAmount,
cashierId: data.cashierId,
items: {
create: data.items.map((item: any) => ({
productId: item.productId,
@@ -81,6 +90,33 @@ export const ordersRoute = new Elysia({ prefix: "/orders" })
},
include: { items: true }
});
// For POS orders: Deduct stock immediately and credit points
if (isPos) {
for (const item of data.items) {
if (item.variantId) {
try {
await prisma.productVariant.update({
where: { id: item.variantId },
data: { stock: { decrement: item.quantity } }
});
} catch (err) {
console.warn(`[POS] Failed to decrement stock for variant ${item.variantId}:`, err);
}
}
}
if (data.customerId && pointsEarned > 0) {
try {
await prisma.customer.update({
where: { id: data.customerId },
data: { points: { increment: pointsEarned } }
});
} catch (err) {
console.warn(`[POS] Failed to credit points for customer ${data.customerId}:`, err);
}
}
}
// If a coupon was used, increment its usedCount
if (data.couponCode) {
@@ -107,13 +143,18 @@ export const ordersRoute = new Elysia({ prefix: "/orders" })
}
}, {
body: t.Object({
channel: t.Optional(t.String()),
customerId: t.Optional(t.Nullable(t.Number())),
customerName: t.String(),
customerEmail: t.String(),
customerPhone: t.String(),
shippingAddress: t.String(),
customerName: t.Optional(t.Nullable(t.String())),
customerEmail: t.Optional(t.Nullable(t.String())),
customerPhone: t.Optional(t.Nullable(t.String())),
shippingAddress: t.Optional(t.Nullable(t.String())),
paymentMethod: t.Optional(t.Nullable(t.String())),
receivedAmount: t.Optional(t.Nullable(t.Number())),
changeAmount: t.Optional(t.Nullable(t.Number())),
cashierId: t.Optional(t.Nullable(t.Number())),
totalAmount: t.Number(),
shippingFee: t.Number(),
shippingFee: t.Optional(t.Number()),
subtotal: t.Optional(t.Number()),
promoLevel1Name: t.Optional(t.Nullable(t.String())),
promoLevel1Disc: t.Optional(t.Number()),