Your IP : 216.73.216.43


Current Path : /home/deltalab/PMS/partner-manager-backend/rest/routes/
Upload File :
Current File : //home/deltalab/PMS/partner-manager-backend/rest/routes/products.js

/* eslint-disable no-return-await */
/* eslint-disable no-continue */
/* eslint-disable no-await-in-loop */
/* eslint-disable no-use-before-define */
/* eslint-disable no-restricted-syntax */
/* eslint-disable max-len */
const { ObjectId } = require('bson');
const express = require('express');
const fs = require('fs');
const formidable = require('formidable');
const AdmZip = require('adm-zip');
const { productModel } = require('../../models/mongoose/product');
const { productMediaModel } = require('../../models/mongoose/product');
const { warehouseModel } = require('../../models/mongoose/warehouse');
const ims = require('../../services/ims');
const utility = require('../../services/utility');
const importService = require('../../services/import');
const { productQuery, masterQuery } = require('../queries');
const { categoryQuery } = require('../queries');

const router = express.Router({ mergeParams: true });

router.get('/:productId', async (req, res) => {
  try {
    const product = await productQuery.readOne(req.params.productId);
    return res.status(200).json({ success: true, data: product });
  } catch (error) {
    console.log(error);
    return res.status(400).send({ success: false, message: 'Error reading the product' });
  }
});

router.put('/updateOffersPriceList', (req, res, next) => {
  if (utility.isPartnerEqualToParams(req)) {
    next();
  } else {
    return res.status(401).send({ success: false, message: 'Not authorized' });
  }
}, async (req, res) => {
  try {
    console.log('Aggiorno le offerte in base al listino prezzi');
    const restParameters = req.body;
    const { offerList } = restParameters;
    const { partnerId } = restParameters;
    const { channelId } = restParameters;

    let productCount = 0;
    for (let i = 0; i < offerList.length; i++) {
      const product = await productModel.findOne({ 'offers._id': offerList[i]._id });
      //ho il prodotto
      for (let j = 0; j < product.offers.length; j++) {
        if (product.offers[j]._id.toString() === offerList[i]._id) {
          //Ho trovato l'offerta persistente
          if (product.offers[j].price !== offerList[i].price) {
            //se il prezzo è diverso, continuo
            product.offers[j].price = offerList[i].price;
            await product.save();
            await ims.referenceUpdate(product, product.sku);
            productCount++;
          }
        }
      }
    }

    return res.status(200).json({ success: true, data: productCount });
  } catch (error) {
    return res.status(400).send({ success: false, message: 'Error updating the product' });
  }
});

