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/public/temp/tmp-1-1728283415292 b/public/temp/tmp-1-1728283415292
new file mode 100644
index 0000000..501cf6e
Binary files /dev/null and b/public/temp/tmp-1-1728283415292 differ
diff --git a/public/temp/tmp-1-1728363850151 b/public/temp/tmp-1-1728363850151
new file mode 100644
index 0000000..49e6ae6
Binary files /dev/null and b/public/temp/tmp-1-1728363850151 differ
diff --git a/public/uploads/Add-RD.xlsx b/public/uploads/Add-RD.xlsx
new file mode 100644
index 0000000..38576dd
Binary files /dev/null and b/public/uploads/Add-RD.xlsx differ
diff --git a/public/uploads/Add-SC.xlsx b/public/uploads/Add-SC.xlsx
new file mode 100644
index 0000000..744e40a
Binary files /dev/null and b/public/uploads/Add-SC.xlsx differ
diff --git a/public/uploads/Add-TM.xlsx b/public/uploads/Add-TM.xlsx
new file mode 100644
index 0000000..49e6ae6
Binary files /dev/null and b/public/uploads/Add-TM.xlsx differ
diff --git a/resources/KYC/KycModel.js b/resources/KYC/KycModel.js
index 00461a0..91c9adf 100644
--- a/resources/KYC/KycModel.js
+++ b/resources/KYC/KycModel.js
@@ -52,7 +52,7 @@ const KycSchema = new Schema(
},
pan_img: {
type: Object,
- required: true,
+ // required: true,
},
aadhar_number: {
type: String,
@@ -60,7 +60,7 @@ const KycSchema = new Schema(
},
aadhar_img: {
type: Object,
- required: true,
+ // required: true,
},
gst_number: {
type: String,
@@ -68,18 +68,18 @@ const KycSchema = new Schema(
},
gst_img: {
type: Object,
- required: true,
+ // required: true,
},
pesticide_license_img: {
type: Object,
- required: true,
+ // required: true,
},
fertilizer_license_img: {
type: Object,
},
selfie_entrance_img: {
type: Object,
- required: true,
+ // required: true,
},
status: {
type: String,
diff --git a/resources/RetailDistributor/RetailDistributerRoutes.js b/resources/RetailDistributor/RetailDistributerRoutes.js
index 7034933..7c48c09 100644
--- a/resources/RetailDistributor/RetailDistributerRoutes.js
+++ b/resources/RetailDistributor/RetailDistributerRoutes.js
@@ -14,12 +14,16 @@ import {
UpdateProfileRD,
updateRDMapped,
updateunmapRD,
+ uploadRetaildistributors,
} from "./RetailDistributorController.js";
import { isAuthenticatedRD } from "../../middlewares/rdAuth.js";
import { authorizeRoles, isAuthenticatedUser } from "../../middlewares/auth.js";
const router = express.Router();
+router
+ .route("/retaildistributor/upload")
+ .post(isAuthenticatedUser, authorizeRoles("admin"), uploadRetaildistributors);
router.route("/rd-login").post(loginRD);
router.route("/rd-get-me").get(isAuthenticatedRD, getmyProfileRD);
router.post("/forgot-password", forgotPasswordRD);
diff --git a/resources/RetailDistributor/RetailDistributorController.js b/resources/RetailDistributor/RetailDistributorController.js
index 989ead4..40ddb9a 100644
--- a/resources/RetailDistributor/RetailDistributorController.js
+++ b/resources/RetailDistributor/RetailDistributorController.js
@@ -5,6 +5,254 @@ import password from "secure-random-password";
import crypto from "crypto";
import { RdOrder } from "../RD_Ordes/rdOrderModal.js";
import sendEmail, { sendOtp } from "../../Utils/sendEmail.js";
+import { KYC } from "../KYC/KycModel.js";
+import { generatePassword } from "../../Utils/generatepassword.js";
+import XLSX from "xlsx";
+import fs from "fs";
+import path from "path";
+
+export const uploadRetaildistributors = 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 = {
+ "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/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;
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",