import mongoose from "mongoose"; import sendEmail from "../../Utils/sendEmail.js"; import RetailDistributor from "../RetailDistributor/RetailDistributorModel.js"; import { InvoiceRd } from "./invoiceModalRD.js"; import { RdOrder } from "./rdOrderModal.js"; import { PDStock } from "../Stock/PdStockModel.js"; // Controller to create a new order by RD export const createOrderRD = async (req, res) => { try { const { paymentMode, shipTo, billTo, orderItems, subtotal, gstTotal, grandTotal, } = req.body; const rdId = req.user._id; // Fetch the Retail Distributor (RD) to find the associated Principal Distributor (PD) const rd = await RetailDistributor.findById(rdId).populate( "principal_distributer" ); if (!rd) { return res.status(404).json({ message: "Retail Distributor not found" }); } const pdId = rd.principal_distributer._id; // Get the associated PD // Create the order const newOrder = new RdOrder({ paymentMode, shipTo, billTo, orderItem: orderItems.map((item) => ({ productId: item._id, SKU: item.SKU, name: item.name, categoryName: item.category.categoryName, // Store category name brandName: item.brand.brandName, // Store brand name price: item.price, GST: item.GST, HSN_Code: item.HSN_Code, description: item.description, image: item.image, quantity: item.count, remainingQuantity: item.count, })), subtotal, gstTotal, grandTotal, addedBy: rdId, // The RD who placed the order (Retail Distributor) pd: pdId, // Reference to the PD associated with the RD }); await newOrder.save(); res .status(201) .json({ message: "Order placed successfully", order: newOrder }); } catch (error) { res.status(500).json({ message: "Server error", error }); } }; export const getPlacedOrdersForRD = async (req, res) => { try { const rdId = req.user?._id; // Assuming the Retail Distributor's ID is obtained from the authenticated request if (!rdId) { return res.status(401).json({ message: "Unauthorized access" }); } // Extract page and limit from query parameters, with default values const page = parseInt(req.query.page, 10) || 1; const limit = parseInt(req.query.limit, 10) || 5; // Calculate how many documents to skip for pagination const skip = (page - 1) * limit; // Fetch total count of orders for this RD (for pagination purposes) const totalOrders = await RdOrder.countDocuments({ addedBy: rdId }); // Fetch orders for the logged-in RD const placedOrders = await RdOrder.find({ addedBy: rdId }) .sort({ createdAt: -1 }) // Sort by creation date, newest first .skip(skip) // Skip documents for pagination .limit(limit); // Limit number of documents returned if (!placedOrders || placedOrders.length === 0) { return res .status(404) .json({ message: "No orders found for this Retail Distributor" }); } // Send the paginated order list and total count of orders res.status(200).json({ placedOrders, totalOrders }); } catch (error) { console.error(error); res.status(500).json({ message: "Server error", error }); } }; export const getSinglePlacedOrderForRD = async (req, res) => { try { const rdId = req.user?._id; if (!rdId) { return res.status(401).json({ message: "Unauthorized access" }); } // Assuming the Retail Distributor's ID is obtained from the authenticated request const orderId = req.params.id; // Assuming the order ID is passed in the URL as a parameter if (!rdId) { return res.status(401).json({ message: "Unauthorized access" }); } if (!orderId) { return res.status(400).json({ message: "Order ID is required" }); } // Fetch the specific order for the logged-in RD const order = await RdOrder.findOne({ _id: orderId, addedBy: rdId }); if (!order) { return res .status(404) .json({ message: "Order not found for this Retail Distributor" }); } // Send the single order document res.status(200).json({ singleOrder: order }); } catch (error) { console.error(error); res.status(500).json({ message: "Server error", error }); } }; export const getPlacedOrderById = async (req, res) => { try { const id = req.params.id; if (!mongoose.Types.ObjectId.isValid(id)) { return res .status(404) .json({ return_msg: "Not Valid id to search the doc " }); } const doc = await RdOrder.findById(id) .populate({ path: "orderItem.productId", }) .populate({ path: "invoices" }); if (doc) { return res .status(200) .json({ singleOrder: doc, return_msg: "Doc found" }); } return res.status(404).json({ return_msg: "Not Found doc " }); } catch (error) { console.log(error); return res.status(500).json({ return_msg: "Internal Server Error" }); } }; export const getPlacedOrdersForPD = async (req, res) => { try { const pdId = req.user?._id; if (!pdId) { return res.status(401).json({ return_message: "Unauthorized access " }); } // Extract page and limit from query parameters const page = parseInt(req.query.page, 10) || 1; const limit = parseInt(req.query.limit, 10) || 5; // Calculate the number of documents to skip const skip = (page - 1) * limit; const totalOrders = await RdOrder.countDocuments({ pd: pdId }); // Fetch all orders where the PD is associated with the order const plcaedOrders = await RdOrder.find({ pd: pdId }) .sort({ createdAt: -1 }) .skip(skip) .limit(limit); if (!plcaedOrders || plcaedOrders.length === 0) { return res .status(404) .json({ message: "No orders found for this Principal Distributor" }); } res.status(200).json({ plcaedOrders, totalOrders }); } catch (error) { console.error(error); res.status(500).json({ message: "Server error", error }); } }; export const getSinglePlacedOrderForPD = async (req, res) => { try { const pdId = req.user?._id; // Assuming the Principal Distributor's ID is obtained from the authenticated request const orderId = req.params.id; // Assuming the order ID is passed in the URL as a parameter if (!pdId) { return res.status(401).json({ message: "Unauthorized access" }); } if (!orderId) { return res.status(400).json({ message: "Order ID is required" }); } // Fetch the specific order for the logged-in PD const order = await RdOrder.findOne({ _id: orderId, pd: pdId }) .populate({ path: "addedBy" }) .populate({ path: "invoices" }); if (!order) { return res .status(404) .json({ message: "Order not found for this Principal Distributor" }); } // Send the single order document res.status(200).json({ singleOrder: order }); } catch (error) { console.error(error); res.status(500).json({ message: "Server error", error }); } }; export const getNewOrders = async (req, res) => { try { const pdId = req.user?._id; if (!pdId) { return res.status(401).json({ return_message: "Unauthorized access " }); } // Extract page and limit from query parameters const page = parseInt(req.query.page, 10) || 1; const limit = parseInt(req.query.limit, 10) || 5; // Calculate the number of documents to skip const skip = (page - 1) * limit; const totalOrders = await RdOrder.countDocuments({ pd: pdId, status: "new", }); // Fetch all orders where the PD is associated with the order const plcaedOrders = await RdOrder.find({ pd: pdId, status: "new" }) .sort({ createdAt: -1 }) .skip(skip) .limit(limit); if (!plcaedOrders || plcaedOrders.length === 0) { return res .status(404) .json({ message: "No orders found for this Principal Distributor" }); } res.status(200).json({ plcaedOrders, totalOrders }); } catch (error) { console.error(error); res.status(500).json({ message: "Server error", error }); } }; export const getPendignOrders = async (req, res) => { try { const pdId = req.user?._id; if (!pdId) { return res.status(401).json({ return_message: "Unauthorized access " }); } // Extract page and limit from query parameters const page = parseInt(req.query.page, 10) || 1; const limit = parseInt(req.query.limit, 10) || 5; // Calculate the number of documents to skip const skip = (page - 1) * limit; const totalOrders = await RdOrder.countDocuments({ pd: pdId, status: "new", }); // Fetch all orders where the PD is associated with the order const plcaedOrders = await RdOrder.find({ pd: pdId, status: "pending" }) .sort({ createdAt: -1 }) .skip(skip) .limit(limit); if (!plcaedOrders || plcaedOrders.length === 0) { return res .status(404) .json({ message: "No orders found for this Principal Distributor" }); } res.status(200).json({ plcaedOrders, totalOrders }); } catch (error) { console.error(error); res.status(500).json({ message: "Server error", error }); } }; export const getCancelledOrders = async (req, res) => { try { const pdId = req.user?._id; if (!pdId) { return res.status(401).json({ return_message: "Unauthorized access " }); } // Extract page and limit from query parameters const page = parseInt(req.query.page, 10) || 1; const limit = parseInt(req.query.limit, 10) || 5; // Calculate the number of documents to skip const skip = (page - 1) * limit; const totalOrders = await RdOrder.countDocuments({ pd: pdId, status: "new", }); // Fetch all orders where the PD is associated with the order const plcaedOrders = await RdOrder.find({ pd: pdId, status: "cancelled" }) .sort({ createdAt: -1 }) .skip(skip) .limit(limit); if (!plcaedOrders || plcaedOrders.length === 0) { return res .status(404) .json({ message: "No orders found for this Principal Distributor" }); } res.status(200).json({ plcaedOrders, totalOrders }); } catch (error) { console.error(error); res.status(500).json({ message: "Server error", error }); } }; export const processOrder = async (req, res) => { try { const { orderId, invoiceItems } = req.body; if (!orderId || !invoiceItems || !Array.isArray(invoiceItems)) { return res .status(400) .json({ error: "Missing required fields or invalid data" }); } // Find the order by ID const order = await RdOrder.findById(orderId).populate("addedBy"); if (!order) { return res.status(404).json({ error: "Order not found" }); } // Validate quantities const exceededItems = invoiceItems .filter((item) => { const orderItem = order.orderItem.find( (i) => i.productId.toString() === item.productId.toString() ); return orderItem && item.processquantity > orderItem.remainingQuantity; }) .map((item) => item.name); if (exceededItems.length > 0) { return res.status(400).json({ error: `The following items have more quantity than remaining in the order: ${exceededItems.join( ", " )}`, }); } // If there are any exceeded items, return an error with their names if (exceededItems.length > 0) { return res.status(400).json({ error: `The following items have more quantity than remaining in the order: ${exceededItems.join( ", " )}`, }); } // Continue with the rest of the logic if no quantity limits are exceeded // Stock calculation part const pdStock = await PDStock.findOne({ userId: order.pd }); if (!pdStock) { return res.status(404).json({ error: "Stock not available" }); } // Update stock and validate const updatedInvoiceItems = invoiceItems.filter((item) => { // Find the product in the stock const productInStock = pdStock.products.find( (p) => p.productid.toString() === item.productId.toString() ); // If the product exists in stock if (productInStock) { // Check if the processquantity is less than or equal to available stock if (item.processquantity <= productInStock.Stock) { // Deduct the quantity from the stock productInStock.Stock -= item.processquantity; // Add the item to updatedInvoiceItems (since stock is sufficient) return true; } } // If no stock or insufficient stock, don't add the item return false; }); // If no items are left after stock validation, return an error if (updatedInvoiceItems.length === 0) { return res .status(400) .json({ error: "No stock available for the requested items." }); } // Save updated stock await pdStock.save(); // Generate unique invoice number const existingInvoices = await InvoiceRd.find({ orderId }); const invoiceNumber = existingInvoices.length + 1; const invoiceId = `ch/${order.uniqueId}/${updatedInvoiceItems.length}/${invoiceNumber}`; // Calculate subtotal, gstTotal, and invoiceAmount for processed items let subtotal = 0; let gstTotal = 0; let invoiceAmount = 0; updatedInvoiceItems.forEach((item) => { const itemSubtotal = item.price * item.processquantity; const itemGST = ((item.price * item.GST) / 100) * item.processquantity; subtotal += itemSubtotal; gstTotal += itemGST; invoiceAmount += itemSubtotal + itemGST; }); // Create a new invoice const newInvoice = new InvoiceRd({ invoiceId, orderId, items: updatedInvoiceItems, subtotal, gstTotal, invoiceAmount, }); // Save the invoice const savedInvoice = await newInvoice.save(); // Update the order's order items with the remaining quantity let allItemsProcessed = true; // Flag to check if all items are processed order.orderItem.forEach((item) => { const invoicedItem = updatedInvoiceItems.find( (i) => i.productId.toString() === item.productId.toString() ); // If the item was invoiced, update the remaining quantity if (invoicedItem) { // Deduct the processed quantity from remaining quantity item.remainingQuantity -= invoicedItem.processquantity; // Ensure remaining quantity does not go negative if (item.remainingQuantity < 0) { item.remainingQuantity = 0; } } // Check if the remaining quantity is greater than 0, even for items not invoiced if (item.remainingQuantity > 0) { allItemsProcessed = false; } }); // Calculate total amount for pending items let pendingTotalAmount = 0; order.orderItem.forEach((item) => { if (item.remainingQuantity > 0) { const itemPendingSubtotal = item.price * item.remainingQuantity; const itemPendingGST = ((item.price * item.GST) / 100) * item.remainingQuantity; pendingTotalAmount += itemPendingSubtotal + itemPendingGST; } }); // Only update order status if all items have been fully processed if (allItemsProcessed) { order.status = "processing"; // All items are fully processed } else { order.status = "pending"; // There are still remaining quantities } // Add the invoice to the order order.invoices.push(savedInvoice._id); // Save the updated order await order.save(); // Prepare the email content const processedItems = updatedInvoiceItems .map( (item, index) => ` ${ index + 1 } ${ item.name } ${ item.image && item.image.length > 0 ? `${item.name}` : "No Image" } ${ item.processquantity } ₹${ item.price } ₹${ (item.GST * item.price) / 100 } ₹${ (item.price + (item.GST * item.price) / 100) * item.processquantity } ` ) .join(""); const pendingItems = order.orderItem .filter((item) => item.remainingQuantity > 0) .map( (item, index) => ` ${ index + 1 } ${ item.name } ${ item.image && item.image.length > 0 ? `${item.name}` : "No Image" } ${ item.remainingQuantity } ₹${ item.price } ₹${ (item.GST * item.price) / 100 } ₹${ (item.price + (item.GST * item.price) / 100) * item.remainingQuantity } ` ) .join(""); // Dynamic email subject and message based on the order status const emailSubject = allItemsProcessed ? `Your Order #${order.uniqueId} is in Processing!` : `Your Order #${order.uniqueId} is Partially Processed!`; const emailMessage = allItemsProcessed ? `