router.post('/', (req, res, next) => {
  if (utility.isPartnerEqualToParams(req)) {
    console.log('il partner è valido, procedo');
    next();
  } else {
    return res.status(401).send({ success: false, message: 'Not authorized' });
  }
}, async (req, res) => {
  try {
    const newReference = { ...req.body };

    if (!newReference.masterId) {
      const newMaster = {};
      newMaster.title = newReference.title;
      newMaster.description = newReference.description;
      newMaster.msrp = newReference.msrp;
      newMaster.partnerId = newReference.partnerId;
      const persistentMaster = masterQuery.createOne(newMaster);
      if (persistentMaster) {
        newReference.masterId = persistentMaster._id;
      }
    }

    const sku = Math.floor(100000 + Math.random() * 900000);
    newReference.sku = `${newReference.customerSku.replace(/\s+/g, '')}-${sku}`.substring(0, 63);
    for (let i = 0; i < newReference.offers.length; i++) {
      const offerSpecificReference = JSON.parse(JSON.stringify(newReference));
      const offer = newReference.offers[i];
      if (offer._id.includes('TEMP')) {
        const id = new ObjectId();
        offer._id = id;
        newReference.offers[i]._id = id;
      }
      offerSpecificReference.offers[i].sku = `${offer.sku.replace(/\s+/g, '')}-${sku}`.substring(0, 63);
      offerSpecificReference.title = offer.title;
      offerSpecificReference.customDescription = offer.description;
      offerSpecificReference.price = offer.price;
      offerSpecificReference.sku = offerSpecificReference.offers[i].sku;
      offerSpecificReference.imsCategories = offer.imsCategories;
      offerSpecificReference.deleted = offer.deleted;
      offerSpecificReference.urlKey = offer.urlKey;
      offerSpecificReference.attributes = offer.attributes;
      // validity
      offerSpecificReference.imsEnabled = newReference.imsEnabled ? offer.imsEnabled : false;

      // VALID FROM

      if (newReference.isValidFrom && offer.isValidFrom) {
        offerSpecificReference.isValidFrom = true;
        offerSpecificReference.validFrom = newReference.validFrom > offer.validFrom ? newReference.validFrom : offer.validFrom;
      }

      if (!newReference.isValidFrom && offer.isValidFrom) {
        offerSpecificReference.isValidFrom = true;
        offerSpecificReference.validFrom = offer.validFrom;
      }

      if (newReference.isValidFrom && !offer.isValidFrom) {
        offerSpecificReference.isValidFrom = true;
        offerSpecificReference.validFrom = newReference.validFrom;
      }

      // VALID UNTIL

      if (newReference.isValidUntil && offer.isValidUntil) {
        offerSpecificReference.isValidUntil = true;
        offerSpecificReference.validUntil = newReference.validUntil < offer.validUntil ? newReference.validUntil : offer.validUntil;
      }

      if (!newReference.isValidUntil && offer.isValidUntil) {
        offerSpecificReference.isValidUntil = true;
        offerSpecificReference.validUntil = offer.validFrom;
      }

      if (newReference.isValidUntil && !offer.isValidUntil) {
        offerSpecificReference.isValidUntil = true;
        offerSpecificReference.validUntil = newReference.validUntil;
      }

      await ims.productCreate(offerSpecificReference, offer.channelId);
    }
    const reference = await productQuery.createOne(newReference);
    return res.status(200).json({ success: true, data: reference });
  } catch (error) {
    console.log(error);
    return res.status(400).send({ success: false, message: 'Error creating the product', error: error.message });
  }
});

