From 90495fdad19f85f54e17f78b3dd53a0011dfcd98 Mon Sep 17 00:00:00 2001 From: Sibunnayak Date: Fri, 4 Oct 2024 10:31:34 +0530 Subject: [PATCH 1/2] RD stock create --- app.js | 2 +- resources/Stock/PdStockController.js | 103 --------- resources/Stock/RdStockModel.js | 26 +++ resources/Stock/StockController.js | 202 ++++++++++++++++++ .../Stock/{PdStockRoute.js => StockRoute.js} | 10 +- 5 files changed, 237 insertions(+), 106 deletions(-) delete mode 100644 resources/Stock/PdStockController.js create mode 100644 resources/Stock/RdStockModel.js create mode 100644 resources/Stock/StockController.js rename resources/Stock/{PdStockRoute.js => StockRoute.js} (52%) diff --git a/app.js b/app.js index 6f12e23..90c940c 100644 --- a/app.js +++ b/app.js @@ -203,7 +203,7 @@ import TaskRoute from "./resources/Task/TaskRoute.js"; // visit RD and PD import VisitRDandPDRoute from "./resources/VisitRD&PD/VisitRD&PDRoute.js"; //Stock -import Stock from "./resources/Stock/PdStockRoute.js"; +import Stock from "./resources/Stock/StockRoute.js"; app.use("/api/v1", user); //Product diff --git a/resources/Stock/PdStockController.js b/resources/Stock/PdStockController.js deleted file mode 100644 index 0aebe55..0000000 --- a/resources/Stock/PdStockController.js +++ /dev/null @@ -1,103 +0,0 @@ -import mongoose from "mongoose"; -import { PDStock } from "./PdStockModel.js"; -import { Product } from "../Products/ProductModel.js"; - -export const getProductsAndStockByUser = async (req, res) => { - try { - const { userId } = req.params; - - // Pagination parameters - const PAGE_SIZE = parseInt(req.query.show) || 10; - const page = parseInt(req.query.page) || 1; - const skip = (page - 1) * PAGE_SIZE; - - // Filtering criteria - const filter = {}; - if (req.query.name) { - filter.name = { - $regex: new RegExp(req.query.name, "i"), - }; - } - if (req.query.category) { - filter.category = mongoose.Types.ObjectId(req.query.category); - } - if (req.query.brand) { - filter.brand = mongoose.Types.ObjectId(req.query.brand); - } - - // Fetch user's PDStock data and products concurrently - const [userStock, products] = await Promise.all([ - PDStock.findOne({ userId: mongoose.Types.ObjectId(userId) }), - Product.aggregate([ - { $match: filter }, - { - $lookup: { - from: "categorymodels", - localField: "category", - foreignField: "_id", - as: "categoryDetails", - }, - }, - { - $lookup: { - from: "brandmodels", - localField: "brand", - foreignField: "_id", - as: "brandDetails", - }, - }, - { - $project: { - category: { $arrayElemAt: ["$categoryDetails.categoryName", 0] }, - brand: { $arrayElemAt: ["$brandDetails.brandName", 0] }, - GST: 1, - HSN_Code: 1, - SKU: 1, - addedBy: 1, - createdAt: 1, - description: 1, - image: 1, - name: 1, - price: 1, - product_Status: 1, - updatedAt: 1, - }, - }, - { $skip: skip }, - { $limit: PAGE_SIZE }, - ]), - ]); - - // Create a stock map for easy lookup - const stockMap = {}; - if (userStock && userStock.products) { - userStock.products.forEach((product) => { - stockMap[product.productid.toString()] = product.Stock; - }); - } - - // Combine products with their respective stock - const productsWithStock = products.map((product) => ({ - ...product, - stock: stockMap[product._id.toString()] || 0, - })); - - // Get total count for pagination purposes - const total = await Product.countDocuments(filter); - - return res.status(200).json({ - success: true, - totalProducts: total, - totalPages: Math.ceil(total / PAGE_SIZE), - currentPage: page, - products: productsWithStock, - }); - } catch (error) { - console.error("Error fetching products with stock:", error); - return res.status(500).json({ - success: false, - message: "Error fetching products and stock", - }); - } -}; - diff --git a/resources/Stock/RdStockModel.js b/resources/Stock/RdStockModel.js new file mode 100644 index 0000000..20a3dfa --- /dev/null +++ b/resources/Stock/RdStockModel.js @@ -0,0 +1,26 @@ +import mongoose from 'mongoose'; + +// Define Product record schema +const ProductRecordSchema = new mongoose.Schema({ + productid: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Product', + required: true, + }, + Stock: { + type: Number, + default: 0, + }, +}); + +// Define main Stock schema +const StockSchema = new mongoose.Schema({ + userId: { + type: mongoose.Schema.Types.ObjectId, + refPath: 'RetailDistributor', + required: true, + }, + products: [ProductRecordSchema], +}, { timestamps: true, versionKey: false }); + +export const RDStock = mongoose.model('RDStock', StockSchema); diff --git a/resources/Stock/StockController.js b/resources/Stock/StockController.js new file mode 100644 index 0000000..2a4b9b1 --- /dev/null +++ b/resources/Stock/StockController.js @@ -0,0 +1,202 @@ +import mongoose from "mongoose"; +import { PDStock } from "./PdStockModel.js"; +import { Product } from "../Products/ProductModel.js"; +import { RDStock } from "./RdStockModel.js"; + +export const getProductsAndStockByPD = async (req, res) => { + try { + const { userId } = req.params; + + // Pagination parameters + const PAGE_SIZE = parseInt(req.query.show) || 10; + const page = parseInt(req.query.page) || 1; + const skip = (page - 1) * PAGE_SIZE; + + // Filtering criteria + const filter = {}; + if (req.query.name) { + filter.name = { + $regex: new RegExp(req.query.name, "i"), + }; + } + if (req.query.category) { + filter.category = mongoose.Types.ObjectId(req.query.category); + } + if (req.query.brand) { + filter.brand = mongoose.Types.ObjectId(req.query.brand); + } + + // Fetch user's PDStock data and products concurrently + const [userStock, products] = await Promise.all([ + PDStock.findOne({ userId: mongoose.Types.ObjectId(userId) }), + Product.aggregate([ + { $match: filter }, + { + $lookup: { + from: "categorymodels", + localField: "category", + foreignField: "_id", + as: "categoryDetails", + }, + }, + { + $lookup: { + from: "brandmodels", + localField: "brand", + foreignField: "_id", + as: "brandDetails", + }, + }, + { + $project: { + category: { $arrayElemAt: ["$categoryDetails.categoryName", 0] }, + brand: { $arrayElemAt: ["$brandDetails.brandName", 0] }, + GST: 1, + HSN_Code: 1, + SKU: 1, + addedBy: 1, + createdAt: 1, + description: 1, + image: 1, + name: 1, + price: 1, + product_Status: 1, + updatedAt: 1, + }, + }, + { $skip: skip }, + { $limit: PAGE_SIZE }, + ]), + ]); + + // Create a stock map for easy lookup + const stockMap = {}; + if (userStock && userStock.products) { + userStock.products.forEach((product) => { + stockMap[product.productid.toString()] = product.Stock; + }); + } + + // Combine products with their respective stock + const productsWithStock = products.map((product) => ({ + ...product, + stock: stockMap[product._id.toString()] || 0, + })); + + // Get total count for pagination purposes + const total = await Product.countDocuments(filter); + + return res.status(200).json({ + success: true, + totalProducts: total, + totalPages: Math.ceil(total / PAGE_SIZE), + currentPage: page, + products: productsWithStock, + }); + } catch (error) { + console.error("Error fetching products with stock:", error); + return res.status(500).json({ + success: false, + message: "Error fetching products and stock", + }); + } +}; + +export const getProductsAndStockByRD = async (req, res) => { + try { + const { userId } = req.params; + + // Pagination parameters + const PAGE_SIZE = parseInt(req.query.show) || 10; + const page = parseInt(req.query.page) || 1; + const skip = (page - 1) * PAGE_SIZE; + + // Filtering criteria + const filter = {}; + if (req.query.name) { + filter.name = { + $regex: new RegExp(req.query.name, "i"), + }; + } + if (req.query.category) { + filter.category = mongoose.Types.ObjectId(req.query.category); + } + if (req.query.brand) { + filter.brand = mongoose.Types.ObjectId(req.query.brand); + } + + // Fetch user's RDStock data and products concurrently + const [userStock, products] = await Promise.all([ + RDStock.findOne({ userId: mongoose.Types.ObjectId(userId) }), + Product.aggregate([ + { $match: filter }, + { + $lookup: { + from: "categorymodels", + localField: "category", + foreignField: "_id", + as: "categoryDetails", + }, + }, + { + $lookup: { + from: "brandmodels", + localField: "brand", + foreignField: "_id", + as: "brandDetails", + }, + }, + { + $project: { + category: { $arrayElemAt: ["$categoryDetails.categoryName", 0] }, + brand: { $arrayElemAt: ["$brandDetails.brandName", 0] }, + GST: 1, + HSN_Code: 1, + SKU: 1, + addedBy: 1, + createdAt: 1, + description: 1, + image: 1, + name: 1, + price: 1, + product_Status: 1, + updatedAt: 1, + }, + }, + { $skip: skip }, + { $limit: PAGE_SIZE }, + ]), + ]); + + // Create a stock map for easy lookup + const stockMap = {}; + if (userStock && userStock.products) { + userStock.products.forEach((product) => { + stockMap[product.productid.toString()] = product.Stock; + }); + } + + // Combine products with their respective stock + const productsWithStock = products.map((product) => ({ + ...product, + stock: stockMap[product._id.toString()] || 0, + })); + + // Get total count for pagination purposes + const total = await Product.countDocuments(filter); + + return res.status(200).json({ + success: true, + totalProducts: total, + totalPages: Math.ceil(total / PAGE_SIZE), + currentPage: page, + products: productsWithStock, + }); + } catch (error) { + console.error("Error fetching products with stock:", error); + return res.status(500).json({ + success: false, + message: "Error fetching products and stock", + }); + } +}; \ No newline at end of file diff --git a/resources/Stock/PdStockRoute.js b/resources/Stock/StockRoute.js similarity index 52% rename from resources/Stock/PdStockRoute.js rename to resources/Stock/StockRoute.js index d49a778..4d014c6 100644 --- a/resources/Stock/PdStockRoute.js +++ b/resources/Stock/StockRoute.js @@ -1,5 +1,5 @@ import express from "express"; -import { getProductsAndStockByUser } from "./PdStockController.js"; +import { getProductsAndStockByPD ,getProductsAndStockByRD} from "./StockController.js"; import { authorizeRoles, isAuthenticatedUser } from "../../middlewares/auth.js"; const router = express.Router(); @@ -7,6 +7,12 @@ router.get( "/pd/stock/:userId", isAuthenticatedUser, authorizeRoles("admin"), - getProductsAndStockByUser + getProductsAndStockByPD +); +router.get( + "/rd/stock/:userId", + isAuthenticatedUser, + authorizeRoles("admin"), + getProductsAndStockByRD ); export default router; From 637419fe896ab33a8efa15fe0251cef9c7a775e3 Mon Sep 17 00:00:00 2001 From: Sibunnayak Date: Tue, 8 Oct 2024 12:17:36 +0530 Subject: [PATCH 2/2] RD,Tm,Sc added using spreadsheet --- public/temp/tmp-1-1728283415292 | Bin 0 -> 10928 bytes public/temp/tmp-1-1728363850151 | Bin 0 -> 10609 bytes public/uploads/Add-RD.xlsx | Bin 0 -> 11083 bytes public/uploads/Add-SC.xlsx | Bin 0 -> 10654 bytes public/uploads/Add-TM.xlsx | Bin 0 -> 10609 bytes resources/KYC/KycModel.js | 10 +- .../RetailDistributerRoutes.js | 8 +- .../RetailDistributorController.js | 248 ++++++++++++++++++ .../SalesCoOrdinatorController.js | 181 +++++++++++++ .../SalesCoOrdinatorModel.js | 2 +- .../SalesCoOrdinatorRoute.js | 4 + .../TerritoryManagerController.js | 181 +++++++++++++ .../TerritoryManagerModel.js | 2 +- .../TerritoryManagerRoute.js | 16 +- 14 files changed, 633 insertions(+), 19 deletions(-) create mode 100644 public/temp/tmp-1-1728283415292 create mode 100644 public/temp/tmp-1-1728363850151 create mode 100644 public/uploads/Add-RD.xlsx create mode 100644 public/uploads/Add-SC.xlsx create mode 100644 public/uploads/Add-TM.xlsx diff --git a/public/temp/tmp-1-1728283415292 b/public/temp/tmp-1-1728283415292 new file mode 100644 index 0000000000000000000000000000000000000000..501cf6e92fd1c7fa5fd8e4ae40b6ec758eae2b4c GIT binary patch literal 10928 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 A1poj5 literal 0 HcmV?d00001 diff --git a/public/uploads/Add-SC.xlsx b/public/uploads/Add-SC.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..744e40a9ebee70e3bad86e590f1de277c62f1dfc GIT binary patch literal 10654 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 { + try { + if (!mongoose.Types.ObjectId.isValid(req.user._id)) { + return res.status(400).json({ message: "Please login again" }); + } + if (!req.files || !req.files.file) { + return res.status(400).json({ message: "No file uploaded" }); + } + + const file = req.files.file; + const filePath = path.join("public", "uploads", file.name); + + // Ensure 'uploads' directory exists + if (!fs.existsSync(path.dirname(filePath))) { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + } + + // Move the file from temp to the uploads directory + await file.mv(filePath); + + // Process the file + const fileBuffer = fs.readFileSync(filePath); + const workbook = XLSX.read(fileBuffer, { type: "buffer" }); + const sheetName = workbook.SheetNames[0]; + const worksheet = workbook.Sheets[sheetName]; + const data = XLSX.utils.sheet_to_json(worksheet, { header: 1 }); + + if (data.length <= 1) { + return res + .status(400) + .json({ message: "Empty spreadsheet or no data found" }); + } + + const headers = data[0]; + + // Map headers from the Excel file to your schema + const headerMapping = { + "Retail Distributor Name": "name", + Email: "email", + "Phone Number": "mobile_number", + "PAN Number": "pan_number", + "Trade Name": "trade_name", + "GST Number": "gst_number", + "Aadhar Number": "aadhar_number", + State: "state", + City: "city", + District: "district", + Address: "address", + Pincode: "pincode", + }; + + const requiredHeaders = Object.keys(headerMapping); + + if (!requiredHeaders.every((header) => headers.includes(header))) { + return res + .status(400) + .json({ message: "Missing required columns in spreadsheet" }); + } + + const errors = []; + const newlyCreated = []; + const updatedDistributors = []; + + for (let i = 1; i < data.length; i++) { + const row = data[i]; + const item = {}; + + headers.forEach((header, index) => { + if (headerMapping[header]) { + item[headerMapping[header]] = + row[index] !== undefined ? row[index] : ""; + } + }); + + // Initialize error tracking for each item + const missingFields = new Set(); + const validationErrors = new Set(); + + // Validate required fields + if (!item.name) missingFields.add("name"); + if (!item.email) missingFields.add("email"); + if (!item.mobile_number) missingFields.add("mobile_number"); + if (!item.pan_number) missingFields.add("pan_number"); + if (!item.gst_number) missingFields.add("gst_number"); + if (!item.trade_name) missingFields.add("trade_name"); + if (!item.aadhar_number) missingFields.add("aadhar_number"); + if (!item.state) missingFields.add("state"); + if (!item.city) missingFields.add("city"); + if (!item.pincode) missingFields.add("pincode"); + if (!item.district) missingFields.add("district"); + if (!item.address) missingFields.add("address"); + + // Check email validity + if (item.email && !validator.isEmail(item.email)) { + validationErrors.add("incorrect mail"); + } + + // Validate mobile number + if (item.mobile_number && !/^\d{10}$/.test(item.mobile_number)) { + validationErrors.add("Invalid Mobile Number (should be 10 digits)"); + } + + // Check GST, PAN, and postal code validation + item.pan_number = item.pan_number ? item.pan_number.toUpperCase() : ""; + item.gst_number = item.gst_number ? item.gst_number.toUpperCase() : ""; + + // Validate PAN Number + if ( + item.pan_number && + !/^[A-Z]{5}[0-9]{4}[A-Z]{1}$/.test(item.pan_number) + ) { + validationErrors.add("Invalid PAN Number"); + } + + // Validate GST Number + if ( + item.gst_number && + !/^(\d{2}[A-Z]{5}\d{4}[A-Z]{1}\d[Z]{1}[A-Z\d]{1})$/.test( + item.gst_number + ) + ) { + validationErrors.add("Invalid GST Number"); + } + // Validate Aadhar number + if (item.aadhar_number && !/^\d{12}$/.test(item.aadhar_number)) { + validationErrors.add("Invalid Aadhar Number (should be 12 digits)"); + } + // Validate Postal Code + if (item.pincode && !/^\d{6}$/.test(item.pincode)) { + validationErrors.add("Invalid Postal Code"); + } + + // Combine all errors into a single message + let errorMessage = ""; + if (missingFields.size > 0) { + errorMessage += `Missing fields: ${Array.from(missingFields).join( + ", " + )}. `; + } + if (validationErrors.size > 0) { + errorMessage += `Validation errors: ${Array.from(validationErrors).join( + ", " + )}.`; + } + + // If there are errors, push them to the errors array + if (errorMessage.trim()) { + errors.push({ + name: item.name || "N/A", + email: item.email || "N/A", + TradeName: item.trade_name || "N/A", + phone: item.mobile_number || "N/A", + panNumber: item.pan_number || "N/A", + gstNumber: item.gst_number || "N/A", + AadharNumber: item.aadhar_number || "N/A", + message: errorMessage.trim(), + }); + continue; + } + + // Generate a password + const password = generatePassword(item.name, item.email); + + // Check for existing user by uniqueId + let Kyc = await KYC.findOne({ email: item.email }); + let Retaildistributor = await RetailDistributor.findOne({ + email: item.email, + }); + if (Kyc) { + // Track updated fields + const updatedFields = []; + + // Check for changes in user details + let kycUpdated = false; + for (let field in item) { + const currentValue = Kyc[field]?.toString(); + const newValue = item[field]?.toString(); + + if (currentValue !== newValue) { + updatedFields.push(field); + Kyc[field] = item[field]; // Update Kyc with the new value + + if (Retaildistributor && field !== 'email') { + Retaildistributor[field] = item[field]; + } + kycUpdated = true; + } + } + + // Update Kyc and Retaildistributor if there are any changes + if (kycUpdated) { + await Kyc.save(); + await Retaildistributor.save(); + updatedDistributors.push({ + ...Kyc._doc, + updatedFields: updatedFields.join(", "), + }); + } + } else { + // Create a new Kyc + Kyc = new KYC({ + ...item, + status: "approved", + }); + const newkyc = await Kyc.save(); + const retailDistributorData = { + name: item.name, + email: item.email, + mobile_number: item.mobile_number, + kyc: newkyc._id, + password, + }; + Retaildistributor = new RetailDistributor(retailDistributorData); + await Retaildistributor.save(); + // Send email with the new password + await sendEmail({ + to: `${item.email}`, // Change to your recipient + from: `${process.env.SEND_EMAIL_FROM}`, // Change to your verified sender + subject: `Cheminova Account Created`, + html: `Your Retail Distributor Account is created successfully. +
name is: ${item.name} +
+
MobileNumber is: ${item.mobile_number}
+
Email is: ${item.email}
+
password is: ${password}