Exciting news! Your order #${order.uniqueId} has entered the processing phase. We're working hard to ensure everything is perfect for you.

Your invoice ID is: ${savedInvoice.invoiceId}

` : `

Good news! Some items of your order #${order.uniqueId} have been processed. The remaining items will be processed soon.

Your invoice ID is: ${savedInvoice.invoiceId}

`; await sendEmail({ to: order.addedBy.email, from: process.env.SEND_EMAIL_FROM, subject: emailSubject, html: `
${emailMessage} Hi ${ order.addedBy.name },

Order Status: ${ order.status.charAt(0).toUpperCase() + order.status.slice(1) }

Processed Items:

${processedItems}
S No. Product Name Image Quantity Price GST Amount SubTotal
Total Amount: ₹${savedInvoice.invoiceAmount.toFixed( 2 )}
${ pendingItems.length > 0 ? `

Pending Items:

${pendingItems}
S No. Product Name Image Quantity Price GST Amount SubTotal
Pending Amount: ₹${pendingTotalAmount.toFixed( 2 )}
` : "" }
If you have any concerns or further questions, please feel free to reply to this email.

Best regards,
Team Cheminova
`, }); res.status(200).json({ message: "Order processed and invoice created successfully", invoiceId: savedInvoice.invoiceId, }); } catch (error) { res .status(500) .json({ error: "An error occurred while processing the order" }); } }; // cancell order export const cancelOrderController = async (req, res) => { // const { cancellationReason, id: _id } = req.bodyconst const pdId = req.user?._id; const cancellationReason = req.body.cancellationReason; const _id = req.params.id; try { // Find the order by ID const order = await RdOrder.findOne({ _id, pd: pdId }) .populate("invoices") .populate("addedBy"); if (!order) { return res.status(404).json({ message: "Order not found" }); } // Update the order status to 'cancelled' order.status = "cancelled"; order.iscancelled = true; order.order_Cancelled_Reason = cancellationReason || "No specific reason provided."; await order.save(); if (order.invoices.length === 0) { // If no invoices are associated with the order await sendEmail({ to: `${order.addedBy.email}`, // Change to your recipient from: `${process.env.SEND_EMAIL_FROM}`, // Change to your verified sender subject: `Order #${order.uniqueId} Cancellation Confirmation`, html: ` Hi ${order.addedBy.name},

