Your IP : 127.0.0.1


Current Path : /home/dev2.destoffenstraat.com/app/Firebear/ImportExport/Model/Import/
Upload File :
Current File : /home/dev2.destoffenstraat.com/app/Firebear/ImportExport/Model/Import/AdvancedPricing.php

<?php
/**
 * @copyright: Copyright © 2017 Firebear Studio. All rights reserved.
 * @author   : Firebear Studio <fbeardev@gmail.com>
 */

namespace Firebear\ImportExport\Model\Import;

use Exception;
use Firebear\ImportExport\Helper\Data as ImportExportHelper;
use Firebear\ImportExport\Traits\Import\Entity as ImportTrait;
use InvalidArgumentException;
use Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing as MagentoAdvancedPricing;
use Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator;
use Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator\Website as WebsiteValidator;
use Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator\TierPrice as TierPriceValidator;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Helper\Data as CatalogHelper;
use Magento\Catalog\Model\Product as ProductModel;
use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface;
use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface as ValidatorInterface;
use Magento\Catalog\Model\Product\Price\TierPrice;
use Magento\CatalogImportExport\Model\Import\Product\StoreResolver;
use Magento\CatalogImportExport\Model\Import\Proxy\Product\ResourceModelFactory;
use Magento\Framework\App\CacheInterface;
use Magento\Framework\App\ProductMetadataInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Serialize\SerializerInterface;
use Magento\Framework\Stdlib\DateTime\DateTime;
use Magento\ImportExport\Model\Import;
use Symfony\Component\Console\Output\ConsoleOutput;

/**
 * Class AdvancedPricing
 *
 * @package Firebear\ImportExport\Model\Import
 */
class AdvancedPricing extends MagentoAdvancedPricing
{
    use ImportTrait;

    const CLEARED_PRODUCTS_CACHE_ID = 'firebear_import_advanced_pricing_cleared_products_';

    /**
     * @var array
     */
    protected $checkDuplicates = [];

    /**
     * @var array
     */
    protected $messageTemplates = [
        RowValidatorInterface::ERROR_DUPLICATE_UNIQUE_ATTRIBUTE => 'Duplicate unique attribute'
    ];

    /**
     * @var bool
     */
    protected $_debugMode;

    /**
     * @var string
     */
    private $productEntityLinkField;

    /**
     * @var array
     */
    protected $entityProducts;

    /**
     * @var ProductMetadataInterface
     */
    protected $productMetadata;

    /**
     * Json Serializer
     *
     * @var SerializerInterface
     */
    protected $serializer;

    /**
     * @var CacheInterface
     */
    protected $cache;

    /**
     * @param Context $context
     * @param DateTime $dateTime
     * @param ResourceModelFactory $resourceFactory
     * @param ProductModel $productModel
     * @param CatalogHelper $catalogHelper
     * @param StoreResolver $storeResolver
     * @param Product $importProduct
     * @param Validator $validator
     * @param WebsiteValidator $websiteValidator
     * @param TierPriceValidator $tierPriceValidator
     * @param ConsoleOutput $output
     * @param ImportExportHelper $helper
     * @param ProductMetadataInterface $productMetadata
     * @param CacheInterface $cache
     */
    public function __construct(
        Context $context,
        DateTime $dateTime,
        ResourceModelFactory $resourceFactory,
        ProductModel $productModel,
        CatalogHelper $catalogHelper,
        StoreResolver $storeResolver,
        Product $importProduct,
        Validator $validator,
        WebsiteValidator $websiteValidator,
        TierPriceValidator $tierPriceValidator,
        ConsoleOutput $output,
        ImportExportHelper $helper,
        ProductMetadataInterface $productMetadata,
        CacheInterface $cache
    ) {
        $version = $productMetadata->getVersion();
        if (version_compare($version, '2.4.2', '<')) {
            parent::__construct(
                $context->getJsonHelper(),
                $context->getImportExportData(),
                $context->getDataSourceModel(),
                $context->getConfig(),
                $context->getResource(),
                $context->getResourceHelper(),
                $context->getStringUtils(),
                $context->getErrorAggregator(),
                $dateTime,
                $resourceFactory,
                $productModel,
                $catalogHelper,
                $storeResolver,
                $importProduct,
                $validator,
                $websiteValidator,
                $tierPriceValidator
            );
        } else {
            parent::__construct(
                $context->getJsonHelper(),
                $context->getImportExportData(),
                $context->getDataSourceModel(),
                $context->getResource(),
                $context->getResourceHelper(),
                $context->getErrorAggregator(),
                $dateTime,
                $resourceFactory,
                $productModel,
                $catalogHelper,
                $storeResolver,
                $importProduct,
                $validator,
                $websiteValidator,
                $tierPriceValidator
            );
        }

        $this->setSerializer($context->getSerializer());
        $this->productMetadata = $productMetadata;
        $this->_logger = $context->getLogger();
        $this->output = $output;
        $this->_debugMode = $helper->getDebugMode();
        $this->cache = $cache;

        foreach ($this->messageTemplates as $errorCode => $message) {
            $this->addMessageTemplate($errorCode, $message);
        }
    }

