Untitled

 avatar
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