router.put('/updateInventory', (req, res, next) => {
  if (utility.isPartnerEqualToParams(req)) {
    console.log('il partner è valido, procedo');
    next();
  } else {
    return res.status(401).send({ success: false, message: 'Not authorized' });
  }
}, async (req, res) => {
  try {
    console.log('arrivato su inventory');
    const restParameters = req.body;
    const { reference } = restParameters;
    const persistentReference = await productModel.findById(restParameters.reference._id);
    let found = false;
    if (reference.trackInventory) {
      // check the inventory levels of the modified product against the persistent one.
      for (let i = 0; i < reference.inventoryLevels.length; i++) {
        const warehouse = await warehouseModel.findById(reference.inventoryLevels[i].warehouseId);
        found = false;
        for (let j = 0; j < persistentReference.inventoryLevels.length; j++) {
          if (persistentReference.inventoryLevels[j].warehouseId.equals(reference.inventoryLevels[i].warehouseId)) {
            found = true;
            console.log(`current availability: ${persistentReference.inventoryLevels[j].amount}`);
            //const startingQuantity = persistentReference.inventoryLevels[j].amount >= 0 ? persistentReference.inventoryLevels[j].amount : 0; //if starting quantity is below 0, consider it as 0 in transfer
            const delta = reference.inventoryLevels[i].amount - persistentReference.inventoryLevels[j].amount;
            if (delta !== 0) {
              const externalWarehouse = delta > 0 && reference.partnerId !== warehouse.partnerId.toString();
              if (externalWarehouse) {
                console.log('diminuisco in inventory level, dovrà essere confermata');
                reference.inventoryLevels[i].amount = persistentReference.inventoryLevels[j].amount;
              }
              console.log(`changed availability in warehouse ${reference.inventoryLevels[i].warehouseId} by ${delta}, new total ${reference.inventoryLevels[i].amount}`);

              const totalQuantity = externalWarehouse ? reference.inventoryLevels[i].amount : reference.inventoryLevels[i].amount + delta;
              console.log('aggiungo al journal');
              ims.addEntryToWarehouseJournal(reference.inventoryLevels[i].warehouseId, reference._id, reference.partnerId, delta, totalQuantity, delta > 0 ? 'INCREASE' : 'DECREASE', reference.refrigerated, !externalWarehouse);
              // await reference.save();
            }
          }
        }

        if (!found) {
          // no inventory level for this product, add it
          console.log(`no warehouse ${reference.inventoryLevels[i].warehouseId} in persistent product, adding it with ${reference.inventoryLevels[i].amount} starting quantity`);
          // reference.inventoryLevels.push({ warehouseId: reference.inventoryLevels[i].warehouseId, amount: reference.inventoryLevels[i].amount });
          const externalWarehouse = reference.inventoryLevels[i].amount > 0 && reference.partnerId !== warehouse.partnerId.toString();
          ims.addEntryToWarehouseJournal(reference.inventoryLevels[i].warehouseId, reference._id, reference.partnerId, reference.inventoryLevels[i].amount, externalWarehouse ? 0 : reference.inventoryLevels[i].amount, 'INITIAL', reference.refrigerated, !externalWarehouse);
          if (externalWarehouse) {
            console.log('new stock in external warehouse, needs to be confirmed');
            reference.inventoryLevels[i].amount = 0;
          }

          // await reference.save();
        }
      }
    }

    return res.status(200).json({ success: true, data: reference });
  } catch (error) {
    return res.status(400).send({ success: false, message: 'Error updating the product' });
  }
});

router.put('/:referenceId', (req, res, next) => {
  console.log('CONTROLLO');
  if (utility.isPartnerEqualToParams(req)) {
    console.log('il partner è valido, procedo');
    next();
  } else {
    return res.status(401).send({ success: false, message: 'Not authorized' });
  }
}, async (req, res) => {
  try {
    console.log('PUT riuscito');
    let validOffers = 0;
    const persistentReference = await productQuery.readOne(req.params.referenceId);
    const toUpdate = req.body;
    console.log('entro nel giro di validazione');
    for (let j = 0; j < toUpdate.offers.length; j++) {
      console.log('entro in update');
      const isOfferPersistent = await ims.isOfferInIMS(toUpdate.offers[j]);
      if (!isOfferPersistent) {
        const offerSpecificReference = JSON.parse(JSON.stringify(toUpdate));
        offerSpecificReference.offers[j].sku = toUpdate.offers[j].sku;
        offerSpecificReference.title = toUpdate.offers[j].title;
        offerSpecificReference.customDescription = toUpdate.offers[j].description;
        offerSpecificReference.price = toUpdate.offers[j].price;
        offerSpecificReference.sku = offerSpecificReference.offers[j].sku;
        offerSpecificReference.deleted = toUpdate.offers[j].deleted;
        offerSpecificReference.urlKey = toUpdate.offers[j].urlKey;
        offerSpecificReference.attributes = toUpdate.offers[j].attributes;

        // validity
        offerSpecificReference.imsEnabled = toUpdate.imsEnabled ? toUpdate.offers[j].imsEnabled : false;

        if ((toUpdate.isValidFrom && toUpdate.offers[j].isValidFrom) || (!toUpdate.isValidFrom && toUpdate.offers[j].isValidFrom)) {
          offerSpecificReference.isValidFrom = toUpdate.offers[j].isValidFrom;
          offerSpecificReference.validFrom = toUpdate.offers[j].validFrom;
        } else {
          offerSpecificReference.isValidFrom = false;
        }

        if ((toUpdate.isValidUntil && toUpdate.offers[j].isValidUntil) || (!toUpdate.isValidUntil && toUpdate.offers[j].isValidUntil)) {
          offerSpecificReference.isValidUntil = toUpdate.offers[j].isValidUntil;
          offerSpecificReference.validUntil = toUpdate.offers[j].validUntil;
        } else {
          offerSpecificReference.isValidUntil = false;
        }

        console.log('creo?');
        const result = await ims.productCreate(offerSpecificReference, toUpdate.offers[j].channelId);
        if (result) {
          validOffers++;
        }
      } else {
        validOffers++;
      }
    }
    console.log('sto per entrare in update ims');
    if (validOffers === toUpdate.offers.length) {
      await ims.referenceUpdate(toUpdate, persistentReference.sku);
    }
    const updatedProduct = await productQuery.updateOne(req.params.referenceId, toUpdate);
    return res.status(200).json({ success: true, data: updatedProduct });
  } catch (error) {
    return res.status(400).send({ success: false, message: 'Error updating the product', error: error.message });
  }
});

