enh
This commit is contained in:
parent
09e1067b69
commit
3d44171f06
11 changed files with 210 additions and 249 deletions
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
# DATABASE_URL=postgresql://postgres:postgres_shafi_password@57.128.212.174:6432/meatfarmer #technocracy
|
||||
DATABASE_URL=postgres://postgres:meatfarmer_master_password@5.223.55.14:7447/meatfarmer #hetzner
|
||||
ENV_MODE=PROD
|
||||
DATABASE_URL=postgresql://postgres:meatfarmer_master_password@57.128.212.174:7447/meatfarmer #technocracy
|
||||
# DATABASE_URL=postgres://postgres:meatfarmer_master_password@5.223.55.14:7447/meatfarmer #hetzner
|
||||
PHONE_PE_BASE_URL=https://api-preprod.phonepe.com/
|
||||
PHONE_PE_CLIENT_ID=TEST-M23F2IGP34ZAR_25090
|
||||
PHONE_PE_CLIENT_VERSION=1
|
||||
|
|
@ -20,8 +21,8 @@ S3_URL=https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com
|
|||
S3_BUCKET_NAME=meatfarmer
|
||||
EXPO_ACCESS_TOKEN=Asvpy8cByRh6T4ksnWScO6PLcio2n35-BwES5zK-
|
||||
JWT_SECRET=my_meatfarmer_jwt_secret_key
|
||||
# REDIS_URL=redis://default:redis_shafi_password@57.128.212.174:6379
|
||||
REDIS_URL=redis://default:redis_shafi_password@5.223.55.14:6379
|
||||
# REDIS_URL=redis://default:redis_shafi_password@5.223.55.14:6379
|
||||
REDIS_URL=redis://default:redis_shafi_password@57.128.212.174:6379
|
||||
APP_URL=http://localhost:4000
|
||||
RAZORPAY_KEY=rzp_test_RdCBBUJ56NLaJK
|
||||
RAZORPAY_SECRET=namEwKBE1ypWxH0QDVg6fWOe
|
||||
|
|
@ -32,6 +33,6 @@ DELIVERY_CHARGE=20
|
|||
|
||||
# Telegram Configuration
|
||||
TELEGRAM_BOT_TOKEN=8410461852:AAGXQCwRPFbndqwTgLJh8kYxST4Z0vgh72U
|
||||
TELEGRAM_CHAT_IDS=
|
||||
TELEGRAM_CHAT_IDS=5147760058
|
||||
# TELEGRAM_BOT_TOKEN=8410461852:AAGXQCwRPFbndqwTgLJh8kYxST4Z0vgh72U
|
||||
# TELEGRAM_CHAT_IDS=-5075171894
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -12,7 +12,7 @@ interface PendingPaymentRecord {
|
|||
|
||||
export const createPaymentNotification = (record: PendingPaymentRecord) => {
|
||||
// Construct message from record data
|
||||
const message = `Payment pending for order ORD${record.order.readableId.toString().padStart(3, '0')}. Please complete before orders close time.`;
|
||||
const message = `Payment pending for order ORD${record.order.id}. Please complete before orders close time.`;
|
||||
|
||||
// TODO: Implement notification sending logic using record.order.userId, record.order.id, message
|
||||
console.log(`Sending notification to user ${record.order.userId} for order ${record.order.id}: ${message}`);
|
||||
|
|
|
|||
|
|
@ -45,3 +45,5 @@ export const deliveryCharge = Number(process.env.DELIVERY_CHARGE as string);
|
|||
export const telegramBotToken = process.env.TELEGRAM_BOT_TOKEN as string;
|
||||
|
||||
export const telegramChatIds = (process.env.TELEGRAM_CHAT_IDS as string)?.split(',').map(id => id.trim()) || [];
|
||||
|
||||
export const isDevMode = (process.env.ENV_MODE as string) === 'dev';
|
||||
|
|
@ -1,47 +1,52 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { orders } from '../db/schema';
|
||||
import redisClient from './redis-client';
|
||||
import { sendTelegramMessage } from './telegram-service';
|
||||
import { inArray } from 'drizzle-orm';
|
||||
|
||||
const ORDER_CHANNEL = 'orders:placed';
|
||||
|
||||
interface SimplifiedOrderItem {
|
||||
productName: string;
|
||||
quantity: number;
|
||||
interface OrderIdMessage {
|
||||
orderIds: number[];
|
||||
}
|
||||
|
||||
interface SimplifiedOrder {
|
||||
deliveryTime: string;
|
||||
orderPlaceTime: string;
|
||||
totalAmount: number;
|
||||
orderItems: SimplifiedOrderItem[];
|
||||
}
|
||||
const formatDateTime = (dateStr: string | null | undefined): string => {
|
||||
if (!dateStr) return 'N/A';
|
||||
return new Date(dateStr).toLocaleString('en-IN', {
|
||||
dateStyle: 'medium',
|
||||
timeStyle: 'short',
|
||||
});
|
||||
};
|
||||
|
||||
interface FormattedOrderData {
|
||||
orders: SimplifiedOrder[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Format order details for Telegram message
|
||||
* @param orderData The order data to format
|
||||
* @returns Formatted message string
|
||||
*/
|
||||
const formatOrderMessage = (orderData: FormattedOrderData): string => {
|
||||
const formatOrderMessageWithFullData = (ordersData: any[]): string => {
|
||||
let message = '🛒 <b>New Order Placed</b>\n\n';
|
||||
|
||||
orderData.orders.forEach((order, index) => {
|
||||
if (orderData.orders.length > 1) {
|
||||
message += `<b>Order ${index + 1}</b>\n`;
|
||||
}
|
||||
ordersData.forEach((order, index) => {
|
||||
message += `<b>Order ${order.id}</b>\n`;
|
||||
|
||||
message += '📦 <b>Items:</b>\n';
|
||||
order.orderItems.forEach(item => {
|
||||
message += ` • ${item.productName} x${item.quantity}\n`;
|
||||
order.orderItems?.forEach((item: any) => {
|
||||
message += ` • ${item.product?.name || 'Unknown'} x${item.quantity}\n`;
|
||||
});
|
||||
|
||||
message += `\n💰 <b>Total:</b> ₹${order.totalAmount}\n`;
|
||||
message += `🚚 <b>Delivery:</b> ${new Date(order.deliveryTime).toLocaleString()}\n`;
|
||||
message += `⏰ <b>Ordered:</b> ${new Date(order.orderPlaceTime).toLocaleString()}\n`;
|
||||
|
||||
if (index < orderData.orders.length - 1) {
|
||||
message += `🚚 <b>Delivery:</b> ${
|
||||
order.isFlashDelivery ? 'Flash Delivery' : formatDateTime(order.slot?.deliveryTime)
|
||||
}\n`;
|
||||
|
||||
message += `\n📍 <b>Address:</b>\n`;
|
||||
message += ` ${order.address?.name || 'N/A'}\n`;
|
||||
message += ` ${order.address?.addressLine1 || ''}\n`;
|
||||
if (order.address?.addressLine2) {
|
||||
message += ` ${order.address.addressLine2}\n`;
|
||||
}
|
||||
message += ` ${order.address?.city || ''}, ${order.address?.state || ''} - ${order.address?.pincode || ''}\n`;
|
||||
if (order.address?.phone) {
|
||||
message += ` 📞 ${order.address.phone}\n`;
|
||||
}
|
||||
|
||||
if (index < ordersData.length - 1) {
|
||||
message += '\n---\n\n';
|
||||
}
|
||||
});
|
||||
|
|
@ -59,14 +64,22 @@ export const startOrderHandler = async (): Promise<void> => {
|
|||
|
||||
await redisClient.subscribe(ORDER_CHANNEL, async (message: string) => {
|
||||
try {
|
||||
const orderDetails = JSON.parse(message);
|
||||
const { orderIds }: OrderIdMessage = JSON.parse(message);
|
||||
console.log('New order received, sending to Telegram...');
|
||||
|
||||
const telegramMessage = formatOrderMessage(orderDetails);
|
||||
const ordersData = await db.query.orders.findMany({
|
||||
where: inArray(orders.id, orderIds),
|
||||
with: {
|
||||
address: true,
|
||||
orderItems: { with: { product: true } },
|
||||
slot: true,
|
||||
},
|
||||
});
|
||||
|
||||
const telegramMessage = formatOrderMessageWithFullData(ordersData);
|
||||
await sendTelegramMessage(telegramMessage);
|
||||
} catch (error) {
|
||||
console.error('Failed to process order message:', error);
|
||||
// Still try to send raw message to Telegram if parsing fails
|
||||
await sendTelegramMessage(`⚠️ Error parsing order: ${message}`);
|
||||
}
|
||||
});
|
||||
|
|
@ -90,11 +103,7 @@ export const stopOrderHandler = async (): Promise<void> => {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Publish order details to the queue
|
||||
* @param orderDetails Full order details to publish
|
||||
*/
|
||||
export const publishOrder = async (orderDetails: FormattedOrderData): Promise<boolean> => {
|
||||
export const publishOrder = async (orderDetails: OrderIdMessage): Promise<boolean> => {
|
||||
try {
|
||||
const message = JSON.stringify(orderDetails);
|
||||
await redisClient.publish(ORDER_CHANNEL, message);
|
||||
|
|
@ -105,40 +114,13 @@ export const publishOrder = async (orderDetails: FormattedOrderData): Promise<bo
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mould raw order data into simplified format and publish
|
||||
* @param createdOrders Array of created orders from the database
|
||||
* @param ordersBySlot Map of slotId to items with product info
|
||||
* @returns Promise<boolean> indicating success or failure
|
||||
*/
|
||||
export const publishFormattedOrder = async (
|
||||
createdOrders: any[],
|
||||
ordersBySlot: Map<number | null, any[]>
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const simplifiedOrders: SimplifiedOrder[] = createdOrders.map(order => {
|
||||
// Get items for this order from ordersBySlot
|
||||
const slotItems = ordersBySlot.get(order.slotId) || [];
|
||||
|
||||
// Map items to simplified format
|
||||
const orderItems: SimplifiedOrderItem[] = slotItems.map(item => ({
|
||||
productName: item.product?.name || 'Unknown Product',
|
||||
quantity: item.quantity,
|
||||
}));
|
||||
|
||||
return {
|
||||
deliveryTime: order.slot?.deliveryTime?.toISOString() || order.deliveryDate?.toISOString() || new Date().toISOString(),
|
||||
orderPlaceTime: order.createdAt?.toISOString() || new Date().toISOString(),
|
||||
totalAmount: parseFloat(order.totalAmount?.toString() || '0'),
|
||||
orderItems,
|
||||
};
|
||||
});
|
||||
|
||||
const formattedData: FormattedOrderData = {
|
||||
orders: simplifiedOrders,
|
||||
};
|
||||
|
||||
return await publishOrder(formattedData);
|
||||
const orderIds = createdOrders.map(order => order.id);
|
||||
return await publishOrder({ orderIds });
|
||||
} catch (error) {
|
||||
console.error('Failed to format and publish order:', error);
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import axios from 'axios';
|
||||
import { telegramBotToken, telegramChatIds } from './env-exporter';
|
||||
import { isDevMode, telegramBotToken, telegramChatIds } from './env-exporter';
|
||||
|
||||
const BOT_TOKEN = telegramBotToken;
|
||||
const CHAT_IDS = telegramChatIds;
|
||||
|
|
@ -8,9 +8,12 @@ const TELEGRAM_API_URL = `https://api.telegram.org/bot${BOT_TOKEN}`;
|
|||
/**
|
||||
* Send a message to Telegram bot
|
||||
* @param message The message text to send
|
||||
* @returns Promise<boolean> indicating success or failure
|
||||
* @returns Promise<boolean | null> indicating success, failure, or null if dev mode
|
||||
*/
|
||||
export const sendTelegramMessage = async (message: string): Promise<boolean> => {
|
||||
export const sendTelegramMessage = async (message: string): Promise<boolean | null> => {
|
||||
if (isDevMode) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
CHAT_IDS.map(async (chatId) => {
|
||||
|
|
@ -35,6 +38,9 @@ export const sendTelegramMessage = async (message: string): Promise<boolean> =>
|
|||
})
|
||||
);
|
||||
|
||||
console.log('sent telegram message successfully')
|
||||
|
||||
|
||||
// Return true if at least one message was sent successfully
|
||||
return results.some((result) => result);
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export const cancelledOrdersRouter = router({
|
|||
const refund = status.order.refunds[0];
|
||||
return {
|
||||
id: status.order.id,
|
||||
readableId: status.order.readableId,
|
||||
readableId: status.order.id,
|
||||
customerName: `${status.order.user.name}`,
|
||||
address: `${status.order.address.addressLine1}, ${status.order.address.city}`,
|
||||
totalAmount: status.order.totalAmount,
|
||||
|
|
@ -134,7 +134,7 @@ export const cancelledOrdersRouter = router({
|
|||
// Format the response similar to the getAll method
|
||||
const formattedOrder = {
|
||||
id: order.id,
|
||||
readableId: order.readableId,
|
||||
readableId: order.id,
|
||||
customerName: order.user.name,
|
||||
address: `${order.address.addressLine1}${order.address.addressLine2 ? ', ' + order.address.addressLine2 : ''}, ${order.address.city}, ${order.address.state} ${order.address.pincode}`,
|
||||
totalAmount: order.totalAmount,
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ export const orderRouter = router({
|
|||
|
||||
return {
|
||||
id: orderData.id,
|
||||
readableId: orderData.readableId,
|
||||
readableId: orderData.id,
|
||||
customerName: `${orderData.user.name}`,
|
||||
customerEmail: orderData.user.email,
|
||||
customerMobile: orderData.user.mobile,
|
||||
|
|
@ -302,7 +302,7 @@ export const orderRouter = router({
|
|||
|
||||
return {
|
||||
id: orderData.id,
|
||||
readableId: orderData.readableId,
|
||||
readableId: orderData.id,
|
||||
customerName: `${orderData.user.name}`,
|
||||
customerEmail: orderData.user.email,
|
||||
customerMobile: orderData.user.mobile,
|
||||
|
|
@ -513,7 +513,7 @@ export const orderRouter = router({
|
|||
|
||||
return {
|
||||
id: order.id,
|
||||
readableId: order.readableId,
|
||||
readableId: order.id,
|
||||
customerName: order.user.name,
|
||||
address: `${order.address.addressLine1}${
|
||||
order.address.addressLine2 ? `, ${order.address.addressLine2}` : ""
|
||||
|
|
@ -607,7 +607,7 @@ export const orderRouter = router({
|
|||
|
||||
return {
|
||||
orderId: order.id.toString(),
|
||||
readableId: order.readableId,
|
||||
readableId: order.id,
|
||||
customerName: order.user.name,
|
||||
address: `${order.address.addressLine1}${
|
||||
order.address.addressLine2 ? `, ${order.address.addressLine2}` : ""
|
||||
|
|
@ -783,10 +783,10 @@ export const orderRouter = router({
|
|||
.sort((first, second) => first.id - second.id);
|
||||
dayjs.extend(utc);
|
||||
return {
|
||||
id: order.id,
|
||||
orderId: order.id.toString(),
|
||||
readableId: order.readableId,
|
||||
customerName: order.user.name,
|
||||
id: order.id,
|
||||
orderId: order.id.toString(),
|
||||
readableId: order.id,
|
||||
customerName: order.user.name,
|
||||
address: `${order.address.addressLine1}${
|
||||
order.address.addressLine2
|
||||
? `, ${order.address.addressLine2}`
|
||||
|
|
|
|||
|
|
@ -275,7 +275,7 @@ export const vendorSnippetsRouter = router({
|
|||
const orderTotal = products.reduce((sum, p) => sum + p.subtotal, 0);
|
||||
|
||||
return {
|
||||
orderId: `ORD${order.readableId.toString().padStart(3, '0')}`,
|
||||
orderId: `ORD${order.id}`,
|
||||
orderDate: order.createdAt.toISOString(),
|
||||
customerName: order.user.name,
|
||||
totalAmount: orderTotal,
|
||||
|
|
@ -434,7 +434,7 @@ export const vendorSnippetsRouter = router({
|
|||
const orderTotal = products.reduce((sum, p) => sum + p.subtotal, 0);
|
||||
|
||||
return {
|
||||
orderId: `ORD${order.readableId.toString().padStart(3, '0')}`,
|
||||
orderId: `ORD${order.id}`,
|
||||
orderDate: order.createdAt.toISOString(),
|
||||
customerName: order.user.name,
|
||||
totalAmount: orderTotal,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import {
|
|||
addresses,
|
||||
productInfo,
|
||||
paymentInfoTable,
|
||||
keyValStore,
|
||||
coupons,
|
||||
couponUsage,
|
||||
payments,
|
||||
|
|
@ -67,7 +66,6 @@ const validateAndGetCoupon = async (
|
|||
|
||||
const applyDiscountToOrder = (
|
||||
orderTotal: number,
|
||||
totalAmount: number,
|
||||
appliedCoupon: typeof coupons.$inferSelect | null,
|
||||
proportion: number
|
||||
) => {
|
||||
|
|
@ -136,14 +134,9 @@ const placeOrderUtil = async (params: {
|
|||
|
||||
const minOrderValue = constants[CONST_KEYS.minRegularOrderValue] ?? 0;
|
||||
const deliveryCharge = constants[CONST_KEYS.deliveryCharge] ?? 0;
|
||||
const flashFreeDeliveryThreshold = constants[CONST_KEYS.flashFreeDeliveryThreshold] ?? 0;
|
||||
const flashDeliveryCharge = constants[CONST_KEYS.flashDeliveryCharge] ?? 0;
|
||||
|
||||
const freeThreshold = params.isFlash ? flashFreeDeliveryThreshold : minOrderValue;
|
||||
const charge = params.isFlash ? flashDeliveryCharge : deliveryCharge;
|
||||
|
||||
const orderGroupId = `${Date.now()}-${userId}`;
|
||||
// Validate address belongs to user
|
||||
|
||||
const address = await db.query.addresses.findFirst({
|
||||
where: and(eq(addresses.userId, userId), eq(addresses.id, addressId)),
|
||||
});
|
||||
|
|
@ -151,7 +144,6 @@ const placeOrderUtil = async (params: {
|
|||
throw new ApiError("Invalid address", 400);
|
||||
}
|
||||
|
||||
// Group items by slotId and validate products
|
||||
const ordersBySlot = new Map<
|
||||
number | null,
|
||||
Array<{
|
||||
|
|
@ -176,9 +168,7 @@ const placeOrderUtil = async (params: {
|
|||
ordersBySlot.get(item.slotId)!.push({ ...item, product });
|
||||
}
|
||||
|
||||
// Validate flash delivery product availability if isFlash is true
|
||||
if (params.isFlash) {
|
||||
// Check if all products are flash-available (flashPrice is optional - fallback to regular price)
|
||||
for (const item of selectedItems) {
|
||||
const product = await db.query.productInfo.findFirst({
|
||||
where: eq(productInfo.id, item.productId),
|
||||
|
|
@ -186,50 +176,102 @@ const placeOrderUtil = async (params: {
|
|||
if (!product?.isFlashAvailable) {
|
||||
throw new ApiError(`Product ${item.productId} is not available for flash delivery`, 400);
|
||||
}
|
||||
// Note: flashPrice validation removed - frontend falls back to regular price if not set
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate total amount across all orders
|
||||
let totalAmount = 0;
|
||||
for (const [slotId, items] of ordersBySlot) {
|
||||
const orderTotal = items.reduce(
|
||||
(sum, item) => {
|
||||
const itemPrice = params.isFlash
|
||||
? parseFloat((item.product.flashPrice || item.product.price).toString())
|
||||
: parseFloat(item.product.price.toString());
|
||||
return sum + itemPrice * item.quantity;
|
||||
},
|
||||
0
|
||||
);
|
||||
for (const [slotId, items] of ordersBySlot) {
|
||||
const orderTotal = items.reduce(
|
||||
(sum, item) => {
|
||||
const itemPrice = params.isFlash
|
||||
? parseFloat((item.product.flashPrice || item.product.price).toString())
|
||||
: parseFloat(item.product.price.toString());
|
||||
return sum + itemPrice * item.quantity;
|
||||
},
|
||||
0
|
||||
);
|
||||
totalAmount += orderTotal;
|
||||
}
|
||||
|
||||
const appliedCoupon = await validateAndGetCoupon(couponId, userId, totalAmount);
|
||||
|
||||
// Calculate delivery charge
|
||||
const expectedDeliveryCharge =
|
||||
totalAmount < minOrderValue ? deliveryCharge : 0;
|
||||
|
||||
// Calculate total amount including delivery charge for first order
|
||||
const totalWithDelivery = totalAmount + expectedDeliveryCharge;
|
||||
|
||||
// Create orders in transaction
|
||||
const createdOrders = await db.transaction(async (tx) => {
|
||||
// Get and increment readable order ID counter
|
||||
let currentReadableId = 1;
|
||||
const existing = await tx.query.keyValStore.findFirst({
|
||||
where: eq(keyValStore.key, CONST_KEYS.readableOrderId),
|
||||
});
|
||||
if (existing?.value != null) {
|
||||
const storedValue = existing.value;
|
||||
const parsedValue = typeof storedValue === 'number'
|
||||
? storedValue
|
||||
: parseInt(String(storedValue), 10) || 0;
|
||||
currentReadableId = parsedValue + 1;
|
||||
}
|
||||
type OrderData = {
|
||||
order: Omit<typeof orders.$inferInsert, "id">;
|
||||
orderItems: Omit<typeof orderItems.$inferInsert, "id">[];
|
||||
orderStatus: Omit<typeof orderStatus.$inferInsert, "id">;
|
||||
};
|
||||
|
||||
// Create shared payment info for all orders
|
||||
const ordersData: OrderData[] = [];
|
||||
let isFirstOrder = true;
|
||||
|
||||
for (const [slotId, items] of ordersBySlot) {
|
||||
const orderTotal = items.reduce(
|
||||
(sum, item) => {
|
||||
const itemPrice = params.isFlash
|
||||
? parseFloat((item.product.flashPrice || item.product.price).toString())
|
||||
: parseFloat(item.product.price.toString());
|
||||
return sum + itemPrice * item.quantity;
|
||||
},
|
||||
0
|
||||
);
|
||||
|
||||
const orderGroupProportion = orderTotal / totalAmount;
|
||||
const orderTotalAmount = isFirstOrder ? totalWithDelivery : totalAmount;
|
||||
|
||||
const { finalOrderTotal: finalOrderAmount } = applyDiscountToOrder(
|
||||
orderTotalAmount,
|
||||
appliedCoupon,
|
||||
orderGroupProportion
|
||||
);
|
||||
|
||||
const order: Omit<typeof orders.$inferInsert, "id"> = {
|
||||
userId,
|
||||
addressId,
|
||||
slotId: params.isFlash ? null : slotId,
|
||||
isCod: paymentMethod === "cod",
|
||||
isOnlinePayment: paymentMethod === "online",
|
||||
paymentInfoId: null,
|
||||
totalAmount: finalOrderAmount.toString(),
|
||||
deliveryCharge: isFirstOrder ? expectedDeliveryCharge.toString() : "0",
|
||||
readableId: -1,
|
||||
userNotes: userNotes || null,
|
||||
orderGroupId,
|
||||
orderGroupProportion: orderGroupProportion.toString(),
|
||||
isFlashDelivery: params.isFlash,
|
||||
};
|
||||
|
||||
const orderItemsData: Omit<typeof orderItems.$inferInsert, "id">[] = items.map(
|
||||
(item) => ({
|
||||
orderId: 0,
|
||||
productId: item.productId,
|
||||
quantity: item.quantity.toString(),
|
||||
price: params.isFlash
|
||||
? item.product.flashPrice || item.product.price
|
||||
: item.product.price,
|
||||
discountedPrice: (
|
||||
params.isFlash
|
||||
? item.product.flashPrice || item.product.price
|
||||
: item.product.price
|
||||
).toString(),
|
||||
})
|
||||
);
|
||||
|
||||
const orderStatusData: Omit<typeof orderStatus.$inferInsert, "id"> = {
|
||||
userId,
|
||||
orderId: 0,
|
||||
paymentStatus: paymentMethod === "cod" ? "cod" : "pending",
|
||||
};
|
||||
|
||||
ordersData.push({ order, orderItems: orderItemsData, orderStatus: orderStatusData });
|
||||
isFirstOrder = false;
|
||||
}
|
||||
|
||||
const createdOrders = await db.transaction(async (tx) => {
|
||||
let sharedPaymentInfoId: number | null = null;
|
||||
if (paymentMethod === "online") {
|
||||
const [paymentInfo] = await tx
|
||||
|
|
@ -243,97 +285,32 @@ const placeOrderUtil = async (params: {
|
|||
sharedPaymentInfoId = paymentInfo.id;
|
||||
}
|
||||
|
||||
const createdOrders: any[] = [];
|
||||
let isFirstOrder = true;
|
||||
|
||||
// Create separate order for each slot group
|
||||
for (const [slotId, items] of ordersBySlot) {
|
||||
// Calculate order-specific total
|
||||
const orderTotal = items.reduce(
|
||||
(sum, item) => {
|
||||
const itemPrice = params.isFlash
|
||||
? parseFloat((item.product.flashPrice || item.product.price).toString())
|
||||
: parseFloat(item.product.price.toString());
|
||||
return sum + itemPrice * item.quantity;
|
||||
},
|
||||
0
|
||||
);
|
||||
|
||||
const orderGroupProportion = orderTotal / totalAmount;
|
||||
|
||||
const orderTotalAmount = isFirstOrder ? totalWithDelivery : totalAmount;
|
||||
|
||||
|
||||
|
||||
// const { finalOrderTotal } = applyDiscountToOrder(
|
||||
// orderTotal,
|
||||
// totalAmount,
|
||||
// appliedCoupon,
|
||||
// orderGroupProportion
|
||||
// );
|
||||
const { finalOrderTotal: finalOrderAmount } = applyDiscountToOrder(
|
||||
orderTotal,
|
||||
orderTotalAmount,
|
||||
appliedCoupon,
|
||||
orderGroupProportion
|
||||
);
|
||||
|
||||
console.log({orderTotalAmount, finalOrderAmount})
|
||||
// Create order record
|
||||
const [order] = await tx
|
||||
.insert(orders)
|
||||
.values({
|
||||
userId,
|
||||
addressId,
|
||||
slotId: params.isFlash ? null : slotId, // No slot assignment for flash delivery
|
||||
isCod: paymentMethod === "cod",
|
||||
isOnlinePayment: paymentMethod === "online",
|
||||
paymentInfoId: sharedPaymentInfoId,
|
||||
totalAmount: finalOrderAmount.toString(),
|
||||
deliveryCharge: isFirstOrder ? expectedDeliveryCharge.toString() : '0',
|
||||
readableId: currentReadableId++,
|
||||
userNotes: userNotes || null,
|
||||
orderGroupId,
|
||||
orderGroupProportion: orderGroupProportion.toString(),
|
||||
isFlashDelivery: params.isFlash,
|
||||
})
|
||||
.returning();
|
||||
|
||||
// Create order items
|
||||
const orderItemsData = items.map((item) => ({
|
||||
orderId: order.id as number,
|
||||
productId: item.productId,
|
||||
quantity: item.quantity.toString(),
|
||||
price: params.isFlash ? (item.product.flashPrice || item.product.price) : item.product.price,
|
||||
discountedPrice: (params.isFlash ? (item.product.flashPrice || item.product.price) : item.product.price).toString(),
|
||||
}));
|
||||
|
||||
await tx.insert(orderItems).values(orderItemsData);
|
||||
|
||||
// Create order status
|
||||
await tx.insert(orderStatus).values({
|
||||
userId,
|
||||
orderId: order.id as number,
|
||||
paymentStatus: paymentMethod === "cod" ? "cod" : "pending",
|
||||
});
|
||||
|
||||
createdOrders.push(order);
|
||||
isFirstOrder = false;
|
||||
}
|
||||
|
||||
// Update readable ID counter
|
||||
await tx
|
||||
.insert(keyValStore)
|
||||
.values({
|
||||
key: CONST_KEYS.readableOrderId,
|
||||
value: currentReadableId,
|
||||
const ordersToInsert: Omit<typeof orders.$inferInsert, "id">[] = ordersData.map(
|
||||
(od) => ({
|
||||
...od.order,
|
||||
paymentInfoId: sharedPaymentInfoId,
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: keyValStore.key,
|
||||
set: { value: currentReadableId },
|
||||
});
|
||||
);
|
||||
|
||||
const insertedOrders = await tx.insert(orders).values(ordersToInsert).returning();
|
||||
|
||||
const allOrderItems: Omit<typeof orderItems.$inferInsert, "id">[] = [];
|
||||
const allOrderStatuses: Omit<typeof orderStatus.$inferInsert, "id">[] = [];
|
||||
|
||||
insertedOrders.forEach((order, index) => {
|
||||
const od = ordersData[index];
|
||||
od.orderItems.forEach((item) => {
|
||||
allOrderItems.push({ ...item, orderId: order.id as number });
|
||||
});
|
||||
allOrderStatuses.push({
|
||||
...od.orderStatus,
|
||||
orderId: order.id as number,
|
||||
});
|
||||
});
|
||||
|
||||
await tx.insert(orderItems).values(allOrderItems);
|
||||
await tx.insert(orderStatus).values(allOrderStatuses);
|
||||
|
||||
// Create Razorpay order for online payments
|
||||
if (paymentMethod === "online" && sharedPaymentInfoId) {
|
||||
const razorpayOrder = await RazorpayPaymentService.createOrder(
|
||||
sharedPaymentInfoId,
|
||||
|
|
@ -346,37 +323,33 @@ const placeOrderUtil = async (params: {
|
|||
);
|
||||
}
|
||||
|
||||
// Remove ordered items from cart
|
||||
await tx.delete(cartItems).where(
|
||||
and(
|
||||
eq(cartItems.userId, userId),
|
||||
inArray(
|
||||
cartItems.productId,
|
||||
selectedItems.map((item) => item.productId)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return createdOrders;
|
||||
return insertedOrders;
|
||||
});
|
||||
|
||||
// Record single coupon usage if applied (regardless of number of orders)
|
||||
await db.delete(cartItems).where(
|
||||
and(
|
||||
eq(cartItems.userId, userId),
|
||||
inArray(
|
||||
cartItems.productId,
|
||||
selectedItems.map((item) => item.productId)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
if (appliedCoupon && createdOrders.length > 0) {
|
||||
await db.insert(couponUsage).values({
|
||||
userId,
|
||||
couponId: appliedCoupon.id,
|
||||
orderId: createdOrders[0].id as number, // Use first order ID
|
||||
orderId: createdOrders[0].id as number,
|
||||
orderItemId: null,
|
||||
usedAt: new Date(),
|
||||
});
|
||||
}
|
||||
|
||||
// Send notifications for each order
|
||||
for (const order of createdOrders) {
|
||||
sendOrderPlacedNotification(userId, order.id.toString());
|
||||
}
|
||||
|
||||
// Publish order details to Redis Pub/Sub
|
||||
await publishFormattedOrder(createdOrders, ordersBySlot);
|
||||
|
||||
return { success: true, data: createdOrders };
|
||||
|
|
@ -540,7 +513,7 @@ export const orderRouter = router({
|
|||
|
||||
return {
|
||||
id: order.id,
|
||||
orderId: `ORD${order.readableId.toString().padStart(3, "0")}`,
|
||||
orderId: `ORD${order.id}`,
|
||||
orderDate: order.createdAt.toISOString(),
|
||||
deliveryStatus,
|
||||
deliveryDate: order.slot?.deliveryTime.toISOString(),
|
||||
|
|
@ -704,7 +677,7 @@ export const orderRouter = router({
|
|||
|
||||
return {
|
||||
id: order.id,
|
||||
orderId: `ORD${order.readableId.toString().padStart(3, "0")}`,
|
||||
orderId: `ORD${order.id}`,
|
||||
orderDate: order.createdAt.toISOString(),
|
||||
deliveryStatus,
|
||||
deliveryDate: order.slot?.deliveryTime.toISOString(),
|
||||
|
|
@ -739,11 +712,8 @@ export const orderRouter = router({
|
|||
const userId = ctx.user.userId;
|
||||
const { id, reason } = input;
|
||||
|
||||
const readableId = Number(id);
|
||||
|
||||
console.log({id, reason})
|
||||
|
||||
|
||||
// Check if order exists and belongs to user
|
||||
const order = await db.query.orders.findFirst({
|
||||
where: eq(orders.id, Number(id)),
|
||||
|
|
|
|||
|
|
@ -63,8 +63,8 @@ const isDevMode = Constants.executionEnvironment !== "standalone";
|
|||
// const BASE_API_URL = API_URL;
|
||||
// const BASE_API_URL = 'http://10.0.2.2:4000';
|
||||
// const BASE_API_URL = 'http://192.168.100.101:4000';
|
||||
// const BASE_API_URL = 'http://192.168.1.7:4000';
|
||||
let BASE_API_URL = "https://mf.freshyo.in";
|
||||
const BASE_API_URL = 'http://192.168.1.3:4000';
|
||||
// let BASE_API_URL = "https://mf.freshyo.in";
|
||||
// let BASE_API_URL = 'http://192.168.100.104:4000';
|
||||
// let BASE_API_URL = 'http://192.168.29.176:4000';
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue