diff --git a/middlewares/rdAuth.js b/middlewares/rdAuth.js index 5f168dd..5e28d46 100644 --- a/middlewares/rdAuth.js +++ b/middlewares/rdAuth.js @@ -12,7 +12,7 @@ export const isAuthenticatedRD = async (req, res, next) => { }); } const getToken = req.headers; - console.log(getToken); + // console.log(getToken); //remove Bearer from token const fronttoken = getToken.authorization.slice(7); @@ -24,7 +24,7 @@ export const isAuthenticatedRD = async (req, res, next) => { message: "incorrect token", }); } - console.log(frontdecoded); + const fuser = await RetailDistributor.findById(frontdecoded._id); // console.log(fuser); req.user = fuser; diff --git a/resources/RD_Ordes/invoiceModalRD.js b/resources/RD_Ordes/invoiceModalRD.js new file mode 100644 index 0000000..001a5c7 --- /dev/null +++ b/resources/RD_Ordes/invoiceModalRD.js @@ -0,0 +1,81 @@ +import mongoose, { Schema } from "mongoose"; + +const orderItemSchema = new Schema({ + productId: { + type: Schema.Types.ObjectId, + ref: "Product", + required: true, + }, + SKU: { + type: String, + required: true, + }, + name: { + type: String, + required: true, + }, + categoryName: { + type: String, // Directly store category name + required: true, + }, + brandName: { + type: String, // Directly store brand name + required: true, + }, + price: { + type: Number, + required: true, + }, + GST: { + type: Number, + required: true, + }, + HSN_Code: { + type: Number, + required: true, + }, + processquantity: { + //updated quantity + type: Number, + required: true, + default: 1, + }, +}); +const invoiceSchema = new mongoose.Schema({ + invoiceId: { type: String, required: true, unique: true }, + orderId: { + type: mongoose.Schema.Types.ObjectId, + ref: "RdOrder", + required: true, + }, + items: [orderItemSchema], + subtotal: { + type: Number, + required: true, + }, + gstTotal: { + type: Number, + required: true, + }, + invoiceAmount: { type: Number, required: true }, + courier_name: { type: String }, + courier_tracking_id: { type: String }, + courierStatus: { + type: String, + enum: ["processing", "dispatched", "delivered"], + default: "processing", + }, + courierstatus_timeline: { + processing: { type: Date }, + dispatched: { type: Date }, + delivered: { type: Date }, + }, +}); +// Middleware to set the processing date only when the invoice is created +invoiceSchema.pre("save", function (next) { + if (this.isNew && !this.courierstatus_timeline.processing) { + this.courierstatus_timeline.processing = new Date(); + } + next(); +}); +export const InvoiceRd = mongoose.model("InvoiceRd", invoiceSchema); diff --git a/resources/RD_Ordes/rdOrderController.js b/resources/RD_Ordes/rdOrderController.js index 68698e4..30b9bd7 100644 --- a/resources/RD_Ordes/rdOrderController.js +++ b/resources/RD_Ordes/rdOrderController.js @@ -1,4 +1,6 @@ +import sendEmail from "../../Utils/sendEmail.js"; import RetailDistributor from "../RetailDistributor/RetailDistributorModel.js"; +import { InvoiceRd } from "./invoiceModalRD.js"; import { RdOrder } from "./rdOrderModal.js"; // Controller to create a new order by RD @@ -45,6 +47,7 @@ export const createOrderRD = async (req, res) => { description: item.description, image: item.image, quantity: item.count, + remainingQuantity: item.count, })), subtotal, gstTotal, @@ -62,6 +65,7 @@ export const createOrderRD = async (req, res) => { 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 @@ -177,7 +181,9 @@ export const getSinglePlacedOrderForPD = async (req, res) => { } // Fetch the specific order for the logged-in PD - const order = await RdOrder.findOne({ _id: orderId, pd: pdId }); + const order = await RdOrder.findOne({ _id: orderId, pd: pdId }) + .populate({ path: "addedBy" }) + .populate({ path: "invoices" }); if (!order) { return res @@ -192,3 +198,637 @@ export const getSinglePlacedOrderForPD = async (req, res) => { 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 = []; + + // Check each item in invoiceItems for quantity limits + for (const item of invoiceItems) { + const orderItem = order.orderItem.find( + (i) => i.productId.toString() === item.productId.toString() + ); + + // If processquantity exceeds remainingQuantity, add the item name to exceededItems + if (orderItem && item.processquantity > orderItem.remainingQuantity) { + exceededItems.push(item.name); + } + } + + // 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 + + // Generate unique invoice number + const existingInvoices = await InvoiceRd.find({ orderId }); + const invoiceNumber = existingInvoices.length + 1; + const invoiceId = `ch/${order.uniqueId}/${invoiceItems.length}/${invoiceNumber}`; + + // Calculate subtotal, gstTotal, and invoiceAmount for processed items + let subtotal = 0; + let gstTotal = 0; + let invoiceAmount = 0; + + invoiceItems.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: invoiceItems, + 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 = invoiceItems.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 = invoiceItems + .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 NameImageQuantityPriceGST AmountSubTotal
Total Amount:₹${savedInvoice.invoiceAmount.toFixed( + 2 + )}
+ + ${ + pendingItems.length > 0 + ? ` +

Pending Items:

+ + + + + + + + + + + + + + ${pendingItems} + + + + + +
S No.Product NameImageQuantityPriceGST AmountSubTotal
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 NameImageQuantityPriceGST AmountSubTotal
Total Amount:₹${details.totalAmount.toFixed( + 2 + )}
+ ` + ) + .join("")} + +