router.put('/delete/:productId', async (req, res) => {
  try {
    const product = await productQuery.readOne(req.params.productId);
    await ims.referenceUpdate(req.body, product.sku);
    const updatedProduct = await productQuery.updateOne(req.params.productId, req.body);
    return res.status(200).json({ success: true, data: updatedProduct });
  } catch (error) {
    console.log(error);
    return res.status(400).send({ success: false, message: 'Error updating the product', error: error.message });
  }
});

router.delete('/:productSku', async (req, res) => {
  try {
    await ims.productDelete(req.params.productSku);
    await productQuery.deleteOne(req.params.productSku);
    return res.status(204).send();
  } catch (error) {
    console.log(error);
    return res.status(400).send({ success: false, message: 'Error deleting the product', error: error.message });
  }
});

// TODO: verificare partner (campo nascosto)
router.post('/import', async (req, res) => {
  try {
    let importedCount = 0;
    const form = formidable({ multiples: true });
    const [fields, files] = await ns.form_parse(req, form)
      .catch((e) => console.log(e));

    let importedSKUs = [];
    const zip = new AdmZip(files.zip.filepath);
    const zipEntries = zip.getEntries(); // an array of ZipEntry records
    let csvFound = false;
    for (const zipEntry of zipEntries) {
      if (zipEntry.entryName.includes('.csv')) {
        // Look for the csv and import it
        console.log('entro in import');
        importedSKUs = await importCSVData(zipEntry.getData().toString('utf8'), fields);
        importedCount = importedSKUs.length;
        csvFound = true;
      }
    }
    if (csvFound) {
      for (const zipEntry of zipEntries) {
        const name = zipEntry.entryName;
        if (name.toLowerCase().includes('.png') || name.toLowerCase().includes('.jpg') || name.toLowerCase().includes('.jpeg')) {
          const filename = name.substring(name.lastIndexOf('/') + 1, name.lastIndexOf('.'));
          const extension = name.substring(name.lastIndexOf('.') + 1);
          for (let i = 0; i < importedSKUs.length; i++) {
            if (filename.includes(importedSKUs[i].sku)) {
              console.log(`upload di ${filename} in corso`);
              importImageToProduct(importedSKUs[i].fullSku, zipEntry, filename, extension);
            }
          }
        }
      }
    }

    if (fs.existsSync(files.zip.filepath)) {
      fs.unlinkSync(files.zip.filepath);
    }
    // });

    return res.status(200).json({ success: true, data: importedCount });
  } catch (error) {
    return res.status(400).send({ success: false, message: 'Error creating the product', error: error.message });
  }
});

const ns = {
  form_parse: async (req, form) => await new Promise((resolve, reject) => form.parse(req, (e, fields, files) => (e ? reject(e) : resolve([fields, files])))),
};

