import { router, publicProcedure, protectedProcedure } from '@/src/trpc/trpc-index' import { z } from 'zod'; import dayjs from 'dayjs'; import { db } from '@/src/db/db_index' import { vendorSnippets, deliverySlotInfo, productInfo, orders, orderItems, users, orderStatus } from '@/src/db/schema' import { eq, and, inArray, isNotNull, gt, sql, asc, ne } from 'drizzle-orm'; import { appUrl } from '@/src/lib/env-exporter' const createSnippetSchema = z.object({ snippetCode: z.string().min(1, "Snippet code is required"), slotId: z.number().optional(), productIds: z.array(z.number().int().positive()).min(1, "At least one product is required"), validTill: z.string().optional(), isPermanent: z.boolean().default(false) }); const updateSnippetSchema = z.object({ id: z.number().int().positive(), updates: createSnippetSchema.partial().extend({ snippetCode: z.string().min(1).optional(), productIds: z.array(z.number().int().positive()).optional(), isPermanent: z.boolean().default(false) }), }); export const vendorSnippetsRouter = router({ create: protectedProcedure .input(createSnippetSchema) .mutation(async ({ input, ctx }) => { const { snippetCode, slotId, productIds, validTill, isPermanent } = input; // Get staff user ID from auth middleware const staffUserId = ctx.staffUser?.id; if (!staffUserId) { throw new Error("Unauthorized"); } // Validate slot exists if(slotId) { const slot = await db.query.deliverySlotInfo.findFirst({ where: eq(deliverySlotInfo.id, slotId), }); if (!slot) { throw new Error("Invalid slot ID"); } } // Validate products exist const products = await db.query.productInfo.findMany({ where: inArray(productInfo.id, productIds), }); if (products.length !== productIds.length) { throw new Error("One or more invalid product IDs"); } // Check if snippet code already exists const existingSnippet = await db.query.vendorSnippets.findFirst({ where: eq(vendorSnippets.snippetCode, snippetCode), }); if (existingSnippet) { throw new Error("Snippet code already exists"); } const result = await db.insert(vendorSnippets).values({ snippetCode, slotId, productIds, isPermanent, validTill: validTill ? new Date(validTill) : undefined, }).returning(); return result[0]; }), getAll: protectedProcedure .query(async () => { console.log('from the vendor snipptes methods') try { const result = await db.query.vendorSnippets.findMany({ with: { slot: true, }, orderBy: (vendorSnippets, { desc }) => [desc(vendorSnippets.createdAt)], }); const snippetsWithProducts = await Promise.all( result.map(async (snippet) => { const products = await db.query.productInfo.findMany({ where: inArray(productInfo.id, snippet.productIds), columns: { id: true, name: true }, }); return { ...snippet, accessUrl: `${appUrl}/vendor-order-list?id=${snippet.snippetCode}`, products: products.map(p => ({ id: p.id, name: p.name })), }; }) ); return snippetsWithProducts; } catch(e) { console.log(e) } return []; }), getById: protectedProcedure .input(z.object({ id: z.number().int().positive() })) .query(async ({ input }) => { const { id } = input; const result = await db.query.vendorSnippets.findFirst({ where: eq(vendorSnippets.id, id), with: { slot: true, }, }); if (!result) { throw new Error("Vendor snippet not found"); } return result; }), update: protectedProcedure .input(updateSnippetSchema) .mutation(async ({ input }) => { const { id, updates } = input; // Check if snippet exists const existingSnippet = await db.query.vendorSnippets.findFirst({ where: eq(vendorSnippets.id, id), }); if (!existingSnippet) { throw new Error("Vendor snippet not found"); } // Validate slot if being updated if (updates.slotId) { const slot = await db.query.deliverySlotInfo.findFirst({ where: eq(deliverySlotInfo.id, updates.slotId), }); if (!slot) { throw new Error("Invalid slot ID"); } } // Validate products if being updated if (updates.productIds) { const products = await db.query.productInfo.findMany({ where: inArray(productInfo.id, updates.productIds), }); if (products.length !== updates.productIds.length) { throw new Error("One or more invalid product IDs"); } } // Check snippet code uniqueness if being updated if (updates.snippetCode && updates.snippetCode !== existingSnippet.snippetCode) { const duplicateSnippet = await db.query.vendorSnippets.findFirst({ where: eq(vendorSnippets.snippetCode, updates.snippetCode), }); if (duplicateSnippet) { throw new Error("Snippet code already exists"); } } const updateData: any = { ...updates }; if (updates.validTill !== undefined) { updateData.validTill = updates.validTill ? new Date(updates.validTill) : null; } const result = await db.update(vendorSnippets) .set(updateData) .where(eq(vendorSnippets.id, id)) .returning(); if (result.length === 0) { throw new Error("Failed to update vendor snippet"); } return result[0]; }), delete: protectedProcedure .input(z.object({ id: z.number().int().positive() })) .mutation(async ({ input }) => { const { id } = input; const result = await db.delete(vendorSnippets) .where(eq(vendorSnippets.id, id)) .returning(); if (result.length === 0) { throw new Error("Vendor snippet not found"); } return { message: "Vendor snippet deleted successfully" }; }), getOrdersBySnippet: publicProcedure .input(z.object({ snippetCode: z.string().min(1, "Snippet code is required") })) .query(async ({ input }) => { const { snippetCode } = input; // Find the snippet const snippet = await db.query.vendorSnippets.findFirst({ where: eq(vendorSnippets.snippetCode, snippetCode), }); if (!snippet) { throw new Error("Vendor snippet not found"); } // Check if snippet is still valid if (snippet.validTill && new Date(snippet.validTill) < new Date()) { throw new Error("Vendor snippet has expired"); } // Query orders that match the snippet criteria const matchingOrders = await db.query.orders.findMany({ where: eq(orders.slotId, snippet.slotId!), with: { orderItems: { with: { product: { with: { unit: true, }, }, }, }, orderStatus: true, user: true, slot: true, }, orderBy: (orders, { desc }) => [desc(orders.createdAt)], }); // Filter orders that contain at least one of the snippet's products const filteredOrders = matchingOrders.filter(order => { const status = order.orderStatus; if (status[0].isCancelled) return false; const orderProductIds = order.orderItems.map(item => item.productId); return snippet.productIds.some(productId => orderProductIds.includes(productId)); }); // Format the response const formattedOrders = filteredOrders.map(order => { // Filter orderItems to only include products attached to the snippet const attachedOrderItems = order.orderItems.filter(item => snippet.productIds.includes(item.productId) ); const products = attachedOrderItems.map(item => ({ orderItemId: item.id, productId: item.productId, productName: item.product.name, quantity: parseFloat(item.quantity), productSize: item.product.productQuantity, price: parseFloat(item.price.toString()), unit: item.product.unit?.shortNotation || 'unit', subtotal: parseFloat(item.price.toString()) * parseFloat(item.quantity), is_packaged: item.is_packaged, is_package_verified: item.is_package_verified, })); const orderTotal = products.reduce((sum, p) => sum + p.subtotal, 0); return { orderId: `ORD${order.id}`, orderDate: order.createdAt.toISOString(), customerName: order.user.name, totalAmount: orderTotal, slotInfo: order.slot ? { time: order.slot.deliveryTime.toISOString(), sequence: order.slot.deliverySequence, } : null, products, matchedProducts: snippet.productIds, // All snippet products are considered matched snippetCode: snippet.snippetCode, }; }); return { success: true, data: formattedOrders, snippet: { id: snippet.id, snippetCode: snippet.snippetCode, slotId: snippet.slotId, productIds: snippet.productIds, validTill: snippet.validTill?.toISOString(), createdAt: snippet.createdAt.toISOString(), isPermanent: snippet.isPermanent, }, }; }), getVendorOrders: protectedProcedure .query(async () => { const vendorOrders = await db.query.orders.findMany({ with: { user: true, orderItems: { with: { product: { with: { unit: true, }, }, }, }, }, orderBy: (orders, { desc }) => [desc(orders.createdAt)], }); return vendorOrders.map(order => ({ id: order.id, status: 'pending', // Default status since orders table may not have status field orderDate: order.createdAt.toISOString(), totalQuantity: order.orderItems.reduce((sum, item) => sum + parseFloat(item.quantity || '0'), 0), products: order.orderItems.map(item => ({ name: item.product.name, quantity: parseFloat(item.quantity || '0'), unit: item.product.unit?.shortNotation || 'unit', })), })); }), getUpcomingSlots: publicProcedure .query(async () => { const threeHoursAgo = dayjs().subtract(3, 'hour').toDate(); const slots = await db.query.deliverySlotInfo.findMany({ where: and( eq(deliverySlotInfo.isActive, true), gt(deliverySlotInfo.deliveryTime, threeHoursAgo) ), orderBy: asc(deliverySlotInfo.deliveryTime), }); return { success: true, data: slots.map(slot => ({ id: slot.id, deliveryTime: slot.deliveryTime.toISOString(), freezeTime: slot.freezeTime.toISOString(), deliverySequence: slot.deliverySequence, })), }; }), getOrdersBySnippetAndSlot: publicProcedure .input(z.object({ snippetCode: z.string().min(1, "Snippet code is required"), slotId: z.number().int().positive("Valid slot ID is required"), })) .query(async ({ input }) => { const { snippetCode, slotId } = input; // Find the snippet const snippet = await db.query.vendorSnippets.findFirst({ where: eq(vendorSnippets.snippetCode, snippetCode), }); if (!snippet) { throw new Error("Vendor snippet not found"); } // Find the slot const slot = await db.query.deliverySlotInfo.findFirst({ where: eq(deliverySlotInfo.id, slotId), }); if (!slot) { throw new Error("Slot not found"); } // Query orders that match the slot and snippet criteria const matchingOrders = await db.query.orders.findMany({ where: eq(orders.slotId, slotId), with: { orderItems: { with: { product: { with: { unit: true, }, }, }, }, orderStatus: true, user: true, slot: true, }, orderBy: (orders, { desc }) => [desc(orders.createdAt)], }); // Filter orders that contain at least one of the snippet's products const filteredOrders = matchingOrders.filter(order => { const status = order.orderStatus; if (status[0]?.isCancelled) return false; const orderProductIds = order.orderItems.map(item => item.productId); return snippet.productIds.some(productId => orderProductIds.includes(productId)); }); // Format the response const formattedOrders = filteredOrders.map(order => { // Filter orderItems to only include products attached to the snippet const attachedOrderItems = order.orderItems.filter(item => snippet.productIds.includes(item.productId) ); const products = attachedOrderItems.map(item => ({ orderItemId: item.id, productId: item.productId, productName: item.product.name, quantity: parseFloat(item.quantity), price: parseFloat(item.price.toString()), unit: item.product.unit?.shortNotation || 'unit', subtotal: parseFloat(item.price.toString()) * parseFloat(item.quantity), productSize: item.product.productQuantity, is_packaged: item.is_packaged, is_package_verified: item.is_package_verified, })); const orderTotal = products.reduce((sum, p) => sum + p.subtotal, 0); return { orderId: `ORD${order.id}`, orderDate: order.createdAt.toISOString(), customerName: order.user.name, totalAmount: orderTotal, slotInfo: order.slot ? { time: order.slot.deliveryTime.toISOString(), sequence: order.slot.deliverySequence, } : null, products, matchedProducts: snippet.productIds, snippetCode: snippet.snippetCode, }; }); return { success: true, data: formattedOrders, snippet: { id: snippet.id, snippetCode: snippet.snippetCode, slotId: snippet.slotId, productIds: snippet.productIds, validTill: snippet.validTill?.toISOString(), createdAt: snippet.createdAt.toISOString(), isPermanent: snippet.isPermanent, }, selectedSlot: { id: slot.id, deliveryTime: slot.deliveryTime.toISOString(), freezeTime: slot.freezeTime.toISOString(), deliverySequence: slot.deliverySequence, }, }; }), updateOrderItemPackaging: publicProcedure .input(z.object({ orderItemId: z.number().int().positive("Valid order item ID required"), is_packaged: z.boolean() })) .mutation(async ({ input, ctx }) => { const { orderItemId, is_packaged } = input; // Get staff user ID from auth middleware // const staffUserId = ctx.staffUser?.id; // if (!staffUserId) { // throw new Error("Unauthorized"); // } // Check if order item exists and get related data const orderItem = await db.query.orderItems.findFirst({ where: eq(orderItems.id, orderItemId), with: { order: { with: { slot: true } } } }); if (!orderItem) { throw new Error("Order item not found"); } // Check if this order item belongs to a slot that has vendor snippets // This ensures only order items from vendor-accessible orders can be updated if (!orderItem.order.slotId) { throw new Error("Order item not associated with a vendor slot"); } const snippetExists = await db.query.vendorSnippets.findFirst({ where: eq(vendorSnippets.slotId, orderItem.order.slotId), }); if (!snippetExists) { throw new Error("No vendor snippet found for this order's slot"); } // Update the is_packaged field const result = await db.update(orderItems) .set({ is_packaged }) .where(eq(orderItems.id, orderItemId)) .returning(); if (result.length === 0) { throw new Error("Failed to update packaging status"); } return { success: true, orderItemId, is_packaged }; }), });