akeneo / magento2-connector-community

Akeneo Connector for Magento 2
Open Software License 3.0
81 stars 89 forks source link

Blank value is not getting updated for product attributes after the product sync in Magento. #466

Open aadithyadckap opened 2 years ago

aadithyadckap commented 2 years ago

Environment and configuration

  1. Akeneo connector version: 100.4.0
  2. Magento Cloud 2.3.3

Steps to reproduce

  1. update the value to one product attribute(long_description) in Akeneo
  2. run the product sync in Magento
  3. update the blank value to that product attribute(long_description) in Akeneo
  4. again run the product sync in Magento

Expected result

After the product sync, the Magento product attributes value should be the same as the Akeneo product attribute value.

Actual result

blank value is not getting updated for product attributes after the product sync in Magento.

  1. Akeneo product edit page product-edit-n-akeneo-1
  2. Magento product edit page product-edit-in-magento-1
bsarran commented 2 years ago

I'm having the same problem, any idea ?

bsarran commented 2 years ago

The problem seems to be that the Akeneo REST API only return non-null values. So the Connector don't receive the new value (null) and so don't update it in Magento ... :(

quentinpnel commented 2 years ago

We had same issue and found hack that works although not ideal. We create 1 sku with a value in all attributes and never change data in akeneo (keep product as not visible on web). Then it will always return your updates if updating all products.

bsarran commented 2 years ago

Thank you @quentinpnel , i'm going to try that, I hope that it will be fixed one day

cengizcoskun commented 2 years ago

+1, same problem..

PieterCappelle commented 1 year ago

Yes, it seems like you're facing an issue with syncing data between Akeneo and Magento, specifically related to attribute values. Based on the steps you've outlined, here's a summary of the problem:

  1. You create a product in Akeneo.
  2. You attach an attribute to the product and fill in the attribute value.
  3. You synchronize the product with Magento.
  4. The attribute value is successfully filled in Magento.
  5. You set the attribute value as empty in Akeneo.
  6. When you sync again with Magento, the attribute value remains filled in Magento.

The reason for this issue is that the Akeneo API only outputs non-null attribute values in the API response. Consequently, when you set the attribute value as empty in Akeneo, it doesn't get communicated to Magento through the synchronization process.


Solution

In a Magento module, we have created a small plugin. Take a look at the PHP file from 'start custom' until 'end custom'. In this section, we fetch all the attributes from the product family and assign them to the first product in the import using reset($products).

The only problem with this solution is that it will result in numerous NULL values in your catalog_product_entity_varchar table since all attributes will be set. However, the data in the database is now accurate.

app/code/Vendor/Module/etc/di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Akeneo\Connector\Job\Product">
        <plugin name="akeneo_product_job_plugin" type="Vendor\Module\Plugin\Job\Product" disabled="false" />
    </type>
</config>

app/code/Vendor/Module/Plugin/Job/Product.php

<?php

namespace Vendor\Module\Plugin\Job;

use Akeneo\Connector\Job\Import as JobImport;
use Akeneo\Connector\Helper\Authenticator;
use Akeneo\Connector\Helper\Config as ConfigHelper;
use Akeneo\Connector\Helper\Import\Product as ProductImportHelper;
use Akeneo\Connector\Helper\ProductFilters;
use Akeneo\Connector\Job\Product as JobProduct;
use Akeneo\Pim\ApiClient\AkeneoPimClientInterface;
use Magento\Framework\DB\Adapter\AdapterInterface;
use Akeneo\Connector\Helper\Import\Entities;

class Product extends JobImport
{
    /**
     * @var ProductImportHelper
     */
    protected $entitiesHelper;

    /**
     * @var ConfigHelper
     */
    protected $configHelper;

    /**
     * @var AkeneoPimClientInterface
     */
    protected $akeneoClient;

    /**
     * @var ProductFilters
     */
    protected $productFilters;

    /**
     * @var mixed[] $filters
     */
    protected $filters;

    /**
     * @var Entities $entities
     */
    protected $entities;

    /**
     * Constructor
     *
     * @param ProductImportHelper $entitiesHelper
     * @param ConfigHelper $configHelper
     * @param Authenticator $authenticator
     * @param ProductFilters $productFilters
     * @param Entities $entities
     */
    public function __construct(
        ProductImportHelper $entitiesHelper,
        ConfigHelper $configHelper,
        Authenticator $authenticator,
        ProductFilters $productFilters,
        Entities $entities
    ) {
        try {
            $this->akeneoClient = $authenticator->getAkeneoApiClient();
        } catch (\Exception $e) {
            $this->akeneoClient = false;
        }

        $this->entitiesHelper = $entitiesHelper;
        $this->configHelper = $configHelper;
        $this->productFilters = $productFilters;
        $this->entities = $entities;
    }

    /**
     * createTable
     *
     * @param JobProduct $subject
     * @param $proceed
     * @return void
     */
    public function aroundCreateTable(JobProduct $subject, $proceed)
    {
        if (empty($this->configHelper->getMappedChannels())) {
            $this->setMessage(__('No website/channel mapped. Please check your configurations.'));
            $this->stop(true);

            return;
        }

        // Stop the import if the family is not imported
        if ($subject->getFamily()) {
            /** @var AdapterInterface $connection */
            $connection = $this->entitiesHelper->getConnection();
            /** @var string $connectorEntitiesTable */
            $connectorEntitiesTable = $this->entities->getTable($this->entities::TABLE_NAME);
            /** @var bool $isFamilyImported */
            $isFamilyImported = (bool)$connection->fetchOne(
                $connection->select()
                    ->from($connectorEntitiesTable, ['code'])
                    ->where('code = ?', $subject->getFamily())
                    ->limit(1)
            );

            if (!$isFamilyImported) {
                $this->setMessage(__('The family %1 is not imported yet, please run Family import.', $subject->getFamily()));
                $this->stop(true);

                return;
            }
        }

        /** @var mixed[] $filters */
        $filters = $this->getFilters($subject->getFamily());

        foreach ($filters as $filter) {
            /** @var PageInterface $products */
            $products = $this->akeneoClient->getProductApi()->listPerPage(1, false, $filter);
            /** @var mixed[] $products */
            $products = $products->getItems();

            if (!empty($products)) {
                break;
            }
        }

        if (empty($products)) {
            $this->setMessage(__('No results from Akeneo for the family: %1', $this->getFamily()));
            $this->stop(true);

            return;
        }

        $product = reset($products);

        //start custom
        //1. fetch the family information (this contains the attributes)
        $family = $this->akeneoClient->getFamilyApi()->get($subject->getFamily());
        if (!empty($family) && isset($family['attributes'])) {
            //2. create array of attributes of the family
            $attributes = array_combine(
                array_map('strtolower', array_values($family['attributes'])),
                array_map('strtolower', array_values($family['attributes']))
            );

            //3. merge the attributes with the product, just to be safe
            $product = array_merge($product, $attributes);
        }
        //end custom

        $this->entitiesHelper->createTmpTableFromApi($product, $subject->getCode());

        /** @var string $message */
        $message = __('Family imported in this batch: %1', $subject->getFamily());
        $this->setMessage($message);
    }

    /**
     * Retrieve product filters
     *
     * @return mixed[]
     */
    protected function getFilters($family = null)
    {
        /** @var mixed[] $filters */
        $filters = $this->productFilters->getFilters($family);
        if (array_key_exists('error', $filters)) {
            $this->setMessage($filters['error']);
            $this->stop(true);
        }

        $this->filters = $filters;

        return $this->filters;
    }
}