diff --git a/resources/ShippingAddresses/ShippingAddressController.js b/resources/ShippingAddresses/ShippingAddressController.js index e176dcd..6ffef53 100644 --- a/resources/ShippingAddresses/ShippingAddressController.js +++ b/resources/ShippingAddresses/ShippingAddressController.js @@ -129,7 +129,7 @@ export const getSingleUserSippingAddress = async (req, res) => { res.status(201).json({ success: true, UserShippingAddress, - message: "All User Shipping Address Fetched", + message: "User Shipping Address Fetched", }); } } catch (error) { diff --git a/resources/ShippingAddressesRD/RDShippingAddressController.js b/resources/ShippingAddressesRD/RDShippingAddressController.js index 9c75435..0c06ec9 100644 --- a/resources/ShippingAddressesRD/RDShippingAddressController.js +++ b/resources/ShippingAddressesRD/RDShippingAddressController.js @@ -152,7 +152,7 @@ export const getSingleUserSippingAddress = async (req, res) => { res.status(201).json({ success: true, UserShippingAddress, - message: "All User Shipping Address Fetched", + message: "User Shipping Address Fetched", }); } } catch (error) { diff --git a/resources/Stock/PdStockModel.js b/resources/Stock/PdStockModel.js index a34fd86..39819cb 100644 --- a/resources/Stock/PdStockModel.js +++ b/resources/Stock/PdStockModel.js @@ -9,6 +9,7 @@ const ProductRecordSchema = new mongoose.Schema({ }, SKU: { type: String, + required: true, }, productName: { type: String, diff --git a/resources/Stock/RdStockModel.js b/resources/Stock/RdStockModel.js index e6be122..0486ebc 100644 --- a/resources/Stock/RdStockModel.js +++ b/resources/Stock/RdStockModel.js @@ -7,6 +7,10 @@ const ProductRecordSchema = new mongoose.Schema({ ref: "Product", required: true, }, + SKU: { + type: String, + required: true, + }, Stock: { type: Number, default: 0, diff --git a/resources/Stock/StockController.js b/resources/Stock/StockController.js index d485313..91e4240 100644 --- a/resources/Stock/StockController.js +++ b/resources/Stock/StockController.js @@ -6,7 +6,7 @@ import XLSX from "xlsx"; import fs from "fs"; import path from "path"; -export const uploadOpeningInventory = async (req, res) => { +export const uploadOpeningInventorypd = async (req, res) => { try { // Ensure valid user ID if (!mongoose.Types.ObjectId.isValid(req.user._id)) { @@ -89,7 +89,10 @@ export const uploadOpeningInventory = async (req, res) => { // Validate required fields if (!item.SKU) missingFields.add("SKU"); if (!item.productName) missingFields.add("Product Name"); - if (item.openingInventory === null || item.openingInventory === undefined) { + if ( + item.openingInventory === null || + item.openingInventory === undefined + ) { missingFields.add("Opening Inventory (Qty)"); } // Combine all errors into a single message @@ -219,6 +222,203 @@ export const uploadOpeningInventory = async (req, res) => { res.status(500).json({ message: "Internal Server Error" }); } }; +export const uploadOpeningInventoryrd = async (req, res) => { + try { + // Ensure valid user ID + if (!mongoose.Types.ObjectId.isValid(req.user._id)) { + return res.status(400).json({ message: "Please login again" }); + } + + // Ensure file is uploaded + 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": "productName", + "Opening Inventory (Qty)": "openingInventory", + }; + + 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 updatedOpeningInventories = []; + + // Fetch the user's stock or create if doesn't exist + let stock = await RDStock.findOne({ userId: req.params.userId }); + if (!stock) { + stock = new RDStock({ userId: req.params.userId, products: [] }); + } + + for (let i = 1; i < data.length; i++) { + const row = data[i]; + // Skip empty rows + if (row.every((cell) => cell === undefined || cell === "")) { + continue; + } + + const item = {}; + headers.forEach((header, index) => { + if (headerMapping[header]) { + item[headerMapping[header]] = + row[index] !== undefined ? row[index] : ""; + } + }); + // Initialize error tracking for each item + const missingFields = new Set(); + const validationErrors = new Set(); + + // Validate required fields + if (!item.SKU) missingFields.add("SKU"); + if (!item.productName) missingFields.add("Product Name"); + if ( + item.openingInventory === null || + item.openingInventory === undefined || + item.openingInventory === "" + ) { + missingFields.add("Opening Inventory (Qty)"); + } + // Combine all errors into a single message + let errorMessage = ""; + if (missingFields.size > 0) { + errorMessage += `Missing fields: ${Array.from(missingFields).join( + ", " + )}. `; + } + const product = await Product.findOne({ SKU: item.SKU }); + if (!product) { + validationErrors.add("Product not found"); + } + if (validationErrors.size > 0) { + errorMessage += `Validation errors: ${Array.from(validationErrors).join( + ", " + )}.`; + } + + // If there are errors, push them to the errors array + if (errorMessage.trim()) { + errors.push({ + SKU: item.SKU, + productName: item.productName, + openingInventory: item.openingInventory, + message: errorMessage.trim(), + }); + continue; + } + + // Cast opening inventory to a number to handle leading zeros and ensure numeric comparisons + const newOpeningInventory = Number(item.openingInventory); + // Check if product exists in user's stock + const existingProduct = stock.products.find( + (p) => p.SKU === item.SKU.toString() + ); + if (existingProduct) { + // Update product if it already exists and the inventory is different + if (Number(existingProduct.openingInventory) !== newOpeningInventory) { + existingProduct.openingInventory = newOpeningInventory; + existingProduct.Stock = newOpeningInventory; + updatedOpeningInventories.push({ + SKU: existingProduct.SKU, + updatedFields: "openingInventory", + openingInventory: newOpeningInventory, + productName: existingProduct.productName, + }); + } + } else { + // Create new product entry + stock.products.push({ + productid: product._id, + SKU: item.SKU, + productName: item.productName, + openingInventory: item.openingInventory, + Stock: item.openingInventory, + }); + newlyCreated.push({ + SKU: item.SKU, + productName: item.productName, + openingInventory: item.openingInventory, + }); + } + } + + // Ensure all products from the Product collection are in PDStock + const allProducts = await Product.find({}); + for (const product of allProducts) { + const existingProductInStock = stock.products.find( + (p) => p.SKU === product.SKU + ); + if (!existingProductInStock) { + stock.products.push({ + productid: product._id, + SKU: product.SKU, + productName: product.name, + openingInventory: 0, + Stock: 0, + }); + newlyCreated.push({ + SKU: product.SKU, + productName: product.name, + openingInventory: 0, + }); + } + } + + // Save the updated stock + await stock.save(); + + // Clean up the uploaded file + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + } + + res.status(200).json({ + message: "File processed successfully", + newlyCreated, + updatedOpeningInventories, + errors, + }); + } catch (error) { + console.error(error); + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + } + res.status(500).json({ message: "Internal Server Error" }); + } +}; export const getProductsAndStockByPD = async (req, res) => { try { const userId = req.params.userId || req.user._id; @@ -392,14 +592,18 @@ export const getProductsAndStockByRD = async (req, res) => { const stockMap = {}; if (userStock && userStock.products) { userStock.products.forEach((product) => { - stockMap[product.productid.toString()] = product.Stock; + stockMap[product.productid.toString()] = { + Stock: product.Stock, + openingInventory: product.openingInventory, + }; }); } // Combine products with their respective stock const productsWithStock = products.map((product) => ({ ...product, - stock: stockMap[product._id.toString()] || 0, + stock: stockMap[product._id.toString()]?.Stock || 0, + openingInventory: stockMap[product._id.toString()]?.openingInventory || 0, })); // Get total count for pagination purposes @@ -568,7 +772,7 @@ export const getStockPD = async (req, res) => { // RD inventory export const createOrUpdateInventoryForRD = async (req, res) => { const userId = req.body.userId ? req.body.userId : req.user._id; - console.log("res came here "); + // console.log("res came here "); try { const { products } = req.body; const allProducts = await Product.find({}, "_id SKU name"); diff --git a/resources/Stock/StockRoute.js b/resources/Stock/StockRoute.js index 7cdde0c..b126813 100644 --- a/resources/Stock/StockRoute.js +++ b/resources/Stock/StockRoute.js @@ -7,7 +7,8 @@ import { getProductsAndStockByRD, getStockPD, getStockRD, - uploadOpeningInventory, + uploadOpeningInventorypd, + uploadOpeningInventoryrd, } from "./StockController.js"; import { authorizeRoles, isAuthenticatedUser } from "../../middlewares/auth.js"; import { isAuthenticatedRD } from "../../middlewares/rdAuth.js"; @@ -15,10 +16,16 @@ const router = express.Router(); // Routes router.post( - "/openinginventories/upload/:userId", + "/openinginventories/pd/upload/:userId", isAuthenticatedUser, authorizeRoles("admin"), - uploadOpeningInventory + uploadOpeningInventorypd +); +router.post( + "/openinginventories/rd/upload/:userId", + isAuthenticatedUser, + authorizeRoles("admin"), + uploadOpeningInventoryrd ); router.get("/pd/stock/:userId", isAuthenticatedUser, getProductsAndStockByPD); router.get("/pd/stock", isAuthenticatedUser, getStockPD);