If you have not requested this email, please ignore it.`, + }); + newlyCreated.push({ Kyc }); + } + } + + res.status(200).json({ + message: "File processed successfully", + newlyCreated, + updatedDistributors, + errors, + }); + } catch (error) { + console.error(error); + res.status(500).json({ message: "Internal Server Error" }); + } +}; + export const loginRD = async (req, res) => { const { email, password } = req.body; diff --git a/resources/SalesCoOrdinators/SalesCoOrdinatorController.js b/resources/SalesCoOrdinators/SalesCoOrdinatorController.js index 56cce6d..bef5b45 100644 --- a/resources/SalesCoOrdinators/SalesCoOrdinatorController.js +++ b/resources/SalesCoOrdinators/SalesCoOrdinatorController.js @@ -1,11 +1,192 @@ // import hashPassword from '../utils/hashPassword'; import crypto from "crypto"; +import mongoose from "mongoose"; import SalesCoOrdinator from "./SalesCoOrdinatorModel.js"; import sendEmail, { sendOtp } from "../../Utils/sendEmail.js"; import validator from "validator"; import password from "secure-random-password"; import catchAsyncErrors from "../../middlewares/catchAsyncErrors.js"; +import { generatePassword } from "../../Utils/generatepassword.js"; +import XLSX from "xlsx"; +import fs from "fs"; +import path from "path"; +export const uploadSalesCoordinators = async (req, res) => { + try { + if (!mongoose.Types.ObjectId.isValid(req.user._id)) { + return res.status(400).json({ message: "Please login again" }); + } + if (!req.files || !req.files.file) { + return res.status(400).json({ message: "No file uploaded" }); + } + const file = req.files.file; + const filePath = path.join("public", "uploads", file.name); + + // Ensure 'uploads' directory exists + if (!fs.existsSync(path.dirname(filePath))) { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + } + + // Move the file from temp to the uploads directory + await file.mv(filePath); + + // Process the file + const fileBuffer = fs.readFileSync(filePath); + const workbook = XLSX.read(fileBuffer, { type: "buffer" }); + const sheetName = workbook.SheetNames[0]; + const worksheet = workbook.Sheets[sheetName]; + const data = XLSX.utils.sheet_to_json(worksheet, { header: 1 }); + + if (data.length <= 1) { + return res + .status(400) + .json({ message: "Empty spreadsheet or no data found" }); + } + + const headers = data[0]; + + // Map headers from the Excel file to your schema + const headerMapping = { + "Sales Coordinator Name": "name", + Email: "email", + "Phone Number": "mobileNumber", + }; + + const requiredHeaders = Object.keys(headerMapping); + + if (!requiredHeaders.every((header) => headers.includes(header))) { + return res + .status(400) + .json({ message: "Missing required columns in spreadsheet" }); + } + + const errors = []; + const newlyCreated = []; + const updatedsalesCoordinators = []; + + for (let i = 1; i < data.length; i++) { + const row = data[i]; + // Skip the row if it's completely empty + if (row.every(cell => cell === undefined || cell === "")) { + continue; + } + const item = {}; + + headers.forEach((header, index) => { + if (headerMapping[header]) { + item[headerMapping[header]] = + row[index] !== undefined ? row[index] : ""; + } + }); + + // Initialize error tracking for each item + const missingFields = new Set(); + const validationErrors = new Set(); + + // Validate required fields + if (!item.name) missingFields.add("name"); + if (!item.email) missingFields.add("email"); + if (!item.mobileNumber) missingFields.add("mobileNumber"); + + // Check email validity + if (item.email && !validator.isEmail(item.email)) { + validationErrors.add("incorrect mail"); + } + + // Validate mobile number + if (item.mobileNumber && !/^\d{10}$/.test(item.mobileNumber)) { + validationErrors.add("Invalid Mobile Number (should be 10 digits)"); + } + + // Combine all errors into a single message + let errorMessage = ""; + if (missingFields.size > 0) { + errorMessage += `Missing fields: ${Array.from(missingFields).join( + ", " + )}. `; + } + if (validationErrors.size > 0) { + errorMessage += `Validation errors: ${Array.from(validationErrors).join( + ", " + )}.`; + } + + // If there are errors, push them to the errors array + if (errorMessage.trim()) { + errors.push({ + name: item.name || "N/A", + email: item.email || "N/A", + phone: item.mobileNumber || "N/A", + message: errorMessage.trim(), + }); + continue; + } + + // Generate a password + const password = generatePassword(item.name, item.email); + + // Check for existing user by uniqueId + let salesCoordinator = await SalesCoOrdinator.findOne({ + email: item.email, + }); + + if (salesCoordinator) { + // Track updated fields + const updatedFields = []; + + // Check for changes in user details + let territoryManagerUpdated = false; + for (let field in item) { + const currentValue = salesCoordinator[field]?.toString(); + const newValue = item[field]?.toString(); + + if (currentValue !== newValue) { + updatedFields.push(field); + salesCoordinator[field] = item[field]; + territoryManagerUpdated = true; + } + } + + if (territoryManagerUpdated) { + await salesCoordinator.save(); + updatedsalesCoordinators.push({ + ...salesCoordinator._doc, + updatedFields: updatedFields.join(", "), + }); + } + } else { + // Create a new salesCoordinator + salesCoordinator = new SalesCoOrdinator({ + ...item, + password, + isVerified: true, + }); + await salesCoordinator.save(); + // Send email with the new password + await sendEmail({ + to: `${item?.email}`, // Change to your recipient + from: `${process.env.SEND_EMAIL_FROM}`, // Change to your verified sender + subject: `Cheminova Account Created`, + html: `Your Sales Coordinator Account is created successfully. +
name is: ${item?.name}
+
MobileNumber is: ${item?.mobileNumber}
+
password is: ${password}