    public function validateRow(array $rowData, $rowNum)
    {
        if (!isset($this->_validatedRows[$rowNum])) {
            $this->_processedRowsCount++;
            $this->_processedEntitiesCount++;

            if (parent::validateRow($rowData, $rowNum)) {
                $sku = $rowData[static::COL_SKU];
                $website = $rowData[static::COL_TIER_PRICE_WEBSITE];
                $group = $rowData[static::COL_TIER_PRICE_CUSTOMER_GROUP];
                $qty = $rowData[static::COL_TIER_PRICE_QTY];

                if (isset($this->checkDuplicates[$sku][$website][$group][$qty])) {
                    $this->addRowError(
                        RowValidatorInterface::ERROR_DUPLICATE_UNIQUE_ATTRIBUTE,
                        $rowNum
                    );
                }
                $this->checkDuplicates[$sku][$website][$group][$qty] = true;
            }
        }

        return !$this->getErrorAggregator()->isRowInvalid($rowNum);
    }

    /**
     * @param array $prices
     * @param string $table
     * @return $this
     * @throws Exception
     */
    protected function processCountExistingPrices($prices, $table)
    {
        $oldSkus = $this->retrieveOldSkus();
        $existProductIds = array_intersect_key($oldSkus, $prices);
        if (!count($existProductIds)) {
            return $this;
        }

        $tableName = $this->_resourceFactory->create()->getTable($table);
        $productEntityLinkField = $this->getProductEntityLinkField();
        $existingPrices = $this->_connection->fetchAll(
            $this->_connection->select()->from(
                $tableName,
                [$productEntityLinkField, 'all_groups', 'customer_group_id', 'qty']
            )->where(
                $productEntityLinkField . ' IN (?)',
                $existProductIds
            )
        );

        foreach ($existingPrices as $existingPrice) {
            foreach ($oldSkus as $sku => $productId) {
                if ($existingPrice[$productEntityLinkField] == $productId && isset($prices[$sku])) {
                    $this->incrementCounterUpdated($prices[$sku], $existingPrice);
                }
            }
        }

        return $this;
    }

    /**
     * @return string
     * @throws Exception
     */
    private function getProductEntityLinkField()
    {
        if (!$this->productEntityLinkField) {
            $this->productEntityLinkField = $this->getMetadataPool()
                ->getMetadata(ProductInterface::class)
                ->getLinkField();
        }

        return $this->productEntityLinkField;
    }