We regret to inform you that your order #${order.uniqueId} has been fully cancelled.

Cancellation Reason: ${order.order_Cancelled_Reason}

If you have any concerns or further questions, please feel free to reply to this email.

Best regards,
Team Cheminova `, }); } else { // If invoices are present const invoices = await InvoiceRd.find({ _id: { $in: order.invoices } }); // Collect all invoice data const invoiceDetails = invoices.map((invoice) => { let subtotal = 0; let gstTotal = 0; let totalAmount = 0; const processedItems = invoice.items .map((item) => { const itemSubtotal = item.price * item.processquantity; const itemGST = ((item.price * item.GST) / 100) * item.processquantity; subtotal += itemSubtotal; gstTotal += itemGST; totalAmount += itemSubtotal + itemGST; const itemImage = item.image && Array.isArray(item.image) ? item.image[0]?.url : ""; return ` ${ item.SKU } ${ item.name } ${
              item.name
            } ${ item.processquantity } ₹${ item.price } ₹${ (item.price * item.GST) / 100 } ₹${ itemSubtotal + itemGST } `; }) .join(""); return { invoiceId: invoice.invoiceId, processedItems, totalAmount, }; }); // Collect all delivered and remaining items let cancelItems = ""; let totalCancelAmount = 0; if (Array.isArray(order.orderItem)) { order.orderItem.forEach((item) => { if (item.remainingQuantity > 0) { const itemSubtotal = item.price * item.remainingQuantity; const itemGST = ((item.price * item.GST) / 100) * item.remainingQuantity; totalCancelAmount += itemSubtotal + itemGST; const itemImage = item.image && Array.isArray(item.image) ? item.image[0]?.url : ""; cancelItems += ` ${ item.SKU } ${ item.name } ${
              item.name
            } ${ item.remainingQuantity } ₹${ item.price } ₹${ (item.price * item.GST) / 100 } ₹${ itemSubtotal + itemGST } `; } }); } await sendEmail({ to: `${order.addedBy.email}`, // Change to your recipient from: `${process.env.SEND_EMAIL_FROM}`, // Change to your verified sender subject: `Order #${order.uniqueId} is Partially Cancelled!`, html: ` Hi ${ order.addedBy.name },