If you have not requested this email, please ignore it.`, + }); + newlyCreated.push({ salesCoordinator }); + } + } + + res.status(200).json({ + message: "File processed successfully", + newlyCreated, + updatedsalesCoordinators, + errors, + }); + } catch (error) { + console.error(error); + res.status(500).json({ message: "Internal Server Error" }); + } +}; export const register = async (req, res) => { let { name, email, countryCode, mobileNumber, territoryManager } = req.body; // console.log(req.body); diff --git a/resources/SalesCoOrdinators/SalesCoOrdinatorModel.js b/resources/SalesCoOrdinators/SalesCoOrdinatorModel.js index bbd6a74..ca2ef55 100644 --- a/resources/SalesCoOrdinators/SalesCoOrdinatorModel.js +++ b/resources/SalesCoOrdinators/SalesCoOrdinatorModel.js @@ -50,11 +50,11 @@ const salescoordinatorSchema = new mongoose.Schema( uniqueId: { type: String, unique: true, - required: true, }, fcm_token: { type: String, default: null, + sparse: true, }, mappedby: { type: mongoose.Schema.Types.ObjectId, diff --git a/resources/SalesCoOrdinators/SalesCoOrdinatorRoute.js b/resources/SalesCoOrdinators/SalesCoOrdinatorRoute.js index a543544..3ff3a91 100644 --- a/resources/SalesCoOrdinators/SalesCoOrdinatorRoute.js +++ b/resources/SalesCoOrdinators/SalesCoOrdinatorRoute.js @@ -20,12 +20,16 @@ import { mappedbyTM, unmapSalesCoOrdinator, getAllSalesCoOrdinatorforTM_App, + uploadSalesCoordinators, } from "./SalesCoOrdinatorController.js"; import { isAuthenticatedSalesCoOrdinator } from "../../middlewares/SalesCoOrdinatorAuth.js"; import { isAuthenticatedTerritoryManager } from "../../middlewares/TerritoryManagerAuth.js"; import { authorizeRoles, isAuthenticatedUser } from "../../middlewares/auth.js"; router.post("/register", register); +router + .route("/upload") + .post(isAuthenticatedUser, authorizeRoles("admin"), uploadSalesCoordinators); router.post("/verify-otp", verifyOtp); router.post("/login", loginSalesCoOrdinator); router.route("/logout").get(logout); diff --git a/resources/TerritoryManagers/TerritoryManagerController.js b/resources/TerritoryManagers/TerritoryManagerController.js index 163feff..3194761 100644 --- a/resources/TerritoryManagers/TerritoryManagerController.js +++ b/resources/TerritoryManagers/TerritoryManagerController.js @@ -1,11 +1,192 @@ // import hashPassword from '../utils/hashPassword'; import crypto from "crypto"; +import mongoose from "mongoose"; import TerritoryManager from "./TerritoryManagerModel.js"; import sendEmail, { sendOtp } from "../../Utils/sendEmail.js"; import validator from "validator"; import password from "secure-random-password"; import catchAsyncErrors from "../../middlewares/catchAsyncErrors.js"; +import { generatePassword } from "../../Utils/generatepassword.js"; +import XLSX from "xlsx"; +import fs from "fs"; +import path from "path"; +export const uploadTerritoryManagers = async (req, res) => { + try { + if (!mongoose.Types.ObjectId.isValid(req.user._id)) { + return res.status(400).json({ message: "Please login again" }); + } + if (!req.files || !req.files.file) { + return res.status(400).json({ message: "No file uploaded" }); + } + const file = req.files.file; + const filePath = path.join("public", "uploads", file.name); + + // Ensure 'uploads' directory exists + if (!fs.existsSync(path.dirname(filePath))) { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + } + + // Move the file from temp to the uploads directory + await file.mv(filePath); + + // Process the file + const fileBuffer = fs.readFileSync(filePath); + const workbook = XLSX.read(fileBuffer, { type: "buffer" }); + const sheetName = workbook.SheetNames[0]; + const worksheet = workbook.Sheets[sheetName]; + const data = XLSX.utils.sheet_to_json(worksheet, { header: 1 }); + + if (data.length <= 1) { + return res + .status(400) + .json({ message: "Empty spreadsheet or no data found" }); + } + + const headers = data[0]; + + // Map headers from the Excel file to your schema + const headerMapping = { + "Territory Manager Name": "name", + Email: "email", + "Phone Number": "mobileNumber", + }; + + const requiredHeaders = Object.keys(headerMapping); + + if (!requiredHeaders.every((header) => headers.includes(header))) { + return res + .status(400) + .json({ message: "Missing required columns in spreadsheet" }); + } + + const errors = []; + const newlyCreated = []; + const updatedtrritoryManagers = []; + + for (let i = 1; i < data.length; i++) { + const row = data[i]; + // Skip the row if it's completely empty + if (row.every((cell) => cell === undefined || cell === "")) { + continue; + } + const item = {}; + + headers.forEach((header, index) => { + if (headerMapping[header]) { + item[headerMapping[header]] = + row[index] !== undefined ? row[index] : ""; + } + }); + + // Initialize error tracking for each item + const missingFields = new Set(); + const validationErrors = new Set(); + + // Validate required fields + if (!item.name) missingFields.add("name"); + if (!item.email) missingFields.add("email"); + if (!item.mobileNumber) missingFields.add("mobileNumber"); + + // Check email validity + if (item.email && !validator.isEmail(item.email)) { + validationErrors.add("incorrect mail"); + } + + // Validate mobile number + if (item.mobileNumber && !/^\d{10}$/.test(item.mobileNumber)) { + validationErrors.add("Invalid Mobile Number (should be 10 digits)"); + } + + // Combine all errors into a single message + let errorMessage = ""; + if (missingFields.size > 0) { + errorMessage += `Missing fields: ${Array.from(missingFields).join( + ", " + )}. `; + } + if (validationErrors.size > 0) { + errorMessage += `Validation errors: ${Array.from(validationErrors).join( + ", " + )}.`; + } + + // If there are errors, push them to the errors array + if (errorMessage.trim()) { + errors.push({ + name: item.name || "N/A", + email: item.email || "N/A", + phone: item.mobileNumber || "N/A", + message: errorMessage.trim(), + }); + continue; + } + + // Generate a password + const password = generatePassword(item.name, item.email); + + // Check for existing user by uniqueId + let territoryManager = await TerritoryManager.findOne({ + email: item.email, + }); + + if (territoryManager) { + // Track updated fields + const updatedFields = []; + + // Check for changes in user details + let territoryManagerUpdated = false; + for (let field in item) { + const currentValue = territoryManager[field]?.toString(); + const newValue = item[field]?.toString(); + + if (currentValue !== newValue) { + updatedFields.push(field); + territoryManager[field] = item[field]; + territoryManagerUpdated = true; + } + } + + if (territoryManagerUpdated) { + await territoryManager.save(); + updatedtrritoryManagers.push({ + ...territoryManager._doc, + updatedFields: updatedFields.join(", "), + }); + } + } else { + // Create a new territoryManager + territoryManager = new TerritoryManager({ + ...item, + password, + isVerified: true, + }); + await territoryManager.save(); + // Send email with the new password + await sendEmail({ + to: `${item?.email}`, // Change to your recipient + from: `${process.env.SEND_EMAIL_FROM}`, // Change to your verified sender + subject: `Cheminova Account Created`, + html: `Your Territory Manager Account is created successfully. +
name is: ${item?.name}
+
MobileNumber is: ${item?.mobileNumber}
+
password is: ${password}

