feat(pos): implement integrated full-screen POS point-of-sale system with inventory deduction and thermal receipt
This commit is contained in:
1 parent
b585277214
commit
5796e4d868
10 files changed
+1549
-23
No files matched your search
@@ -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()),
|
||||
|
||||
Reference in new issue
Block a user