We would like to inform you that your order #${ order.uniqueId } has been partially cancelled.

Cancellation Reason: ${ order.order_Cancelled_Reason }

Delivered Items:

${invoiceDetails .map( (details) => `
Invoice ID: ${ details.invoiceId }
${details.processedItems}
S No. Product Name Image Quantity Price GST Amount SubTotal
Total Amount: ₹${details.totalAmount.toFixed( 2 )}
` ) .join("")}

Cancelled Items:

${cancelItems}
S No. Product Name Image Quantity Price GST Amount SubTotal
Total Refund Amount: ₹${totalCancelAmount.toFixed( 2 )}

If you have any concerns or further questions, please feel free to reply to this email.

Best regards,
Team Cheminova `, }); } res.status(200).json({ message: "Order cancelled successfully" }); } catch (error) { console.error("Error cancelling order:", error); res .status(500) .json({ message: "An error occurred while cancelling the order" }); } }; export const getAllOrdersByDistributor = async (req, res) => { const { distributorId } = req.params; const { orderId, status } = req.query; // Added status to query parameters // console.log(distributorId, orderId, status); // Handle pagination parameters const page = parseInt(req.query.page, 10) || 1; const limit = parseInt(req.query.limit, 10) || 5; const skip = (page - 1) * limit; // console.log(distributorId, orderId, status, page, limit, skip); try { let orders; // Search by orderId if (orderId) { const id = new RegExp(orderId, "i"); orders = await RdOrder.find({ addedBy: distributorId, uniqueId: { $regex: id }, }) .sort({ createdAt: -1 }) .skip(skip) .limit(limit); // console.log(orders); // Return empty array if no orders are found if (!orders || orders.length === 0) { return res.status(200).json([]); } // Get total count for pagination const totalOrders = await RdOrder.countDocuments({ addedBy: distributorId, uniqueId: { $regex: id }, }); // Send response with pagination info return res.status(200).json({ totalOrders, totalPages: Math.ceil(totalOrders / limit), currentPage: page, orders, }); } // Search by status else if (status) { // Create a regex for case-insensitive search const regex = new RegExp(status, "i"); orders = await RdOrder.find({ addedBy: distributorId, status: { $regex: regex }, }) .sort({ createdAt: -1 }) .skip(skip) .limit(limit); // console.log(orders); // Return empty array if no orders are found if (!orders || orders.length === 0) { return res.status(200).json([]); } // Get total count for pagination const totalOrders = await RdOrder.countDocuments({ addedBy: distributorId, status: { $regex: regex }, }); // console.log(totalOrders); // Send response with pagination info return res.status(200).json({ totalOrders, totalPages: Math.ceil(totalOrders / limit), currentPage: page, orders, }); } // Default behavior to return all orders else { orders = await RdOrder.find({ addedBy: distributorId }) .sort({ createdAt: -1 }) .skip(skip) .limit(limit); // Return empty array if no orders are found if (!orders || orders.length === 0) { return res.status(200).json([]); } // Get total count for pagination const totalOrders = await RdOrder.countDocuments({ addedBy: distributorId, }); // Send response with pagination info return res.status(200).json({ totalOrders, totalPages: Math.ceil(totalOrders / limit), currentPage: page, orders, }); } } catch (error) { console.error("Error fetching orders:", error); res.status(500).json({ message: "Server error", error }); } };