If you have not requested this email, please ignore it.`, + }); + newlyCreated.push({ territoryManager }); + } + } + + res.status(200).json({ + message: "File processed successfully", + newlyCreated, + updatedtrritoryManagers, + errors, + }); + } catch (error) { + console.error(error); + res.status(500).json({ message: "Internal Server Error" }); + } +}; export const register = async (req, res) => { let { name, email, countryCode, mobileNumber } = req.body; countryCode = countryCode?.trim(); diff --git a/resources/TerritoryManagers/TerritoryManagerModel.js b/resources/TerritoryManagers/TerritoryManagerModel.js index 0d6436f..250c174 100644 --- a/resources/TerritoryManagers/TerritoryManagerModel.js +++ b/resources/TerritoryManagers/TerritoryManagerModel.js @@ -50,11 +50,11 @@ const territorymanagerSchema = new mongoose.Schema( uniqueId: { type: String, unique: true, - required: true, }, fcm_token: { type: String, default: null, + sparse: true, }, }, { timestamps: true } diff --git a/resources/TerritoryManagers/TerritoryManagerRoute.js b/resources/TerritoryManagers/TerritoryManagerRoute.js index 82ee8c5..8c24a17 100644 --- a/resources/TerritoryManagers/TerritoryManagerRoute.js +++ b/resources/TerritoryManagers/TerritoryManagerRoute.js @@ -16,11 +16,15 @@ import { ChangePassword, getOneTerritoryManager, logout, + uploadTerritoryManagers, } from "./TerritoryManagerController.js"; import { isAuthenticatedTerritoryManager } from "../../middlewares/TerritoryManagerAuth.js"; import { authorizeRoles, isAuthenticatedUser } from "../../middlewares/auth.js"; router.post("/register", register); +router + .route("/upload") + .post(isAuthenticatedUser, authorizeRoles("admin"), uploadTerritoryManagers); router.post("/verify-otp", verifyOtp); router.post("/login", loginTerritoryManager); router.route("/logout").get(logout); @@ -69,11 +73,7 @@ router.patch( authorizeRoles("admin"), UpdateProfile ); -router.patch( - "/profile/update", - isAuthenticatedTerritoryManager, - UpdateProfile -); +router.patch("/profile/update", isAuthenticatedTerritoryManager, UpdateProfile); //change password router.put( "/password/update/:id", @@ -82,11 +82,7 @@ router.put( ChangePassword ); -router.put( - "/password/update", - isAuthenticatedTerritoryManager, - ChangePassword -); +router.put("/password/update", isAuthenticatedTerritoryManager, ChangePassword); //delete TerritoryManager router.delete( "/delete/:id",