freshyo/apps/backend/src/lib/post-order-handler.ts
2026-08-02 10:03:21 +05:30

201 lines
6.4 KiB
TypeScript

import {
getOrdersByIdsWithFullData,
getOrderByIdWithFullData,
composeSkuName,
composeUnitNotation,
} from '@/src/dbService'
import { sendTelegramMessage } from '@/src/lib/telegram-service'
import { queueDataPusher } from '@/src/lib/queue-data-pusher'
import { ensureWorkerInit } from './worker-init';
import { getAppUrl } from '@/src/lib/env-exporter'
interface OrderIdMessage {
orderIds: number[];
}
interface CancellationMessage {
orderId: number;
cancelledBy: 'user' | 'admin';
reason: string;
cancelledAt: string;
}
const formatDateTime = (dateStr: string | null | undefined): string => {
if (!dateStr) return 'N/A';
return new Date(dateStr).toLocaleString('en-IN', {
dateStyle: 'medium',
timeStyle: 'short',
timeZone: 'Asia/Kolkata',
});
};
const buildTelegramLinks = (orderId: number, userId?: number | null): string => {
const baseUrl = getAppUrl() || 'https://ui.freshyo.in'
const orderUrl = `${baseUrl}/manage-orders/order-details/${orderId}`
const orderLink = `↪ <a href="${orderUrl}">Order</a>`
if (!userId) {
return orderLink
}
const userUrl = `${baseUrl}/user-management/${userId}`
const userLink = `↪ <a href="${userUrl}">User</a>`
return `${orderLink} | ${userLink}`
}
const formatOrderMessageWithFullData = (ordersData: any[]): string => {
console.log('formatting the msg')
let message = '🛒 <b>New Order Placed</b>\n\n';
ordersData.forEach((order, index) => {
message += `<b>Order ${order.id}</b>\n`;
message += '📦 <b>Items:</b>\n';
order.orderItems?.forEach((item: any) => {
const sku = item.sku
const features = sku?.features || []
const name = composeSkuName(sku?.product?.name || 'Unknown', features)
const unit = composeUnitNotation(features)
message += `${name} ${unit} x${item.quantity}\n`;
});
message += `\n💰 <b>Total:</b> ₹${order.totalAmount}\n`;
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`;
}
message += `\n${buildTelegramLinks(order.id, order.userId)}\n`
if (index < ordersData.length - 1) {
message += '\n---\n\n';
}
});
return message;
};
const formatCancellationMessage = (orderData: any, cancellationData: CancellationMessage): string => {
const message = `❌ <b>Order Cancelled</b>
<b>Order #${orderData.id}</b>
👤 <b>Name:</b> ${orderData.address?.name || 'N/A'}
📞 <b>Phone:</b> ${orderData.address?.phone || 'N/A'}
📦 <b>Items:</b>
${orderData.orderItems?.map((item: any) => `${item.sku?.product?.name || 'Unknown'} x${item.quantity}`).join('\n') || ' N/A'}
💰 <b>Total:</b> ₹${orderData.totalAmount}
💳 <b>Refund:</b> ${orderData.refundStatus === 'na' ? 'N/A (COD)' : orderData.refundStatus || 'Pending'}
❓ <b>Reason:</b> ${cancellationData.reason}
👤 <b>Cancelled by:</b> ${cancellationData.cancelledBy === 'admin' ? 'Admin' : 'User'}
⏰ <b>Time:</b> ${formatDateTime(cancellationData.cancelledAt)}
${buildTelegramLinks(orderData.id, orderData.userId)}
`;
return message;
};
export const handleOrderPlaced = async (orderIds: number[], rawMessage?: string): Promise<void> => {
try {
const ordersData = await getOrdersByIdsWithFullData(orderIds)
const telegramMessage = formatOrderMessageWithFullData(ordersData)
await sendTelegramMessage(telegramMessage)
} catch (error) {
console.error('Failed to process order message:', error)
const fallback = rawMessage ? `⚠️ Error parsing order: ${rawMessage}` : '⚠️ Error parsing order'
await sendTelegramMessage(fallback)
}
}
export const handleOrderCancelled = async (
cancellationData: CancellationMessage,
rawMessage?: string
): Promise<void> => {
try {
console.log('Order cancellation received, sending to Telegram...')
const orderData = await getOrderByIdWithFullData(cancellationData.orderId)
if (!orderData) {
console.error('Order not found for cancellation:', cancellationData.orderId)
await sendTelegramMessage(`⚠️ Order ${cancellationData.orderId} was cancelled but could not be found in database`)
return
}
const refundStatus = orderData.refunds?.[0]?.refundStatus || 'pending'
const telegramMessage = formatCancellationMessage({ ...orderData, refundStatus }, cancellationData)
await sendTelegramMessage(telegramMessage)
} catch (error) {
console.error('Failed to process cancellation message:', error)
const fallback = rawMessage ? `⚠️ Error processing cancellation: ${rawMessage}` : '⚠️ Error processing cancellation'
await sendTelegramMessage(fallback)
}
}
/**
* Start the post order handler
* Subscribes to the orders:placed channel and sends to Telegram
*/
export const publishOrder = async (orderDetails: OrderIdMessage): Promise<boolean> => {
console.log('publishing order')
try {
await queueDataPusher.pushOrderPlacedQueue({
name: 'order-placed',
orderIds: orderDetails.orderIds,
})
return true;
} catch (error) {
console.error('Failed to publish order:', error);
return false;
}
};
export const publishFormattedOrder = async (
createdOrders: any[],
ordersBySlot: Map<number | null, any[]>
): Promise<boolean> => {
try {
const orderIds = createdOrders.map(order => order.id);
return await publishOrder({ orderIds });
} catch (error) {
console.error('Failed to format and publish order:', error);
return false;
}
};
export const publishCancellation = async (
orderId: number,
cancelledBy: 'user' | 'admin',
reason: string
): Promise<boolean> => {
try {
const message: CancellationMessage = {
orderId,
cancelledBy,
reason,
cancelledAt: new Date().toISOString(),
};
await queueDataPusher.pushOrderCancelledQueue({
name: 'order-cancelled',
...message,
})
console.log('Cancellation published to queue:', orderId);
return true;
} catch (error) {
console.error('Failed to publish cancellation:', error);
return false;
}
};