async function importCSVData(csvData, fields) {
  console.log('importCSVData');
  const importedSKUs = [];
  const csvRecordsArray = csvData.split(/\r\n|\n/);
  const headersRow = importService.getHeaderArray(csvRecordsArray);
  // const rows = importService.getDataRecordsArrayFromCSVFile(csv, headersRow.length, partnerId);
  for (let i = 1; i < csvRecordsArray.length; i++) {
    const currentRecord = (csvRecordsArray[i]).split(';');
    console.log(`${currentRecord.length} - ${headersRow.length}`);
    if (currentRecord.length === headersRow.length) {
      const [sku, title, description, customDescription, brand, stringMsrp, stringPrice, attributeSetId, stringWeight, quantity, imsCategories, imsTaxCode, requiresShipping, width, height, length] = currentRecord;
      // const [title, variant, sku, stringPrice, stringCost, stringWeight, attributeSetId, shopifyTypeId, description, imsCategories, experiences, recipes, regions, creationDate, publishDate, image, taxTitle, taxRate] = currentRecord;
      const product = {};
      product.title = title;
      product.description = description;
      product.customDescription = customDescription;
      product.brand = brand;
      product.partnerId = fields.partnerId;
      product.categoryId = fields.categoryId;
      product.sku = sku;
      const random = Math.floor(100000 + Math.random() * 900000);
      product.sku = `${product.sku.replace(/\s+/g, '')}-${random}`.substring(0, 63);
      product.msrp = parseFloat(stringMsrp); // parseFloat(stringMsrp);
      product.price = parseFloat(stringPrice);
      product.weight = parseFloat(stringWeight);
      product.categoryId = fields.categoryId;
      const attributeSet = await ims.readAttributeSetByName('all', attributeSetId);
      product.attributeSetId = parseInt(attributeSet.id, 10); // attributeSetId

      // product.quantity = parseFloat(currentRecord[7]);
      product.imsCategories = await getCategoryData(imsCategories.split(','));
      product.imsTaxCode = imsTaxCode;
      product.channelId = fields.channelId;
      product.inventoryLevels = [];
      product.trackInventory = true; // quantity > 0;
      product.refrigerated = false;
      product.showMsrp = true;
      product.requiresShipping = requiresShipping;
      if (requiresShipping === '1') {
        product.size = {};
        product.size.height = height;
        product.size.length = length;
        product.size.width = width;
      }
      if (quantity && parseInt(quantity, 10) > 0) {
        product.inventoryLevels.push({ warehouseId: fields.warehouseId, amount: quantity });
      }

      product.attributes = [];
      for (let j = 16; j < headersRow.length; j++) { // change starting index based on the product schema
        product.attributes.push({ attribute: { name: headersRow[j] }, value: currentRecord[j] });
      }

      try {
        await ims.productCreate(product, product.channelId);
        await productQuery.createOne(product);
        importedSKUs.push({ fullSku: product.sku, sku });
      } catch (error) {
        console.log('import saltato');
        continue;
      }
    }
  }
  return importedSKUs;
}

async function getCategoryData(categories) {
  const categoryIds = [];
  for (let i = 0; i < categories.length; i++) {
    if (Number.isNaN(parseInt(categories[i], 10))) {
      const category = await categoryQuery.readCategoryByName('all', categories[i]);
      categoryIds.push(category.items[0].id);
    } else {
      categoryIds.push(categories[i]);
    }
  }
  return categoryIds;
}

async function importImageToProduct(sku, zipEntry, filename, extension) {
  const data = zipEntry.getData();
  const buff = Buffer.from(data);
  const base64data = buff.toString('base64');

  const product = await productModel.findOne({ sku });
  const imsImageResult = await ims.uploadImageToProduct(sku, filename, extension, base64data);
  if (product) {
    console.log('inserisco media');
    product.media = [];
    const productMedia = new productMediaModel();
    productMedia.name = `${filename}.${extension}`;
    productMedia.type = `image/${extension === 'jpg' ? 'jpeg' : extension}`;
    productMedia.data = `${base64data}`;
    productMedia.imsgid = imsImageResult;
    product.media.push(productMedia);
    await product.save();
  }
}

module.exports = router;