diff --git a/app.js b/app.js index 6a0efd6..b5caf77 100644 --- a/app.js +++ b/app.js @@ -186,6 +186,9 @@ import attendance from "./resources/Attendance/AttandanceRoute.js"; import leave from "./resources/Leaves/LeaveRoute.js"; // Notification import notification from "./resources/Notification/notificationRoute.js" +//Inventory +import InventoryRoute from "./resources/Inventory/InventoryRoute.js"; + app.use("/api/v1", user); //Product @@ -253,6 +256,9 @@ app.use("/api/v1", leave); // notification route app.use("/api",notification) +//Inventory +app.use("/api/inventory", InventoryRoute); + //config specialty // app.use("/api/config/specialty", SpecialtiesRouter); //specialties diff --git a/resources/Inventory/InventoryController.js b/resources/Inventory/InventoryController.js new file mode 100644 index 0000000..694a23a --- /dev/null +++ b/resources/Inventory/InventoryController.js @@ -0,0 +1,145 @@ +import { Inventory } from "../Inventory/InventoryModel.js"; +import User from "../user/userModel.js"; +import { KYC } from "../KYC/KycModel.js"; +import ShippingAddress from "../ShippingAddresses/ShippingAddressModel.js"; +import TerritoryManager from "../TerritoryManagers/TerritoryManagerModel.js"; +import SalesCoordinator from "../SalesCoOrdinators/SalesCoOrdinatorModel.js"; +// Add inventory data +export const addInventory = async (req, res) => { + try { + const { products, addedFor, addedForId } = req.body; + const userId = req.user._id; + const userType = req.userType; + // console.log("req.user", req.user); + const newInventory = new Inventory({ + userId, + userType, + addedFor, + addedForId, + products, + }); + + await newInventory.save(); + res.status(201).json({ + success: true, + message: "Inventory added successfully", + data: newInventory, + }); + } catch (error) { + res.status(500).json({ success: false, message: "Server error", error }); + } +}; + +// Get all distributors (PD or RD) +export const getDistributors = async (req, res) => { + try { + const { type } = req.params; + + if (!["PrincipalDistributor", "RetailDistributor"].includes(type)) { + return res.status(400).json({ message: "Invalid distributor type" }); + } + + let distributors; +// console.log("type",type); + if (type === "PrincipalDistributor") { + // Fetch all PrincipalDistributors + const principalDistributors = await User.find({ role: "principal-Distributor" }); +// console.log("principalDistributors",principalDistributors); + // Map each PrincipalDistributor to include their ShippingAddress + distributors = await Promise.all(principalDistributors.map(async (distributor) => { + const shippingAddress = await ShippingAddress.findOne({ user: distributor._id }); + return { + ...distributor.toObject(), + shippingAddress, + }; + })); + } else { + // For RetailDistributor, fetch approved KYC documents + distributors = await KYC.find({ status: "approved" }); + } + + res.status(200).json(distributors); + } catch (error) { + res.status(500).json({ message: error.message }); + } +}; + +export const getAllInventories = async (req, res) => { + try { + const inventories = await Inventory.find(); + + const populatedInventories = await Promise.all(inventories.map(async (inventory) => { + // Populate user details based on userType + let user = null; + if (inventory.userType === 'TerritoryManager') { + user = await TerritoryManager.findById(inventory.userId); + } else if (inventory.userType === 'SalesCoOrdinator') { + user = await SalesCoordinator.findById(inventory.userId); + } + + // Populate addedFor details based on addedFor + let addedForData = null; + if (inventory.addedFor === 'PrincipalDistributor') { + addedForData = await User.findById(inventory.addedForId); + const shippingAddress = await ShippingAddress.findOne({ user: addedForData._id }); + addedForData = { + ...addedForData.toObject(), + shippingAddress, + }; + } else if (inventory.addedFor === 'RetailDistributor') { + addedForData = await KYC.findById(inventory.addedForId); + } + + return { + ...inventory.toObject(), + user, + addedForData, + }; + })); + + res.status(200).json(populatedInventories); + } catch (error) { + res.status(500).json({ message: error.message }); + } +}; + +export const getSingleInventory = async (req, res) => { + try { + const { id } = req.params; + + const inventory = await Inventory.findById(id); + if (!inventory) { + return res.status(404).json({ message: "Inventory not found" }); + } + + // Populate user details based on userType + let user = null; + if (inventory.userType === 'TerritoryManager') { + user = await TerritoryManager.findById(inventory.userId); + } else if (inventory.userType === 'SalesCoOrdinator') { + user = await SalesCoordinator.findById(inventory.userId); + } + + // Populate addedFor details based on addedFor + let addedForData = null; + if (inventory.addedFor === 'PrincipalDistributor') { + addedForData = await User.findById(inventory.addedForId); + const shippingAddress = await ShippingAddress.findOne({ user: addedForData._id }); + addedForData = { + ...addedForData.toObject(), + shippingAddress, + }; + } else if (inventory.addedFor === 'RetailDistributor') { + addedForData = await KYC.findById(inventory.addedForId); + } + + res.status(200).json({ + ...inventory.toObject(), + user, + addedForData, + }); + } catch (error) { + res.status(500).json({ message: error.message }); + } +}; + diff --git a/resources/Inventory/InventoryModel.js b/resources/Inventory/InventoryModel.js new file mode 100644 index 0000000..db6a91f --- /dev/null +++ b/resources/Inventory/InventoryModel.js @@ -0,0 +1,48 @@ +import mongoose from 'mongoose'; + +// Define Product record schema +const ProductRecordSchema = new mongoose.Schema({ + SKU: { + type: String, + required: true, + }, + ProductName: { + type: String, + required: true, + }, + Sale: { + type: Number, + required: true, + }, + Inventory: { + type: Number, + required: true, + }, +}); + +// Define main Inventory schema +const InventorySchema = new mongoose.Schema({ + userId: { + type: mongoose.Schema.Types.ObjectId, + refPath: 'userType', + required: true, + }, + userType: { + type: String, + required: true, + enum: ['SalesCoOrdinator', 'TerritoryManager'], + }, + addedFor: { + type: String, + required: true, + enum: ['PrincipalDistributor', 'RetailDistributor'], + }, + addedForId: { + type: mongoose.Schema.Types.ObjectId, + refPath: 'addedFor', + required: true, + }, + products: [ProductRecordSchema], +}, { timestamps: true, versionKey: false }); + +export const Inventory = mongoose.model('Inventory', InventorySchema); diff --git a/resources/Inventory/InventoryRoute.js b/resources/Inventory/InventoryRoute.js new file mode 100644 index 0000000..36e64b7 --- /dev/null +++ b/resources/Inventory/InventoryRoute.js @@ -0,0 +1,36 @@ +import express from "express"; +import { addInventory, getDistributors,getAllInventories,getSingleInventory } from "./InventoryController.js"; +import { isAuthenticatedSalesCoOrdinator } from "../../middlewares/SalesCoOrdinatorAuth.js"; +import { isAuthenticatedTerritoryManager } from "../../middlewares/TerritoryManagerAuth.js"; +import { authorizeRoles, isAuthenticatedUser } from "../../middlewares/auth.js"; +const router = express.Router(); + +// Route to add inventory data +router.post("/add-SC", isAuthenticatedSalesCoOrdinator, addInventory); +router.post("/add-TM", isAuthenticatedTerritoryManager, addInventory); +// Route to get all PD or RD names based on type +router.get( + "/distributors-SC/:type", + isAuthenticatedSalesCoOrdinator, + getDistributors +); +router.get( + "/distributors-TM/:type", + isAuthenticatedTerritoryManager, + getDistributors +); + +// Admin routes +router.get( + "/all", + isAuthenticatedUser, + authorizeRoles("admin"), + getAllInventories +); +router.get( + "/:id", + isAuthenticatedUser, + authorizeRoles("admin"), + getSingleInventory +); +export default router; diff --git a/resources/Products/ProductController.js b/resources/Products/ProductController.js index 54e74ed..76db1b8 100644 --- a/resources/Products/ProductController.js +++ b/resources/Products/ProductController.js @@ -41,6 +41,7 @@ export const uploadProducts = async (req, res) => { // Map headers from the Excel file to your schema const headerMapping = { + SKU: "SKU", "Product Name": "name", "category Name": "category", price: "price", @@ -73,13 +74,14 @@ export const uploadProducts = async (req, res) => { const missingFields = new Set(); const notFoundErrors = new Set(); - let { name, category, price, GST, description, special_instructions } = item; + let { SKU, name, category, price, GST, description, special_instructions } = item; // Trim leading and trailing spaces from product name and GST name = name ? name.trim() : ""; GST = GST ? GST.toString().trim() : ""; // Validate required fields + if (!SKU) missingFields.add("SKU"); if (!name) missingFields.add("name"); if (!category) missingFields.add("category"); if (price === undefined || price === "") missingFields.add("price"); @@ -129,6 +131,7 @@ export const uploadProducts = async (req, res) => { // If there are errors, push them to the errors array if (errorMessage.trim()) { errors.push({ + SKU: SKU || "N/A", productName: name || "N/A", category: category || "N/A", GST: GST || "N/A", @@ -142,10 +145,8 @@ export const uploadProducts = async (req, res) => { description = description !== undefined ? description : ""; special_instructions = special_instructions !== undefined ? special_instructions : ""; - // Check for existing product - let existingProduct = await Product.findOne({ - name: new RegExp(`^${name}$`, "i"), - }).exec(); + // Check for existing product by SKU + let existingProduct = await Product.findOne({ SKU }).exec(); if (existingProduct) { // Track changes @@ -185,7 +186,7 @@ export const uploadProducts = async (req, res) => { if (updatedFields.length > 0) { try { await Product.updateOne( - { _id: existingProduct._id }, + { SKU: existingProduct.SKU }, { $set: { category: item.category || existingProduct.category, @@ -203,7 +204,7 @@ export const uploadProducts = async (req, res) => { }); } catch (error) { errors.push({ - productName: name, + SKU, message: "Failed to update product", }); } @@ -214,6 +215,7 @@ export const uploadProducts = async (req, res) => { // Create new product if (item.category && item.GST) { const productData = { + SKU, name, category: item.category, price, @@ -232,7 +234,7 @@ export const uploadProducts = async (req, res) => { }); } catch (error) { errors.push({ - productName: name, + SKU, message: "Failed to create product", }); } @@ -249,8 +251,6 @@ export const uploadProducts = async (req, res) => { newlyCreated: newlyCreated, updatedProducts: updatedProducts, errors, - newlyCreated, - updatedProducts, }); } catch (error) { console.error("Error:", error); @@ -263,34 +263,46 @@ export const createProduct = async (req, res) => { try { let findProduct = ""; let product = { _id: "" }; - // console.log("req.body", req.body); + const sku = req.body?.SKU; + + if (!sku) { + return res.status(400).json({ message: "SKU is required!" }); + } + + // Check if SKU exists in the request body if (req.body?.product_id) { findProduct = await Product.findById(req.body.product_id); } - const name = req.body?.name; + if (!findProduct) { - const data = await Product.findOne({ - name: { $regex: new RegExp(`^${name}$`, "ig") }, + // Check if a product with the same SKU already exists + const existingProduct = await Product.findOne({ + SKU: { $regex: new RegExp(`^${sku}$`, "i") }, }).exec(); - if (data) - return res - .status(400) - .json({ message: "Product name already exists!" }); + + if (existingProduct) { + return res.status(400).json({ message: "Product with this SKU already exists!" }); + } + + // Add user ID to the request body req.body.addedBy = req.user._id; product = await Product.create(req.body); } else { - const data = await Product.findOne({ + // Check if another product with the same SKU exists, excluding the current one + const existingProduct = await Product.findOne({ _id: { $ne: findProduct._id }, - name: { $regex: new RegExp(`^${name}$`, "ig") }, + SKU: { $regex: new RegExp(`^${sku}$`, "i") }, }).exec(); - if (data) - return res - .status(400) - .json({ message: "Product name already exists!" }); - product = await Product.findByIdAndUpdate(req.body.product_id, req.body); + + if (existingProduct) { + return res.status(400).json({ message: "Product with this SKU already exists!" }); + } + + product = await Product.findByIdAndUpdate(req.body.product_id, req.body, { new: true }); } + res.status(201).json({ - message: "Product details added successfully!", + message: "Product details added/updated successfully!", product_id: product._id, }); } catch (error) { @@ -300,6 +312,7 @@ export const createProduct = async (req, res) => { } }; + /////////////////////////////////////////////////////////////////////////////////////// export const updateProduct = async (req, res) => { try { @@ -435,14 +448,35 @@ export const getAllProductUser = async (req, res) => { const PAGE_SIZE = parseInt(req.query?.show || "10"); const page = parseInt(req.query?.page - 1 || "0"); let obj = {}; - if (req.query?.name) + + // Filter by category + if (req.query?.category) { + const category = await CategoryModel.findOne({ name: req.query.category }); + if (category) { + obj.category = category._id; + } else { + return res.status(400).json({ + success: false, + msg: "Category not found!", + }); + } + } + // Filter by SKU or product name + if (req.query?.SKU) { + obj.SKU = req.query.SKU; + } + if (req.query?.name) { obj.name = { - $regex: new RegExp(req.query.name), - $options: "i", + $regex: new RegExp(req.query.name, 'i'), }; - if (req.query?.category) obj.category = req.query.category; + } + obj.product_Status = "Active"; + + // Get total count const total = await Product.countDocuments(obj); + + // Get filtered products const product = await Product.find(obj) .populate({ path: "category addedBy GST", @@ -450,10 +484,7 @@ export const getAllProductUser = async (req, res) => { }) .limit(PAGE_SIZE) .skip(PAGE_SIZE * page) - // .sort("name") - .sort({ - createdAt: -1, - }) + .sort({ createdAt: -1 }) .exec(); if (product) { @@ -463,11 +494,16 @@ export const getAllProductUser = async (req, res) => { total_pages: Math.ceil(total / PAGE_SIZE), product, }); + } else { + return res.status(404).json({ + success: false, + msg: "No products found!", + }); } } catch (error) { res.status(500).json({ success: false, - msg: error.message ? error.message : "Something went wrong!", + msg: error.message || "Something went wrong!", }); } }; diff --git a/resources/Products/ProductModel.js b/resources/Products/ProductModel.js index bf8c86c..485de87 100644 --- a/resources/Products/ProductModel.js +++ b/resources/Products/ProductModel.js @@ -3,6 +3,12 @@ const { Schema, model } = mongoose; const productSchema = new Schema( { + SKU: { + type: String, + required: [true, "Please Enter product SKU"], + unique: true, + trim: true, + }, name: { type: String, maxLength: [35, "name cannot exceed 25 characters"], diff --git a/resources/user/userController.js b/resources/user/userController.js index 19b8d1a..d4eab78 100644 --- a/resources/user/userController.js +++ b/resources/user/userController.js @@ -91,6 +91,7 @@ export const uploadPrincipaldistributors = async (req, res) => { // Map headers from the Excel file to your schema const headerMapping = { + "PD ID (From SAP)": "uniqueId", "Principal Distributor Name": "name", "Email": "email", "Phone Number": "phone", @@ -128,6 +129,7 @@ export const uploadPrincipaldistributors = async (req, res) => { const validationErrors = new Set(); // Validate required fields + if (!item.uniqueId) missingFields.add("uniqueId"); if (!item.name) missingFields.add("name"); if (!item.email) missingFields.add("email"); if (!item.phone) missingFields.add("phone"); @@ -180,6 +182,7 @@ export const uploadPrincipaldistributors = async (req, res) => { // If there are errors, push them to the errors array if (errorMessage.trim()) { errors.push({ + uniqueId: item.uniqueId || "N/A", name: item.name || "N/A", email: item.email || "N/A", phone: item.phone || "N/A", @@ -193,35 +196,32 @@ export const uploadPrincipaldistributors = async (req, res) => { // Generate a password const password = generatePassword(item.name, item.email); item.role = "principal-Distributor"; - const currentYear = new Date().getFullYear().toString().slice(-2); - const randomChars = crypto.randomBytes(4).toString("hex").toUpperCase(); - item.uniqueId = `${currentYear}-${randomChars}`; - // Check for existing user - let user = await User.findOne({ email: item.email }); + // Check for existing user by uniqueId + let distributor = await User.findOne({ uniqueId: item.uniqueId }); - if (user) { + if (distributor) { // Track updated fields const updatedFields = []; - const addressFields = ['panNumber', 'gstNumber', 'state','city', 'street', 'tradeName', 'postalCode']; - const existingAddress = await ShippingAddress.findOne({ user: user._id }); + const addressFields = ['panNumber', 'gstNumber', 'state', 'city', 'street', 'tradeName', 'postalCode']; + const existingAddress = await ShippingAddress.findOne({ user: distributor._id }); // Check for changes in user details let userUpdated = false; - if (user.name !== item.name) { + if (distributor.name !== item.name) { updatedFields.push("name"); - user.name = item.name; + distributor.name = item.name; userUpdated = true; } - if (user.phone !== item.phone.toString()) { + if (distributor.phone !== item.phone.toString()) { updatedFields.push("phone"); - user.phone = item.phone; + distributor.phone = item.phone; userUpdated = true; } // Update user if (userUpdated) { - await user.save(); + await distributor.save(); } // Check for changes in address details @@ -234,7 +234,7 @@ export const uploadPrincipaldistributors = async (req, res) => { panNumber: item.panNumber, tradeName: item.tradeName, gstNumber: item.gstNumber, - user: user._id, + user: distributor._id, }; let addressUpdated = false; @@ -248,7 +248,7 @@ export const uploadPrincipaldistributors = async (req, res) => { }); if (addressUpdated) { - await ShippingAddress.updateOne({ user: user._id }, addressData); + await ShippingAddress.updateOne({ user: distributor._id }, addressData); if (addressUpdates.length > 0) { updatedFields.push(`Address fields: ${addressUpdates.join(", ")}`); } @@ -262,13 +262,13 @@ export const uploadPrincipaldistributors = async (req, res) => { // Add to updatedDistributors only if there are updated fields if (updatedFields.length > 0) { updatedDistributors.push({ - ...user._doc, + ...distributor._doc, updatedFields: updatedFields.join(", ") }); } } else { // Create a new user - user = new User({ + distributor = new User({ name: item.name, email: item.email, phone: item.phone, @@ -276,7 +276,7 @@ export const uploadPrincipaldistributors = async (req, res) => { role: item.role, uniqueId: item.uniqueId, }); - await user.save(); + await distributor.save(); // Send email with the new user details await sendEmail({ @@ -293,7 +293,7 @@ export const uploadPrincipaldistributors = async (req, res) => { `, }); - newlyCreated.push(user._doc); + newlyCreated.push(distributor._doc); } } @@ -314,7 +314,7 @@ export const uploadPrincipaldistributors = async (req, res) => { }); } catch (error) { console.error("Error processing file:", error); - res.status(500).json({ message: "Internal server error" }); + res.status(500).json({ message: "Error processing file", error: error.message }); } }; @@ -394,12 +394,13 @@ export const uploadPrincipaldistributors = async (req, res) => { // }; export const registerUser = async (req, res) => { try { - const { name, email, phone, accessTo, role } = req.body; + const { name, email, phone, accessTo, role,PD_ID } = req.body; // console.log(req.body); const password = generatePassword(name, email); // console.log(password); // Check if user already exists - let user = await User.findOne({ email }); + let user = await User.findOne({ uniqueId: PD_ID }); + // console.log(user); if (user) { // If user exists, update their details if needed user.name = name; @@ -421,6 +422,7 @@ export const registerUser = async (req, res) => { // Create a new user if not found user = new User({ + uniqueId: PD_ID, name, email, password, @@ -428,11 +430,11 @@ export const registerUser = async (req, res) => { role, accessTo, }); - // console.log(user); - // Generate uniqueId - const currentYear = new Date().getFullYear().toString().slice(-2); - const randomChars = crypto.randomBytes(4).toString("hex").toUpperCase(); - user.uniqueId = `${currentYear}-${randomChars}`; + // console.log("user",user); + // // Generate uniqueId + // const currentYear = new Date().getFullYear().toString().slice(-2); + // const randomChars = crypto.randomBytes(4).toString("hex").toUpperCase(); + // user.uniqueId = `${currentYear}-${randomChars}`; // Save the new user to the database await user.save();