From 16425a287909193138aa9222900be974a425f883 Mon Sep 17 00:00:00 2001 From: Sibunnayak Date: Wed, 7 Aug 2024 16:54:19 +0530 Subject: [PATCH] upload spreadsheet and create product --- app.js | 1 + package-lock.json | 106 +++++++- package.json | 3 +- public/temp/tmp-2-1722948449609 | Bin 0 -> 26207 bytes resources/Products/ProductController.js | 326 ++++++++++++++++++------ resources/Products/ProductModel.js | 6 +- resources/Products/ProductRoute.js | 18 +- 7 files changed, 375 insertions(+), 85 deletions(-) create mode 100644 public/temp/tmp-2-1722948449609 diff --git a/app.js b/app.js index c31536f..6de17c2 100644 --- a/app.js +++ b/app.js @@ -39,6 +39,7 @@ app.use(express.static(publicPath)); app.use( fileUpload({ useTempFiles: true, + tempFileDir: join(publicPath, 'temp'), }) ); diff --git a/package-lock.json b/package-lock.json index 5056ee6..e07c4b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,8 @@ "secure-random-password": "^0.2.3", "stripe": "^14.16.0", "uuid": "^9.0.1", - "validator": "^13.7.0" + "validator": "^13.7.0", + "xlsx": "^0.18.5" } }, "node_modules/@aws-crypto/sha256-browser": { @@ -1714,6 +1715,15 @@ "node": ">= 0.6" } }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/agent-base": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", @@ -2071,6 +2081,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", @@ -2170,6 +2193,15 @@ "lodash": ">=4.0" } }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2306,6 +2338,18 @@ "node": ">= 0.10" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/croner": { "version": "4.1.97", "resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz", @@ -2744,6 +2788,15 @@ "node": ">= 0.6" } }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -4805,6 +4858,18 @@ "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", "license": "BSD-3-Clause" }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -5125,6 +5190,24 @@ "node": ">=12" } }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/ws": { "version": "7.5.10", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", @@ -5146,6 +5229,27 @@ } } }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 97038a3..314bc6d 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "secure-random-password": "^0.2.3", "stripe": "^14.16.0", "uuid": "^9.0.1", - "validator": "^13.7.0" + "validator": "^13.7.0", + "xlsx": "^0.18.5" } } diff --git a/public/temp/tmp-2-1722948449609 b/public/temp/tmp-2-1722948449609 new file mode 100644 index 0000000000000000000000000000000000000000..da090b532fb739ec22aae9ae0baf6252f6f9735f GIT binary patch literal 26207 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 F^naPdd4>Q0 literal 0 HcmV?d00001 diff --git a/resources/Products/ProductController.js b/resources/Products/ProductController.js index 03f4267..01a611e 100644 --- a/resources/Products/ProductController.js +++ b/resources/Products/ProductController.js @@ -1,12 +1,236 @@ +// import ExcelJS from 'exceljs'; import { Product } from "./ProductModel.js"; import cloudinary from "../../Utils/cloudinary.js"; import { v4 as uuidv4 } from "uuid"; import { CategoryModel } from "../Category/CategoryModel.js"; +import { Tax } from "../Tax/tax_model.js"; +import XLSX from "xlsx"; +import fs from "fs"; +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 = { + "Product Name": "name", + "category Name": "category", + price: "price", + "GST Name": "GST", + description: "description", + special_instructions: "special_instructions", + }; + + 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 productsProcessed = []; + + 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 { 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 (!name) missingFields.add("name"); + if (!category) missingFields.add("category"); + if (price === undefined || price === "") missingFields.add("price"); + if (!GST) missingFields.add("GST"); + + // Validate category + if (category) { + const categoryDoc = await CategoryModel.findOne({ + categoryName: { $regex: new RegExp(`^${category.trim()}$`, "i") }, + }).exec(); + if (categoryDoc) { + item.category = categoryDoc._id; + } else { + notFoundErrors.add("category"); + } + } else { + missingFields.add("category"); + } + + // Validate GST + if (GST) { + const gstDoc = await Tax.findOne({ + name: { $regex: new RegExp(`^${GST}$`, "i") }, + }).exec(); + if (gstDoc) { + item.GST = gstDoc._id; + } else { + notFoundErrors.add("GST"); + } + } else { + missingFields.add("GST"); + } + + // 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({ + productName: name || "N/A", + category: category || "N/A", + GST: GST || "N/A", + message: errorMessage.trim(), + }); + continue; + } + + // 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 + let existingProduct = await Product.findOne({ + name: new RegExp(`^${name}$`, "i"), + }).exec(); + + if (existingProduct) { + // Validate that the existing product can be updated + const updateErrors = []; + if (missingFields.size > 0) { + updateErrors.push(`Missing fields: ${Array.from(missingFields).join(", ")}`); + } + if (notFoundErrors.size > 0) { + updateErrors.push(`Not found: ${Array.from(notFoundErrors).join(", ")}`); + } + + if (updateErrors.length > 0) { + errors.push({ + productName: name, + message: updateErrors.join(". "), + }); + continue; + } + + // Update existing product + try { + await Product.updateOne( + { _id: existingProduct._id }, + { + $set: { + category: item.category || existingProduct.category, + price: price !== undefined && price !== "" ? price : existingProduct.price, + GST: item.GST || existingProduct.GST, + description: description, // Ensure description is included + special_instructions: special_instructions, // Ensure special_instructions is included + product_Status: item.product_Status || existingProduct.product_Status || "Active", + }, + } + ); + productsProcessed.push({ ...existingProduct._doc, ...item }); // Track updated product + } catch (error) { + errors.push({ + productName: name, + message: "Failed to update product", + }); + } + continue; + } + + // Create new product + if (item.category && item.GST) { + const productData = { + name, + category: item.category, + price, + GST: item.GST, + description: description, // Ensure description is included + special_instructions: special_instructions, // Ensure special_instructions is included + product_Status: item.product_Status || "Active", + addedBy: req.user._id, + }; + try { + const newProduct = await Product.create(productData); + productsProcessed.push(newProduct); // Track new product + } catch (error) { + errors.push({ + productName: name, + 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!", + productsProcessed: productsProcessed.length, // Total processed products + errors, + }); + } catch (error) { + console.error("Error:", error); + res.status(500).json({ message: error.message || "Something went wrong!" }); + } +}; + + export const createProduct = async (req, res) => { try { let findProduct = ""; let product = { _id: "" }; - + // console.log("req.body", req.body); if (req.body?.product_id) { findProduct = await Product.findById(req.body.product_id); } @@ -71,11 +295,15 @@ export const updateProduct = async (req, res) => { // Process new images for (const file of filesArray) { - if (file && file.tempFilePath) { // Check if file has tempFilePath + if (file && file.tempFilePath) { + // Check if file has tempFilePath try { - const result = await cloudinary.v2.uploader.upload(file.tempFilePath, { - folder: "chemiNova/product", - }); + const result = await cloudinary.v2.uploader.upload( + file.tempFilePath, + { + folder: "chemiNova/product", + } + ); newImagesLinks.push({ public_id: result.public_id, url: result.secure_url, @@ -83,7 +311,10 @@ export const updateProduct = async (req, res) => { // console.log("Uploaded image:", result.secure_url); } catch (uploadError) { console.error("Error uploading image:", uploadError); - return res.status(500).json({ message: "Failed to upload image", error: uploadError.message }); + return res.status(500).json({ + message: "Failed to upload image", + error: uploadError.message, + }); } } } @@ -109,7 +340,9 @@ export const updateProduct = async (req, res) => { product.image = allImages; await product.save(); - res.status(200).json({ message: "Product updated successfully!", images: allImages }); + res + .status(200) + .json({ message: "Product updated successfully!", images: allImages }); } catch (error) { console.error(error); // Log error for debugging res.status(500).json({ message: "Server error!", error: error.message }); @@ -120,55 +353,49 @@ export const updateProduct = async (req, res) => { //get All Product export const getAllProductAdmin = async (req, res) => { try { - const PAGE_SIZE = parseInt(req.query?.show || "10"); - const page = parseInt(req.query?.page - 1 || "0"); + const PAGE_SIZE = parseInt(req.query.show) || 10; + const page = parseInt(req.query.page) || 1; + const skip = (page - 1) * PAGE_SIZE; - // Create filter object based on query parameters let filter = {}; - if (req.query?.name) { + if (req.query.name) { filter.name = { - $regex: new RegExp(req.query.name, "i"), // Case-insensitive search + $regex: new RegExp(req.query.name, "i"), }; } - if (req.query?.category) { - filter.category = mongoose.Types.ObjectId(req.query.category); // Ensure category is an ObjectId - } - if (req.query?.FeatureProduct) { - filter.featured_Product = req.query.FeatureProduct === "true"; // Convert string to boolean + if (req.query.category) { + filter.category = mongoose.Types.ObjectId(req.query.category); } - // Count total products matching the filter const total = await Product.countDocuments(filter); - // Fetch products with pagination and sorting const products = await Product.find(filter) .populate({ path: "category addedBy GST", select: "categoryName name tax", }) .limit(PAGE_SIZE) - .skip(PAGE_SIZE * page) - .sort({ - featured_Product: -1, - createdAt: -1, - }) + .skip(skip) + .sort({ createdAt: -1 }) .exec(); return res.status(200).json({ success: true, total_data: total, total_pages: Math.ceil(total / PAGE_SIZE), - products, // Changed from `product` to `products` to match the response variable + products, }); } catch (error) { + console.error(error); // Add logging for better debugging res.status(500).json({ success: false, - msg: error.message ? error.message : "Something went wrong!", + msg: error.message || "Something went wrong!", }); } }; + //get All Product User(website) export const getAllProductUser = async (req, res) => { try { @@ -181,8 +408,6 @@ export const getAllProductUser = async (req, res) => { $options: "i", }; if (req.query?.category) obj.category = req.query.category; - if (req.query?.FeatureProduct) - obj.featured_Product = req.query.FeatureProduct; obj.product_Status = "Active"; const total = await Product.countDocuments(obj); const product = await Product.find(obj) @@ -194,7 +419,6 @@ export const getAllProductUser = async (req, res) => { .skip(PAGE_SIZE * page) // .sort("name") .sort({ - featured_Product: -1, createdAt: -1, }) .exec(); @@ -248,49 +472,6 @@ export const ChangeProductStatus = async (req, res) => { }); } }; -//Change Product status -export const ChangeFeatueProductStatus = async (req, res) => { - try { - const data = await Product.findById(req.params.id); - if (data) { - if (data?.featured_Product === false) { - const totalFeatueProduct = await Product.countDocuments({ - featured_Product: true, - }); - if (totalFeatueProduct > 2) { - return res.status(400).json({ - success: false, - msg: "Maximum 3 Featue Product can be..", - }); - } - let product = await Product.findByIdAndUpdate( - req.params.id, - { featured_Product: true }, - { new: true } // Return the updated document - ); - return res.status(200).json({ - success: true, - msg: "Changed status as Featue Product", - }); - } else { - let product = await Product.findByIdAndUpdate( - req.params.id, - { featured_Product: false }, - { new: true } // Return the updated document - ); - return res.status(200).json({ - success: true, - msg: "Changed status as not Featue Product", - }); - } - } - } catch (error) { - res.status(500).json({ - success: false, - msg: error.message ? error.message : "Something went wrong!", - }); - } -}; //get One Product export const getOneProduct = async (req, res) => { try { @@ -534,7 +715,6 @@ export const getAllProductsDevicesFirst = async (req, res) => { // } // }; - export const deleteImageFromCloudinary = async (req, res) => { const { public_id } = req.params; diff --git a/resources/Products/ProductModel.js b/resources/Products/ProductModel.js index 4c9c593..bf8c86c 100644 --- a/resources/Products/ProductModel.js +++ b/resources/Products/ProductModel.js @@ -26,16 +26,12 @@ const productSchema = new Schema( description: { type: String, maxLength: [400, "description cannot exceed 100 characters"], - required: [true, "Please Enter product Description"], + // required: [true, "Please Enter product Description"], }, special_instructions: { type: String, }, - featured_Product: { - type: Boolean, - default: false, // Initially, products are not featured - }, image: [ { public_id: { diff --git a/resources/Products/ProductRoute.js b/resources/Products/ProductRoute.js index ea0908d..39213ff 100644 --- a/resources/Products/ProductRoute.js +++ b/resources/Products/ProductRoute.js @@ -10,10 +10,21 @@ import { getAllProductUser, getAllProductsDevicesFirst, ChangeProductStatus, - ChangeFeatueProductStatus, + uploadProducts, } from "./ProductController.js"; -const router = express.Router(); + import { isAuthenticatedUser, authorizeRoles } from "../../middlewares/auth.js"; + +const router = express.Router(); + +router + .route('/products/upload').post( + isAuthenticatedUser, + authorizeRoles('admin'), + uploadProducts +); + + router .route("/product/create/") .post(isAuthenticatedUser, authorizeRoles("admin"), createProduct); @@ -23,9 +34,6 @@ router //change Product status router.route("/product/admin/status/:id").patch(ChangeProductStatus); -router - .route("/product/admin/feature_product/status/:id") - .patch(ChangeFeatueProductStatus); //get all product user router.route("/product/getAll/user/").get(getAllProductUser);