    /**
     * @return $this
     * @throws Exception
     */
    protected function saveAndReplaceAdvancedPrices()
    {
        $behavior = $this->getBehavior();
        if (Import::BEHAVIOR_REPLACE == $behavior) {
            $this->_cachedSkuToDelete = null;
        }
        $listSku = [];
        $tierPrices = [];
        $bunchSize = 0;
        $skusPerWebsite = [];
        while ($bunch = $this->_dataSourceModel->getNextBunch()) {
            $bunchSize = $bunchSize ?: count($bunch);
            foreach ($bunch as $rowNum => $rowData) {
                $rowData = $this->joinIdenticalyData($rowData);
                if (!$this->validateRow($rowData, $rowNum)) {
                    $this->addLogWriteln(
                        __('price from sku: %1 is not validated', $rowData[self::COL_SKU]),
                        $this->output,
                        'info'
                    );
                    continue;
                }
                $time = explode(" ", microtime());
                $startTime = $time[0] + $time[1];
                $sku = $rowData[self::COL_SKU];
                $rowData = $this->customChangeData($rowData);
                if (!$this->validateRow($rowData, $rowNum)) {
                    $this->addRowError(RowValidatorInterface::ERROR_SKU_IS_EMPTY, $rowNum);
                    continue;
                }
                if ($this->getErrorAggregator()->hasToBeTerminated()) {
                    $this->getErrorAggregator()->addRowToSkip($rowNum);
                    continue;
                }
                $rowSku = $rowData[self::COL_SKU];
                $listSku[] = $rowSku;
                if (!empty($rowData[self::COL_TIER_PRICE_WEBSITE])) {
                    $websiteId = $this->getWebsiteId($rowData[self::COL_TIER_PRICE_WEBSITE]);
                    if (!empty($websiteId)) {
                        $skusPerWebsite[$websiteId][] = $sku;
                    }
                    $array = [
                        'all_groups' => $rowData[self::COL_TIER_PRICE_CUSTOMER_GROUP] == self::VALUE_ALL_GROUPS,
                        'customer_group_id' => $this->getCustomerGroupId(
                            $rowData[self::COL_TIER_PRICE_CUSTOMER_GROUP]
                        ),
                        'qty' => $rowData[self::COL_TIER_PRICE_QTY],
                        'value' => $rowData[self::COL_TIER_PRICE],
                        'website_id' => $websiteId
                    ];
                    if (isset($rowData[self::COL_TIER_PRICE_TYPE])) {
                        switch ($rowData[self::COL_TIER_PRICE_TYPE]) {
                            case self::TIER_PRICE_TYPE_FIXED:
                                $array['value'] = $rowData[self::COL_TIER_PRICE];
                                $array['percentage_value'] = null;
                                break;
                            case self::TIER_PRICE_TYPE_PERCENT:
                                $array['value'] = 0;
                                $array['percentage_value'] = $rowData[self::COL_TIER_PRICE];
                                break;
                        }
                    }
                    $tierPrices[$rowSku][] = $array;
                }
                $time = explode(" ", microtime());
                $endTime = $time[0] + $time[1];
                $totalTime = $endTime - $startTime;
                $totalTime = round($totalTime, 5);
                $this->addLogWriteln(__('price from sku: %1 .... %2s', $sku, $totalTime), $this->output, 'info');
            }
            $this->getEntities($listSku);

            if ($behavior == Import::BEHAVIOR_APPEND) {
                $this->processCountExistingPrices($tierPrices, self::TABLE_TIER_PRICE)
                    ->processCountNewPrices($tierPrices);

                $this->saveProductPrices($tierPrices, self::TABLE_TIER_PRICE);
                if ($listSku) {
                    $this->setUpdatedAt($listSku);
                }
            }

            if ($behavior == Import::BEHAVIOR_REPLACE) {
                if ($listSku) {
                    $this->processCountNewPrices($tierPrices);
                    $deleteItems = true;
                    foreach ($skusPerWebsite as $websiteId => $skus) {
                        $uniqueListSku = array_unique($skus);
                        $cachedProductSku = $this->loadClearedProductsSku();
                        $clearedProductsSku = $cachedProductSku[$websiteId] ?? [];
                        foreach (array_chunk($uniqueListSku, $bunchSize) as $bunchDelete) {
                            $productsForDelete = array_diff($bunchDelete, $clearedProductsSku);
                            if ($productsForDelete) {
                                if ($this->deleteProductTierPricesPerWebsite(
                                    $productsForDelete,
                                    $websiteId,
                                    self::TABLE_TIER_PRICE
                                )) {
                                    $cachedProductSku[$websiteId] = array_merge($clearedProductsSku, $uniqueListSku);
                                    $this->saveClearedProductsSku($cachedProductSku);
                                } else {
                                    $deleteItems = false;
                                }
                            }
                        }
                    }
                    if ($deleteItems) {
                        foreach (array_chunk($tierPrices, $bunchSize, true) as $bunchTierPrices) {
                            $this->saveProductPrices($bunchTierPrices, self::TABLE_TIER_PRICE);
                        }
                        $this->setUpdatedAt($listSku);
                    }
                }
            }
        }

        return $this;
    }

    /**
     * Delete the tier prices per website
     *
     * @param array $productsForDelete
     * @param $websiteId
     * @param $table
     * @return bool
     * @throws Exception
     */
    protected function deleteProductTierPricesPerWebsite(array $productsForDelete, $websiteId, $table)
    {
        $tableName = $this->_resourceFactory->create()->getTable($table);
        $productEntityLinkField = $this->getProductEntityLinkField();
        if ($tableName && !empty($productsForDelete)) {
                    $productIds = $this->_connection->fetchCol(
                        $this->_connection->select()
                            ->from(['cpe' => $this->_catalogProductEntity], $productEntityLinkField)
                            ->join(['cpw' => 'catalog_product_website'], 'cpe.entity_id = cpw.product_id')
                            ->where('sku IN (?)', $productsForDelete)
                            ->where('cpw.website_id IN (?)', $websiteId)
                    );
            if (!empty($productIds)) {
                try {
                    $condition = $this->_connection->quoteInto($productEntityLinkField . ' IN (?)', $productIds) .
                        ' AND ' . $this->_connection->quoteInto(TierPrice::WEBSITE_ID . ' IN (?)', $websiteId);
                    $this->countItemsDeleted += $this->_connection->delete(
                        $tableName,
                        $condition
                    );
                    return true;
                } catch (\Exception $e) {
                    return false;
                }
            } else {
                $this->addRowError(ValidatorInterface::ERROR_SKU_IS_EMPTY, 0);
                return false;
            }
        }
        return false;
    }