Cancelled Items:

+ + + + + + + + + + + + + + ${cancelItems} + + + + + +
S No.Product NameImageQuantityPriceGST AmountSubTotal
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" }); + } +}; diff --git a/resources/RD_Ordes/rdOrderModal.js b/resources/RD_Ordes/rdOrderModal.js index dff1c48..21b08db 100644 --- a/resources/RD_Ordes/rdOrderModal.js +++ b/resources/RD_Ordes/rdOrderModal.js @@ -49,18 +49,10 @@ const orderItemSchema = new Schema({ required: true, default: 1, }, -}); - -const StatusHistorySchema = new mongoose.Schema({ - status: { - type: String, - enum: ["new", "dispatched", "cancelled", "processing", "delivered"], // Ensure this matches your status enum + remainingQuantity: { + type: Number, required: true, }, - timestamp: { - type: Date, - default: Date.now, - }, }); const rdOrderSchema = new Schema( @@ -93,9 +85,19 @@ const rdOrderSchema = new Schema( }, status: { type: String, - enum: ["new", "dispatched", "cancelled", "processing", "delivered"], + enum: [ + "new", + "pending", + "processing", + "dispatched", + "cancelled", + "delivered", + ], default: "new", }, + invoices: [ + { type: mongoose.Schema.Types.ObjectId, ref: "InvoiceRd" }, // Similar to PdOrder invoices + ], statusUpdatedAt: { type: Date, default: Date.now, @@ -115,15 +117,16 @@ const rdOrderSchema = new Schema( ref: "User", // Reference to the PD associated with the RD required: true, }, - status_timeline: { - new: { type: Date }, - paid: { type: Date }, - processing: { type: Date }, - dispatched: { type: Date }, - delivered: { type: Date }, - cancelled: { type: Date }, - returned: { type: Date }, - }, + // status_timeline: { + // new: { type: Date }, + // paid: { type: Date }, + // processing: { type: Date }, + // dispatched: { type: Date }, + // delivered: { type: Date }, + // cancelled: { type: Date }, + // returned: { type: Date }, + // }, + iscancelled: { type: Boolean, default: false, @@ -135,21 +138,21 @@ const rdOrderSchema = new Schema( courier_tracking_id: { type: String }, isDelivered: { type: Boolean, required: true, default: false }, DeliveredDate: { type: String, default: "" }, - statusHistory: [StatusHistorySchema], // Add this field to store the status history + // To store the status history }, { timestamps: true } ); -// Middleware to update the statusUpdatedAt field whenever status changes +// Middleware to generate uniqueId and update statusHistory rdOrderSchema.pre("save", function (next) { - if (this.isModified("status")) { - this.statusUpdatedAt = Date.now(); - // Add the new status and timestamp to statusHistory - this.statusHistory.push({ - status: this.status, - timestamp: this.statusUpdatedAt, - }); + if (this.isNew) { + const year = new Date().getFullYear().toString().slice(-2); // Get the last 2 digits of the year + const orderItemCount = this.orderItem.length; // Count the number of order items + const unique6CharId = nanoid(6); // Generate a 6-character unique ID + + this.uniqueId = `${year}-${orderItemCount}-${unique6CharId}`; } + next(); }); diff --git a/resources/RD_Ordes/rdOrderRoutes.js b/resources/RD_Ordes/rdOrderRoutes.js index 884de69..5432ea0 100644 --- a/resources/RD_Ordes/rdOrderRoutes.js +++ b/resources/RD_Ordes/rdOrderRoutes.js @@ -1,10 +1,15 @@ import express from "express"; import { + cancelOrderController, createOrderRD, + getCancelledOrders, + getNewOrders, + getPendignOrders, getPlacedOrdersForPD, getPlacedOrdersForRD, getSinglePlacedOrderForPD, getSinglePlacedOrderForRD, + processOrder, } from "./rdOrderController.js"; import { isAuthenticatedRD } from "../../middlewares/rdAuth.js"; import { isAuthenticatedUser } from "../../middlewares/auth.js"; @@ -22,7 +27,19 @@ router .get(isAuthenticatedUser, getPlacedOrdersForPD); router - .route("/pd-get-all-place-order/:id") + .route("/pd-get-single-place-order/:id") .get(isAuthenticatedUser, getSinglePlacedOrderForPD); +router.route("/pd-process-order").post(isAuthenticatedUser, processOrder); +router.route("/pd-get-new-orders").get(isAuthenticatedUser, getNewOrders); +router + .route("/pd-get-pending-orders") + .get(isAuthenticatedUser, getPendignOrders); +router + .route("/pd-get-cancelled-orders") + .get(isAuthenticatedUser, getCancelledOrders); +router + .route("/pd-cancel-order/:id") + .put(isAuthenticatedUser, cancelOrderController); + export default router;