diff --git a/app.js b/app.js index 77f5d9e..2a5385c 100644 --- a/app.js +++ b/app.js @@ -151,6 +151,7 @@ import SalesCoOrdinatorRoute from "./resources/SalesCoOrdinators/SalesCoOrdinato import TerritoryManagerRoute from "./resources/TerritoryManagers/TerritoryManagerRoute.js"; // category Route import categoryRoute from "./resources/Category/categoryRoutes.js"; +import brandroute from "./resources/Brands/BrandsRoutes.js"; import RegistrationImageRoute from "./resources/RegistrationImage/RegistrationImageRoute.js"; import loginImageRoute from "./resources/LoginImage/LoginImageRoute.js"; import shopImageRoute from "./resources/ShopPageImage/ShopPageImageRoute.js"; @@ -199,6 +200,8 @@ app.use("/api", ProductRouter); // Category app.use("/api/category", categoryRoute); +//Brand +app.use("/api/brand", brandroute); // registration image app.use("/api/registerImage", RegistrationImageRoute); app.use("/api/loginImage", loginImageRoute); diff --git a/public/temp/tmp-1-1724319327212 b/public/temp/tmp-1-1724319327212 new file mode 100644 index 0000000..570d4e2 Binary files /dev/null and b/public/temp/tmp-1-1724319327212 differ diff --git a/resources/Brands/BrandsController.js b/resources/Brands/BrandsController.js new file mode 100644 index 0000000..554b066 --- /dev/null +++ b/resources/Brands/BrandsController.js @@ -0,0 +1,111 @@ +import mongoose from "mongoose"; +import { BrandModel } from "./BrandsModel.js"; + +// Add new Brand +export const addBrand = async (req, res) => { + const { brandName } = req.body; + + if (!req?.user) { + return res.status(400).json({ message: "Please login!" }); + } + + try { + if (!mongoose.Types.ObjectId.isValid(req.user._id)) { + return res.status(400).json({ message: "Please login again." }); + } + + if (!brandName) { + return res.status(400).json({ message: "Please provide a brand name" }); + } + + const brand = await BrandModel.create({ + brandName, + addedBy: req.user._id, + }); + + return res.status(201).json({ success: true, brand, message: "Brand added successfully" }); + } catch (error) { + res.status(500).json({ + success: false, + message: error.message || "Something went wrong", + }); + } +}; + +// Get all Brands +export const getBrands = async (req, res) => { + try { + const brands = await BrandModel.find().sort({ createdAt: -1 }); + if (!brands.length) { + return res.status(404).json({ message: "No brands found" }); + } + + res.status(200).json({ success: true, brands }); + } catch (error) { + res.status(500).json({ + success: false, + message: error.message || "Something went wrong", + }); + } +}; + +// Update Brand +export const updateBrand = async (req, res) => { + const { _id } = req.params; + const { brandName } = req.body; + + if (!req?.user) { + return res.status(400).json({ message: "Please login!" }); + } + + if (!mongoose.Types.ObjectId.isValid(_id)) { + return res.status(404).json({ message: "Invalid brand ID" }); + } + + try { + const updatedBrand = await BrandModel.findByIdAndUpdate( + _id, + { brandName }, + { new: true, runValidators: true } + ); + + if (!updatedBrand) { + return res.status(404).json({ message: "Brand not found" }); + } + + res.status(200).json({ success: true, updatedBrand, message: "Brand updated successfully" }); + } catch (error) { + res.status(500).json({ + success: false, + message: error.message || "Something went wrong", + }); + } +}; + +// Delete Brand +export const deleteBrand = async (req, res) => { + const { _id } = req.params; + + if (!req?.user) { + return res.status(400).json({ message: "Please login!" }); + } + + if (!mongoose.Types.ObjectId.isValid(_id)) { + return res.status(404).json({ message: "Invalid brand ID" }); + } + + try { + const deletedBrand = await BrandModel.findByIdAndDelete(_id); + + if (!deletedBrand) { + return res.status(404).json({ message: "Brand not found" }); + } + + res.status(200).json({ success: true, deletedBrand, message: "Brand deleted successfully" }); + } catch (error) { + res.status(500).json({ + success: false, + message: error.message || "Something went wrong", + }); + } +}; diff --git a/resources/Brands/BrandsModel.js b/resources/Brands/BrandsModel.js new file mode 100644 index 0000000..c1343b7 --- /dev/null +++ b/resources/Brands/BrandsModel.js @@ -0,0 +1,18 @@ +import mongoose from "mongoose"; + +const BrandSchema = new mongoose.Schema( + { + brandName: { + type: String, + required: [true, "Name of brand required"], + }, + addedBy: { + type: mongoose.Schema.ObjectId, + ref: "User", + required: true, + }, + }, + { timestamps: true } +); + +export const BrandModel = mongoose.model("BrandModel", BrandSchema); diff --git a/resources/Brands/BrandsRoutes.js b/resources/Brands/BrandsRoutes.js new file mode 100644 index 0000000..6a0f869 --- /dev/null +++ b/resources/Brands/BrandsRoutes.js @@ -0,0 +1,26 @@ +import express from "express"; +import { isAuthenticatedUser, authorizeRoles } from "../../middlewares/auth.js"; +import { + addBrand, + deleteBrand, + getBrands, + updateBrand, +} from "./BrandsController.js"; + +const router = express.Router(); + +router + .route("/add") + .post(isAuthenticatedUser, authorizeRoles("admin"), addBrand); + +router.route("/getBrands").get(getBrands); + +router + .route("/update/:_id") + .patch(isAuthenticatedUser, authorizeRoles("admin"), updateBrand); + +router + .route("/delete/:_id") + .delete(isAuthenticatedUser, authorizeRoles("admin"), deleteBrand); + +export default router; diff --git a/resources/Category/CategoryModel.js b/resources/Category/CategoryModel.js index c05dae5..1f1aac9 100644 --- a/resources/Category/CategoryModel.js +++ b/resources/Category/CategoryModel.js @@ -6,7 +6,6 @@ const CategorySchema = new mongoose.Schema( type: String, required: [true, "Name of category required "], }, - categoryImage: {}, addedBy: { type: mongoose.Schema.ObjectId, ref: "User", diff --git a/resources/Category/categoryController.js b/resources/Category/categoryController.js index 0bc68e6..e3eaebd 100644 --- a/resources/Category/categoryController.js +++ b/resources/Category/categoryController.js @@ -1,53 +1,43 @@ import mongoose from "mongoose"; import { CategoryModel } from "./CategoryModel.js"; -import cloudinary from "../../Utils/cloudinary.js"; // Add new Category export const addCategory = async (req, res) => { const { categoryName } = req.body; - const { categoryImage } = req.files; - // console.log(categoryName, categoryImage); - if (!req?.user) return res.status(400).json({ message: "please login !" }); + if (!req?.user) { + return res.status(400).json({ message: "Please login!" }); + } + try { if (!mongoose.Types.ObjectId.isValid(req.user._id)) { - return res.status(400).json({ message: "please login again " }); + return res.status(400).json({ message: "Please login again." }); } - const result = await cloudinary.v2.uploader.upload( - categoryImage.tempFilePath, - { - folder: "GetSygnal/category", - } - ); - if (result) { - const category = await CategoryModel.create({ - categoryName, - categoryImage: result, - addedBy: req.user._id, - }); - if (category) { - return res - .status(201) - .json({ success: true, category, message: "category Added" }); - } + if (!categoryName) { + return res.status(400).json({ message: "Please provide a category name" }); } + + const category = await CategoryModel.create({ + categoryName, + addedBy: req.user._id, + }); + + return res.status(201).json({ success: true, category, message: "Category added successfully" }); } catch (error) { res.status(500).json({ success: false, - message: error.message ? error.message : "Something went Wrong", + message: error.message || "Something went wrong", }); } }; +// Get all Categories export const getCategories = async (req, res) => { try { - // if (!req?.user) return res.status(400).json({ message: "please login !" }); - const categories = await CategoryModel.find().sort({ - createdAt: -1, - }); + const categories = await CategoryModel.find().sort({ createdAt: -1 }); - if (!categories) { + if (!categories.length) { return res.status(404).json({ message: "No categories found" }); } @@ -55,96 +45,68 @@ export const getCategories = async (req, res) => { } catch (error) { res.status(500).json({ success: false, - message: error.message ? error.message : "Something went wrong", + message: error.message || "Something went wrong", }); } }; +// Update Category export const updateCategory = async (req, res) => { - try { - if (!req?.user) return res.status(400).json({ message: "please login !" }); - const { _id } = req.params; - const { categoryName } = req.body; - const olderImage = req.body?.olderImage; - const categoryImag = req.files?.categoryImage; + const { _id } = req.params; + const { categoryName } = req.body; - if (!mongoose.Types.ObjectId.isValid(_id)) { - return res.status(404).json({ error: "Can not find the document " }); - } - - // find the document with the id to delete the image from cloudinary - if (JSON.parse(olderImage).length == 0) { - const deletefromCloudinary = await CategoryModel.findOne({ _id: _id }); - - const deleteresponse = await cloudinary.v2.uploader.destroy( - deletefromCloudinary.categoryImage.public_id - ); - if (deleteresponse) { - const result = await cloudinary.v2.uploader.upload( - categoryImag.tempFilePath, - { - folder: "GetSygnal/category", - } - ); - const update = await CategoryModel.findOneAndUpdate( - { _id: _id }, - { categoryName: categoryName, categoryImage: result }, // Provide the updated categoryName - { new: true } // To return the updated document - ); - if (!update) { - return res - .status(404) - .json({ message: "Can not update document, something went wrong" }); - } else { - return res.status(200).json({ success: true, update }); - } - } - } else { - const update = await CategoryModel.findOneAndUpdate( - { _id: _id }, - { categoryName: categoryName, categoryImage: JSON.parse(olderImage) }, // Provide the updated categoryName - { new: true } // To return the updated document - ); - if (update) { - return res.status(200).json({ success: true, update }); - } - } - } catch (error) { - res.status(500).json({ - success: false, - message: error.message ? error.message : "Something went wrong", - }); + if (!req?.user) { + return res.status(400).json({ message: "Please login!" }); + } + + if (!mongoose.Types.ObjectId.isValid(_id)) { + return res.status(404).json({ message: "Invalid category ID" }); } -}; -export const deleteCategory = async (req, res) => { try { - if (!req?.user) return res.status(400).json({ message: "please login !" }); - const { _id } = req.params; - if (!mongoose.Types.ObjectId.isValid(_id)) { - return res.status(404).json({ error: "Can not find the document " }); - } - - const deletefromCloudinary = await CategoryModel.findOne({ _id: _id }); - - const deleteresponse = await cloudinary.v2.uploader.destroy( - deletefromCloudinary.categoryImage.public_id + const updatedCategory = await CategoryModel.findByIdAndUpdate( + _id, + { categoryName }, + { new: true, runValidators: true } ); - if (deleteresponse) { - const deleteCategory = await CategoryModel.findOneAndDelete({ _id: _id }); - if (!deleteCategory) { - return res.status(404).json({ - error: "Can not find the document with the provided id to delete ", - }); - } - res.status(200).json({ success: true, deleteCategory }); - } else { - return res.status(404).json({ error: "can not delete the category " }); + + if (!updatedCategory) { + return res.status(404).json({ message: "Category not found" }); } + + res.status(200).json({ success: true, updatedCategory, message: "Category updated successfully" }); } catch (error) { res.status(500).json({ success: false, - message: error.message ? error.message : "Something went wrong", + message: error.message || "Something went wrong", + }); + } +}; + +// Delete Category +export const deleteCategory = async (req, res) => { + const { _id } = req.params; + + if (!req?.user) { + return res.status(400).json({ message: "Please login!" }); + } + + if (!mongoose.Types.ObjectId.isValid(_id)) { + return res.status(404).json({ message: "Invalid category ID" }); + } + + try { + const deletedCategory = await CategoryModel.findByIdAndDelete(_id); + + if (!deletedCategory) { + return res.status(404).json({ message: "Category not found" }); + } + + res.status(200).json({ success: true, deletedCategory, message: "Category deleted successfully" }); + } catch (error) { + res.status(500).json({ + success: false, + message: error.message || "Something went wrong", }); } }; diff --git a/resources/Category/categoryRoutes.js b/resources/Category/categoryRoutes.js index 66c8cc5..0875a6a 100644 --- a/resources/Category/categoryRoutes.js +++ b/resources/Category/categoryRoutes.js @@ -10,20 +10,20 @@ const router = express.Router(); router .route("/add") - .post(isAuthenticatedUser, authorizeRoles("admin", "Employee"), addCategory); + .post(isAuthenticatedUser, authorizeRoles("admin"), addCategory); router.route("/getCategories").get(getCategories); router .route("/update/:_id") .patch( isAuthenticatedUser, - authorizeRoles("admin", "Employee"), + authorizeRoles("admin"), updateCategory ); router .route("/delete/:_id") .delete( isAuthenticatedUser, - authorizeRoles("admin", "Employee"), + authorizeRoles("admin"), deleteCategory ); diff --git a/resources/Inventory/InventoryController.js b/resources/Inventory/InventoryController.js index 19af1d1..bc13496 100644 --- a/resources/Inventory/InventoryController.js +++ b/resources/Inventory/InventoryController.js @@ -79,8 +79,6 @@ export const getDistributors = async (req, res) => { export const getAllInventories = async (req, res) => { try { const { page = 1, show = 10, startDate, endDate, name } = req.query; - - // Build query for date filtering const query = {}; if (startDate && endDate) { @@ -98,18 +96,17 @@ export const getAllInventories = async (req, res) => { $lte: new Date(end).setDate(end.getDate() + 1), }; } - } else if (startDate) { + } else if (startDate && endDate==='') { query.createdAt = { $gte: new Date(startDate), $lte: new Date(), }; - } else if (endDate) { + } else if (endDate && startDate==='') { query.createdAt = { $lte: new Date(endDate), }; } - // Fetch all matching documents (without pagination) to calculate total data const allInventories = await Inventory.find(query).sort({ createdAt: -1 }); // Populate additional details @@ -127,14 +124,17 @@ export const getAllInventories = async (req, res) => { if (inventory.addedFor === "PrincipalDistributor") { addedForData = await User.findById(inventory.addedForId); - const shippingAddress = await ShippingAddress.findOne({ - user: addedForData._id, - }); - addedForData = { - ...addedForData.toObject(), - shippingAddress, - }; - tradeName = addedForData.shippingAddress?.tradeName?.toLowerCase() || ""; + if (addedForData) { + const shippingAddress = await ShippingAddress.findOne({ + user: addedForData._id, + }); + addedForData = { + ...addedForData.toObject(), + shippingAddress, + }; + tradeName = + addedForData.shippingAddress?.tradeName?.toLowerCase() || ""; + } } else if (inventory.addedFor === "RetailDistributor") { addedForData = await KYC.findById(inventory.addedForId); tradeName = addedForData?.trade_name?.toLowerCase() || ""; @@ -149,11 +149,13 @@ export const getAllInventories = async (req, res) => { }) ); - // Apply name filter + // Apply name filter if the name parameter is provided let filteredInventories = populatedInventories; if (name) { filteredInventories = filteredInventories.filter( - (inventory) => inventory.tradeName && inventory.tradeName.includes(name.toLowerCase()) + (inventory) => + inventory.tradeName && + inventory.tradeName.includes(name.toLowerCase()) ); } @@ -161,8 +163,10 @@ export const getAllInventories = async (req, res) => { const total_data = filteredInventories.length; // Apply pagination after filtering - const paginatedInventories = filteredInventories - .slice((page - 1) * show, page * show); + const paginatedInventories = filteredInventories.slice( + (page - 1) * show, + page * show + ); // Calculate total pages const total_pages = Math.ceil(total_data / show); @@ -171,10 +175,11 @@ export const getAllInventories = async (req, res) => { res.status(200).json({ total_data, total_pages, - current_page: page, + current_page: Number(page), inventories: paginatedInventories, }); } catch (error) { + console.error("Error in getAllInventories:", error); res.status(500).json({ message: error.message }); } }; diff --git a/resources/Products/ProductController.js b/resources/Products/ProductController.js index 22f9271..dde518e 100644 --- a/resources/Products/ProductController.js +++ b/resources/Products/ProductController.js @@ -2,6 +2,8 @@ import { Product } from "./ProductModel.js"; import cloudinary from "../../Utils/cloudinary.js"; import { v4 as uuidv4 } from "uuid"; import { CategoryModel } from "../Category/CategoryModel.js"; +import { BrandModel } from "../Brands/BrandsModel.js"; +import User from "../user/userModel.js"; import { Tax } from "../Tax/tax_model.js"; import XLSX from "xlsx"; import fs from "fs"; @@ -9,6 +11,267 @@ import path from "path"; import mongoose from "mongoose"; // Function to handle product upload +// export const uploadProducts = async (req, res) => { +// try { +// 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 = { +// SKU: "SKU", +// "Product Name": "name", +// "Category Name": "category", +// "Brand Name": "brand", +// Price: "price", +// "GST (in %)": "GST", +// "HSN Code": "HSN_Code", +// "Description (Optional)": "description", +// }; + +// 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 updatedProducts = []; + +// 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 notFoundErrors = new Set(); + +// let { SKU, name, category, brand, price, GST, HSN_Code, description } = item; + +// // Trim leading and trailing spaces from product name and GST +// name = name ? name.trim() : ""; + +// // Validate required fields +// if (!SKU) missingFields.add("SKU"); +// if (!name) missingFields.add("name"); +// if (!category) missingFields.add("category"); +// if (!brand) missingFields.add("brand"); +// if (price === undefined || price === "") missingFields.add("price"); +// if (!GST) missingFields.add("GST"); +// if (!HSN_Code) missingFields.add("HSN_Code"); + +// // Validate or create category +// let categoryName = ""; +// if (category) { +// let categoryDoc = await CategoryModel.findOne({ +// categoryName: { $regex: new RegExp(`^${category.trim()}$`, "i") }, +// }).exec(); +// if (!categoryDoc) { +// // If category not found, create a new one +// categoryDoc = await CategoryModel.create({ +// categoryName: category.trim(), +// addedBy: req.user._id, +// }); +// } +// item.category = categoryDoc._id; +// categoryName = categoryDoc.categoryName; +// } + +// // Validate or create brand +// let brandName = ""; +// if (brand) { +// let brandDoc = await BrandModel.findOne({ +// brandName: { $regex: new RegExp(`^${brand.trim()}$`, "i") }, +// }).exec(); +// if (!brandDoc) { +// // If brand not found, create a new one +// brandDoc = await BrandModel.create({ +// brandName: brand.trim(), +// addedBy: req.user._id, +// }); +// } +// item.brand = brandDoc._id; +// brandName = brandDoc.brandName; +// } + +// // Combine all errors into a single message +// let errorMessage = ""; +// if (missingFields.size > 0) { +// errorMessage += `Missing fields: ${Array.from(missingFields).join(", ")}. `; +// } +// if (notFoundErrors.size > 0) { +// errorMessage += `Not found: ${Array.from(notFoundErrors).join(", ")}.`; +// } + +// // 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", +// brand: brand || "N/A", +// GST: GST || "N/A", +// HSN_Code: HSN_Code || "N/A", +// price: price || "N/A", +// message: errorMessage.trim(), +// }); +// continue; +// } + +// // Ensure fields are set to empty strings if not provided +// description = description !== undefined ? description : ""; + +// // Check for existing product by SKU +// let existingProduct = await Product.findOne({ SKU }).exec(); + +// if (existingProduct) { +// // Track changes +// const updatedFields = []; +// let updatedProduct = { ...existingProduct._doc }; + +// // Fetch existing category name and brand name +// const existingCategory = await CategoryModel.findById(existingProduct.category).exec(); +// const existingBrand = await BrandModel.findById(existingProduct.brand).exec(); + +// // Update product fields if they have changed +// if (name !== existingProduct.name) { +// updatedFields.push("name"); +// updatedProduct.name = name; +// } +// if (category && existingProduct.category.toString() !== item.category.toString()) { +// updatedFields.push("category"); +// updatedProduct.category = categoryName; +// } else { +// updatedProduct.category = existingCategory ? existingCategory.categoryName : ""; +// } +// if (price !== undefined && price !== "" && existingProduct.price !== price) { +// updatedFields.push("price"); +// updatedProduct.price = price; +// } +// if (brand && existingProduct.brand.toString() !== item.brand.toString()) { +// updatedFields.push("brand"); +// updatedProduct.brand = brandName; +// } else { +// updatedProduct.brand = existingBrand ? existingBrand.brandName : ""; +// } +// if (HSN_Code !== existingProduct.HSN_Code) { +// updatedFields.push("HSN_Code"); +// updatedProduct.HSN_Code = HSN_Code; +// } +// if (GST !== existingProduct.GST) { +// updatedFields.push("GST"); +// updatedProduct.GST = GST; +// } +// if (description !== existingProduct.description) { +// updatedFields.push("description"); +// updatedProduct.description = description; +// } + +// // Only update if there are changes +// if (updatedFields.length > 0) { +// try { +// await Product.updateOne( +// { SKU: existingProduct.SKU }, +// { +// $set: { +// category: item.category || existingProduct.category, +// price: price !== undefined && price !== "" ? price : existingProduct.price, +// GST: GST || existingProduct.GST, +// HSN_Code: HSN_Code || existingProduct.HSN_Code, +// name: name, +// description: description, +// product_Status: item.product_Status || existingProduct.product_Status || "Active", +// }, +// } +// ); +// updatedProducts.push({ +// ...updatedProduct, +// updatedFields: updatedFields.join(", "), // Track updated fields +// }); +// } catch (error) { +// errors.push({ +// SKU, +// message: "Failed to update product", +// }); +// } +// } +// continue; +// } + +// // Create new product +// if (item.category && item.brand) { +// const productData = { +// SKU, +// name, +// category: item.category, +// brand: item.brand, +// price, +// GST, +// HSN_Code, +// description: description, +// product_Status: item.product_Status || "Active", +// addedBy: req.user._id, +// }; +// try { +// const newProduct = await Product.create(productData); +// newlyCreated.push({ +// ...newProduct._doc, +// category: categoryName, +// brand: brandName, +// }); +// } catch (error) { +// errors.push({ +// SKU, +// message: "Failed to create product", +// }); +// } +// } +// } + +// fs.unlinkSync(filePath); // Clean up uploaded file + +// res.status(201).json({ +// message: errors.length > 0 ? "Products processed with errors!" : "Products processed successfully!", +// newlyCreated: newlyCreated, +// updatedProducts: updatedProducts, +// errors, +// }); +// } catch (error) { +// console.error("Error:", error); +// res.status(500).json({ message: "Internal server error" }); +// } +// }; export const uploadProducts = async (req, res) => { try { if (!req.files || !req.files.file) { @@ -45,11 +308,12 @@ export const uploadProducts = async (req, res) => { const headerMapping = { SKU: "SKU", "Product Name": "name", - "category Name": "category", - price: "price", - "GST Name": "GST", - description: "description", - special_instructions: "special_instructions", + "Category Name": "category", + "Brand Name": "brand", + Price: "price", + "GST (in %)": "GST", + "HSN Code": "HSN_Code", + "Description (Optional)": "description", }; const requiredHeaders = Object.keys(headerMapping); @@ -64,6 +328,12 @@ export const uploadProducts = async (req, res) => { const newlyCreated = []; const updatedProducts = []; + const capitalizeWords = (str) => + str + .toLowerCase() + .replace(/\b\w/g, (char) => char.toUpperCase()) + .trim(); + for (let i = 1; i < data.length; i++) { const row = data[i]; const item = {}; @@ -75,61 +345,66 @@ export const uploadProducts = async (req, res) => { } }); + // Check if the row has meaningful data, skip if it's mostly empty + const hasValidData = Object.values(item).some((value) => value && value.trim()); + if (!hasValidData) { + continue; + } + // Initialize error tracking for each item const missingFields = new Set(); - const notFoundErrors = new Set(); - let { - SKU, - name, - category, - price, - GST, - description, - special_instructions, - } = item; + let { SKU, name, category, brand, price, GST, HSN_Code, description } = + item; - // Trim leading and trailing spaces from product name and GST + // Trim leading and trailing spaces and apply case formatting name = name ? name.trim() : ""; - GST = GST ? GST.toString().trim() : ""; + category = category ? capitalizeWords(category) : ""; + brand = brand ? capitalizeWords(brand) : ""; // Validate required fields if (!SKU) missingFields.add("SKU"); if (!name) missingFields.add("name"); if (!category) missingFields.add("category"); + if (!brand) missingFields.add("brand"); if (price === undefined || price === "") missingFields.add("price"); if (!GST) missingFields.add("GST"); + if (!HSN_Code) missingFields.add("HSN_Code"); - // Validate category - let categoryName = ""; + // Validate or create category + let categoryDoc = null; + let categoryname = ""; if (category) { - const categoryDoc = await CategoryModel.findOne({ - categoryName: { $regex: new RegExp(`^${category.trim()}$`, "i") }, + categoryDoc = await CategoryModel.findOne({ + categoryName: { $regex: new RegExp(`^${category}$`, "i") }, }).exec(); - if (categoryDoc) { - item.category = categoryDoc._id; - categoryName = categoryDoc.categoryName; - } else { - notFoundErrors.add("category"); + + if (!categoryDoc) { + categoryDoc = await CategoryModel.create({ + categoryName: category, + addedBy: req.user._id, + }); } - } else { - missingFields.add("category"); + categoryname = categoryDoc.categoryName; + item.category = categoryDoc._id; } - // Validate GST - let gstName = ""; - if (GST) { - const gstDoc = await Tax.findOne({ - name: { $regex: new RegExp(`^${GST}$`, "i") }, + // Validate or create brand + let brandDoc = null; + let brandname = ""; + if (brand) { + brandDoc = await BrandModel.findOne({ + brandName: { $regex: new RegExp(`^${brand}$`, "i") }, }).exec(); - if (gstDoc) { - item.GST = gstDoc._id; - gstName = gstDoc.name; - } else { - notFoundErrors.add("GST"); + + if (!brandDoc) { + brandDoc = await BrandModel.create({ + brandName: brand, + addedBy: req.user._id, + }); } - } else { - missingFields.add("GST"); + brandname = brandDoc.brandName; + item.brand = brandDoc._id; } // Combine all errors into a single message @@ -139,17 +414,16 @@ export const uploadProducts = async (req, res) => { ", " )}. `; } - if (notFoundErrors.size > 0) { - errorMessage += `Not found: ${Array.from(notFoundErrors).join(", ")}.`; - } // 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", + category: categoryname || "N/A", + brand: brandname || "N/A", GST: GST || "N/A", + HSN_Code: HSN_Code || "N/A", price: price || "N/A", message: errorMessage.trim(), }); @@ -158,8 +432,6 @@ export const uploadProducts = async (req, res) => { // Ensure fields are set to empty strings if not provided description = description !== undefined ? description : ""; - special_instructions = - special_instructions !== undefined ? special_instructions : ""; // Check for existing product by SKU let existingProduct = await Product.findOne({ SKU }).exec(); @@ -169,22 +441,19 @@ export const uploadProducts = async (req, res) => { const updatedFields = []; let updatedProduct = { ...existingProduct._doc }; - // Fetch existing category name and GST name - const existingCategory = await CategoryModel.findById( - existingProduct.category - ).exec(); - const existingGST = await Tax.findById(existingProduct.GST).exec(); - + // Update product fields if they have changed + if (name !== existingProduct.name) { + updatedFields.push("name"); + updatedProduct.name = name; + } if ( - category && + categoryDoc && existingProduct.category.toString() !== item.category.toString() ) { updatedFields.push("category"); - updatedProduct.category = categoryName; + updatedProduct.category = categoryname; } else { - updatedProduct.category = existingCategory - ? existingCategory.categoryName - : ""; + updatedProduct.category = categoryDoc.categoryName; } if ( price !== undefined && @@ -194,20 +463,27 @@ export const uploadProducts = async (req, res) => { updatedFields.push("price"); updatedProduct.price = price; } - if (GST && existingProduct.GST.toString() !== item.GST.toString()) { - updatedFields.push("GST"); - updatedProduct.GST = gstName; + if ( + brandDoc && + existingProduct.brand.toString() !== item.brand.toString() + ) { + updatedFields.push("brand"); + updatedProduct.brand = brandname; } else { - updatedProduct.GST = existingGST ? existingGST.name : ""; + updatedProduct.brand = brandDoc.brandName; + } + if (HSN_Code !== existingProduct.HSN_Code) { + updatedFields.push("HSN_Code"); + updatedProduct.HSN_Code = HSN_Code; + } + if (GST !== existingProduct.GST) { + updatedFields.push("GST"); + updatedProduct.GST = GST; } if (description !== existingProduct.description) { updatedFields.push("description"); updatedProduct.description = description; } - if (special_instructions !== existingProduct.special_instructions) { - updatedFields.push("special_instructions"); - updatedProduct.special_instructions = special_instructions; - } // Only update if there are changes if (updatedFields.length > 0) { @@ -217,13 +493,15 @@ export const uploadProducts = async (req, res) => { { $set: { category: item.category || existingProduct.category, + brand: item.brand || existingProduct.brand, price: price !== undefined && price !== "" ? price : existingProduct.price, - GST: item.GST || existingProduct.GST, + GST: GST || existingProduct.GST, + HSN_Code: HSN_Code || existingProduct.HSN_Code, + name: name, description: description, - special_instructions: special_instructions, product_Status: item.product_Status || existingProduct.product_Status || @@ -246,15 +524,16 @@ export const uploadProducts = async (req, res) => { } // Create new product - if (item.category && item.GST) { + if (item.category && item.brand) { const productData = { SKU, name, category: item.category, + brand: item.brand, price, - GST: item.GST, + GST, + HSN_Code, description: description, - special_instructions: special_instructions, product_Status: item.product_Status || "Active", addedBy: req.user._id, }; @@ -262,8 +541,8 @@ export const uploadProducts = async (req, res) => { const newProduct = await Product.create(productData); newlyCreated.push({ ...newProduct._doc, - category: categoryName, - GST: gstName, + category: categoryDoc.categoryName, + brand: brandDoc.brandName, }); } catch (error) { errors.push({ @@ -286,8 +565,8 @@ export const uploadProducts = async (req, res) => { errors, }); } catch (error) { - console.error("Error:", error); - res.status(500).json({ message: error.message || "Something went wrong!" }); + console.error("Error uploading products:", error); + res.status(500).json({ message: "Server error" }); } }; @@ -296,7 +575,7 @@ export const createProduct = async (req, res) => { let findProduct = ""; let product = { _id: "" }; const sku = req.body?.SKU; - + // console.log(req.body); if (!sku) { return res.status(400).json({ message: "SKU is required!" }); } @@ -450,13 +729,16 @@ export const getAllProductAdmin = async (req, res) => { if (req.query.category) { filter.category = mongoose.Types.ObjectId(req.query.category); } + if (req.query.brand) { + filter.brand = mongoose.Types.ObjectId(req.query.brand); + } const total = await Product.countDocuments(filter); const products = await Product.find(filter) .populate({ - path: "category addedBy GST", - select: "categoryName name tax", + path: "category addedBy brand", + select: "categoryName name brandName", }) .limit(PAGE_SIZE) .skip(skip) @@ -485,10 +767,12 @@ export const getAllProductUser = async (req, res) => { const PAGE_SIZE = parseInt(req.query?.show || "10"); const page = parseInt(req.query?.page || "1") - 1; let filter = {}; - + // Filter by category name if (req.query?.category) { - const category = await CategoryModel.findOne({ categoryName: req.query.category }); + const category = await CategoryModel.findOne({ + categoryName: req.query.category, + }); if (category) { filter.category = category._id; } else { @@ -498,7 +782,9 @@ export const getAllProductUser = async (req, res) => { }); } } - + if (req.query.brand) { + filter.brand = mongoose.Types.ObjectId(req.query.brand); + } // Filter by SKU if (req.query?.SKU) { filter.SKU = req.query.SKU; @@ -507,7 +793,7 @@ export const getAllProductUser = async (req, res) => { // Filter by product name using regex for case-insensitive partial matching if (req.query?.name) { filter.name = { - $regex: new RegExp(req.query.name, 'i'), + $regex: new RegExp(req.query.name, "i"), }; } @@ -520,8 +806,8 @@ export const getAllProductUser = async (req, res) => { // Retrieve products with pagination, filtering, and sorting const products = await Product.find(filter) .populate({ - path: "category addedBy GST", - select: "categoryName name tax", + path: "category addedBy brand", + select: "categoryName name brandName", }) .limit(PAGE_SIZE) .skip(PAGE_SIZE * page) @@ -588,8 +874,8 @@ export const ChangeProductStatus = async (req, res) => { export const getOneProduct = async (req, res) => { try { const data = await Product.findById(req.params.id).populate({ - path: "category addedBy GST", - select: "name categoryName tax", + path: "category addedBy brand", + select: "categoryName name brandName", }); if (data) { return res.status(200).json({ diff --git a/resources/Products/ProductModel.js b/resources/Products/ProductModel.js index 485de87..895925c 100644 --- a/resources/Products/ProductModel.js +++ b/resources/Products/ProductModel.js @@ -20,24 +20,27 @@ const productSchema = new Schema( type: Schema.Types.ObjectId, ref: "CategoryModel", }, - + brand: { + type: Schema.Types.ObjectId, + ref: "BrandModel", + }, price: { type: Number, required: true, }, GST: { - type: mongoose.Schema.ObjectId, - ref: "Tax", + type: Number, + required: true, + }, + HSN_Code: { + type: Number, + required: true, }, description: { type: String, maxLength: [400, "description cannot exceed 100 characters"], // required: [true, "Please Enter product Description"], }, - - special_instructions: { - type: String, - }, image: [ { public_id: {