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
|
ENV_MODE=PROD
|
||||||
DATABASE_URL=postgres://postgres:meatfarmer_master_password@5.223.55.14:7447/meatfarmer #hetzner
|
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_BASE_URL=https://api-preprod.phonepe.com/
|
||||||
PHONE_PE_CLIENT_ID=TEST-M23F2IGP34ZAR_25090
|
PHONE_PE_CLIENT_ID=TEST-M23F2IGP34ZAR_25090
|
||||||
PHONE_PE_CLIENT_VERSION=1
|
PHONE_PE_CLIENT_VERSION=1
|
||||||
|
|
@ -20,8 +21,8 @@ S3_URL=https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com
|
||||||
S3_BUCKET_NAME=meatfarmer
|
S3_BUCKET_NAME=meatfarmer
|
||||||
EXPO_ACCESS_TOKEN=Asvpy8cByRh6T4ksnWScO6PLcio2n35-BwES5zK-
|
EXPO_ACCESS_TOKEN=Asvpy8cByRh6T4ksnWScO6PLcio2n35-BwES5zK-
|
||||||
JWT_SECRET=my_meatfarmer_jwt_secret_key
|
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
|
APP_URL=http://localhost:4000
|
||||||
RAZORPAY_KEY=rzp_test_RdCBBUJ56NLaJK
|
RAZORPAY_KEY=rzp_test_RdCBBUJ56NLaJK
|
||||||
RAZORPAY_SECRET=namEwKBE1ypWxH0QDVg6fWOe
|
RAZORPAY_SECRET=namEwKBE1ypWxH0QDVg6fWOe
|
||||||
|
|
@ -32,6 +33,6 @@ DELIVERY_CHARGE=20
|
||||||
|
|
||||||
# Telegram Configuration
|
# Telegram Configuration
|
||||||
TELEGRAM_BOT_TOKEN=8410461852:AAGXQCwRPFbndqwTgLJh8kYxST4Z0vgh72U
|
TELEGRAM_BOT_TOKEN=8410461852:AAGXQCwRPFbndqwTgLJh8kYxST4Z0vgh72U
|
||||||
TELEGRAM_CHAT_IDS=
|
TELEGRAM_CHAT_IDS=5147760058
|
||||||
# TELEGRAM_BOT_TOKEN=8410461852:AAGXQCwRPFbndqwTgLJh8kYxST4Z0vgh72U
|
# TELEGRAM_BOT_TOKEN=8410461852:AAGXQCwRPFbndqwTgLJh8kYxST4Z0vgh72U
|
||||||
# TELEGRAM_CHAT_IDS=-5075171894
|
# 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) => {
|
export const createPaymentNotification = (record: PendingPaymentRecord) => {
|
||||||
// Construct message from record data
|
// 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
|
// 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}`);
|
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 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 telegramChatIds = (process.env.TELEGRAM_CHAT_IDS as string)?.split(',').map(id => id.trim()) || [];
|
||||||
|
|
||||||
|
export const isDevMode = (process.env.ENV_MODE as string) === 'dev';
|
||||||
|
|
@ -1,51 +1,56 @@
|
||||||
|
import { db } from '../db/db_index';
|
||||||
|
import { orders } from '../db/schema';
|
||||||
import redisClient from './redis-client';
|
import redisClient from './redis-client';
|
||||||
import { sendTelegramMessage } from './telegram-service';
|
import { sendTelegramMessage } from './telegram-service';
|
||||||
|
import { inArray } from 'drizzle-orm';
|
||||||
|
|
||||||
const ORDER_CHANNEL = 'orders:placed';
|
const ORDER_CHANNEL = 'orders:placed';
|
||||||
|
|
||||||
interface SimplifiedOrderItem {
|
interface OrderIdMessage {
|
||||||
productName: string;
|
orderIds: number[];
|
||||||
quantity: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SimplifiedOrder {
|
const formatDateTime = (dateStr: string | null | undefined): string => {
|
||||||
deliveryTime: string;
|
if (!dateStr) return 'N/A';
|
||||||
orderPlaceTime: string;
|
return new Date(dateStr).toLocaleString('en-IN', {
|
||||||
totalAmount: number;
|
dateStyle: 'medium',
|
||||||
orderItems: SimplifiedOrderItem[];
|
timeStyle: 'short',
|
||||||
}
|
});
|
||||||
|
};
|
||||||
|
|
||||||
interface FormattedOrderData {
|
const formatOrderMessageWithFullData = (ordersData: any[]): string => {
|
||||||
orders: SimplifiedOrder[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format order details for Telegram message
|
|
||||||
* @param orderData The order data to format
|
|
||||||
* @returns Formatted message string
|
|
||||||
*/
|
|
||||||
const formatOrderMessage = (orderData: FormattedOrderData): string => {
|
|
||||||
let message = '🛒 <b>New Order Placed</b>\n\n';
|
let message = '🛒 <b>New Order Placed</b>\n\n';
|
||||||
|
|
||||||
orderData.orders.forEach((order, index) => {
|
ordersData.forEach((order, index) => {
|
||||||
if (orderData.orders.length > 1) {
|
message += `<b>Order ${order.id}</b>\n`;
|
||||||
message += `<b>Order ${index + 1}</b>\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
message += '📦 <b>Items:</b>\n';
|
message += '📦 <b>Items:</b>\n';
|
||||||
order.orderItems.forEach(item => {
|
order.orderItems?.forEach((item: any) => {
|
||||||
message += ` • ${item.productName} x${item.quantity}\n`;
|
message += ` • ${item.product?.name || 'Unknown'} x${item.quantity}\n`;
|
||||||
});
|
});
|
||||||
|
|
||||||
message += `\n💰 <b>Total:</b> ₹${order.totalAmount}\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`;
|
message += `🚚 <b>Delivery:</b> ${
|
||||||
|
order.isFlashDelivery ? 'Flash Delivery' : formatDateTime(order.slot?.deliveryTime)
|
||||||
if (index < orderData.orders.length - 1) {
|
}\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';
|
message += '\n---\n\n';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -56,21 +61,29 @@ const formatOrderMessage = (orderData: FormattedOrderData): string => {
|
||||||
export const startOrderHandler = async (): Promise<void> => {
|
export const startOrderHandler = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
console.log('Starting post order handler...');
|
console.log('Starting post order handler...');
|
||||||
|
|
||||||
await redisClient.subscribe(ORDER_CHANNEL, async (message: string) => {
|
await redisClient.subscribe(ORDER_CHANNEL, async (message: string) => {
|
||||||
try {
|
try {
|
||||||
const orderDetails = JSON.parse(message);
|
const { orderIds }: OrderIdMessage = JSON.parse(message);
|
||||||
console.log('New order received, sending to Telegram...');
|
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);
|
await sendTelegramMessage(telegramMessage);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to process order message:', 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}`);
|
await sendTelegramMessage(`⚠️ Error parsing order: ${message}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Post order handler started successfully');
|
console.log('Post order handler started successfully');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to start post order handler:', error);
|
console.error('Failed to start post order handler:', error);
|
||||||
|
|
@ -90,11 +103,7 @@ export const stopOrderHandler = async (): Promise<void> => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export const publishOrder = async (orderDetails: OrderIdMessage): Promise<boolean> => {
|
||||||
* Publish order details to the queue
|
|
||||||
* @param orderDetails Full order details to publish
|
|
||||||
*/
|
|
||||||
export const publishOrder = async (orderDetails: FormattedOrderData): Promise<boolean> => {
|
|
||||||
try {
|
try {
|
||||||
const message = JSON.stringify(orderDetails);
|
const message = JSON.stringify(orderDetails);
|
||||||
await redisClient.publish(ORDER_CHANNEL, message);
|
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 (
|
export const publishFormattedOrder = async (
|
||||||
createdOrders: any[],
|
createdOrders: any[],
|
||||||
ordersBySlot: Map<number | null, any[]>
|
ordersBySlot: Map<number | null, any[]>
|
||||||
): Promise<boolean> => {
|
): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
const simplifiedOrders: SimplifiedOrder[] = createdOrders.map(order => {
|
const orderIds = createdOrders.map(order => order.id);
|
||||||
// Get items for this order from ordersBySlot
|
return await publishOrder({ orderIds });
|
||||||
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);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to format and publish order:', error);
|
console.error('Failed to format and publish order:', error);
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { telegramBotToken, telegramChatIds } from './env-exporter';
|
import { isDevMode, telegramBotToken, telegramChatIds } from './env-exporter';
|
||||||
|
|
||||||
const BOT_TOKEN = telegramBotToken;
|
const BOT_TOKEN = telegramBotToken;
|
||||||
const CHAT_IDS = telegramChatIds;
|
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
|
* Send a message to Telegram bot
|
||||||
* @param message The message text to send
|
* @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 {
|
try {
|
||||||
const results = await Promise.all(
|
const results = await Promise.all(
|
||||||
CHAT_IDS.map(async (chatId) => {
|
CHAT_IDS.map(async (chatId) => {
|
||||||
|
|
@ -33,7 +36,10 @@ export const sendTelegramMessage = async (message: string): Promise<boolean> =>
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log('sent telegram message successfully')
|
||||||
|
|
||||||
|
|
||||||
// Return true if at least one message was sent successfully
|
// Return true if at least one message was sent successfully
|
||||||
return results.some((result) => result);
|
return results.some((result) => result);
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ export const cancelledOrdersRouter = router({
|
||||||
const refund = status.order.refunds[0];
|
const refund = status.order.refunds[0];
|
||||||
return {
|
return {
|
||||||
id: status.order.id,
|
id: status.order.id,
|
||||||
readableId: status.order.readableId,
|
readableId: status.order.id,
|
||||||
customerName: `${status.order.user.name}`,
|
customerName: `${status.order.user.name}`,
|
||||||
address: `${status.order.address.addressLine1}, ${status.order.address.city}`,
|
address: `${status.order.address.addressLine1}, ${status.order.address.city}`,
|
||||||
totalAmount: status.order.totalAmount,
|
totalAmount: status.order.totalAmount,
|
||||||
|
|
@ -134,7 +134,7 @@ export const cancelledOrdersRouter = router({
|
||||||
// Format the response similar to the getAll method
|
// Format the response similar to the getAll method
|
||||||
const formattedOrder = {
|
const formattedOrder = {
|
||||||
id: order.id,
|
id: order.id,
|
||||||
readableId: order.readableId,
|
readableId: order.id,
|
||||||
customerName: order.user.name,
|
customerName: order.user.name,
|
||||||
address: `${order.address.addressLine1}${order.address.addressLine2 ? ', ' + order.address.addressLine2 : ''}, ${order.address.city}, ${order.address.state} ${order.address.pincode}`,
|
address: `${order.address.addressLine1}${order.address.addressLine2 ? ', ' + order.address.addressLine2 : ''}, ${order.address.city}, ${order.address.state} ${order.address.pincode}`,
|
||||||
totalAmount: order.totalAmount,
|
totalAmount: order.totalAmount,
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ export const orderRouter = router({
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: orderData.id,
|
id: orderData.id,
|
||||||
readableId: orderData.readableId,
|
readableId: orderData.id,
|
||||||
customerName: `${orderData.user.name}`,
|
customerName: `${orderData.user.name}`,
|
||||||
customerEmail: orderData.user.email,
|
customerEmail: orderData.user.email,
|
||||||
customerMobile: orderData.user.mobile,
|
customerMobile: orderData.user.mobile,
|
||||||
|
|
@ -302,7 +302,7 @@ export const orderRouter = router({
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: orderData.id,
|
id: orderData.id,
|
||||||
readableId: orderData.readableId,
|
readableId: orderData.id,
|
||||||
customerName: `${orderData.user.name}`,
|
customerName: `${orderData.user.name}`,
|
||||||
customerEmail: orderData.user.email,
|
customerEmail: orderData.user.email,
|
||||||
customerMobile: orderData.user.mobile,
|
customerMobile: orderData.user.mobile,
|
||||||
|
|
@ -513,7 +513,7 @@ export const orderRouter = router({
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: order.id,
|
id: order.id,
|
||||||
readableId: order.readableId,
|
readableId: order.id,
|
||||||
customerName: order.user.name,
|
customerName: order.user.name,
|
||||||
address: `${order.address.addressLine1}${
|
address: `${order.address.addressLine1}${
|
||||||
order.address.addressLine2 ? `, ${order.address.addressLine2}` : ""
|
order.address.addressLine2 ? `, ${order.address.addressLine2}` : ""
|
||||||
|
|
@ -607,7 +607,7 @@ export const orderRouter = router({
|
||||||
|
|
||||||
return {
|
return {
|
||||||
orderId: order.id.toString(),
|
orderId: order.id.toString(),
|
||||||
readableId: order.readableId,
|
readableId: order.id,
|
||||||
customerName: order.user.name,
|
customerName: order.user.name,
|
||||||
address: `${order.address.addressLine1}${
|
address: `${order.address.addressLine1}${
|
||||||
order.address.addressLine2 ? `, ${order.address.addressLine2}` : ""
|
order.address.addressLine2 ? `, ${order.address.addressLine2}` : ""
|
||||||
|
|
@ -783,10 +783,10 @@ export const orderRouter = router({
|
||||||
.sort((first, second) => first.id - second.id);
|
.sort((first, second) => first.id - second.id);
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
return {
|
return {
|
||||||
id: order.id,
|
id: order.id,
|
||||||
orderId: order.id.toString(),
|
orderId: order.id.toString(),
|
||||||
readableId: order.readableId,
|
readableId: order.id,
|
||||||
customerName: order.user.name,
|
customerName: order.user.name,
|
||||||
address: `${order.address.addressLine1}${
|
address: `${order.address.addressLine1}${
|
||||||
order.address.addressLine2
|
order.address.addressLine2
|
||||||
? `, ${order.address.addressLine2}`
|
? `, ${order.address.addressLine2}`
|
||||||
|
|
|
||||||
|
|
@ -275,7 +275,7 @@ export const vendorSnippetsRouter = router({
|
||||||
const orderTotal = products.reduce((sum, p) => sum + p.subtotal, 0);
|
const orderTotal = products.reduce((sum, p) => sum + p.subtotal, 0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
orderId: `ORD${order.readableId.toString().padStart(3, '0')}`,
|
orderId: `ORD${order.id}`,
|
||||||
orderDate: order.createdAt.toISOString(),
|
orderDate: order.createdAt.toISOString(),
|
||||||
customerName: order.user.name,
|
customerName: order.user.name,
|
||||||
totalAmount: orderTotal,
|
totalAmount: orderTotal,
|
||||||
|
|
@ -434,7 +434,7 @@ export const vendorSnippetsRouter = router({
|
||||||
const orderTotal = products.reduce((sum, p) => sum + p.subtotal, 0);
|
const orderTotal = products.reduce((sum, p) => sum + p.subtotal, 0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
orderId: `ORD${order.readableId.toString().padStart(3, '0')}`,
|
orderId: `ORD${order.id}`,
|
||||||
orderDate: order.createdAt.toISOString(),
|
orderDate: order.createdAt.toISOString(),
|
||||||
customerName: order.user.name,
|
customerName: order.user.name,
|
||||||
totalAmount: orderTotal,
|
totalAmount: orderTotal,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import {
|
||||||
addresses,
|
addresses,
|
||||||
productInfo,
|
productInfo,
|
||||||
paymentInfoTable,
|
paymentInfoTable,
|
||||||
keyValStore,
|
|
||||||
coupons,
|
coupons,
|
||||||
couponUsage,
|
couponUsage,
|
||||||
payments,
|
payments,
|
||||||
|
|
@ -67,7 +66,6 @@ const validateAndGetCoupon = async (
|
||||||
|
|
||||||
const applyDiscountToOrder = (
|
const applyDiscountToOrder = (
|
||||||
orderTotal: number,
|
orderTotal: number,
|
||||||
totalAmount: number,
|
|
||||||
appliedCoupon: typeof coupons.$inferSelect | null,
|
appliedCoupon: typeof coupons.$inferSelect | null,
|
||||||
proportion: number
|
proportion: number
|
||||||
) => {
|
) => {
|
||||||
|
|
@ -136,14 +134,9 @@ const placeOrderUtil = async (params: {
|
||||||
|
|
||||||
const minOrderValue = constants[CONST_KEYS.minRegularOrderValue] ?? 0;
|
const minOrderValue = constants[CONST_KEYS.minRegularOrderValue] ?? 0;
|
||||||
const deliveryCharge = constants[CONST_KEYS.deliveryCharge] ?? 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}`;
|
const orderGroupId = `${Date.now()}-${userId}`;
|
||||||
// Validate address belongs to user
|
|
||||||
const address = await db.query.addresses.findFirst({
|
const address = await db.query.addresses.findFirst({
|
||||||
where: and(eq(addresses.userId, userId), eq(addresses.id, addressId)),
|
where: and(eq(addresses.userId, userId), eq(addresses.id, addressId)),
|
||||||
});
|
});
|
||||||
|
|
@ -151,7 +144,6 @@ const placeOrderUtil = async (params: {
|
||||||
throw new ApiError("Invalid address", 400);
|
throw new ApiError("Invalid address", 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Group items by slotId and validate products
|
|
||||||
const ordersBySlot = new Map<
|
const ordersBySlot = new Map<
|
||||||
number | null,
|
number | null,
|
||||||
Array<{
|
Array<{
|
||||||
|
|
@ -176,9 +168,7 @@ const placeOrderUtil = async (params: {
|
||||||
ordersBySlot.get(item.slotId)!.push({ ...item, product });
|
ordersBySlot.get(item.slotId)!.push({ ...item, product });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate flash delivery product availability if isFlash is true
|
|
||||||
if (params.isFlash) {
|
if (params.isFlash) {
|
||||||
// Check if all products are flash-available (flashPrice is optional - fallback to regular price)
|
|
||||||
for (const item of selectedItems) {
|
for (const item of selectedItems) {
|
||||||
const product = await db.query.productInfo.findFirst({
|
const product = await db.query.productInfo.findFirst({
|
||||||
where: eq(productInfo.id, item.productId),
|
where: eq(productInfo.id, item.productId),
|
||||||
|
|
@ -186,50 +176,102 @@ const placeOrderUtil = async (params: {
|
||||||
if (!product?.isFlashAvailable) {
|
if (!product?.isFlashAvailable) {
|
||||||
throw new ApiError(`Product ${item.productId} is not available for flash delivery`, 400);
|
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;
|
let totalAmount = 0;
|
||||||
for (const [slotId, items] of ordersBySlot) {
|
for (const [slotId, items] of ordersBySlot) {
|
||||||
const orderTotal = items.reduce(
|
const orderTotal = items.reduce(
|
||||||
(sum, item) => {
|
(sum, item) => {
|
||||||
const itemPrice = params.isFlash
|
const itemPrice = params.isFlash
|
||||||
? parseFloat((item.product.flashPrice || item.product.price).toString())
|
? parseFloat((item.product.flashPrice || item.product.price).toString())
|
||||||
: parseFloat(item.product.price.toString());
|
: parseFloat(item.product.price.toString());
|
||||||
return sum + itemPrice * item.quantity;
|
return sum + itemPrice * item.quantity;
|
||||||
},
|
},
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
totalAmount += orderTotal;
|
totalAmount += orderTotal;
|
||||||
}
|
}
|
||||||
|
|
||||||
const appliedCoupon = await validateAndGetCoupon(couponId, userId, totalAmount);
|
const appliedCoupon = await validateAndGetCoupon(couponId, userId, totalAmount);
|
||||||
|
|
||||||
// Calculate delivery charge
|
|
||||||
const expectedDeliveryCharge =
|
const expectedDeliveryCharge =
|
||||||
totalAmount < minOrderValue ? deliveryCharge : 0;
|
totalAmount < minOrderValue ? deliveryCharge : 0;
|
||||||
|
|
||||||
// Calculate total amount including delivery charge for first order
|
|
||||||
const totalWithDelivery = totalAmount + expectedDeliveryCharge;
|
const totalWithDelivery = totalAmount + expectedDeliveryCharge;
|
||||||
|
|
||||||
// Create orders in transaction
|
type OrderData = {
|
||||||
const createdOrders = await db.transaction(async (tx) => {
|
order: Omit<typeof orders.$inferInsert, "id">;
|
||||||
// Get and increment readable order ID counter
|
orderItems: Omit<typeof orderItems.$inferInsert, "id">[];
|
||||||
let currentReadableId = 1;
|
orderStatus: Omit<typeof orderStatus.$inferInsert, "id">;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
let sharedPaymentInfoId: number | null = null;
|
||||||
if (paymentMethod === "online") {
|
if (paymentMethod === "online") {
|
||||||
const [paymentInfo] = await tx
|
const [paymentInfo] = await tx
|
||||||
|
|
@ -243,97 +285,32 @@ const placeOrderUtil = async (params: {
|
||||||
sharedPaymentInfoId = paymentInfo.id;
|
sharedPaymentInfoId = paymentInfo.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const createdOrders: any[] = [];
|
const ordersToInsert: Omit<typeof orders.$inferInsert, "id">[] = ordersData.map(
|
||||||
let isFirstOrder = true;
|
(od) => ({
|
||||||
|
...od.order,
|
||||||
// Create separate order for each slot group
|
paymentInfoId: sharedPaymentInfoId,
|
||||||
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,
|
|
||||||
})
|
})
|
||||||
.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) {
|
if (paymentMethod === "online" && sharedPaymentInfoId) {
|
||||||
const razorpayOrder = await RazorpayPaymentService.createOrder(
|
const razorpayOrder = await RazorpayPaymentService.createOrder(
|
||||||
sharedPaymentInfoId,
|
sharedPaymentInfoId,
|
||||||
|
|
@ -346,37 +323,33 @@ const placeOrderUtil = async (params: {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove ordered items from cart
|
return insertedOrders;
|
||||||
await tx.delete(cartItems).where(
|
|
||||||
and(
|
|
||||||
eq(cartItems.userId, userId),
|
|
||||||
inArray(
|
|
||||||
cartItems.productId,
|
|
||||||
selectedItems.map((item) => item.productId)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return createdOrders;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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) {
|
if (appliedCoupon && createdOrders.length > 0) {
|
||||||
await db.insert(couponUsage).values({
|
await db.insert(couponUsage).values({
|
||||||
userId,
|
userId,
|
||||||
couponId: appliedCoupon.id,
|
couponId: appliedCoupon.id,
|
||||||
orderId: createdOrders[0].id as number, // Use first order ID
|
orderId: createdOrders[0].id as number,
|
||||||
orderItemId: null,
|
orderItemId: null,
|
||||||
usedAt: new Date(),
|
usedAt: new Date(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send notifications for each order
|
|
||||||
for (const order of createdOrders) {
|
for (const order of createdOrders) {
|
||||||
sendOrderPlacedNotification(userId, order.id.toString());
|
sendOrderPlacedNotification(userId, order.id.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Publish order details to Redis Pub/Sub
|
|
||||||
await publishFormattedOrder(createdOrders, ordersBySlot);
|
await publishFormattedOrder(createdOrders, ordersBySlot);
|
||||||
|
|
||||||
return { success: true, data: createdOrders };
|
return { success: true, data: createdOrders };
|
||||||
|
|
@ -540,7 +513,7 @@ export const orderRouter = router({
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: order.id,
|
id: order.id,
|
||||||
orderId: `ORD${order.readableId.toString().padStart(3, "0")}`,
|
orderId: `ORD${order.id}`,
|
||||||
orderDate: order.createdAt.toISOString(),
|
orderDate: order.createdAt.toISOString(),
|
||||||
deliveryStatus,
|
deliveryStatus,
|
||||||
deliveryDate: order.slot?.deliveryTime.toISOString(),
|
deliveryDate: order.slot?.deliveryTime.toISOString(),
|
||||||
|
|
@ -704,7 +677,7 @@ export const orderRouter = router({
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: order.id,
|
id: order.id,
|
||||||
orderId: `ORD${order.readableId.toString().padStart(3, "0")}`,
|
orderId: `ORD${order.id}`,
|
||||||
orderDate: order.createdAt.toISOString(),
|
orderDate: order.createdAt.toISOString(),
|
||||||
deliveryStatus,
|
deliveryStatus,
|
||||||
deliveryDate: order.slot?.deliveryTime.toISOString(),
|
deliveryDate: order.slot?.deliveryTime.toISOString(),
|
||||||
|
|
@ -739,10 +712,7 @@ export const orderRouter = router({
|
||||||
const userId = ctx.user.userId;
|
const userId = ctx.user.userId;
|
||||||
const { id, reason } = input;
|
const { id, reason } = input;
|
||||||
|
|
||||||
const readableId = Number(id);
|
|
||||||
|
|
||||||
console.log({id, reason})
|
console.log({id, reason})
|
||||||
|
|
||||||
|
|
||||||
// Check if order exists and belongs to user
|
// Check if order exists and belongs to user
|
||||||
const order = await db.query.orders.findFirst({
|
const order = await db.query.orders.findFirst({
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,8 @@ const isDevMode = Constants.executionEnvironment !== "standalone";
|
||||||
// const BASE_API_URL = API_URL;
|
// const BASE_API_URL = API_URL;
|
||||||
// const BASE_API_URL = 'http://10.0.2.2:4000';
|
// 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.100.101:4000';
|
||||||
// const BASE_API_URL = 'http://192.168.1.7:4000';
|
const BASE_API_URL = 'http://192.168.1.3:4000';
|
||||||
let BASE_API_URL = "https://mf.freshyo.in";
|
// 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.100.104:4000';
|
||||||
// let BASE_API_URL = 'http://192.168.29.176:4000';
|
// let BASE_API_URL = 'http://192.168.29.176:4000';
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue