Автоматическое изменение типов продуктов в Magento 2

в 23:28, , рубрики: Magento, magento 2, Разработка под e-commerce

Иногда по тем или иным причинам контент менеджерам необходимо конвертировать тот или иной продукт из Simple в Virtual или наоборот, сменить ему атрибут сет и так далее… Чаще всего это ложится на плечи разработчиков. И тут я хотел бы описать 1 новую и очень интересную особенность Magento 2, которая никак не описана в официальной документации.

Для изменения типа продукта в Magento 1.* контент менеджерам пришлось бы создавать новые продукты вручную, делать их копиями оригинальных товаров и так далее или просить разработчиков сделать это через код. В Magento 2.* внедрена новая «особенность» для достижения этой цели. Прежде же чем разобраться с ней (я назову ее «авто конверсия») вам следует иметь в виду несколько моментов, для получения правильных результатов.

Авто конверсия с точки зрения пользователя

Давайте посмотрим в админ панель, вернее как это работает с точки зрения контент менеджера. Как и прежде вы можете выбрать тип нового товара до того, как вы его создадите.

В Magento 1 это отображается на Admin > Catalog > Manage Products > Страница выбора атрибут сета и типа продукта после нажатия на “Add Product”.
Автоматическое изменение типов продуктов в Magento 2 - 1

В Magento 2 для этого на странице Admin > Products > Catalog нажмите на стрелку с права от кнопки «Add Product».
Автоматическое изменение типов продуктов в Magento 2 - 2

После выбора необходимого типа Magento покажет вам страницу редактирования нового товара с предопределенными опциями и/или новыми секциями для выбранного типа. Если вы выберите тип продукта Simple product или просто нажмете на “Add Product” то увидите страницу редактирования Simple продукта, но обратите внимание на следующие 3 вещи:
Автоматическое изменение типов продуктов в Magento 2 - 3

  1. Атрибут Вес (Weight)
  2. Секция Configurations
  3. Секция Downloadable Information

Эти 3 секции определяют тип продукта.

Magento 2 может автоматически (при сохранении) изменять типы продуктов как у новых так и уже существующих продуктов следующих товаров:

Рассмотрим, что для этого необходимо сделать.

Simple и Virtual продукты
Автоматическое изменение типов продуктов в Magento 2 - 4
Если вы установите вес «Weight» = «The item has no weight» (именно нет веса, а не удалите просто значение из инпут поля) то продукт будет сохранен как Virtual продукт, но как только вы вернетесь на страницу редактирования товара и укажите, что у товара есть вес, то он будет конвертирован в Simple продукт сразу же после сохранения.

Downloadable продукт
Автоматическое изменение типов продуктов в Magento 2 - 5
Для того, что превратить Simple продукт в Downloadable вам нужно установить «Weight» = «The item has no weight» и ниже в секции «Downloadable Information» добавить 1 или более ссылок. Чтобы превратить этот товар обратно в Simple продукт достаточно установить «Weight» = «The item has weight» и сохранить продукт.

Configurable продукт
Автоматическое изменение типов продуктов в Magento 2 - 6
Для превращения Simple продукта в Configurable, достаточно добавить любой дочерний товар к нему как указано выше. А для того, чтобы сделать его снова Simple продуктом достаточно удалить все конфигурации и нажать на save. Как вы уже поняли, это значит, что в Magento 2 не может быть Configurable продукта без детей ( как это было в Magento 1).

Вот собственно и все.

На заметку

Перед тем как использовать авто конверсию в конфигурируемый товар надо иметь в виду, что в Magento 2 изменен принцип получения цены от Configurable продукта. В Magento 1 Configurable продукты имели свою собственную цену и полностью игнорировали цену связанных с ними продуктов. В Magento 2 применен другой подход. Сейчас Configurable продукты используют рассчитанную цену из привязанных детей. Обратной стороной медали является то, что при удалении всех привязанных продуктов товар конвертируется в Simple продукт, но если удалить все связанные с ним продукты с Grid страницы в админ панели, то продукт не конвертируется ( почему это так, я опишу позже ). Учитывая новую логику, то что товар является Configurable только если у него есть дети, в противном случае он «конвертируется» в Simple продукт, это может привести к некоторым не предвидимым ошибкам в коде, особенно если вы работаете с импортом товаров.
* В Magento 2.1 некоторые из этих ошибок исправлены и при прямом обращении к методу $product->getFinalPrice() теперь выбрасывается исключение

Fatal error:  Uncaught exception 'MagentoFrameworkExceptionLocalizedException' with message 'Configurable product "…sku…" does not have sub-products' in vendormagentomodule-configurable-productPricingPriceConfigurablePriceResolver.php:52

Для разработчиков

Теперь давайте поговорим о том, как именно все это работает. Я бы хотел начать с самого главного, с класса MagentoCatalogModelProductTypeTransitionManager.

