Inventory

This commit is contained in:
Sibunnayak 2024-08-14 11:24:51 +05:30
parent c89b57b89f
commit 01e12dedaf
7 changed files with 341 additions and 62 deletions

6
app.js
View File

@ -186,6 +186,9 @@ import attendance from "./resources/Attendance/AttandanceRoute.js";
import leave from "./resources/Leaves/LeaveRoute.js"; import leave from "./resources/Leaves/LeaveRoute.js";
// Notification // Notification
import notification from "./resources/Notification/notificationRoute.js" import notification from "./resources/Notification/notificationRoute.js"
//Inventory
import InventoryRoute from "./resources/Inventory/InventoryRoute.js";
app.use("/api/v1", user); app.use("/api/v1", user);
//Product //Product
@ -253,6 +256,9 @@ app.use("/api/v1", leave);
// notification route // notification route
app.use("/api",notification) app.use("/api",notification)
//Inventory
app.use("/api/inventory", InventoryRoute);
//config specialty //config specialty
// app.use("/api/config/specialty", SpecialtiesRouter); // app.use("/api/config/specialty", SpecialtiesRouter);
//specialties //specialties

View File

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

View File

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

View File

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

View File

@ -41,6 +41,7 @@ export const uploadProducts = async (req, res) => {
// Map headers from the Excel file to your schema // Map headers from the Excel file to your schema
const headerMapping = { const headerMapping = {
SKU: "SKU",
"Product Name": "name", "Product Name": "name",
"category Name": "category", "category Name": "category",
price: "price", price: "price",
@ -73,13 +74,14 @@ export const uploadProducts = async (req, res) => {
const missingFields = new Set(); const missingFields = new Set();
const notFoundErrors = 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 // Trim leading and trailing spaces from product name and GST
name = name ? name.trim() : ""; name = name ? name.trim() : "";
GST = GST ? GST.toString().trim() : ""; GST = GST ? GST.toString().trim() : "";
// Validate required fields // Validate required fields
if (!SKU) missingFields.add("SKU");
if (!name) missingFields.add("name"); if (!name) missingFields.add("name");
if (!category) missingFields.add("category"); if (!category) missingFields.add("category");
if (price === undefined || price === "") missingFields.add("price"); 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 there are errors, push them to the errors array
if (errorMessage.trim()) { if (errorMessage.trim()) {
errors.push({ errors.push({
SKU: SKU || "N/A",
productName: name || "N/A", productName: name || "N/A",
category: category || "N/A", category: category || "N/A",
GST: GST || "N/A", GST: GST || "N/A",
@ -142,10 +145,8 @@ export const uploadProducts = async (req, res) => {
description = description !== undefined ? description : ""; description = description !== undefined ? description : "";
special_instructions = special_instructions !== undefined ? special_instructions : ""; special_instructions = special_instructions !== undefined ? special_instructions : "";
// Check for existing product // Check for existing product by SKU
let existingProduct = await Product.findOne({ let existingProduct = await Product.findOne({ SKU }).exec();
name: new RegExp(`^${name}$`, "i"),
}).exec();
if (existingProduct) { if (existingProduct) {
// Track changes // Track changes
@ -185,7 +186,7 @@ export const uploadProducts = async (req, res) => {
if (updatedFields.length > 0) { if (updatedFields.length > 0) {
try { try {
await Product.updateOne( await Product.updateOne(
{ _id: existingProduct._id }, { SKU: existingProduct.SKU },
{ {
$set: { $set: {
category: item.category || existingProduct.category, category: item.category || existingProduct.category,
@ -203,7 +204,7 @@ export const uploadProducts = async (req, res) => {
}); });
} catch (error) { } catch (error) {
errors.push({ errors.push({
productName: name, SKU,
message: "Failed to update product", message: "Failed to update product",
}); });
} }
@ -214,6 +215,7 @@ export const uploadProducts = async (req, res) => {
// Create new product // Create new product
if (item.category && item.GST) { if (item.category && item.GST) {
const productData = { const productData = {
SKU,
name, name,
category: item.category, category: item.category,
price, price,
@ -232,7 +234,7 @@ export const uploadProducts = async (req, res) => {
}); });
} catch (error) { } catch (error) {
errors.push({ errors.push({
productName: name, SKU,
message: "Failed to create product", message: "Failed to create product",
}); });
} }
@ -249,8 +251,6 @@ export const uploadProducts = async (req, res) => {
newlyCreated: newlyCreated, newlyCreated: newlyCreated,
updatedProducts: updatedProducts, updatedProducts: updatedProducts,
errors, errors,
newlyCreated,
updatedProducts,
}); });
} catch (error) { } catch (error) {
console.error("Error:", error); console.error("Error:", error);
@ -263,34 +263,46 @@ export const createProduct = async (req, res) => {
try { try {
let findProduct = ""; let findProduct = "";
let product = { _id: "" }; 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) { if (req.body?.product_id) {
findProduct = await Product.findById(req.body.product_id); findProduct = await Product.findById(req.body.product_id);
} }
const name = req.body?.name;
if (!findProduct) { if (!findProduct) {
const data = await Product.findOne({ // Check if a product with the same SKU already exists
name: { $regex: new RegExp(`^${name}$`, "ig") }, const existingProduct = await Product.findOne({
SKU: { $regex: new RegExp(`^${sku}$`, "i") },
}).exec(); }).exec();
if (data)
return res if (existingProduct) {
.status(400) return res.status(400).json({ message: "Product with this SKU already exists!" });
.json({ message: "Product name already exists!" }); }
// Add user ID to the request body
req.body.addedBy = req.user._id; req.body.addedBy = req.user._id;
product = await Product.create(req.body); product = await Product.create(req.body);
} else { } 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 }, _id: { $ne: findProduct._id },
name: { $regex: new RegExp(`^${name}$`, "ig") }, SKU: { $regex: new RegExp(`^${sku}$`, "i") },
}).exec(); }).exec();
if (data)
return res if (existingProduct) {
.status(400) return res.status(400).json({ message: "Product with this SKU already exists!" });
.json({ message: "Product name already exists!" });
product = await Product.findByIdAndUpdate(req.body.product_id, req.body);
} }
product = await Product.findByIdAndUpdate(req.body.product_id, req.body, { new: true });
}
res.status(201).json({ res.status(201).json({
message: "Product details added successfully!", message: "Product details added/updated successfully!",
product_id: product._id, product_id: product._id,
}); });
} catch (error) { } catch (error) {
@ -300,6 +312,7 @@ export const createProduct = async (req, res) => {
} }
}; };
/////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////
export const updateProduct = async (req, res) => { export const updateProduct = async (req, res) => {
try { try {
@ -435,14 +448,35 @@ export const getAllProductUser = async (req, res) => {
const PAGE_SIZE = parseInt(req.query?.show || "10"); const PAGE_SIZE = parseInt(req.query?.show || "10");
const page = parseInt(req.query?.page - 1 || "0"); const page = parseInt(req.query?.page - 1 || "0");
let obj = {}; 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 = { obj.name = {
$regex: new RegExp(req.query.name), $regex: new RegExp(req.query.name, 'i'),
$options: "i",
}; };
if (req.query?.category) obj.category = req.query.category; }
obj.product_Status = "Active"; obj.product_Status = "Active";
// Get total count
const total = await Product.countDocuments(obj); const total = await Product.countDocuments(obj);
// Get filtered products
const product = await Product.find(obj) const product = await Product.find(obj)
.populate({ .populate({
path: "category addedBy GST", path: "category addedBy GST",
@ -450,10 +484,7 @@ export const getAllProductUser = async (req, res) => {
}) })
.limit(PAGE_SIZE) .limit(PAGE_SIZE)
.skip(PAGE_SIZE * page) .skip(PAGE_SIZE * page)
// .sort("name") .sort({ createdAt: -1 })
.sort({
createdAt: -1,
})
.exec(); .exec();
if (product) { if (product) {
@ -463,11 +494,16 @@ export const getAllProductUser = async (req, res) => {
total_pages: Math.ceil(total / PAGE_SIZE), total_pages: Math.ceil(total / PAGE_SIZE),
product, product,
}); });
} else {
return res.status(404).json({
success: false,
msg: "No products found!",
});
} }
} catch (error) { } catch (error) {
res.status(500).json({ res.status(500).json({
success: false, success: false,
msg: error.message ? error.message : "Something went wrong!", msg: error.message || "Something went wrong!",
}); });
} }
}; };

View File

@ -3,6 +3,12 @@ const { Schema, model } = mongoose;
const productSchema = new Schema( const productSchema = new Schema(
{ {
SKU: {
type: String,
required: [true, "Please Enter product SKU"],
unique: true,
trim: true,
},
name: { name: {
type: String, type: String,
maxLength: [35, "name cannot exceed 25 characters"], maxLength: [35, "name cannot exceed 25 characters"],

View File

@ -91,6 +91,7 @@ export const uploadPrincipaldistributors = async (req, res) => {
// Map headers from the Excel file to your schema // Map headers from the Excel file to your schema
const headerMapping = { const headerMapping = {
"PD ID (From SAP)": "uniqueId",
"Principal Distributor Name": "name", "Principal Distributor Name": "name",
"Email": "email", "Email": "email",
"Phone Number": "phone", "Phone Number": "phone",
@ -128,6 +129,7 @@ export const uploadPrincipaldistributors = async (req, res) => {
const validationErrors = new Set(); const validationErrors = new Set();
// Validate required fields // Validate required fields
if (!item.uniqueId) missingFields.add("uniqueId");
if (!item.name) missingFields.add("name"); if (!item.name) missingFields.add("name");
if (!item.email) missingFields.add("email"); if (!item.email) missingFields.add("email");
if (!item.phone) missingFields.add("phone"); 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 there are errors, push them to the errors array
if (errorMessage.trim()) { if (errorMessage.trim()) {
errors.push({ errors.push({
uniqueId: item.uniqueId || "N/A",
name: item.name || "N/A", name: item.name || "N/A",
email: item.email || "N/A", email: item.email || "N/A",
phone: item.phone || "N/A", phone: item.phone || "N/A",
@ -193,35 +196,32 @@ export const uploadPrincipaldistributors = async (req, res) => {
// Generate a password // Generate a password
const password = generatePassword(item.name, item.email); const password = generatePassword(item.name, item.email);
item.role = "principal-Distributor"; 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 // Check for existing user by uniqueId
let user = await User.findOne({ email: item.email }); let distributor = await User.findOne({ uniqueId: item.uniqueId });
if (user) { if (distributor) {
// Track updated fields // Track updated fields
const updatedFields = []; const updatedFields = [];
const addressFields = ['panNumber', 'gstNumber', 'state', 'city', 'street', 'tradeName', 'postalCode']; const addressFields = ['panNumber', 'gstNumber', 'state', 'city', 'street', 'tradeName', 'postalCode'];
const existingAddress = await ShippingAddress.findOne({ user: user._id }); const existingAddress = await ShippingAddress.findOne({ user: distributor._id });
// Check for changes in user details // Check for changes in user details
let userUpdated = false; let userUpdated = false;
if (user.name !== item.name) { if (distributor.name !== item.name) {
updatedFields.push("name"); updatedFields.push("name");
user.name = item.name; distributor.name = item.name;
userUpdated = true; userUpdated = true;
} }
if (user.phone !== item.phone.toString()) { if (distributor.phone !== item.phone.toString()) {
updatedFields.push("phone"); updatedFields.push("phone");
user.phone = item.phone; distributor.phone = item.phone;
userUpdated = true; userUpdated = true;
} }
// Update user // Update user
if (userUpdated) { if (userUpdated) {
await user.save(); await distributor.save();
} }
// Check for changes in address details // Check for changes in address details
@ -234,7 +234,7 @@ export const uploadPrincipaldistributors = async (req, res) => {
panNumber: item.panNumber, panNumber: item.panNumber,
tradeName: item.tradeName, tradeName: item.tradeName,
gstNumber: item.gstNumber, gstNumber: item.gstNumber,
user: user._id, user: distributor._id,
}; };
let addressUpdated = false; let addressUpdated = false;
@ -248,7 +248,7 @@ export const uploadPrincipaldistributors = async (req, res) => {
}); });
if (addressUpdated) { if (addressUpdated) {
await ShippingAddress.updateOne({ user: user._id }, addressData); await ShippingAddress.updateOne({ user: distributor._id }, addressData);
if (addressUpdates.length > 0) { if (addressUpdates.length > 0) {
updatedFields.push(`Address fields: ${addressUpdates.join(", ")}`); 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 // Add to updatedDistributors only if there are updated fields
if (updatedFields.length > 0) { if (updatedFields.length > 0) {
updatedDistributors.push({ updatedDistributors.push({
...user._doc, ...distributor._doc,
updatedFields: updatedFields.join(", ") updatedFields: updatedFields.join(", ")
}); });
} }
} else { } else {
// Create a new user // Create a new user
user = new User({ distributor = new User({
name: item.name, name: item.name,
email: item.email, email: item.email,
phone: item.phone, phone: item.phone,
@ -276,7 +276,7 @@ export const uploadPrincipaldistributors = async (req, res) => {
role: item.role, role: item.role,
uniqueId: item.uniqueId, uniqueId: item.uniqueId,
}); });
await user.save(); await distributor.save();
// Send email with the new user details // Send email with the new user details
await sendEmail({ 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) { } catch (error) {
console.error("Error processing file:", 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) => { export const registerUser = async (req, res) => {
try { try {
const { name, email, phone, accessTo, role } = req.body; const { name, email, phone, accessTo, role,PD_ID } = req.body;
// console.log(req.body); // console.log(req.body);
const password = generatePassword(name, email); const password = generatePassword(name, email);
// console.log(password); // console.log(password);
// Check if user already exists // 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) {
// If user exists, update their details if needed // If user exists, update their details if needed
user.name = name; user.name = name;
@ -421,6 +422,7 @@ export const registerUser = async (req, res) => {
// Create a new user if not found // Create a new user if not found
user = new User({ user = new User({
uniqueId: PD_ID,
name, name,
email, email,
password, password,
@ -428,11 +430,11 @@ export const registerUser = async (req, res) => {
role, role,
accessTo, accessTo,
}); });
// console.log(user); // console.log("user",user);
// Generate uniqueId // // Generate uniqueId
const currentYear = new Date().getFullYear().toString().slice(-2); // const currentYear = new Date().getFullYear().toString().slice(-2);
const randomChars = crypto.randomBytes(4).toString("hex").toUpperCase(); // const randomChars = crypto.randomBytes(4).toString("hex").toUpperCase();
user.uniqueId = `${currentYear}-${randomChars}`; // user.uniqueId = `${currentYear}-${randomChars}`;
// Save the new user to the database // Save the new user to the database
await user.save(); await user.save();