This commit is contained in:
ROSHAN GARG 2024-10-08 14:08:09 +05:30
commit 7875bb31ba
19 changed files with 868 additions and 123 deletions

2
app.js
View File

@ -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

Binary file not shown.

Binary file not shown.

BIN
public/uploads/Add-RD.xlsx Normal file

Binary file not shown.

BIN
public/uploads/Add-SC.xlsx Normal file

Binary file not shown.

BIN
public/uploads/Add-TM.xlsx Normal file

Binary file not shown.

View File

@ -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,

View File

@ -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);

View File

@ -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.
<br/>name is: <strong>${item.name}</strong>
<br/>
<br/>MobileNumber is: <strong>${item.mobile_number}</strong><br/>
<br/>Email is: <strong>${item.email}</strong><br/>
<br/>password is: <strong>${password}</strong><br/><br/>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;

View File

@ -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.
<br/>name is: <strong>${item?.name}</strong><br/>
<br/>MobileNumber is: <strong>${item?.mobileNumber}</strong><br/>
<br/>password is: <strong>${password}</strong><br/><br/>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);

View File

@ -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,

View File

@ -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);

View File

@ -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",
});
}
};

View File

@ -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);

View File

@ -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",
});
}
};

View File

@ -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;

View File

@ -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.
<br/>name is: <strong>${item?.name}</strong><br/>
<br/>MobileNumber is: <strong>${item?.mobileNumber}</strong><br/>
<br/>password is: <strong>${password}</strong><br/><br/>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();

View File

@ -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 }

View File

@ -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",