...
public function __construct(
        MagentoCatalogModelProductEditWeightResolver $weightResolver,
        array $compatibleTypes
    ) {
        $this->compatibleTypes = $compatibleTypes;
        $this->weightResolver = $weightResolver;
    }

    public function processProduct(Product $product)
    {
        if (in_array($product->getTypeId(), $this->compatibleTypes)) {
            $product->setTypeInstance(null);
            $productTypeId = $this->weightResolver->resolveProductHasWeight($product)
                ? Type::TYPE_SIMPLE
                : Type::TYPE_VIRTUAL;
            $product->setTypeId($productTypeId);
        }
    }
...

Это достаточно простой класс, который обладает 2мя методами __construct и processProduct. Вся магия заключена во втором методе, processProduct($product). Как вы видите, он проверяет есть ли тип переданного продукта в массиве совместимых типов и если продукт имеет вес то это Simple продукт, если нет то Virtual продукт.

В Magento 2.1. как вы уже догадались, совместимы следующие типы:

  • Simple
  • Virtual
  • Downloadable
  • Configurable

Все они передаются через di.xml

...
<type name="MagentoCatalogModelProductTypeTransitionManager">
        <arguments>
            <argument name="compatibleTypes" xsi:type="array">
                <item name="simple" xsi:type="const">MagentoCatalogModelProductType::TYPE_SIMPLE</item>
                <item name="virtual" xsi:type="const">MagentoCatalogModelProductType::TYPE_VIRTUAL</item>
            </argument>
        </arguments>
    </type>
...

Это значит, что только эти типы могут быть конвертированы в Simple или Virtual продукты.

Как было сказано выше, Simple продукт может быть конвертирован в конфигурируемый и загружаемый товар так же. Вся магия в следующих классах (плагинах):

MagentoDownloadableModelProductTypeTransitionManagerPluginDownloadable::aroundProcessProduct() проверяет, что тип продукта Simple, Virtual, Downloadable, с фронтенда были постом отправлены данные с ключем downloadable и то, что этот продукт не имеет веса. Если это так, то он Downloadable, в противном случае запускается оригинальный метод.

...
public function aroundProcessProduct(
        MagentoCatalogModelProductTypeTransitionManager $subject,
        Closure $proceed,
        MagentoCatalogModelProduct $product
    ) {
        $isTypeCompatible = in_array(
            $product->getTypeId(),
            [
                MagentoCatalogModelProductType::TYPE_SIMPLE,
                MagentoCatalogModelProductType::TYPE_VIRTUAL,
                MagentoDownloadableModelProductType::TYPE_DOWNLOADABLE
            ]
        );
        $downloadableData = $this->request->getPost('downloadable');
        $hasDownloadableData = false;
        if (isset($downloadableData)) {
            foreach ($downloadableData as $data) {
                foreach ($data as $rowData) {
                    if (empty($rowData['is_delete'])) {
                        $hasDownloadableData = true;
                        break 2;
                    }
                }
            }
        }
        if ($isTypeCompatible && $hasDownloadableData && !$this->weightResolver->resolveProductHasWeight($product)) {
            $product->setTypeId(MagentoDownloadableModelProductType::TYPE_DOWNLOADABLE);
            return;
        }
        $proceed($product);
    }
...


MagentoConfigurableProductModelProductTypeTransitionManagerPluginConfigurable::aroundProcessProduct() плагин меняет тип продукта на “configurable” если в реквесте имеются данные с ключем “attributes”, в противном случае запускается оригинальный метод.

...
public function aroundProcessProduct(
        MagentoCatalogModelProductTypeTransitionManager $subject,
        Closure $proceed,
        MagentoCatalogModelProduct $product
    ) {
        $attributes = $this->request->getParam('attributes');
        if (!empty($attributes)) {
            $product->setTypeId(MagentoConfigurableProductModelProductTypeConfigurable::TYPE_CODE);
            return;
        }
        $proceed($product);
    }
...

Как все это работает

Если вы попробуете сохранить товар из админ панели как было написано выше то он сменит свой тип на 1 из указанных выше благодаря этому коду, но если просто попробовать сохранить модель продукта через метод save() то ничего не изменится. Дело в контроллере, который обрабатывает сохранение товаров.
MagentoCatalogControllerAdminhtmlProductSave::execute() использует инстанс класса MagentoCatalogModelProductTypeTransitionManager

...
$product = $this->initializationHelper->initialize($this->productBuilder->build($this->getRequest()));
$this->productTypeManager->processProduct($product);
…
$product->save();
...

Как вы видите конвертирование товара никак не привязано к методу save() и вызывается непосредственно перед созданием продукта.

Вместо заключение

Для пользователей и разработчиков, я думаю стало чуть понятнее как именно работает новая особенность Magento 2. Меня удивляет, что за все время существования Magento 2 ни где не написали о ней.
Так же хочется добавить, что в Magento 2 достаточно просто добавить новые типы товаров и если вам может понадобиться позволить пользователям конвертировать их между собой то вам достаточно будет добавить пару строчек в di.xml вашего модуля и создать свой собственный плагин.

Автор: Oxidant

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js