RD,Tm,Sc added using spreadsheet
This commit is contained in:
parent
e9a74b3d63
commit
637419fe89
BIN
public/temp/tmp-1-1728283415292
Normal file
BIN
public/temp/tmp-1-1728283415292
Normal file
Binary file not shown.
BIN
public/temp/tmp-1-1728363850151
Normal file
BIN
public/temp/tmp-1-1728363850151
Normal file
Binary file not shown.
BIN
public/uploads/Add-RD.xlsx
Normal file
BIN
public/uploads/Add-RD.xlsx
Normal file
Binary file not shown.
BIN
public/uploads/Add-SC.xlsx
Normal file
BIN
public/uploads/Add-SC.xlsx
Normal file
Binary file not shown.
BIN
public/uploads/Add-TM.xlsx
Normal file
BIN
public/uploads/Add-TM.xlsx
Normal file
Binary file not shown.
@ -52,7 +52,7 @@ const KycSchema = new Schema(
|
|||||||
},
|
},
|
||||||
pan_img: {
|
pan_img: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
// required: true,
|
||||||
},
|
},
|
||||||
aadhar_number: {
|
aadhar_number: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -60,7 +60,7 @@ const KycSchema = new Schema(
|
|||||||
},
|
},
|
||||||
aadhar_img: {
|
aadhar_img: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
// required: true,
|
||||||
},
|
},
|
||||||
gst_number: {
|
gst_number: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -68,18 +68,18 @@ const KycSchema = new Schema(
|
|||||||
},
|
},
|
||||||
gst_img: {
|
gst_img: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
// required: true,
|
||||||
},
|
},
|
||||||
pesticide_license_img: {
|
pesticide_license_img: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
// required: true,
|
||||||
},
|
},
|
||||||
fertilizer_license_img: {
|
fertilizer_license_img: {
|
||||||
type: Object,
|
type: Object,
|
||||||
},
|
},
|
||||||
selfie_entrance_img: {
|
selfie_entrance_img: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
// required: true,
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -13,12 +13,16 @@ import {
|
|||||||
UpdateProfileRD,
|
UpdateProfileRD,
|
||||||
updateRDMapped,
|
updateRDMapped,
|
||||||
updateunmapRD,
|
updateunmapRD,
|
||||||
|
uploadRetaildistributors,
|
||||||
} from "./RetailDistributorController.js";
|
} from "./RetailDistributorController.js";
|
||||||
import { isAuthenticatedRD } from "../../middlewares/rdAuth.js";
|
import { isAuthenticatedRD } from "../../middlewares/rdAuth.js";
|
||||||
import { authorizeRoles, isAuthenticatedUser } from "../../middlewares/auth.js";
|
import { authorizeRoles, isAuthenticatedUser } from "../../middlewares/auth.js";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/retaildistributor/upload")
|
||||||
|
.post(isAuthenticatedUser, authorizeRoles("admin"), uploadRetaildistributors);
|
||||||
router.route("/rd-login").post(loginRD);
|
router.route("/rd-login").post(loginRD);
|
||||||
router.route("/rd-get-me").get(isAuthenticatedRD, getmyProfileRD);
|
router.route("/rd-get-me").get(isAuthenticatedRD, getmyProfileRD);
|
||||||
router.post("/forgot-password", forgotPasswordRD);
|
router.post("/forgot-password", forgotPasswordRD);
|
||||||
@ -37,7 +41,7 @@ router
|
|||||||
authorizeRoles("admin"),
|
authorizeRoles("admin"),
|
||||||
getAllRetailDistributorApproved
|
getAllRetailDistributorApproved
|
||||||
);
|
);
|
||||||
router
|
router
|
||||||
.route("/getAllRDandorder")
|
.route("/getAllRDandorder")
|
||||||
.get(
|
.get(
|
||||||
isAuthenticatedUser,
|
isAuthenticatedUser,
|
||||||
@ -60,7 +64,7 @@ router
|
|||||||
router
|
router
|
||||||
.route("/mapped/:id")
|
.route("/mapped/:id")
|
||||||
.put(isAuthenticatedUser, authorizeRoles("admin"), updateRDMapped);
|
.put(isAuthenticatedUser, authorizeRoles("admin"), updateRDMapped);
|
||||||
router
|
router
|
||||||
.route("/unmap/:id")
|
.route("/unmap/:id")
|
||||||
.patch(isAuthenticatedUser, authorizeRoles("admin"), updateunmapRD);
|
.patch(isAuthenticatedUser, authorizeRoles("admin"), updateunmapRD);
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -5,6 +5,254 @@ import password from "secure-random-password";
|
|||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import { RdOrder } from "../RD_Ordes/rdOrderModal.js";
|
import { RdOrder } from "../RD_Ordes/rdOrderModal.js";
|
||||||
import sendEmail, { sendOtp } from "../../Utils/sendEmail.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) => {
|
export const loginRD = async (req, res) => {
|
||||||
const { email, password } = req.body;
|
const { email, password } = req.body;
|
||||||
|
|
||||||
|
@ -1,11 +1,192 @@
|
|||||||
// import hashPassword from '../utils/hashPassword';
|
// import hashPassword from '../utils/hashPassword';
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
|
import mongoose from "mongoose";
|
||||||
import SalesCoOrdinator from "./SalesCoOrdinatorModel.js";
|
import SalesCoOrdinator from "./SalesCoOrdinatorModel.js";
|
||||||
import sendEmail, { sendOtp } from "../../Utils/sendEmail.js";
|
import sendEmail, { sendOtp } from "../../Utils/sendEmail.js";
|
||||||
import validator from "validator";
|
import validator from "validator";
|
||||||
import password from "secure-random-password";
|
import password from "secure-random-password";
|
||||||
import catchAsyncErrors from "../../middlewares/catchAsyncErrors.js";
|
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) => {
|
export const register = async (req, res) => {
|
||||||
let { name, email, countryCode, mobileNumber, territoryManager } = req.body;
|
let { name, email, countryCode, mobileNumber, territoryManager } = req.body;
|
||||||
// console.log(req.body);
|
// console.log(req.body);
|
||||||
|
@ -50,11 +50,11 @@ const salescoordinatorSchema = new mongoose.Schema(
|
|||||||
uniqueId: {
|
uniqueId: {
|
||||||
type: String,
|
type: String,
|
||||||
unique: true,
|
unique: true,
|
||||||
required: true,
|
|
||||||
},
|
},
|
||||||
fcm_token: {
|
fcm_token: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
|
sparse: true,
|
||||||
},
|
},
|
||||||
mappedby: {
|
mappedby: {
|
||||||
type: mongoose.Schema.Types.ObjectId,
|
type: mongoose.Schema.Types.ObjectId,
|
||||||
|
@ -20,12 +20,16 @@ import {
|
|||||||
mappedbyTM,
|
mappedbyTM,
|
||||||
unmapSalesCoOrdinator,
|
unmapSalesCoOrdinator,
|
||||||
getAllSalesCoOrdinatorforTM_App,
|
getAllSalesCoOrdinatorforTM_App,
|
||||||
|
uploadSalesCoordinators,
|
||||||
} from "./SalesCoOrdinatorController.js";
|
} from "./SalesCoOrdinatorController.js";
|
||||||
import { isAuthenticatedSalesCoOrdinator } from "../../middlewares/SalesCoOrdinatorAuth.js";
|
import { isAuthenticatedSalesCoOrdinator } from "../../middlewares/SalesCoOrdinatorAuth.js";
|
||||||
import { isAuthenticatedTerritoryManager } from "../../middlewares/TerritoryManagerAuth.js";
|
import { isAuthenticatedTerritoryManager } from "../../middlewares/TerritoryManagerAuth.js";
|
||||||
import { authorizeRoles, isAuthenticatedUser } from "../../middlewares/auth.js";
|
import { authorizeRoles, isAuthenticatedUser } from "../../middlewares/auth.js";
|
||||||
|
|
||||||
router.post("/register", register);
|
router.post("/register", register);
|
||||||
|
router
|
||||||
|
.route("/upload")
|
||||||
|
.post(isAuthenticatedUser, authorizeRoles("admin"), uploadSalesCoordinators);
|
||||||
router.post("/verify-otp", verifyOtp);
|
router.post("/verify-otp", verifyOtp);
|
||||||
router.post("/login", loginSalesCoOrdinator);
|
router.post("/login", loginSalesCoOrdinator);
|
||||||
router.route("/logout").get(logout);
|
router.route("/logout").get(logout);
|
||||||
|
@ -1,11 +1,192 @@
|
|||||||
// import hashPassword from '../utils/hashPassword';
|
// import hashPassword from '../utils/hashPassword';
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
|
import mongoose from "mongoose";
|
||||||
import TerritoryManager from "./TerritoryManagerModel.js";
|
import TerritoryManager from "./TerritoryManagerModel.js";
|
||||||
import sendEmail, { sendOtp } from "../../Utils/sendEmail.js";
|
import sendEmail, { sendOtp } from "../../Utils/sendEmail.js";
|
||||||
import validator from "validator";
|
import validator from "validator";
|
||||||
import password from "secure-random-password";
|
import password from "secure-random-password";
|
||||||
import catchAsyncErrors from "../../middlewares/catchAsyncErrors.js";
|
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) => {
|
export const register = async (req, res) => {
|
||||||
let { name, email, countryCode, mobileNumber } = req.body;
|
let { name, email, countryCode, mobileNumber } = req.body;
|
||||||
countryCode = countryCode?.trim();
|
countryCode = countryCode?.trim();
|
||||||
|
@ -50,11 +50,11 @@ const territorymanagerSchema = new mongoose.Schema(
|
|||||||
uniqueId: {
|
uniqueId: {
|
||||||
type: String,
|
type: String,
|
||||||
unique: true,
|
unique: true,
|
||||||
required: true,
|
|
||||||
},
|
},
|
||||||
fcm_token: {
|
fcm_token: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
|
sparse: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ timestamps: true }
|
{ timestamps: true }
|
||||||
|
@ -16,11 +16,15 @@ import {
|
|||||||
ChangePassword,
|
ChangePassword,
|
||||||
getOneTerritoryManager,
|
getOneTerritoryManager,
|
||||||
logout,
|
logout,
|
||||||
|
uploadTerritoryManagers,
|
||||||
} from "./TerritoryManagerController.js";
|
} from "./TerritoryManagerController.js";
|
||||||
import { isAuthenticatedTerritoryManager } from "../../middlewares/TerritoryManagerAuth.js";
|
import { isAuthenticatedTerritoryManager } from "../../middlewares/TerritoryManagerAuth.js";
|
||||||
import { authorizeRoles, isAuthenticatedUser } from "../../middlewares/auth.js";
|
import { authorizeRoles, isAuthenticatedUser } from "../../middlewares/auth.js";
|
||||||
|
|
||||||
router.post("/register", register);
|
router.post("/register", register);
|
||||||
|
router
|
||||||
|
.route("/upload")
|
||||||
|
.post(isAuthenticatedUser, authorizeRoles("admin"), uploadTerritoryManagers);
|
||||||
router.post("/verify-otp", verifyOtp);
|
router.post("/verify-otp", verifyOtp);
|
||||||
router.post("/login", loginTerritoryManager);
|
router.post("/login", loginTerritoryManager);
|
||||||
router.route("/logout").get(logout);
|
router.route("/logout").get(logout);
|
||||||
@ -69,11 +73,7 @@ router.patch(
|
|||||||
authorizeRoles("admin"),
|
authorizeRoles("admin"),
|
||||||
UpdateProfile
|
UpdateProfile
|
||||||
);
|
);
|
||||||
router.patch(
|
router.patch("/profile/update", isAuthenticatedTerritoryManager, UpdateProfile);
|
||||||
"/profile/update",
|
|
||||||
isAuthenticatedTerritoryManager,
|
|
||||||
UpdateProfile
|
|
||||||
);
|
|
||||||
//change password
|
//change password
|
||||||
router.put(
|
router.put(
|
||||||
"/password/update/:id",
|
"/password/update/:id",
|
||||||
@ -82,11 +82,7 @@ router.put(
|
|||||||
ChangePassword
|
ChangePassword
|
||||||
);
|
);
|
||||||
|
|
||||||
router.put(
|
router.put("/password/update", isAuthenticatedTerritoryManager, ChangePassword);
|
||||||
"/password/update",
|
|
||||||
isAuthenticatedTerritoryManager,
|
|
||||||
ChangePassword
|
|
||||||
);
|
|
||||||
//delete TerritoryManager
|
//delete TerritoryManager
|
||||||
router.delete(
|
router.delete(
|
||||||
"/delete/:id",
|
"/delete/:id",
|
||||||
|
Loading…
Reference in New Issue
Block a user