diff --git a/resources/Stock/PdStockModel.js b/resources/Stock/PdStockModel.js index 2fc699f..a34fd86 100644 --- a/resources/Stock/PdStockModel.js +++ b/resources/Stock/PdStockModel.js @@ -10,9 +10,20 @@ const ProductRecordSchema = new mongoose.Schema({ SKU: { type: String, }, + productName: { + type: String, + required: true, + }, openingInventory: { type: Number, default: 0, + set: (value) => { + if (typeof value === "string") { + // Convert to number and remove leading zeros + return Number(value.replace(/^0+/, "")) || undefined; + } + return value; + }, }, Stock: { type: Number, diff --git a/resources/Stock/StockController.js b/resources/Stock/StockController.js index 53bbf9a..dbfce80 100644 --- a/resources/Stock/StockController.js +++ b/resources/Stock/StockController.js @@ -2,7 +2,222 @@ import mongoose from "mongoose"; import { PDStock } from "./PdStockModel.js"; import { Product } from "../Products/ProductModel.js"; import { RDStock } from "./RdStockModel.js"; +import XLSX from "xlsx"; +import fs from "fs"; +import path from "path"; +export const uploadOpeningInventory = 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 PDStock.findOne({ userId: req.params.userId }); + if (!stock) { + stock = new PDStock({ 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) 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; + } + // // Validate the required fields + // if (!item.SKU || !item.productName || !item.openingInventory) { + // errors.push({ + // SKU: item.SKU || "N/A", + // productName: item.productName || "N/A", + // openingInventory: item.openingInventory || "N/A", + // message: "Missing required fields", + // }); + // continue; + // } + + // const product = await Product.findOne({ SKU: item.SKU }); + // if (!product) { + // errors.push({ + // SKU: item.SKU, + // productName: item.productName, + // openingInventory: item.openingInventory, + // message: "Product not found", + // }); + // 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; @@ -206,47 +421,127 @@ export const getProductsAndStockByRD = async (req, res) => { }; // Create or Update Stock -export const createOrUpdateInventory = async (req, res) => { - const userId = req.user._id; - try { - const { products } = req.body; // products: [{ productid, Stock }] +// export const createOrUpdateInventory = async (req, res) => { +// const userId = req.body.userId ? req.body.userId : req.user._id; +// try { +// const { products } = req.body; // products: [{ productid, Stock }] +// console.log(products); +// // Fetch all products in the system +// const allProducts = await Product.find({}, "_id SKU"); // Fetch only _id and SKU fields +// const allProductIds = allProducts.map((p) => p._id.toString()); - // Fetch all products in the system - const allProducts = await Product.find({}, "_id SKU"); // Fetch only _id and SKU fields - const allProductIds = allProducts.map((p) => p._id.toString()); +// // Find existing stock data for the user +// let stock = await PDStock.findOne({ userId }); + +// const updatedProducts = allProductIds.map((productId) => { +// const productInRequest = products.find((p) => p.productid === productId); +// const existingProduct = stock?.products.find( +// (p) => p.productid.toString() === productId +// ); + +// if (existingProduct) { +// // Product exists, only update opening inventory +// return { +// ...existingProduct, +// openingInventory: productInRequest +// ? productInRequest.openingInventory +// : existingProduct.openingInventory, +// Stock: productInRequest ? productInRequest.openingInventory : 0, +// }; +// } else { +// // New product, set both stock and opening inventory to the same value + +// return { +// productid: productId, +// SKU: allProducts.find((p) => p._id.toString() === productId).SKU, +// openingInventory: productInRequest +// ? productInRequest.openingInventory +// : 0, +// Stock: productInRequest ? productInRequest.openingInventory : 0, +// }; +// } +// }); + +// if (stock) { +// // Update existing stock entry +// stock.products = updatedProducts; +// await stock.save(); +// return res +// .status(200) +// .json({ message: "Stock updated successfully", stock }); +// } else { +// // Create new stock entry +// const newStock = new PDStock({ userId, products: updatedProducts }); +// await newStock.save(); +// return res +// .status(201) +// .json({ message: "Stock created successfully", stock: newStock }); +// } +// } catch (error) { +// console.error("Error updating or creating stock:", error); +// res.status(500).json({ message: "Server error", error }); +// } +// }; +export const createOrUpdateInventory = async (req, res) => { + const userId = req.body.userId ? req.body.userId : req.user._id; + // console.log(userId); + try { + const { products } = req.body; // products: [{ SKU, openingInventory }] + // console.log(products); + + // Fetch all products in the system (get _id, SKU, and name) + const allProducts = await Product.find({}, "_id SKU name"); // Find existing stock data for the user let stock = await PDStock.findOne({ userId }); - const updatedProducts = allProductIds.map((productId) => { - const productInRequest = products.find((p) => p.productid === productId); - const existingProduct = stock?.products.find( - (p) => p.productid.toString() === productId + // If no products found in the request, return an error + if (!products || products.length === 0) { + return res.status(400).json({ message: "No products provided." }); + } + + const updatedProducts = []; + + // Loop through the requested products and process them + for (const reqProduct of products) { + const { SKU, openingInventory } = reqProduct; + + // Find the product by SKU in the Product collection + const productInSystem = allProducts.find((p) => p.SKU === SKU); + + if (!productInSystem) { + // Skip products that don't exist in the system + console.log( + `Product with SKU ${SKU} not found in the system. Skipping...` + ); + continue; + } + + // Find the product in existing PDStock (if any) + const existingProductInStock = stock?.products.find( + (p) => p.productid.toString() === productInSystem._id.toString() ); - if (existingProduct) { - // Product exists, only update opening inventory - return { - ...existingProduct, - openingInventory: productInRequest - ? productInRequest.openingInventory - : existingProduct.openingInventory, - Stock: productInRequest ? productInRequest.openingInventory : 0, - }; + if (existingProductInStock) { + // If the product exists in stock, update opening inventory and stock + existingProductInStock.openingInventory = + openingInventory || existingProductInStock.openingInventory; + existingProductInStock.Stock = + openingInventory || existingProductInStock.Stock; + updatedProducts.push(existingProductInStock); } else { - // New product, set both stock and opening inventory to the same value - - return { - productid: productId, - SKU: allProducts.find((p) => p._id.toString() === productId).SKU, - openingInventory: productInRequest - ? productInRequest.openingInventory - : 0, - Stock: productInRequest ? productInRequest.openingInventory : 0, + // If the product doesn't exist in PDStock, create a new entry + const newProductInStock = { + productid: productInSystem._id, + SKU: productInSystem.SKU, + productName: productInSystem.name, + openingInventory: openingInventory || 0, + Stock: openingInventory || 0, }; + updatedProducts.push(newProductInStock); } - }); - + } + // console.log(updatedProducts); if (stock) { // Update existing stock entry stock.products = updatedProducts; diff --git a/resources/Stock/StockRoute.js b/resources/Stock/StockRoute.js index 4fb9fae..eb81cb0 100644 --- a/resources/Stock/StockRoute.js +++ b/resources/Stock/StockRoute.js @@ -5,11 +5,19 @@ import { getProductsAndStockByPD, getProductsAndStockByRD, getStockPD, + uploadOpeningInventory, } from "./StockController.js"; import { authorizeRoles, isAuthenticatedUser } from "../../middlewares/auth.js"; import { isAuthenticatedRD } from "../../middlewares/rdAuth.js"; const router = express.Router(); +// Routes +router.post( + "/openinginventories/upload/:userId", + isAuthenticatedUser, + authorizeRoles("admin"), + uploadOpeningInventory +); router.get("/pd/stock/:userId", isAuthenticatedUser, getProductsAndStockByPD); router.get("/pd/stock", isAuthenticatedUser, getStockPD); router.put("/pd/stock-update", isAuthenticatedUser, createOrUpdateInventory);