enh
This commit is contained in:
parent
b812992419
commit
3d7e023965
8 changed files with 121 additions and 12 deletions
6
apps/admin-ui/.expo/types/router.d.ts
vendored
6
apps/admin-ui/.expo/types/router.d.ts
vendored
File diff suppressed because one or more lines are too long
|
|
@ -29,7 +29,6 @@ const MenuItemComponent: React.FC<MenuItemComponentProps> = ({ item, router }) =
|
|||
|
||||
return (
|
||||
<Pressable
|
||||
key={item.route}
|
||||
onPress={() => item.onPress ? item.onPress() : router.push(item.route as any)}
|
||||
style={({ pressed }) => [
|
||||
tw`flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm`,
|
||||
|
|
@ -226,7 +225,7 @@ export default function Dashboard() {
|
|||
<View style={tw`flex-row flex-wrap gap-3`}>
|
||||
{quickActions.map((item) => (
|
||||
<Pressable
|
||||
key={item.route}
|
||||
key={`quick-${item.route}`}
|
||||
onPress={() => item.onPress ? item.onPress() : router.push(item.route as any)}
|
||||
style={({ pressed }) => [
|
||||
tw`bg-white rounded-xl p-3 shadow-sm border border-gray-100 items-center`,
|
||||
|
|
@ -264,7 +263,7 @@ export default function Dashboard() {
|
|||
</View>
|
||||
<MyText style={tw`text-gray-700 font-bold text-base`}>{category.title}</MyText>
|
||||
</View>
|
||||
{categoryItems.map(item => <MenuItemComponent key={item.route} item={item} router={router} />)}
|
||||
{categoryItems.map(item => <MenuItemComponent key={`menu-${item.route}`} item={item} router={router} />)}
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Stack } from 'expo-router';
|
|||
export default function Layout() {
|
||||
return (
|
||||
<Stack>
|
||||
<Stack.Screen name="index" options={{ title: 'Delivery Sequences', headerShown: true }} />
|
||||
<Stack.Screen name="index" options={{ title: 'Delivery Sequences', headerShown: false }} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ import { Stack } from 'expo-router';
|
|||
export default function Layout() {
|
||||
return (
|
||||
<Stack>
|
||||
<Stack.Screen name="index" options={{ title: 'Orders', headerShown: true }} />
|
||||
<Stack.Screen name="index" options={{ title: 'Orders', headerShown: false }} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import './notif-job';
|
||||
import { initializeAllStores } from '../stores/store-initializer';
|
||||
import { startOrderHandler, publishOrder } from './post-order-handler';
|
||||
import { startOrderHandler, startCancellationHandler, publishOrder } from './post-order-handler';
|
||||
import { deleteOrders } from './delete-orders';
|
||||
|
||||
/**
|
||||
|
|
@ -9,6 +9,7 @@ import { deleteOrders } from './delete-orders';
|
|||
* - Role Manager (fetches and caches all roles)
|
||||
* - Const Store (syncs constants from DB to Redis)
|
||||
* - Post Order Handler (Redis Pub/Sub subscriber)
|
||||
* - Cancellation Handler (Redis Pub/Sub subscriber for order cancellations)
|
||||
* - Other services can be added here in the future
|
||||
*/
|
||||
export const initFunc = async (): Promise<void> => {
|
||||
|
|
@ -18,6 +19,7 @@ export const initFunc = async (): Promise<void> => {
|
|||
await Promise.all([
|
||||
initializeAllStores(),
|
||||
startOrderHandler(),
|
||||
startCancellationHandler(),
|
||||
]);
|
||||
|
||||
console.log('Application initialization completed successfully');
|
||||
|
|
|
|||
|
|
@ -1,15 +1,23 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { orders } from '../db/schema';
|
||||
import { orders, orderStatus } from '../db/schema';
|
||||
import redisClient from './redis-client';
|
||||
import { sendTelegramMessage } from './telegram-service';
|
||||
import { inArray } from 'drizzle-orm';
|
||||
import { inArray, eq } from 'drizzle-orm';
|
||||
|
||||
const ORDER_CHANNEL = 'orders:placed';
|
||||
const CANCELLED_CHANNEL = 'orders:cancelled';
|
||||
|
||||
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', {
|
||||
|
|
@ -55,6 +63,28 @@ const formatOrderMessageWithFullData = (ordersData: any[]): string => {
|
|||
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.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)}
|
||||
`;
|
||||
|
||||
return message;
|
||||
};
|
||||
|
||||
/**
|
||||
* Start the post order handler
|
||||
* Subscribes to the orders:placed channel and sends to Telegram
|
||||
|
|
@ -104,6 +134,56 @@ export const stopOrderHandler = async (): Promise<void> => {
|
|||
}
|
||||
};
|
||||
|
||||
export const startCancellationHandler = async (): Promise<void> => {
|
||||
try {
|
||||
console.log('Starting cancellation handler...');
|
||||
|
||||
await redisClient.subscribe(CANCELLED_CHANNEL, async (message: string) => {
|
||||
try {
|
||||
const cancellationData: CancellationMessage = JSON.parse(message);
|
||||
console.log('Order cancellation received, sending to Telegram...');
|
||||
|
||||
const orderData = await db.query.orders.findFirst({
|
||||
where: eq(orders.id, cancellationData.orderId),
|
||||
with: {
|
||||
address: true,
|
||||
orderItems: { with: { product: true } },
|
||||
refunds: true,
|
||||
},
|
||||
});
|
||||
|
||||
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);
|
||||
await sendTelegramMessage(`⚠️ Error processing cancellation: ${message}`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Cancellation handler started successfully');
|
||||
} catch (error) {
|
||||
console.error('Failed to start cancellation handler:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const stopCancellationHandler = async (): Promise<void> => {
|
||||
try {
|
||||
await redisClient.unsubscribe(CANCELLED_CHANNEL);
|
||||
console.log('Cancellation handler stopped');
|
||||
} catch (error) {
|
||||
console.error('Error stopping cancellation handler:', error);
|
||||
}
|
||||
};
|
||||
|
||||
export const publishOrder = async (orderDetails: OrderIdMessage): Promise<boolean> => {
|
||||
try {
|
||||
const message = JSON.stringify(orderDetails);
|
||||
|
|
@ -127,3 +207,24 @@ export const publishFormattedOrder = async (
|
|||
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 redisClient.publish(CANCELLED_CHANNEL, JSON.stringify(message));
|
||||
console.log('Cancellation published to Redis:', orderId);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Failed to publish cancellation:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import {
|
|||
sendOrderPackagedNotification,
|
||||
sendOrderDeliveredNotification,
|
||||
} from "../../lib/notif-job";
|
||||
import { publishCancellation } from "../../lib/post-order-handler";
|
||||
|
||||
const updateOrderNotesSchema = z.object({
|
||||
orderId: z.number(),
|
||||
|
|
@ -955,6 +956,9 @@ export const orderRouter = router({
|
|||
return { orderId: order.id, userId: order.userId };
|
||||
});
|
||||
|
||||
// Publish to Redis for Telegram notification
|
||||
await publishCancellation(result.orderId, 'admin', reason);
|
||||
|
||||
return { success: true, message: "Order cancelled successfully" };
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import {
|
|||
import { RazorpayPaymentService } from "../../lib/payments-utils";
|
||||
import { getNextDeliveryDate } from "../common-apis/common";
|
||||
import { CONST_KEYS, getConstant, getConstants } from "../../lib/const-store";
|
||||
import { publishFormattedOrder } from "../../lib/post-order-handler";
|
||||
import { publishFormattedOrder, publishCancellation } from "../../lib/post-order-handler";
|
||||
|
||||
|
||||
const validateAndGetCoupon = async (
|
||||
|
|
@ -785,6 +785,9 @@ export const orderRouter = router({
|
|||
result.orderId.toString()
|
||||
);
|
||||
|
||||
// Publish to Redis for Telegram notification
|
||||
await publishCancellation(result.orderId, 'user', reason);
|
||||
|
||||
return { success: true, message: "Order cancelled successfully" };
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue