Untitled
unknown
plain_text
4 months ago
74 kB
2
Indexable
// controllers/productController.js const mongoose = require('mongoose'); const multer = require('multer'); const xlsx = require('xlsx'); const fs = require('fs'); const fsp = fs.promises; const path = require('path'); const unzipper = require('unzipper'); const Product = require('../Models/productModel.js'); const SubCategory = require('../Models/subcategoryModel.js'); const Group = require('../Models/groupModel.js'); const Brand = require('../Models/brandModel.js'); const Category = require('../Models/categoryModel.js'); const User = require('../Models/userModel.js'); const Retailer = require('../Models/retailerModel.js'); const Image = require('../Models/imageModel.js'); const catchAsync = require('../utils/catchAsync.js'); const AppError = require('../utils/appError.js'); const { isValidObjectId } = require('mongoose'); const Distributeur = require('../Models/distributeurModel.js'); const { VariantValidate } = require('../middleware/productRequest.js'); const VariantAttribute = require('../Models/variantattributeModel.js'); const VariantValue = require('../Models/variantvalueModel.js'); const ProductVariant = require('../Models/productvariantModel.js'); const Client = require('../Models/clientModel.js'); const defaultImage = '/img/products/default-product-image.png'; // Configure multer for ZIP file upload const upload = multer({ dest: 'uploads/' }); // Helper functions to parse booleans and safely trim strings const parseBoolean = (value) => { return value && ['TRUE', 'true', 'True', true].includes(value); }; const safeTrim = (value) => { return value ? value.toString().trim() : ''; }; // Caches to minimize database queries const categoryCache = new Map(); const subcategoryCache = new Map(); const groupCache = new Map(); const brandCache = new Map(); // Helper functions for finding or creating categories, subcategories, groups, and brands const findOrCreateCategory = async (categoryStr) => { if (categoryCache.has(categoryStr)) { return categoryCache.get(categoryStr); } let categoryDoc = await Category.findOne({ category: categoryStr }); if (!categoryDoc) { categoryDoc = new Category({ category: categoryStr }); await categoryDoc.save(); } categoryCache.set(categoryStr, categoryDoc); return categoryDoc; }; const findOrCreateSubCategory = async (subcategoryStr, categoryDoc) => { const cacheKey = `${subcategoryStr}_${categoryDoc._id}`; if (subcategoryCache.has(cacheKey)) { return subcategoryCache.get(cacheKey); } let subcategoryDoc = await SubCategory.findOne({ subcategory: subcategoryStr, category: categoryDoc._id }); if (!subcategoryDoc) { subcategoryDoc = new SubCategory({ subcategory: subcategoryStr, category: categoryDoc._id }); await subcategoryDoc.save(); // Update category with new subcategory categoryDoc.subcategories = categoryDoc.subcategories || []; categoryDoc.subcategories.push(subcategoryDoc._id); await categoryDoc.save(); } subcategoryCache.set(cacheKey, subcategoryDoc); return subcategoryDoc; }; const findOrCreateGroup = async (groupStr, subcategoryDoc) => { const cacheKey = `${groupStr}_${subcategoryDoc ? subcategoryDoc._id : 'null'}`; if (groupCache.has(cacheKey)) { return groupCache.get(cacheKey); } let groupDoc = await Group.findOne({ group: groupStr, subcategory: subcategoryDoc ? subcategoryDoc._id : null }); if (!groupDoc) { groupDoc = new Group({ group: groupStr, subcategory: subcategoryDoc ? subcategoryDoc._id : null }); await groupDoc.save(); // Update subcategory with new group if (subcategoryDoc) { subcategoryDoc.groups = subcategoryDoc.groups || []; subcategoryDoc.groups.push(groupDoc._id); await subcategoryDoc.save(); } } groupCache.set(cacheKey, groupDoc); return groupDoc; }; const findOrCreateBrand = async (brandStr) => { if (brandCache.has(brandStr)) { return brandCache.get(brandStr); } let brandDoc = await Brand.findOne({ brand: brandStr }); if (!brandDoc) { brandDoc = new Brand({ brand: brandStr }); await brandDoc.save(); } brandCache.set(brandStr, brandDoc); return brandDoc; }; // Function to convert images for products without variants (unchanged) const convertImagesToBuffers = async (sku, directory) => { let folderPath = path.join(directory, String(sku)); console.log(`Checking folder path: ${folderPath}`); try { await fsp.access(folderPath); } catch (err) { folderPath = path.join(directory, 'Products', String(sku)); console.log(`Trying nested folder path: ${folderPath}`); try { await fsp.access(folderPath); } catch (err) { throw new Error(`Image folder for SKU ${sku} does not exist.`); } } const allowedImageTypes = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']; const files = await fsp.readdir(folderPath); const imageFiles = files.filter((file) => allowedImageTypes.includes(path.extname(file).toLowerCase())); if (imageFiles.length === 0) { throw new Error(`No images found for SKU ${sku}`); } const imageBuffers = await Promise.all( imageFiles.map(async (fileName) => { const filePath = path.join(folderPath, fileName); return await fsp.readFile(filePath); }) ); return imageBuffers; }; const getVariantImageBuffer = async (parentSku, variantSku, directory) => { let folderPath = path.join(directory, String(parentSku)); console.log(`Checking variant folder path: ${folderPath} for variant SKU: ${variantSku}`); try { await fsp.access(folderPath); } catch (err) { folderPath = path.join(directory, 'Products', String(parentSku)); console.log(`Trying nested folder path for variant: ${folderPath}`); try { await fsp.access(folderPath); } catch (err) { throw new Error(`Image folder for Parent SKU ${parentSku} does not exist.`); } } const allowedImageTypes = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']; const files = await fsp.readdir(folderPath); // Find file whose basename (without extension) exactly matches variantSku const matchingFiles = files.filter((file) => { const ext = path.extname(file).toLowerCase(); const base = path.basename(file, ext); return allowedImageTypes.includes(ext) && base === variantSku; }); if (matchingFiles.length === 0) { throw new Error(`No image found for variant SKU ${variantSku} in folder ${parentSku}`); } const filePath = path.join(folderPath, matchingFiles[0]); return await fsp.readFile(filePath); }; // Function to find files recursively const findFilesRecursively = async (directory, fileExtension) => { let foundFiles = []; const files = await fsp.readdir(directory, { withFileTypes: true }); for (const file of files) { const fullPath = path.join(directory, file.name); if (file.isDirectory()) { const subFiles = await findFilesRecursively(fullPath, fileExtension); foundFiles = foundFiles.concat(subFiles); } else if (file.name.toLowerCase().endsWith(fileExtension)) { foundFiles.push(fullPath); } } return foundFiles; }; // Helper function to clean up files and directories const cleanUp = async (...paths) => { for (const pathToDelete of paths) { try { await fsp.rm(pathToDelete, { recursive: true, force: true }); } catch (err) { console.error(`Error deleting ${pathToDelete}:`, err); } } }; // Main function to upload products exports.uploadProducts = catchAsync(async (req, res, next) => { const file = req.file; if (!file) { return next(new AppError('Please upload a ZIP file', 400)); } const zipFilePath = file.path; const extractPath = path.join(__dirname, '../uploads', 'extracted'); await fsp.mkdir(extractPath, { recursive: true }); try { // Unzip the file await unzipper.Open.file(zipFilePath).then((d) => d.extract({ path: extractPath })); // Process the extracted files const extractedContents = await fsp.readdir(extractPath); console.log('Extracted files:', extractedContents); // Recursively search for Excel files const excelFilePaths = await findFilesRecursively(extractPath, '.xlsx'); if (excelFilePaths.length === 0) { await cleanUp(extractPath); return next(new AppError('Excel files not found in the uploaded ZIP file', 400)); } for (const excelFilePath of excelFilePaths) { console.log('Found Excel file:', excelFilePath); const workbook = xlsx.readFile(excelFilePath); const workbookName = path.basename(excelFilePath); if (workbookName.includes('Products_Without_Variants')) { const sheetName = workbook.SheetNames[0]; const productsWithoutVariantsSheet = workbook.Sheets[sheetName]; if (productsWithoutVariantsSheet) { const productsWithoutVariants = xlsx.utils.sheet_to_json(productsWithoutVariantsSheet); await processProductsWithoutVariants(productsWithoutVariants, extractPath); } } else if (workbookName.includes('Products_With_Variants')) { const mainProductsSheet = workbook.Sheets['Main_Products']; const productsVariantsSheet = workbook.Sheets['Products_Variants']; if (mainProductsSheet && productsVariantsSheet) { const parentProductsData = xlsx.utils.sheet_to_json(mainProductsSheet, { defval: '' }); const variantsData = xlsx.utils.sheet_to_json(productsVariantsSheet, { defval: '' }); await processProductsWithVariants(parentProductsData, variantsData, extractPath); } else { console.error('Required sheets not found in Products_With_Variants.xlsx'); } } } await cleanUp(zipFilePath, extractPath); res.status(200).json({ message: 'Products uploaded successfully' }); } catch (error) { console.error('Error processing file:', error); await cleanUp(zipFilePath, extractPath); return next(new AppError(error.message, 400)); } }); // Function to process products without variants (remains unchanged) const processProductsWithoutVariants = async (products, extractPath) => { for (const productData of products) { try { const { sku, quantity, name_en, name_fr, name_ar, description_en, description_fr, description_ar, category, subcategory, group, brand, initialPrice, retailerPrices_first, retailerPrices_second, retailerPrices_third, distributeurPrices_first, distributeurPrices_second, distributeurPrices_third, publicPrice, visibility, isNew } = productData; if (!category || !brand) { console.error(`Category and brand are required for product SKU: ${sku}`); continue; } const categoryStr = safeTrim(category); const subcategoryStr = safeTrim(subcategory); const groupStr = safeTrim(group); const brandStr = safeTrim(brand); const [categoryDoc, brandDoc] = await Promise.all([ findOrCreateCategory(categoryStr), findOrCreateBrand(brandStr) ]); const subcategoryDoc = subcategoryStr ? await findOrCreateSubCategory(subcategoryStr, categoryDoc) : null; const groupDoc = groupStr ? await findOrCreateGroup(groupStr, subcategoryDoc) : null; let imageBuffers; try { imageBuffers = await convertImagesToBuffers(sku, extractPath); } catch (error) { console.error(`Error loading images for SKU ${sku}:`, error.message); continue; } const imageDocuments = imageBuffers.map((buffer) => ({ image: buffer })); const imageInsertionResults = await Image.insertMany(imageDocuments); const imgs = imageInsertionResults.map((doc) => doc._id); const product = new Product({ sku, quantity, name: { en: name_en, fr: name_fr, ar: name_ar }, description: { en: description_en, fr: description_fr, ar: description_ar }, images: imgs, categories: [categoryDoc._id], subcategories: subcategoryDoc ? [subcategoryDoc._id] : [], groups: groupDoc ? [groupDoc._id] : [], brand: brandDoc._id, pricing: { initialPrice, publicPrice, retailerPrices: { first: retailerPrices_first, second: retailerPrices_second, third: retailerPrices_third }, distributeurPrices: { first: distributeurPrices_first, second: distributeurPrices_second, third: distributeurPrices_third } }, visibility: parseBoolean(visibility), isNew: parseBoolean(isNew), isvariant: false }); await product.save(); } catch (error) { console.error(`Error processing product SKU ${productData.sku}:`, error.message); continue; } } }; // Function to process products with variants const processProductsWithVariants = async (parentProductsData, variantsData, extractPath) => { const parentProductsMap = {}; // Process parent products for (const productData of parentProductsData) { try { const { parent_sku, parent_name_en, parent_name_fr, parent_name_ar, parent_description_en, parent_description_fr, parent_description_ar, category, subcategory, group, brand, visibility, isNew } = productData; if (!category || !brand) { console.error(`Category or brand missing for SKU: ${parent_sku}`); continue; } const categoryStr = safeTrim(category); const subcategoryStr = safeTrim(subcategory); const groupStr = safeTrim(group); const brandStr = safeTrim(brand); const [categoryDoc, brandDoc] = await Promise.all([ findOrCreateCategory(categoryStr), findOrCreateBrand(brandStr) ]); const subcategoryDoc = subcategoryStr ? await findOrCreateSubCategory(subcategoryStr, categoryDoc) : null; const groupDoc = groupStr ? await findOrCreateGroup(groupStr, subcategoryDoc) : null; // Load images for the parent product from the folder named by parent_sku let imageBuffers; try { imageBuffers = await convertImagesToBuffers(parent_sku, extractPath); } catch (error) { console.error(`Error loading images for parent SKU ${parent_sku}:`, error.message); continue; } const imageDocuments = imageBuffers.map((buffer) => ({ image: buffer })); const imageInsertionResults = await Image.insertMany(imageDocuments); const imgs = imageInsertionResults.map((doc) => doc._id); // Create the parent product document let parentProduct = new Product({ sku: parent_sku, name: { en: parent_name_en, fr: parent_name_fr, ar: parent_name_ar }, description: { en: parent_description_en, fr: parent_description_fr, ar: parent_description_ar }, images: imgs, categories: [categoryDoc._id], subcategories: subcategoryDoc ? [subcategoryDoc._id] : [], groups: groupDoc ? [groupDoc._id] : [], brand: brandDoc._id, visibility: parseBoolean(visibility), isNew: parseBoolean(isNew), isvariant: true, variants: [] }); await parentProduct.save(); parentProductsMap[parent_sku] = parentProduct; } catch (error) { console.error(`Error processing parent product with SKU ${productData.parent_sku}:`, error.message); continue; } } // Process variants for (const variantData of variantsData) { try { const { parent_sku, variant_sku, variant_quantity, variant_pricing_initialPrice, variant_pricing_publicPrice, variant_pricing_retailerPrices_first, variant_pricing_retailerPrices_second, variant_pricing_retailerPrices_third, variant_pricing_distributeurPrices_first, variant_pricing_distributeurPrices_second, variant_pricing_distributeurPrices_third } = variantData; if (!variant_sku) { console.error(`Missing variant_sku in product data for parent SKU ${parent_sku}`); continue; } // Get the parent product const parentProduct = parentProductsMap[parent_sku]; if (!parentProduct) { console.error(`Parent product with SKU ${parent_sku} not found.`); continue; } const variantAttributes = []; let index = 1; while (variantData[`variant_attribute_name_${index}`]) { const attributeName = variantData[`variant_attribute_name_${index}`]; const attributeValue = variantData[`variant_attribute_value_${index}`]; if (!attributeName || !attributeValue) { index++; continue; } let attributeDoc = await VariantAttribute.findOne({ attributeName, product: parentProduct._id }); if (!attributeDoc) { attributeDoc = new VariantAttribute({ attributeName, product: parentProduct._id }); await attributeDoc.save(); } let valueDoc = await VariantValue.findOne({ attribute: attributeDoc._id, value: attributeValue }); if (!valueDoc) { valueDoc = new VariantValue({ attribute: attributeDoc._id, value: attributeValue }); await valueDoc.save(); } variantAttributes.push(valueDoc._id); index++; } // Attempt to load the variant image from the parent's folder based on variant_sku filename let variantImageBuffer; try { variantImageBuffer = await getVariantImageBuffer(parent_sku, variant_sku, extractPath); } catch (error) { console.warn( `No variant image found for variant SKU ${variant_sku} in parent folder ${parent_sku}: ${error.message}` ); variantImageBuffer = null; } let variantImageId; if (variantImageBuffer) { const variantImageDoc = { image: variantImageBuffer }; const variantImageInsertionResult = await Image.create(variantImageDoc); variantImageId = variantImageInsertionResult._id; } else { // Fallback to parent's first image variantImageId = parentProduct.images[0]; } // Create the variant document const productVariant = new ProductVariant({ sku: variant_sku, quantity: variant_quantity, pricing: { initialPrice: variant_pricing_initialPrice, publicPrice: variant_pricing_publicPrice, retailerPrices: { first: variant_pricing_retailerPrices_first, second: variant_pricing_retailerPrices_second, third: variant_pricing_retailerPrices_third }, distributeurPrices: { first: variant_pricing_distributeurPrices_first, second: variant_pricing_distributeurPrices_second, third: variant_pricing_distributeurPrices_third } }, attributevalues: variantAttributes, image: variantImageId }); await productVariant.save(); parentProduct.variants.push(productVariant._id); await parentProduct.save(); } catch (error) { console.error(`Error processing variant with SKU ${variantData.variant_sku}:`, error.message); continue; } } }; exports.addProduct = catchAsync(async (req, res, next) => { let { sku, quantity, name, description, categories, subcategories = [], groups = [], brand, tags, pricing } = req.body; quantity = parseInt(quantity); const skuRegex = /^[a-zA-Z0-9_-]{4,16}$/; // Validation checks if (!sku || !skuRegex.test(sku)) { return next(new AppError('Invalid SKU format', 400)); } if (!quantity || typeof quantity !== 'number' || quantity < 0) { return next(new AppError('Invalid quantity format', 400)); } if (!categories || !Array.isArray(categories) || !categories.length) { return next(new AppError('Categories array cannot be empty', 400)); } const existingProduct = await Product.findOne({ sku }); if (existingProduct) { return next(new AppError('Product with the same SKU already exists', 400)); } // Processing images const imageDocuments = req.body.images.map((base64Str) => ({ image: new Buffer.from(base64Str, 'base64') })); const imageInsertionResults = await Image.insertMany(imageDocuments); const imgs = imageInsertionResults.map((doc) => doc._id); // Filter out any invalid ObjectIds from subcategories and groups const validSubcategories = subcategories.filter((subcatId) => isValidObjectId(subcatId)); const validGroups = groups.filter((groupId) => isValidObjectId(groupId)); // Validate brand if provided let validBrand; if (brand && isValidObjectId(brand)) { const existingBrand = await Brand.findById(brand); if (!existingBrand) { return next(new AppError('Brand not found', 400)); } validBrand = brand; } // Create and save the new product const product = new Product({ sku, quantity, name, description, images: imgs, categories, subcategories: validSubcategories, groups: validGroups, brand: validBrand, tags, pricing, visibility: true }); await product.save(); res.status(201).json({ success: true, message: 'The product has been successfully added', product }); }); exports.updateProduct = catchAsync(async (req, res, next) => { let body = JSON.parse(JSON.stringify(req.body)); const productId = req.params.id; const session = await mongoose.startSession(); session.startTransaction(); try { if (!isValidObjectId(productId)) { return next(new AppError('Invalid product ID', 400)); } const product = await Product.findById(productId); if (!product) { return next(new AppError('Product not found', 400)); } let { sku, quantity, name, description, categories, subcategories, groups, brand, pricing, images } = body; // Update SKU if provided and unique if (sku && sku !== product.sku) { const existingProduct = await Product.findOne({ sku }); if (existingProduct && existingProduct._id.toString() !== productId) { return next(new AppError('Product with the same SKU already exists', 400)); } product.sku = sku; } if (quantity) { product.quantity = parseInt(quantity); } if (name) { product.name = name; } if (description) { product.description = description; } if (categories) { const ids = await checkCategories(categories, next); console.log(ids); product.categories = ids; } if (subcategories) { const ids = await checkSubCategories(subcategories, categories, next); product.subcategories = ids; } if (groups) { const ids = await checkGroups(groups, subcategories, next); product.groups = ids; } if (pricing) { product.pricing = pricing; // Make sure to validate or adjust pricing structure as needed } // Update brand if valid ObjectId if (brand && isValidObjectId(brand)) { const existingBrand = await Brand.findById(brand); if (!existingBrand) { return next(new AppError('Brand not found', 404)); } product.brand = brand; } // Process images if provided if (images && images.length) { const imageDocuments = images.map((base64Str) => ({ image: new Buffer.from(base64Str, 'base64') })); await Image.deleteMany({ _id: { $in: product.images } }, { session }); const imageInsertionResults = await Image.insertMany(imageDocuments, { session }); product.images = imageInsertionResults.map((doc) => doc._id); } await product.save(); await session.commitTransaction(); session.endSession(); res.status(200).json({ message: 'Product updated successfully', product }); } catch (error) { await session.abortTransaction(); session.endSession(); return next(error); } }); exports.viewProduct = catchAsync(async (req, res, next) => { const productId = req.params.id; let data = {}; let count = null; // buyer let buyer = null; if (req?.user.role == 'retailer') { buyer = await Retailer.findById({ _id: req?.user.profile }); } else if (req?.user.role == 'distributeur') { buyer = await Distributeur.findById({ _id: req?.user.profile }); } // Validate the productId if (!isValidObjectId(productId)) { return res.status(400).json({ message: 'Invalid product ID' }); } const pathsAndFields = [ { path: 'subcategories', select: 'subcategory' }, { path: 'categories', select: 'category' }, { path: 'groups', select: 'group' }, { path: 'brand', select: 'brand' }, { path: 'offer', select: ['discount', 'status'] }, { path: 'reviews.user', select: 'name' } ]; let product = await Product.findById(productId).populate(pathsAndFields); if (!product) { return next(new AppError('Product not found', 400)); } if (!product.visibility) { return next(new AppError('product not visible', 400)); } if (product.isvariant == true) { const attributes = await VariantAttribute.find({ product: product._id }); if (attributes.length < 1) { return next(new AppError('Invalid visibility value', 400)); } count = attributes.length; for (let prodvar_id of product.variants) { const prod = await ProductVariant.findById(prodvar_id).populate({ path: 'attributevalues', populate: { path: 'attribute' } }); let obj = data; for (let i = 0; i < attributes.length; i++) { for (let k = 0; k < prod.attributevalues.length; k++) { if (String(attributes[i]._id) == String(prod.attributevalues[k].attribute._id)) { if (obj.id) { if (!obj.values[prod.attributevalues[k].value.toString()]) { obj.values[prod.attributevalues[k].value.toString()] = {}; } } else { obj.id = prod.attributevalues[k].attribute._id; obj.name = prod.attributevalues[k].attribute.attributeName; obj.values = {}; obj.values[prod.attributevalues[k].value.toString()] = {}; } if (i == attributes.length - 1) { obj.values[prod.attributevalues[k].value.toString()] = {}; // prod variant let objVar = {}; if (buyer) { if (req?.user.role == 'retailer') { objVar.publicPrice = prod.pricing.retailerPrices[buyer.pricingType.toString()]; } else if (req?.user.role == 'distributeur') { objVar.publicPrice = prod.pricing.distributeurPrices[buyer.pricingType.toString()]; } } else { objVar.publicPrice = prod.pricing.publicPrice; } objVar.quantity = prod.quantity; objVar.image = prod?.image; objVar.name = prod?.name; objVar.sku = prod?.sku; objVar._id = prod?._id; objVar.id = prod?.id; obj.values[prod.attributevalues[k].value.toString()] = objVar; } else { obj = obj.values[prod.attributevalues[k].value.toString()]; } } } } } } // product objet let obj = {}; obj._id = product?._id; obj.name = product?.name; obj.description = product?.description; obj.images = product?.images; obj.visibility = product?.visibility; obj.categories = product?.categories; obj.isVariant = product.isvariant; obj.isNew = product.isNew; console.log('ooooo', product.offer); obj.offer = product?.offer?.discount ?? null; if (!product.isvariant) { if (buyer) { if (req?.user.role == 'retailer') { obj.publicPrice = product.pricing.retailerPrices[buyer.pricingType.toString()]; } else if (req?.user.role == 'distributeur') { obj.publicPrice = product.pricing.distributeurPrices[buyer.pricingType.toString()]; } } else { obj.publicPrice = product.pricing.publicPrice; } obj.quantity = product?.quantity; } obj.reviews = product?.reviews; //product.offer = product.offer?.at(0)?.discount ?? null; const relatedProducts = await RelatedProducts(productId, next); const relatedArray = relatedProducts.reduce((result, product) => { let obj = {}; obj._id = product?._id; obj.name = product?.name; obj.description = product?.description; obj.images = product?.images; obj.visibility = product?.visibility; obj.categories = product?.categories; obj.isVariant = product?.isvariant; obj.isNew = product?.isNew; obj.offer = product?.offer?.at(0)?.discount ?? null; if (!product.isvariant) { if (buyer) { if (req?.user.role == 'retailer') { obj.publicPrice = product.pricing?.retailerPrices[buyer.pricingType.toString()]; } else if (req?.user.role == 'distributeur') { obj.publicPrice = product.pricing.distributeurPrices[buyer.pricingType.toString()] // Number.parseFloat(total).toFixed(2); } } else { obj.publicPrice = product.pricing.publicPrice; } obj.quantity = product?.quantity; } else { obj.quantity = product?.variant[0]?.totalQuantity; obj.publicPrice = product?.variant[0]?.publicPrice; } result.push(obj); return result; }, []); const result = {}; result.countReviews = product.reviews.length; result.product = obj; result.relatedProducts = relatedArray; if (product.isvariant == true) { result.variant = data; result.count = count; } res.status(200).json({ message: 'Product fetched successfully', data: result }); }); exports.deleteProduct = catchAsync(async (req, res, next) => { const productId = req.params.id; const session = await mongoose.startSession(); try { session.startTransaction(); const productToDelete = await Product.findById(productId); if (!productToDelete) { return res.status(400).json({ message: 'Product not found' }); } if (productToDelete.isvariant == true) { const attributes = await VariantAttribute.find({ product: productToDelete._id }).select('_id'); await VariantValue.deleteMany({ attribute: { $in: attributes } }, { session }); await VariantAttribute.deleteMany({ product: productToDelete._id }, { session }); await ProductVariant.deleteMany({ _id: { $in: productToDelete.variants } }, { session }); } // Now delete each associated image if (productToDelete.images && productToDelete.images.length > 0) { for (const imageId of productToDelete.images) { // Assuming imageId is the actual ID, if it's a reference to an image object, you might need to adjust await Image.deleteOne({ _id: imageId }, { session }); } } await Product.deleteOne({ _id: productToDelete._id }, { session }); await session.commitTransaction(); session.endSession(); res.status(200).json({ message: 'Product deleted successfully' }); } catch (error) { await session.abortTransaction(); session.endSession(); return next(error); } }); exports.getProducts = catchAsync(async (req, res, next) => { let { categories, min_price, max_price, offer, name} = req.query; let limit = parseInt(req.query.limit) || 10; let page = parseInt(req.query.page) || 1; const skip = (page - 1) * limit; const obj = {}; if (offer) { obj.offer = { $exists: true }; } if (name) { obj.$or = [ { 'name.fr': { $regex: '.*' + name + '.*', $options: 'i' } }, // Match in French { 'name.en': { $regex: '.*' + name + '.*', $options: 'i' } } // Match in English ]; } let buyer = null; if (req?.user.role == 'retailer') { buyer = await Retailer.findById({ _id: req?.user.profile }); } else if (req?.user.role == 'distributeur') { buyer = await Distributeur.findById({ _id: req?.user.profile }); } // categories if (categories) { categories = JSON.parse(categories); const ids_categories = []; for (const category_name of categories) { let category = await Category.find({ category: category_name }); if (category.length < 1) { return next(new AppError('category not found', 400)); } ids_categories.push(category[0]._id); } obj.categories = { $in: ids_categories }; } const filterprice = {}; if (min_price) { if (req?.user?.role == 'retailer') { if (buyer?.pricingType) { const type_p = buyer?.pricingType.toString(); filterprice['pricing.retailerPrices.' + type_p] = { ...filterprice['pricing.retailerPrices.' + type_p], $gte: parseFloat(min_price) }; } } if (req?.user?.role == 'distributeur') { if (buyer?.pricingType) { const type_p = buyer?.pricingType.toString(); filterprice['pricing.distributeurPrices.' + type_p] = { ...filterprice['pricing.distributeurPrices.' + type_p], $gte: parseFloat(min_price) }; } } else { filterprice['pricing.publicPrice'] = { ...filterprice['pricing.publicPrice'], $gte: parseFloat(min_price) }; } } if (max_price) { if (req?.user?.role == 'retailer') { if (buyer?.pricingType) { const type_p = buyer?.pricingType.toString(); filterprice['pricing.retailerPrices.' + type_p] = { ...filterprice['pricing.retailerPrices.' + type_p], $lte: parseFloat(max_price) }; } } if (req?.user?.role == 'distributeur') { if (buyer?.pricingType) { const type_p = buyer?.pricingType.toString(); filterprice['pricing.distributeurPrices.' + type_p] = { ...filterprice['pricing.distributeurPrices.' + type_p], $lte: parseFloat(max_price) }; } } else { filterprice['pricing.publicPrice'] = { ...filterprice['pricing.publicPrice'], $lte: parseFloat(max_price) }; } } const rests = await Product.aggregate([ { $match: obj }, { $lookup: { from: 'productvariants', localField: 'variants', foreignField: '_id', pipeline: [{ $match: filterprice }, { $project: { quantity: 1, pricing: 1 } }], as: 'variantsDetails' } }, { $addFields: { firstVariant: { $cond: { if: { $and: [{ $eq: ['$isvariant', true] }, { $gt: [{ $size: '$variantsDetails' }, 0] }] }, then: { variant: { $arrayElemAt: ['$variants', 0] } }, else: null } } } }, { $match: { $or: [{ $and: [{ isvariant: false }, filterprice] }, { firstVariant: { $ne: null } }] } }, { $lookup: { from: 'categories', localField: 'categories', foreignField: '_id', pipeline: [{ $project: { category: 1 } }], as: 'categories' } }, { $lookup: { from: 'groups', localField: 'groups', foreignField: '_id', pipeline: [{ $project: { group: 1 } }], as: 'groups' } }, { $lookup: { from: 'subcategories', localField: 'subcategories', foreignField: '_id', pipeline: [{ $project: { subcategory: 1 } }], as: 'subcategories' } }, { $lookup: { from: 'brand', localField: 'brand', foreignField: '_id', pipeline: [{ $project: { brand: 1 } }], as: 'brand' } }, { $lookup: { from: 'offers', localField: 'offer', foreignField: '_id', pipeline: [{ $match: { status: true } }, { $project: { discount: 1, status: 1 } }], as: 'offer' } }, { $skip: skip }, { $limit: limit }, ]); const data = rests.reduce((result, product) => { let obj = {}; obj._id = product?._id; obj.name = product?.name; obj.description = product?.description; obj.image = product?.images[0]; obj.visibility = product?.visibility; obj.categories = product?.categories; obj.isVariant = product.isvariant; obj.isNew = product.isNew; obj.offer = product.offer?.at(0)?.discount ?? null; if (!product.isvariant) { obj.quantity = product?.quantity; if (buyer) { if (req?.user.role == 'retailer') { obj.publicPrice = product.pricing.retailerPrices[buyer.pricingType.toString()]; } else if (req?.user.role == 'distributeur') { obj.publicPrice = product.pricing.distributeurPrices[buyer.pricingType.toString()]; } } else { obj.publicPrice = product.pricing.publicPrice; } } else { obj.quantity = product?.variantsDetails?.at(0)?.quantity; if (buyer) { if (req?.user.role == 'retailer') { obj.publicPrice = product?.variantsDetails?.at(0)?.pricing.retailerPrices[buyer.pricingType.toString()]; } else if (req?.user.role == 'distributeur') { obj.publicPrice = product?.variantsDetails?.at(0)?.pricing.distributeurPrices[buyer.pricingType.toString()]; } } else { console.log('product *********** ', product); obj.publicPrice = product?.variantsDetails?.at(0)?.pricing.publicPrice; } } result.push(obj); return result; }, []); //}); res.status(200).json({ data }); }); // Route pour la recherche SKU exports.searchbySku = async (req, res) => { try { // Extract SKU and pagination parameters from query parameters const { sku } = req.query; let { page, limit } = req.query; // Check if SKU is provided if (!sku) { return res.status(400).json({ error: 'SKU is required' }); } // Default to page 1 if not provided or invalid and limit to 10 if not provided or invalid page = page ? parseInt(page, 10) : 1; limit = limit ? parseInt(limit, 10) : 10; // Calculate the skip value const skip = (page - 1) * limit; const productsCounts = await Product.aggregate([ { $lookup: { from: 'productvariants', localField: 'variants', foreignField: '_id', pipeline: [ { $facet: { filteredVariants: [ { $match: { sku: { $regex: '.*' + sku + '.*', $options: 'i' } // Apply filter for external use } } ], aggregatedVariants: [ { $group: { _id: null, // Group all variants (ignoring the filter) totalQuantity: { $sum: '$quantity' }, initialPrice: { $avg: '$pricing.initialPrice' }, publicPrice: { $avg: '$pricing.publicPrice' } } } ] } }, { $project: { filteredVariants: 1, // Keep filtered variants aggregatedData: { $arrayElemAt: ['$aggregatedVariants', 0] } // Extract aggregated results } } ], as: 'variantsData' } }, { $addFields: { ismatched: { $gt: [ { $size: { $ifNull: [ { $arrayElemAt: ['$variantsData.filteredVariants', 0] }, // Extract filteredVariants [] ] } }, 0 ] } } }, { $match: { $or: [{ ismatched: true }, { sku: { $regex: '.*' + sku + '.*', $options: 'i' } }] } }, { $skip: skip }, { $limit: limit }, { $count: 'totalCount' // Add this stage to count the number of products } ]); const products = await Product.aggregate([ { $lookup: { from: 'productvariants', localField: 'variants', foreignField: '_id', pipeline: [ { $facet: { filteredVariants: [ { $match: { sku: { $regex: '.*' + sku + '.*', $options: 'i' } // Apply filter for external use } } ], aggregatedVariants: [ { $group: { _id: null, // Group all variants (ignoring the filter) totalQuantity: { $sum: '$quantity' }, initialPrice: { $avg: '$pricing.initialPrice' }, publicPrice: { $avg: '$pricing.publicPrice' } } } ] } }, { $project: { filteredVariants: 1, // Keep filtered variants aggregatedData: { $arrayElemAt: ['$aggregatedVariants', 0] } // Extract aggregated results } } ], as: 'variantsData' } }, { $addFields: { ismatched: { $gt: [ { $size: { $ifNull: [ { $arrayElemAt: ['$variantsData.filteredVariants', 0] }, // Extract filteredVariants [] ] } }, 0 ] } } }, { $match: { $or: [{ ismatched: true }, { sku: { $regex: '.*' + sku + '.*', $options: 'i' } }] } }, { $skip: skip }, { $limit: limit } ]); const data = products.reduce((result, product) => { let obj = {}; obj._id = product?._id; obj.name = product?.name; obj.image = product?.images[0]; obj.visibility = product?.visibility; obj.categories = product?.categories; obj.isVariant = product.isvariant; if (!product.isvariant) { obj.quantity = product?.quantity; obj.initialPrice = product?.pricing?.initialPrice; obj.publicPrice = product?.pricing?.publicPrice; } else { console.log(product?.variantsData?.at(0)?.aggregatedData); obj.quantity = product?.variantsData?.at(0)?.aggregatedData?.totalQuantity; obj.initialPrice = product?.variantsData?.at(0)?.aggregatedData?.initialPrice; obj.publicPrice = product?.variantsData?.at(0)?.aggregatedData?.publicPrice; } result.push(obj); return result; }, []); // Find total number of products to calculate total pages const totalProducts = await Product.countDocuments({ sku: { $regex: '^' + sku, $options: 'i' }, visibility: true }); const totalPages = Math.ceil(totalProducts / limit); // Handle case where no products are found with the specified SKU if (products.length === 0) { return res.status(400).json({ message: 'No products found with the specified SKU' }); } const totalCount = productsCounts[0]?.totalCount || 0; // Return the found products along with pagination data res.json({ data, pagination: { totalCount, totalPages, currentPage: page, limit } }); } catch (error) { console.error(error); res.status(500).json({ error: 'Internal Server Error' }); } }; exports.productInStock = catchAsync(async (req, res, next) => { if (req.body.stock == 1) { let products = await Product.find({ quantity: { $gt: 0 }, visibility: true }).populate({ path: 'offer', select: ['discount', 'status'] }); if (products) { res.status(200).json({ products }); } else { return next(new AppError('There are no products with this filter', 400)); } } else if (req.body.stock == 0) { let products = await Product.find({ quantity: { $eq: 0 }, visibility: true }).populate({ path: 'offer', select: ['discount', 'status'] }); if (products) { res.status(200).json({ products }); } else { return next(new AppError('There are no products with this filter', 400)); } } else { return next(new AppError('invalad value', 400)); } }); exports.productVisibility = catchAsync(async (req, res, next) => { if (req.body.visibility == 1) { let products = await Product.find({ visibility: true }).populate({ path: 'offer', select: ['discount', 'status'] }); if (products) { res.status(200).json({ products }); } else { return next(new AppError('There are no products with this filter', 400)); } } if (req.body.visibility == 0) { let products = await Product.find({ visibility: false }).populate({ path: 'offer', select: ['discount', 'status'] }); if (products) { res.status(200).json({ products }); } else { return next(new AppError('There are no products with this filter', 400)); } } }); exports.productByCategory = catchAsync(async (req, res, next) => { let category = await Category.findById(req.body.id_category); if (category) { const products = await Product.find({ categories: req.body.id_category, visibility: true }).populate({ path: 'offer', select: ['discount', 'status'] }); if (products) { res.status(200).json({ products }); } else { return next(new AppError('this product no existe', 400)); } } else { return next(new AppError('this category no existe', 400)); } }); exports.deleteProducts = catchAsync(async (req, res, next) => { const productIds = req.body.products; if (!Array.isArray(productIds) || productIds.length === 0) { return next(new AppError('Invalid or empty product IDs array', 400)); } // Convert string IDs to mongoose ObjectId const ids = productIds.map((id) => mongoose.Types.ObjectId(id)); // Find products to retrieve their images before deletion const products = await Product.find({ _id: { $in: ids } }); if (products.length === 0) { return next(new AppError('No products found for deletion', 404)); } // Collect all image IDs to delete const imagesToDelete = products.reduce((acc, product) => { if (product.images && product.images.length) { acc.push(...product.images.map((imageId) => mongoose.Types.ObjectId(imageId))); } return acc; }, []); // Delete images if there are any to delete if (imagesToDelete.length > 0) { await Image.deleteMany({ _id: { $in: imagesToDelete } }); } // Finally, delete the products await Product.deleteMany({ _id: { $in: ids } }); res.status(200).json({ message: 'Products and associated images deleted successfully', deletedCount: products.length }); }); exports.productsVisibilityChange = catchAsync(async (req, res, next) => { const productIds = req.body.products; let visibility = req.body.visibility; let obj = {}; if (visibility) { if (visibility == 'true') { obj.visibility = true; } else if (visibility == 'false') { obj.visibility = false; } else { return next(new AppError('invalid visibility value', 400)); } } else { return next(new AppError('visiility required', 400)); } if (productIds) { if (Array.isArray(productIds)) { let ids = new Array(); for (let j = 0; j < productIds.length; j++) { try { ids.push(new mongoose.Types.ObjectId(productIds[j])); } catch (e) { return next(new AppError('Invalid id.', 401)); } } const productsapdated = await Product.updateMany({ _id: { $in: ids } }, obj); if (productsapdated) { res.status(200).json({ message: 'Product visibility updated successfully', productsapdated }); } else { return next(new AppError('no products', 400)); } } else { return next(new AppError('invalid format for products array', 400)); } } }); exports.productsStockStatusChange = catchAsync(async (req, res, next) => { const productIds = req.body.products; let status = req.body.stockstatus; let obj = {}; if (status) { if (status == true) { obj.quantity = 0; } else { return next(new AppError('invalid status value', 400)); } } else { return next(new AppError('status required', 400)); } console.log(productIds); if (productIds) { if (Array.isArray(productIds)) { let ids = new Array(); for (let j = 0; j < productIds.length; j++) { try { ids.push(new mongoose.Types.ObjectId(productIds[j])); } catch (e) { return next(new AppError('Invalid id.', 401)); } } const productsapdated = await Product.updateMany({ _id: { $in: ids } }, obj); if (productsapdated) { res.status(200).json({ message: 'Products stockstatus changed successfully', productsapdated }); } else { return next(new AppError('no products', 400)); } } else { return next(new AppError('invalid format for products array', 400)); } } }); exports.productFilter = catchAsync(async (req, res, next) => { let { visibility, status, isunder, id_category, offer } = req.body; const page = parseInt(req.query.page) || 1; // Default to page 1 if not provided const limit = parseInt(req.query.limit) || 10; // Default to limit 10 if not provided const skip = (page - 1) * limit; try { var Id = new mongoose.Types.ObjectId(id_category); } catch (e) { return next(new AppError('Invalid id.', 401)); } let obj = {}; if (visibility) { if (visibility === 'true') { obj.visibility = true; } else if (visibility === 'false') { obj.visibility = false; } else { return next(new AppError('Invalid visibility value', 400)); } } if (status) { if (status == 'true') { obj.quantity = { $gt: 0 }; } else if (status == 'false') { obj.quantity = 0; } else { return next(new AppError('Invalid status value', 400)); } } if (isunder) { if (isunder == 'true') { obj.quantity = { $lte: 20 }; } } if (offer) { if (offer == 'true') { obj.offer = { $exists: true }; } else if (offer == 'false') { obj.offer = { $exists: false }; } else { return next(new AppError('Invalid offer value', 400)); } } if (id_category) { let category = await Category.findById(Id); if (category) { obj.categories = Id; } else { return next(new AppError('Invalid category value', 400)); } } const countResult = await Product.aggregate([ { $match: obj }, { $lookup: { from: 'productvariants', localField: 'variants', foreignField: '_id', pipeline: [ { $project: { pricing: 1, quantity: 1 } }, { $group: { _id: null, totalQuantity: { $sum: '$quantity' }, initialPrice: { $avg: '$pricing.initialPrice' }, publicPrice: { $avg: '$pricing.publicPrice' } } } ], as: 'variant' } }, // filter { $match: { $or: [ { isvariant: true, 'variant.0': { $exists: true } }, { isvariant: false } ] } }, { $count: 'totalCount' } ]); // Only count matching documents const count = countResult.length > 0 ? countResult[0].totalCount : 0; const rests = await Product.aggregate([ { $match: obj }, { $lookup: { from: 'productvariants', localField: 'variants', foreignField: '_id', pipeline: [ { $project: { pricing: 1, quantity: 1 } }, { $group: { _id: null, totalQuantity: { $sum: '$quantity' }, initialPrice: { $avg: '$pricing.initialPrice' }, publicPrice: { $avg: '$pricing.publicPrice' } } } ], as: 'variant' } }, { $lookup: { from: 'categories', localField: 'categories', foreignField: '_id', pipeline: [{ $project: { category: 1 } }], as: 'categories' } }, { $lookup: { from: 'offer', localField: 'offer', foreignField: '_id', pipeline: [{ $project: { discount: 1, status: 1 } }], as: 'offer' } }, // filter { $match: { $or: [ { isvariant: true, 'variant.0': { $exists: true } }, { isvariant: false } ] } }, { $skip: skip }, // Skip documents for pagination { $limit: limit } // Limit the number of documents returned ]); const data = rests.reduce((result, product) => { let obj = {}; obj._id = product?._id; obj.name = product?.name; obj.image = product?.images[0]; obj.visibility = product?.visibility; obj.categories = product?.categories; obj.isVariant = product.isvariant; if (!product.isvariant) { obj.quantity = product?.quantity; obj.initialPrice = product?.pricing?.initialPrice; obj.publicPrice = product?.pricing?.publicPrice; } else { console.log(product?.variant[0]); obj.quantity = product?.variant[0]?.totalQuantity; obj.initialPrice = product?.variant[0]?.initialPrice; obj.publicPrice = product?.variant[0]?.publicPrice; } result.push(obj); return result; }, []); res.status(200).json({ products: data, count, page, limit, totalPages: Math.ceil(count / limit) }); }); const checkCategories = async (categories, next) => { let ids = new Array(); if (categories.length > 0) { for (const category of categories) { if (!category) { return next(new AppError('id is required', 400)); } let id = toObjectId(category, next); const existingCategory = await Category.find({ _id: id }); ids.push(id); if (existingCategory?.length < 1) { return next(new AppError('Category not found', 400)); } } } return ids; }; const checkSubCategories = async (subcategories, categories, next) => { let ids = new Array(); if (subcategories.length > 0) { for (const subcategory of subcategories) { if (!subcategory) { return next(new AppError('id is required', 400)); } let id = toObjectId(subcategory, next); const existingSubCategory = await SubCategory.find({ _id: id, category: { $in: categories } }); ids.push(id); if (existingSubCategory?.length < 1) { return next(new AppError('SubCategory not found in this category', 400)); } } } return ids; }; const checkGroups = async (groups, subcategories, next) => { let ids = new Array(); if (groups.length > 0) { for (const group of groups) { if (!group) { return next(new AppError('id is required', 400)); } let id = toObjectId(group, next); const existingGroup = await Group.find({ _id: id, subcategory: { $in: subcategories } }); ids.push(id); if (existingGroup?.length < 1) { return next(new AppError('group not found in this category', 400)); } } } return ids; }; exports.viewProductForDashboard = catchAsync(async (req, res, next) => { const productId = req.params.id; const result = {}; if (!isValidObjectId(productId)) { return res.status(400).json({ message: 'Invalid product ID' }); } const pathsAndFields = [ { path: 'subcategories', select: 'subcategory' }, { path: 'categories', select: 'category' }, { path: 'groups', select: 'group' }, { path: 'brand', select: 'brand' }, { path: 'offer', select: ['discount', 'status'] } ]; // .populate(pathsAndFields).exec(); const product = await Product.findById(productId).populate(pathsAndFields); if (!product) { return next(new AppError('product not found', 400)); } result.product = product; if (product.isvariant == true) { const variants = await ProductVariant.find({ _id: { $in: product.variants } }).populate({ path: 'attributevalues', select: 'value', populate: { path: 'attribute', select: 'attributeName' } }); result.variants = variants; } res.status(200).json({ message: 'Product fetched successfully', data: result }); }); exports.AddToWishlist = catchAsync(async (req, res, next) => { // Extract necessary information from the request body const { productID } = req.body; // Check if the product exists and is available for wishlisting const product = await Product.findById(productID); if (!product) { return next(new AppError(`Product with ID ${productID} not found`, 400)); } if (req.user.role == 'client') { const client = await Client.findById(req.user.profile); if (!client) { return next(new AppError('Client not found', 400)); } // Check if the product already exists in the client's wishlist const productExistsInWishlist = await Client.findOne({ _id: req.user.profile, wishlist: { $in: [productID] } }); if (productExistsInWishlist) { return next(new AppError('Product already exists in the wishlist', 400)); } // Add the product to the wishlist client.wishlist.push(product); await client.save(); // Return success response res.status(200).json({ message: 'Product added to wishlist successfully', wishlist: client.wishlist }); } else if (req.user.role == 'retailer') { const retailer = await Retailer.findById(req.user.profile); if (!retailer) { return next(new AppError('Retailer not found', 400)); } // Check if the product already exists in the client's wishlist const productExistsInWishlist = await Retailer.findOne({ _id: req.user.profile, wishlist: { $in: [productID] } }); if (productExistsInWishlist) { return next(new AppError('Product already exists in the wishlist', 400)); } // Add the product to the wishlist retailer.wishlist.push(product); await retailer.save(); // Return success response res.status(200).json({ message: 'Product added to wishlist successfully', wishlist: retailer.wishlist }); } else if (req.user.role == 'distributeur') { const distributeur = await Distributeur.findById(req.user.profile); if (!distributeur) { return next(new AppError('distributeur not found', 400)); } // Check if the product already exists in the client's wishlist const productExistsInWishlist = await Distributeur.findOne({ _id: req.user.profile, wishlist: { $in: [productID] } }); if (productExistsInWishlist) { return next(new AppError('Product already exists in the wishlist', 400)); } // Add the product to the wishlist distributeur.wishlist.push(product); await distributeur.save(); // Return success response res.status(200).json({ message: 'Product added to wishlist successfully', wishlist: distributeur.wishlist }); } else { return next(new AppError('role incorrect ', 400)); } }); exports.RemoveFromWishlist = catchAsync(async (req, res, next) => { // Extract necessary information from the request body const { productID } = req.body; if (req.user.role == 'client') { const client = await Client.findById(req.user.profile); if (!client) { return next(new AppError('Client not found', 400)); } // Check if the product exists in the client's wishlist if (!client.wishlist.includes(productID)) { return next(new AppError('Product does not exist in the wishlist', 400)); } // Remove the product from the client's wishlist using $pull await Client.findByIdAndUpdate(client._id, { $pull: { wishlist: productID } }); // Return success response res.status(200).json({ message: 'Product removed from wishlist successfully' }); } else if (req.user.role == 'retailer') { const retailer = await Retailer.findById(req.user.profile); if (!retailer) { return next(new AppError('retailer not found', 400)); } // Check if the product exists in the client's wishlist if (!retailer.wishlist.includes(productID)) { return next(new AppError('Product does not exist in the wishlist', 400)); } // Remove the product from the client's wishlist using $pull await Retailer.findByIdAndUpdate(retailer._id, { $pull: { wishlist: productID } }); // Return success response res.status(200).json({ message: 'Product removed from wishlist successfully' }); } else if (req.user.role == 'distributeur') { const distributeur = await Distributeur.findById(req.user.profile); if (!distributeur) { return next(new AppError('Client not found', 400)); } // Check if the product exists in the client's wishlist if (!distributeur.wishlist.includes(productID)) { return next(new AppError('Product does not exist in the wishlist', 400)); } // Remove the product from the client's wishlist using $pull await Distributeur.findByIdAndUpdate(distributeur._id, { $pull: { wishlist: productID } }); // Return success response res.status(200).json({ message: 'Product removed from wishlist successfully' }); } else { return res.status(400).json({ message: 'Role incorrect' }); } }); exports.getWishList = catchAsync(async (req, res, next) => { if (req.user.role == 'client') { const client = await User.aggregate([ { $match: { _id: req.user._id } }, { $lookup: { from: 'clients', localField: 'profile', foreignField: '_id', as: 'client', pipeline: [ { $lookup: { from: 'products', localField: 'wishlist', foreignField: '_id', pipeline: [ { $lookup: { from: 'offers', localField: 'offer', foreignField: '_id', pipeline: [{ $project: { discount: 1, status: 1 } }], as: 'offer' } } ], as: 'wishlist' } } ] } } ]); if (!client.length) { return next(new AppError('user not find', 400)); } res.status(200).json({ message: 'wishlist', wishlist: client[0]?.client[0]?.wishlist }); } else if (req.user.role == 'retailer') { const retailer = await User.aggregate([ { $match: { _id: req.user._id, next } }, { $lookup: { from: 'retailers', localField: 'profile', foreignField: '_id', as: 'retailer', pipeline: [ { $lookup: { from: 'products', localField: 'wishlist', foreignField: '_id', pipeline: [ { $lookup: { from: 'offers', localField: 'offer', foreignField: '_id', pipeline: [{ $project: { discount: 1, status: 1 } }], as: 'offer' } } ], as: 'wishlist' } } ] } } ]); if (!retailer.length) { return next(new AppError('user not find', 400)); } res.status(200).json({ message: 'wishlist', wishlist: retailer[0]?.retailer[0]?.wishlist }); } else if (req.user.role == 'distributeur') { const distributeur = await User.aggregate([ { $match: { _id: req.user._id } }, { $lookup: { from: 'distributeurs', localField: 'profile', foreignField: '_id', as: 'distributeur', pipeline: [ { $lookup: { from: 'products', localField: 'wishlist', foreignField: '_id', pipeline: [ { $lookup: { from: 'offers', localField: 'offer', foreignField: '_id', pipeline: [{ $project: { discount: 1, status: 1 } }], as: 'offer' } } ], as: 'wishlist' } } ] } } ]); if (!distributeur.length) { return next(new AppError('user not find', 400)); } res.status(200).json({ message: 'wishlist', wishlist: distributeur[0]?.distributeur[0]?.wishlist }); } else { return next(new AppError('invalid role', 400)); } }); const RelatedProducts = async (id, next) => { let list = new Array(); let ids_list = new Set(); ids_list.add(id.toString()); let prod = await Product.findById(id); if (prod.groups.length > 0) { list.push( /* ...(await Product.find({ visibility: true, groups: { $in: prod.groups }, _id: { $nin: Array.from(ids_list).map((id) => toObjectId(id, next)) } }) .populate({ path: 'offer', select: ['discount', 'status'] }) .limit(10)) */ ...(await Product.aggregate([ { $match: { visibility: true, groups: { $in: prod.groups }, _id: { $nin: Array.from(ids_list).map((id) => toObjectId(id, next)) } } }, { $lookup: { from: 'productvariants', localField: 'variants', foreignField: '_id', pipeline: [ { $project: { pricing: 1, quantity: 1 } }, { $group: { _id: null, totalQuantity: { $sum: '$quantity' }, initialPrice: { $avg: '$pricing.initialPrice' }, publicPrice: { $avg: '$pricing.publicPrice' } } } ], as: 'variant' } }, { $lookup: { from: 'offers', localField: 'offer', foreignField: '_id', pipeline: [{ $match: { status: true } }, { $project: { discount: 1, status: 1 } }], as: 'offer' } }, // filter { $match: { $or: [ { isvariant: true, 'variant.0': { $exists: true } }, { isvariant: false } ] } }, { $limit: 10 - list.length } ])) ); for (const product of list) { ids_list.add(product._id.toString()); } } if (list.length < 10) { if (prod.subcategories.length > 0) { list.push( ...(await Product.aggregate([ { $match: { visibility: true, subcategories: { $in: prod.subcategories }, _id: { $nin: Array.from(ids_list).map((id) => toObjectId(id, next)) } } }, { $lookup: { from: 'productvariants', localField: 'variants', foreignField: '_id', pipeline: [ { $project: { pricing: 1, quantity: 1 } }, { $group: { _id: null, totalQuantity: { $sum: '$quantity' }, initialPrice: { $avg: '$pricing.initialPrice' }, publicPrice: { $avg: '$pricing.publicPrice' } } } ], as: 'variant' } }, { $lookup: { from: 'offers', localField: 'offer', foreignField: '_id', pipeline: [{ $match: { status: true } }, { $project: { discount: 1, status: 1 } }], as: 'offer' } }, // filter { $match: { $or: [ { isvariant: true, 'variant.0': { $exists: true } }, { isvariant: false } ] } }, { $limit: 10 - list.length } ])) ); } for (const product of list) { ids_list.add(product._id.toString()); } } if (list.length < 10) { if (prod.categories.length > 0) { list.push( /* ...(await Product.find({ visibility: true, categories: { $in: prod.categories }, _id: { $nin: Array.from(ids_list).map((id) => toObjectId(id, next)) } }) .populate({ path: 'offer', select: ['discount', 'status'] }) .limit(10 - list.length)) */ ...(await Product.aggregate([ { $match: { visibility: true, categories: { $in: prod.categories }, _id: { $nin: Array.from(ids_list).map((id) => toObjectId(id, next)) } } }, { $lookup: { from: 'productvariants', localField: 'variants', foreignField: '_id', pipeline: [ { $project: { pricing: 1, quantity: 1 } }, { $group: { _id: null, totalQuantity: { $sum: '$quantity' }, initialPrice: { $avg: '$pricing.initialPrice' }, publicPrice: { $avg: '$pricing.publicPrice' } } } ], as: 'variant' } }, { $lookup: { from: 'offers', localField: 'offer', foreignField: '_id', pipeline: [{ $match: { status: true } }, { $project: { discount: 1, status: 1 } }], as: 'offer' } }, // filter { $match: { $or: [ { isvariant: true, 'variant.0': { $exists: true } }, { isvariant: false } ] } }, { $limit: 10 - list.length } ])) ); } for (const product of list) { ids_list.add(product._id.toString()); } } return list; }; const toObjectId = (id, next) => { try { return new mongoose.Types.ObjectId(id); } catch (e) { return next(new AppError('Invalid id.', 401)); } };
Editor is loading...
Leave a Comment