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 === null || item.openingInventory === undefined) { 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; // Pagination parameters const PAGE_SIZE = parseInt(req.query.show) || 10; const page = parseInt(req.query.page) || 1; const skip = (page - 1) * PAGE_SIZE; // Filtering criteria const filter = {}; if (req.query.name) { filter.name = { $regex: new RegExp(req.query.name, "i"), }; } if (req.query.category) { filter.category = mongoose.Types.ObjectId(req.query.category); } if (req.query.brand) { filter.brand = mongoose.Types.ObjectId(req.query.brand); } // Fetch user's PDStock data and products concurrently const [userStock, products] = await Promise.all([ PDStock.findOne({ userId: mongoose.Types.ObjectId(userId) }), Product.aggregate([ { $match: filter }, { $lookup: { from: "categorymodels", localField: "category", foreignField: "_id", as: "categoryDetails", }, }, { $lookup: { from: "brandmodels", localField: "brand", foreignField: "_id", as: "brandDetails", }, }, { $project: { category: { $arrayElemAt: ["$categoryDetails.categoryName", 0] }, brand: { $arrayElemAt: ["$brandDetails.brandName", 0] }, GST: 1, HSN_Code: 1, SKU: 1, addedBy: 1, createdAt: 1, description: 1, image: 1, name: 1, price: 1, product_Status: 1, updatedAt: 1, }, }, { $skip: skip }, { $limit: PAGE_SIZE }, ]), ]); // Create a stock map for easy lookup from userStock const stockMap = {}; if (userStock && userStock.products) { userStock.products.forEach((product) => { stockMap[product.productid.toString()] = { Stock: product.Stock, openingInventory: product.openingInventory, }; }); } // Combine products with their respective stock and opening inventory const productsWithStock = products.map((product) => ({ ...product, stock: stockMap[product._id.toString()]?.Stock || 0, openingInventory: stockMap[product._id.toString()]?.openingInventory || 0, })); // Get total count for pagination purposes const total = await Product.countDocuments(filter); return res.status(200).json({ success: true, totalProducts: total, totalPages: Math.ceil(total / PAGE_SIZE), currentPage: page, products: productsWithStock, }); } catch (error) { console.error("Error fetching products with stock:", error); return res.status(500).json({ success: false, message: "Error fetching products and stock", }); } }; export const getProductsAndStockByRD = async (req, res) => { try { const userId = req.params.userId || req.user._id; // Pagination parameters const PAGE_SIZE = parseInt(req.query.show) || 10; const page = parseInt(req.query.page) || 1; const skip = (page - 1) * PAGE_SIZE; // Filtering criteria const filter = {}; if (req.query.name) { filter.name = { $regex: new RegExp(req.query.name, "i"), }; } if (req.query.category) { filter.category = mongoose.Types.ObjectId(req.query.category); } if (req.query.brand) { filter.brand = mongoose.Types.ObjectId(req.query.brand); } // Fetch user's RDStock data and products concurrently const [userStock, products] = await Promise.all([ RDStock.findOne({ userId: mongoose.Types.ObjectId(userId) }), Product.aggregate([ { $match: filter }, { $lookup: { from: "categorymodels", localField: "category", foreignField: "_id", as: "categoryDetails", }, }, { $lookup: { from: "brandmodels", localField: "brand", foreignField: "_id", as: "brandDetails", }, }, { $project: { category: { $arrayElemAt: ["$categoryDetails.categoryName", 0] }, brand: { $arrayElemAt: ["$brandDetails.brandName", 0] }, GST: 1, HSN_Code: 1, SKU: 1, addedBy: 1, createdAt: 1, description: 1, image: 1, name: 1, price: 1, product_Status: 1, updatedAt: 1, }, }, { $skip: skip }, { $limit: PAGE_SIZE }, ]), ]); // Create a stock map for easy lookup const stockMap = {}; if (userStock && userStock.products) { userStock.products.forEach((product) => { stockMap[product.productid.toString()] = product.Stock; }); } // Combine products with their respective stock const productsWithStock = products.map((product) => ({ ...product, stock: stockMap[product._id.toString()] || 0, })); // Get total count for pagination purposes const total = await Product.countDocuments(filter); return res.status(200).json({ success: true, totalProducts: total, totalPages: Math.ceil(total / PAGE_SIZE), currentPage: page, products: productsWithStock, }); } catch (error) { console.error("Error fetching products with stock:", error); return res.status(500).json({ success: false, message: "Error fetching products and stock", }); } }; // Create or Update 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()); // // 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 }); // 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 (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 { // 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; 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 getStockPD = async (req, res) => { try { const userId = req.user._id; // userId from request // Fetch all products with their _id, name, and SKU const allProducts = await Product.find({}, "_id name SKU"); // Map products into an object for easy access const productMap = new Map( allProducts.map((p) => [p._id.toString(), { name: p.name, sku: p.SKU }]) ); // Fetch the user's stock from the PDStock model let stock = await PDStock.findOne({ userId }); let productsWithStock = []; if (stock) { // Create a map of product stocks from the user's stock const stockMap = new Map( stock.products.map((p) => [ p.productid.toString(), { Stock: p.Stock, openingInventory: p.openingInventory || 0 }, ]) ); // Iterate over all products, assigning stock and opening inventory productsWithStock = allProducts.map((product) => { const productStock = stockMap.get(product._id.toString()) || { Stock: 0, openingInventory: 0, }; return { productid: product._id, name: product.name, SKU: product.SKU, Stock: productStock.Stock, openingInventory: productStock.openingInventory, }; }); } else { // If no stock entry exists, initialize all products with stock and opening inventory as 0 productsWithStock = allProducts.map((product) => ({ productid: product._id, name: product.name, SKU: product.SKU, Stock: 0, openingInventory: 0, })); } return res.status(200).json({ message: "Stock fetched successfully", stocks: productsWithStock, }); } catch (error) { console.error("Error fetching stock:", error); res.status(500).json({ message: "Server error", error }); } }; export const getAllUsersWithStock = async (req, res) => { try { // Destructure query parameters for pagination and filtering const { page = 1, show = 10, name = "", SBU = "" } = req.query; // Convert page and show to numbers const currentPage = parseInt(page, 10); const itemsPerPage = parseInt(show, 10); // Create filters for user based on name and SBU const userFilters = {}; if (name) { userFilters.name = { $regex: name, $options: "i" }; // Case-insensitive regex for name } if (SBU) { userFilters.SBU = { $regex: SBU, $options: "i" }; // Case-insensitive regex for SBU } // Find stock records and populate user details const stockRecords = await PDStock.find() .populate({ path: "userId", model: "User", // Ensure the correct model is used select: "uniqueId SBU name email phone", // Select specific fields match: userFilters, // Apply user filters for name and SBU }) .skip((currentPage - 1) * itemsPerPage) // Pagination .select("-products") .limit(itemsPerPage); // Filter out stock records where userId is null (no match found) const filteredStockRecords = stockRecords.filter( (record) => record.userId !== null ); // Count total records after filtering const totalRecords = await PDStock.countDocuments({ userId: { $exists: true }, }); // Return the filtered stock records with pagination info res.status(200).json({ success: true, data: filteredStockRecords, pagination: { currentPage, itemsPerPage, totalRecords, totalPages: Math.ceil(totalRecords / itemsPerPage), }, }); } catch (error) { console.error(error); res.status(500).json({ success: false, message: "Error fetching users and stock records", }); } };