diff --git a/app.js b/app.js index 3b306e0..63a657f 100644 --- a/app.js +++ b/app.js @@ -132,6 +132,7 @@ app.use( //auth import user from "./resources/user/userRoute.js"; import ProductRouter from "./resources/Products/ProductRoute.js"; +import ProductManualRouter from "./resources/ProductMannual/ProductManualRoute.js"; //Businesses // import BusinessRoute from "./resources/Businesses/BusinessRoute.js"; @@ -199,6 +200,8 @@ app.use("/api/v1", user); //Product app.use("/api", ProductRouter); +//Product Manual +app.use("/api/productmanual", ProductManualRouter); //businesses // app.use("/api/businesses", BusinessRoute); // Design diff --git a/public/uploads/Add-PD.xlsx b/public/uploads/Add-PD.xlsx index b7a1ef1..21ce549 100644 Binary files a/public/uploads/Add-PD.xlsx and b/public/uploads/Add-PD.xlsx differ diff --git a/resources/ProductMannual/ProductManualController.js b/resources/ProductMannual/ProductManualController.js new file mode 100644 index 0000000..870273f --- /dev/null +++ b/resources/ProductMannual/ProductManualController.js @@ -0,0 +1,181 @@ +import ProductManual from "./ProductManualModel.js"; +import cloudinary from "../../Utils/cloudinary.js"; + +// Create a new product manual +export const createProductManual = async (req, res) => { + const { title } = req.body; + + try { + let productManualDetails; + + // Check if a file is provided + if (req.files && req.files.product_manual) { + const pdfFile = req.files.product_manual; + const result = await cloudinary.v2.uploader.upload(pdfFile.tempFilePath, { + folder: "chemiNova/ProductManuals", + resource_type: "raw", // For PDF or other non-image files + }); + + productManualDetails = { + public_id: result.public_id, + url: result.secure_url, + }; + } + + // Create the product manual + const productManual = await ProductManual.create({ + title, + product_manual: productManualDetails, + }); + + res.status(201).json({ + success: true, + productManual, + message: "Product manual created successfully", + }); + } catch (error) { + console.error("Error creating product manual:", error); + res.status(500).json({ + success: false, + message: error.message || "Internal server error", + }); + } +}; + +// Get all product manuals +export const getAllProductManuals = async (req, res) => { + try { + const productManuals = await ProductManual.find(); + res.status(200).json({ + success: true, + productManuals, + }); + } catch (error) { + console.error("Error fetching product manuals:", error); + res.status(500).json({ + success: false, + message: error.message || "Internal server error", + }); + } +}; + +// Get a single product manual by ID +export const getSingleProductManual = async (req, res) => { + const { id } = req.params; + + try { + const productManual = await ProductManual.findById(id); + + if (!productManual) { + return res.status(404).json({ + success: false, + message: "Product manual not found", + }); + } + + res.status(200).json({ + success: true, + productManual, + }); + } catch (error) { + console.error("Error fetching product manual:", error); + res.status(500).json({ + success: false, + message: error.message || "Internal server error", + }); + } +}; + +// Update a product manual +export const updateProductManual = async (req, res) => { + const { id } = req.params; + const { title } = req.body; + + try { + const productManual = await ProductManual.findById(id); + + if (!productManual) { + return res.status(404).json({ + success: false, + message: "Product manual not found", + }); + } + + // Check if a new file is provided + if (req.files && req.files.product_manual) { + // Delete the old file from Cloudinary + if (productManual.product_manual.public_id) { + await cloudinary.v2.uploader.destroy(productManual.product_manual.public_id, { + resource_type: "raw", + }); + } + + // Upload the new file to Cloudinary + const pdfFile = req.files.product_manual; + const result = await cloudinary.v2.uploader.upload(pdfFile.tempFilePath, { + folder: "chemiNova/ProductManuals", + resource_type: "raw", + }); + + // Update the product manual details + productManual.product_manual = { + public_id: result.public_id, + url: result.secure_url, + }; + } + + // Update the title + productManual.title = title || productManual.title; + + await productManual.save(); + + res.status(200).json({ + success: true, + productManual, + message: "Product manual updated successfully", + }); + } catch (error) { + console.error("Error updating product manual:", error); + res.status(500).json({ + success: false, + message: error.message || "Internal server error", + }); + } +}; + +// Delete a product manual +export const deleteProductManual = async (req, res) => { + const { id } = req.params; + + try { + const productManual = await ProductManual.findById(id); + + if (!productManual) { + return res.status(404).json({ + success: false, + message: "Product manual not found", + }); + } + + // Delete the file from Cloudinary + if (productManual.product_manual.public_id) { + await cloudinary.v2.uploader.destroy(productManual.product_manual.public_id, { + resource_type: "raw", + }); + } + + // Delete the product manual from the database + await ProductManual.findByIdAndDelete(id); + + res.status(200).json({ + success: true, + message: "Product manual deleted successfully", + }); + } catch (error) { + console.error("Error deleting product manual:", error); + res.status(500).json({ + success: false, + message: error.message || "Internal server error", + }); + } +}; \ No newline at end of file diff --git a/resources/ProductMannual/ProductManualModel.js b/resources/ProductMannual/ProductManualModel.js new file mode 100644 index 0000000..a21de31 --- /dev/null +++ b/resources/ProductMannual/ProductManualModel.js @@ -0,0 +1,25 @@ +import mongoose from "mongoose"; + +const ProductManualSchema = new mongoose.Schema( + { + title: { + type: String, + required: true, + }, + product_manual: { + public_id: { + type: String, + required: true, + }, + url: { + type: String, + required: true, + }, + }, + }, + { timestamps: true } +); + +const ProductManual = mongoose.model("ProductManual", ProductManualSchema); + +export default ProductManual; diff --git a/resources/ProductMannual/ProductManualRoute.js b/resources/ProductMannual/ProductManualRoute.js new file mode 100644 index 0000000..d48d6c3 --- /dev/null +++ b/resources/ProductMannual/ProductManualRoute.js @@ -0,0 +1,90 @@ +import express from "express"; +import { + createProductManual, + getAllProductManuals, + getSingleProductManual, + updateProductManual, + deleteProductManual, +} from "./ProductManualController.js"; +import { + isAuthenticatedUser, + authorizeRoles, +} from "../../middlewares/auth.js"; +import { isAuthenticatedSalesCoOrdinator } from "../../middlewares/SalesCoOrdinatorAuth.js"; +import { isAuthenticatedTerritoryManager } from "../../middlewares/TerritoryManagerAuth.js"; + +const router = express.Router(); + +// Route for creating a product manual (Only Admin can create) +router + .route("/create") + .post(isAuthenticatedUser, authorizeRoles("admin"), createProductManual); + +// Route for getting all product manuals (accessible to Sales Coordinator, Territory Manager, and Admin) +router.route("/").get( + (req, res, next) => { + // Allow access if the user is a sales coordinator, territory manager, or admin + isAuthenticatedSalesCoOrdinator(req, res, (err) => { + if (err) { + isAuthenticatedTerritoryManager(req, res, (err) => { + if (err) { + isAuthenticatedUser(req, res, (err) => { + if (err || !["admin"].includes(req.user.role)) { + return res.status(403).json({ + success: false, + message: "Access denied. Unauthorized role.", + }); + } + next(); + }); + } else { + next(); + } + }); + } else { + next(); + } + }); + }, + getAllProductManuals +); + +// Route for getting a single product manual by ID (accessible to Sales Coordinator, Territory Manager, and Admin) +router.route("/:id").get( + (req, res, next) => { + // Allow access if the user is a sales coordinator, territory manager, or admin + isAuthenticatedSalesCoOrdinator(req, res, (err) => { + if (err) { + isAuthenticatedTerritoryManager(req, res, (err) => { + if (err) { + isAuthenticatedUser(req, res, (err) => { + if (err || !["admin"].includes(req.user.role)) { + return res.status(403).json({ + success: false, + message: "Access denied. Unauthorized role.", + }); + } + next(); + }); + } else { + next(); + } + }); + } else { + next(); + } + }); + }, + getSingleProductManual +); +// Route to update a product manual by ID +router + .route("/update/:id") + .put(isAuthenticatedUser, authorizeRoles("admin"), updateProductManual); + +// Route to delete a product manual by ID +router + .route("/delete/:id") + .delete(isAuthenticatedUser, authorizeRoles("admin"), deleteProductManual); +export default router; +// /api/productmanual \ No newline at end of file diff --git a/resources/Task/TaskController.js b/resources/Task/TaskController.js index bc382f0..e6ca2a4 100644 --- a/resources/Task/TaskController.js +++ b/resources/Task/TaskController.js @@ -12,6 +12,7 @@ export const assignTask = async (req, res) => { taskAssignedTo, addedFor, addedForId, + tradename, } = req.body; const currentYear = new Date().getFullYear().toString().slice(-2); @@ -29,6 +30,7 @@ export const assignTask = async (req, res) => { taskAssignedBy: req.user._id, addedFor, addedForId, + tradename, }); res.status(201).json({ @@ -73,8 +75,33 @@ export const getTasksByStatus = async (req, res) => { }); } }; +export const getTasksbytask = async (req, res) => { + try { + const { task } = req.params; + // Validate the provided status + if (!["Visit RD/PD", "Update Sales Data", "Update Inventory Data", "Collect KYC"].includes(task)) { + return res.status(400).json({ + success: false, + message: "Invalid task type provided.", + }); + } + const tasks = await Task.find({ + taskAssignedTo: req.user._id, + task: task, + }).sort({ createdAt: -1 }); + res.status(200).json({ + success: true, + tasks, + }); + } catch (error) { + res.status(500).json({ + success: false, + message: error.message, + }); + } +}; export const updateTaskStatus = async (req, res) => { try { const { taskId } = req.params; diff --git a/resources/Task/TaskModel.js b/resources/Task/TaskModel.js index 988d8b5..0cf0be3 100644 --- a/resources/Task/TaskModel.js +++ b/resources/Task/TaskModel.js @@ -12,12 +12,12 @@ const TaskSchema = new mongoose.Schema( task: { type: String, required: true, - enum: ["Visit Retailers", "Update Sales Data", "Update Inventory Data", "Collect KYC"], + enum: ["Visit RD/PD", "Update Sales Data", "Update Inventory Data", "Collect KYC"], }, note: { type: String, required: function () { - return this.task === "Collect KYC"; + return this.task === "Collect KYC" || this.task === "Visit RD/PD"; }, }, taskStatus: { @@ -60,6 +60,12 @@ const TaskSchema = new mongoose.Schema( return this.task === "Update Inventory Data"; }, }, + tradename: { + type: String, + required: function () { + return this.task === "Update Inventory Data"; + }, + }, }, { timestamps: true } ); diff --git a/resources/Task/TaskRoute.js b/resources/Task/TaskRoute.js index c0eb6ab..a2b639e 100644 --- a/resources/Task/TaskRoute.js +++ b/resources/Task/TaskRoute.js @@ -3,6 +3,7 @@ import { assignTask, getTasksByStatus, updateTaskStatus, + getTasksbytask, } from "./TaskController.js"; import { isAuthenticatedSalesCoOrdinator } from "../../middlewares/SalesCoOrdinatorAuth.js"; import { isAuthenticatedTerritoryManager } from "../../middlewares/TerritoryManagerAuth.js"; @@ -22,7 +23,11 @@ router.get( isAuthenticatedSalesCoOrdinator, getTasksByStatus ); - +router.get( + "/task/type/:task", + isAuthenticatedSalesCoOrdinator, + getTasksbytask +); // Route to update task status router.put( "/update-task-status/:taskId", diff --git a/resources/user/userController.js b/resources/user/userController.js index 620b722..2e2247d 100644 --- a/resources/user/userController.js +++ b/resources/user/userController.js @@ -563,7 +563,19 @@ export const uploadPrincipaldistributors = async (req, res) => { uniqueId: item.uniqueId, }); await distributor.save(); - + await sendEmail({ + to: distributor.email, + from: process.env.SEND_EMAIL_FROM, + subject: `Cheminova Account Created`, + html: ` + Your Principal Distributor Account is created successfully. +
Name: ${distributor.name}
+
Mobile Number: ${distributor.phone}
+
Password: ${password}

+ Click here to login

+ If you have not requested this email, please ignore it. + `, + }); // Now create the address for the new user const addressData = { street: item.street,