    /**
     * @param $skuList
     */
    protected function saveClearedProductsSku($skuList)
    {
        if (is_array($skuList)) {
            $skuList = $this->serializer->serialize($skuList);
        }

        $this->cache->save(
            $skuList,
            static::CLEARED_PRODUCTS_CACHE_ID . ($this->_parameters['job_id'] ?? ''),
            ['fire_bear_import_cache'],
            3600 * 24
        );
    }

    /**
     * @return array
     */
    protected function loadClearedProductsSku()
    {
        $skuList = $this->cache->load(static::CLEARED_PRODUCTS_CACHE_ID . ($this->_parameters['job_id'] ?? ''));
        if ($skuList) {
            return $this->serializer->unserialize($skuList) ?? [];
        }
        return [];
    }

    /**
     * @param $listSku
     * @throws Exception
     */
    protected function getEntities($listSku)
    {
        $this->entityProducts = $this->_connection->fetchAll(
            $this->_connection->select()->from(
                $this->_catalogProductEntity,
                ['sku', $this->getProductEntityLinkField()]
            )->where('sku in(?)', $listSku)
        );
    }

    /**
     * @param $field
     * @return array
     */
    protected function getEntity($field)
    {
        $array = [];
        if (!empty($this->entityProducts)) {
            foreach ($this->entityProducts as $value) {
                $array[] = $value[$field];
            }
        }

        return $array;
    }

    /**
     * @return array
     * @throws Exception
     */
    protected function retrieveOldSkus()
    {
        $select = $this->_connection->select()->from(
            $this->_catalogProductEntity,
            ['sku', $this->getProductEntityLinkField()]
        );
        if ($skus = $this->getEntity('sku')) {
            $select->where('sku in(?)', $this->getEntity('sku'));
        }
        $this->_oldSkus = $this->_connection->fetchPairs(
            $select
        );
        return $this->_oldSkus;
    }

    /**
     * @return $this|MagentoAdvancedPricing
     * @throws LocalizedException
     */
    protected function _saveValidatedBunches()
    {
        $source = $this->_getSource();
        $currentDataSize = 0;
        $bunchRows = [];
        $startNewBunch = false;
        $nextRowBackup = [];
        $maxDataSize = $this->_resourceHelper->getMaxDataSize();
        $bunchSize = $this->_importExportData->getBunchSize();

        $source->rewind();
        $this->_dataSourceModel->cleanBunches();
        $file = null;
        $jobId = null;
        if (isset($this->_parameters['file'])) {
            $file = $this->_parameters['file'];
        }
        if (isset($this->_parameters['job_id'])) {
            $jobId = $this->_parameters['job_id'];
        }

        while ($source->valid() || $bunchRows) {
            if ($startNewBunch || !$source->valid()) {
                $this->_dataSourceModel->saveBunches(
                    $this->getEntityTypeCode(),
                    $this->getBehavior(),
                    $jobId,
                    $file,
                    $bunchRows
                );
                $bunchRows = $nextRowBackup;
                $currentDataSize = strlen($this->getSerializer()->serialize($bunchRows));
                $startNewBunch = false;
                $nextRowBackup = [];
            }
            if ($source->valid()) {
                try {
                    $rowData = $source->current();
                } catch (InvalidArgumentException $e) {
                    $this->addRowError($e->getMessage(), $this->_processedRowsCount);
                    $this->_processedRowsCount++;
                    $source->next();
                    continue;
                }

                $this->_processedRowsCount++;
                $rowData = $this->customBunchesData($rowData);
                $rowSize = strlen($this->getSerializer()->serialize($rowData));

                $isBunchSizeExceeded = $bunchSize > 0 && count($bunchRows) >= $bunchSize;

                if ($currentDataSize + $rowSize >= $maxDataSize || $isBunchSizeExceeded) {
                    $startNewBunch = true;
                    $nextRowBackup = [$source->key() => $rowData];
                } else {
                    $bunchRows[$source->key()] = $rowData;
                    $currentDataSize += $rowSize;
                }

                $source->next();
            }
        }

        return $this;
    }

    /**
     * Retrieve All Fields Source
     *
     * @return array
     */
    public function getAllFields()
    {
        return $this->validColumnNames;
    }
}