cancel
Showing results for 
Search instead for 
Did you mean: 

Help please creating a custom product type extended from bundle product type in magento 2?

SOLVED

Help please creating a custom product type extended from bundle product type in magento 2?

i've extended bundle product type of magento 2 named Box product, everything works excepts, add option is not appearing in extended new type... Here are the files, Please check..

Arslan/BoxType/etc/product_types.xml

<?xml version="1.0" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Catalog:etc/product_types.xsd">
<type label="Box Product" modelInstance="Arslan\BoxType\Model\Product\Type\Box" name="box" composite='true' indexPriority="40" sortOrder="50">
    <priceModel instance="Arslan\BoxType\Model\Product\Price"/>
    <allowedSelectionTypes>
        <type name="simple" />
        <type name="virtual" />
    </allowedSelectionTypes>
    <customAttributes>
        <attribute name="refundable" value="true"/>
    </customAttributes>
</type>

now the model Arslan/BoxType/Model/Product/Type/Box.php

<?php
 namespace Arslan\BoxType\Model\Product\Type;

class Box extends \Magento\Bundle\Model\Product\Type
{

    const TYPE_ID = 'box';

   /**
   * {@inheritdoc}
   */
  public function deleteTypeSpecificData(\Magento\Catalog\Model\Product $product)
{
    // method intentionally empty
}
}

Now Arslan/BoxType/Setup/InstallData.php

<?php


 namespace Arslan\BoxType\Setup;

 use Magento\Framework\Setup\InstallDataInterface;
 use Magento\Framework\Setup\ModuleContextInterface;
 use Magento\Framework\Setup\ModuleDataSetupInterface;
 use Magento\Eav\Setup\EavSetup;
 use Magento\Eav\Setup\EavSetupFactory;

 class InstallData implements InstallDataInterface
 {

    private $eavSetupFactory;

/**
 * Constructor
 *
 * @param \Magento\Eav\Setup\EavSetupFactory $eavSetupFactory
 */
public function __construct(EavSetupFactory $eavSetupFactory)
{
    $this->eavSetupFactory = $eavSetupFactory;
}

/**
 * {@inheritdoc}
 */
public function install(
    ModuleDataSetupInterface $setup,
    ModuleContextInterface $context
) {
    $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);

    // associate these attributes with new product type
    $fieldList = [
        'price',
        'special_price',
        'special_from_date',
        'special_to_date',
        'minimal_price',
        'cost',
        'tier_price',
        'weight',
        'price_type',
        'sku_type',
        'weight_type',
        'price_view',
        'shipment_type',
    ];

    // make these attributes applicable to new product type
    foreach ($fieldList as $field) {
        $applyTo = explode(
            ',',
            $eavSetup->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $field, 'apply_to')
        );
        if (!in_array(\Arslan\BoxType\Model\Product\Type\Box::TYPE_ID, $applyTo)) {
            $applyTo[] = \Arslan\BoxType\Model\Product\Type\Box::TYPE_ID;
            $eavSetup->updateAttribute(
                \Magento\Catalog\Model\Product::ENTITY,
                $field,
                'apply_to',
                implode(',', $applyTo)
            );
        }
    }
}
}

Now The Missing Portion in my extended product type is "add options" Please see the screenshot

i72Zv

1 ACCEPTED SOLUTION

Accepted Solutions

Re: Help please creating a custom product type extended from bundle product type in magento 2?

Hi @arslantS 

 

You need to change modifier. For example (vendor/magento/module-bundle/etc/adminhtml/di.xml):

<virtualType name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool">
    <arguments>
        <argument name="modifiers" xsi:type="array">
            <item name="box" xsi:type="array">
                <item name="class" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\Composite</item>
                <item name="sortOrder" xsi:type="number">180</item>
            </item>
            <item name="bundle_stock_data" xsi:type="array">
                <item name="class" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\StockData</item>
                <item name="sortOrder" xsi:type="number">190</item>
            </item>
        </argument>
    </arguments>
</virtualType>

If you open Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\Composite then check 

/**
 * {@inheritdoc}
 */
public function modifyMeta(array $meta)
{
    if ($this->locator->getProduct()->getTypeId() === Type::TYPE_CODE) {
        foreach ($this->modifiers as $bundleClass) {
            /** @var ModifierInterface $bundleModifier */
            $bundleModifier = $this->objectManager->get($bundleClass);
            if (!$bundleModifier instanceof ModifierInterface) {
                throw new \InvalidArgumentException(
                    'Type "' . $bundleClass . '" is not an instance of ' . ModifierInterface::class
                );
            }
            $meta = $bundleModifier->modifyMeta($meta);
        }
    }
    return $meta;
}

Here Type::TYPE_CODE is 'bundle', that's why you missing some option.

 

So create your own modifier and modify type code will working.

 

Create Arslan/BoxType/etc/adminhtml/di.xml 

<virtualType name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool">
    <arguments>
        <argument name="modifiers" xsi:type="array">
            <item name="box" xsi:type="array">
                <item name="class" xsi:type="string">Arslan\BoxType\Ui\DataProvider\Product\Form\Modifier\Composite</item>
                <item name="sortOrder" xsi:type="number">180</item>
            </item>
            <item name="bundle_stock_data" xsi:type="array">
                <item name="class" xsi:type="string">Arslan\BoxType\Ui\DataProvider\Product\Form\Modifier\StockData</item>
                <item name="sortOrder" xsi:type="number">190</item>
            </item>
        </argument>
    </arguments>
</virtualType>
<type name="Arslan\BoxType\Ui\DataProvider\Product\Form\Modifier\Composite">
<arguments>
<argument name="modifiers" xsi:type="array">
<item name="bundleSku" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundleSku</item>
<item name="bundlePrice" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundlePrice</item>
<item name="bundleWeight" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundleWeight</item>
<item name="bundleQuantity" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundleQuantity</item>
<item name="bundleAdvancedPricing" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundleAdvancedPricing</item>
<item name="bundlePanel" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundlePanel</item>
<item name="bundleCustomOptions" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundleCustomOptions</item>
</argument>
</arguments>
</type>

Create Arslan\BoxType\Ui\DataProvider\Product\Form\Modifier\Composite.php 

namespace Arslan\BoxType\Ui\DataProvider\Product\Form\Modifier;

use Magento\Catalog\Model\Locator\LocatorInterface;
use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
use Magento\Framework\ObjectManagerInterface;
use Magento\Ui\DataProvider\Modifier\ModifierInterface;
use Magento\Bundle\Api\ProductOptionRepositoryInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundlePanel;

/**
* Class Bundle customizes Bundle product creation flow
*/
class Composite extends AbstractModifier
{
/**
* @var LocatorInterface
*/
protected $locator;

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

/**
* Object Manager
*
* @var ObjectManagerInterface
*/
protected $objectManager;

/**
* @var ProductOptionRepositoryInterface
*/
protected $optionsRepository;

/**
* @var ProductRepositoryInterface
*/
protected $productRepository;

/**
* @param LocatorInterface $locator
* @param ObjectManagerInterface $objectManager
* @param ProductOptionRepositoryInterface $optionsRepository
* @param ProductRepositoryInterface $productRepository
* @param array $modifiers
*/
public function __construct(
LocatorInterface $locator,
ObjectManagerInterface $objectManager,
ProductOptionRepositoryInterface $optionsRepository,
ProductRepositoryInterface $productRepository,
array $modifiers = []
) {
$this->locator = $locator;
$this->objectManager = $objectManager;
$this->optionsRepository = $optionsRepository;
$this->productRepository = $productRepository;
$this->modifiers = $modifiers;
}

/**
* {@inheritdoc}
*/
public function modifyMeta(array $meta)
{
if ($this->locator->getProduct()->getTypeId() === 'box') {
foreach ($this->modifiers as $bundleClass) {
/** @var ModifierInterface $bundleModifier */
$bundleModifier = $this->objectManager->get($bundleClass);
if (!$bundleModifier instanceof ModifierInterface) {
throw new \InvalidArgumentException(
'Type "' . $bundleClass . '" is not an instance of ' . ModifierInterface::class
);
}
$meta = $bundleModifier->modifyMeta($meta);
}
}
return $meta;
}

/**
* {@inheritdoc}
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
public function modifyData(array $data)
{
/** @var \Magento\Catalog\Api\Data\ProductInterface $product */
$product = $this->locator->getProduct();
$modelId = $product->getId();
$isBundleProduct = $product->getTypeId() === 'box';
if ($isBundleProduct && $modelId) {
$data[$modelId][BundlePanel::CODE_BUNDLE_OPTIONS][BundlePanel::CODE_BUNDLE_OPTIONS] = [];
/** @var \Magento\Bundle\Api\Data\OptionInterface $option */
foreach ($this->optionsRepository->getList($product->getSku()) as $option) {
$selections = [];
/** @var \Magento\Bundle\Api\Data\LinkInterface $productLink */
foreach ($option->getProductLinks() as $productLink) {
$linkedProduct = $this->productRepository->get($productLink->getSku());
$integerQty = 1;
if ($linkedProduct->getExtensionAttributes()->getStockItem()) {
if ($linkedProduct->getExtensionAttributes()->getStockItem()->getIsQtyDecimal()) {
$integerQty = 0;
}
}
$selections[] = [
'selection_id' => $productLink->getId(),
'option_id' => $productLink->getOptionId(),
'product_id' => $linkedProduct->getId(),
'name' => $linkedProduct->getName(),
'sku' => $linkedProduct->getSku(),
'is_default' => ($productLink->getIsDefault()) ? '1' : '0',
'selection_price_value' => $productLink->getPrice(),
'selection_price_type' => $productLink->getPriceType(),
'selection_qty' => $integerQty ? (int)$productLink->getQty() : $productLink->getQty(),
'selection_can_change_qty' => $productLink->getCanChangeQuantity(),
'selection_qty_is_integer' => (bool)$integerQty,
'position' => $productLink->getPosition(),
'delete' => '',
];
}
$data[$modelId][BundlePanel::CODE_BUNDLE_OPTIONS][BundlePanel::CODE_BUNDLE_OPTIONS][] = [
'position' => $option->getPosition(),
'option_id' => $option->getOptionId(),
'title' => $option->getTitle(),
'default_title' => $option->getDefaultTitle(),
'type' => $option->getType(),
'required' => ($option->getRequired()) ? '1' : '0',
'bundle_selections' => $selections,
];
}
}

return $data;
}
}

 

-----
If Issue Solved, Click Kudos and Accept As solutions.
Sohel Rana, 7x Magento 2, 2x Magento 1 Certified

View solution in original post

6 REPLIES 6

Re: Help please creating a custom product type extended from bundle product type in magento 2?

Hi @arslantS 

 

You need to change modifier. For example (vendor/magento/module-bundle/etc/adminhtml/di.xml):

<virtualType name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool">
    <arguments>
        <argument name="modifiers" xsi:type="array">
            <item name="box" xsi:type="array">
                <item name="class" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\Composite</item>
                <item name="sortOrder" xsi:type="number">180</item>
            </item>
            <item name="bundle_stock_data" xsi:type="array">
                <item name="class" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\StockData</item>
                <item name="sortOrder" xsi:type="number">190</item>
            </item>
        </argument>
    </arguments>
</virtualType>

If you open Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\Composite then check 

/**
 * {@inheritdoc}
 */
public function modifyMeta(array $meta)
{
    if ($this->locator->getProduct()->getTypeId() === Type::TYPE_CODE) {
        foreach ($this->modifiers as $bundleClass) {
            /** @var ModifierInterface $bundleModifier */
            $bundleModifier = $this->objectManager->get($bundleClass);
            if (!$bundleModifier instanceof ModifierInterface) {
                throw new \InvalidArgumentException(
                    'Type "' . $bundleClass . '" is not an instance of ' . ModifierInterface::class
                );
            }
            $meta = $bundleModifier->modifyMeta($meta);
        }
    }
    return $meta;
}

Here Type::TYPE_CODE is 'bundle', that's why you missing some option.

 

So create your own modifier and modify type code will working.

 

Create Arslan/BoxType/etc/adminhtml/di.xml 

<virtualType name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool">
    <arguments>
        <argument name="modifiers" xsi:type="array">
            <item name="box" xsi:type="array">
                <item name="class" xsi:type="string">Arslan\BoxType\Ui\DataProvider\Product\Form\Modifier\Composite</item>
                <item name="sortOrder" xsi:type="number">180</item>
            </item>
            <item name="bundle_stock_data" xsi:type="array">
                <item name="class" xsi:type="string">Arslan\BoxType\Ui\DataProvider\Product\Form\Modifier\StockData</item>
                <item name="sortOrder" xsi:type="number">190</item>
            </item>
        </argument>
    </arguments>
</virtualType>
<type name="Arslan\BoxType\Ui\DataProvider\Product\Form\Modifier\Composite">
<arguments>
<argument name="modifiers" xsi:type="array">
<item name="bundleSku" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundleSku</item>
<item name="bundlePrice" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundlePrice</item>
<item name="bundleWeight" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundleWeight</item>
<item name="bundleQuantity" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundleQuantity</item>
<item name="bundleAdvancedPricing" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundleAdvancedPricing</item>
<item name="bundlePanel" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundlePanel</item>
<item name="bundleCustomOptions" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundleCustomOptions</item>
</argument>
</arguments>
</type>

Create Arslan\BoxType\Ui\DataProvider\Product\Form\Modifier\Composite.php 

namespace Arslan\BoxType\Ui\DataProvider\Product\Form\Modifier;

use Magento\Catalog\Model\Locator\LocatorInterface;
use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
use Magento\Framework\ObjectManagerInterface;
use Magento\Ui\DataProvider\Modifier\ModifierInterface;
use Magento\Bundle\Api\ProductOptionRepositoryInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundlePanel;

/**
* Class Bundle customizes Bundle product creation flow
*/
class Composite extends AbstractModifier
{
/**
* @var LocatorInterface
*/
protected $locator;

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

/**
* Object Manager
*
* @var ObjectManagerInterface
*/
protected $objectManager;

/**
* @var ProductOptionRepositoryInterface
*/
protected $optionsRepository;

/**
* @var ProductRepositoryInterface
*/
protected $productRepository;

/**
* @param LocatorInterface $locator
* @param ObjectManagerInterface $objectManager
* @param ProductOptionRepositoryInterface $optionsRepository
* @param ProductRepositoryInterface $productRepository
* @param array $modifiers
*/
public function __construct(
LocatorInterface $locator,
ObjectManagerInterface $objectManager,
ProductOptionRepositoryInterface $optionsRepository,
ProductRepositoryInterface $productRepository,
array $modifiers = []
) {
$this->locator = $locator;
$this->objectManager = $objectManager;
$this->optionsRepository = $optionsRepository;
$this->productRepository = $productRepository;
$this->modifiers = $modifiers;
}

/**
* {@inheritdoc}
*/
public function modifyMeta(array $meta)
{
if ($this->locator->getProduct()->getTypeId() === 'box') {
foreach ($this->modifiers as $bundleClass) {
/** @var ModifierInterface $bundleModifier */
$bundleModifier = $this->objectManager->get($bundleClass);
if (!$bundleModifier instanceof ModifierInterface) {
throw new \InvalidArgumentException(
'Type "' . $bundleClass . '" is not an instance of ' . ModifierInterface::class
);
}
$meta = $bundleModifier->modifyMeta($meta);
}
}
return $meta;
}

/**
* {@inheritdoc}
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
public function modifyData(array $data)
{
/** @var \Magento\Catalog\Api\Data\ProductInterface $product */
$product = $this->locator->getProduct();
$modelId = $product->getId();
$isBundleProduct = $product->getTypeId() === 'box';
if ($isBundleProduct && $modelId) {
$data[$modelId][BundlePanel::CODE_BUNDLE_OPTIONS][BundlePanel::CODE_BUNDLE_OPTIONS] = [];
/** @var \Magento\Bundle\Api\Data\OptionInterface $option */
foreach ($this->optionsRepository->getList($product->getSku()) as $option) {
$selections = [];
/** @var \Magento\Bundle\Api\Data\LinkInterface $productLink */
foreach ($option->getProductLinks() as $productLink) {
$linkedProduct = $this->productRepository->get($productLink->getSku());
$integerQty = 1;
if ($linkedProduct->getExtensionAttributes()->getStockItem()) {
if ($linkedProduct->getExtensionAttributes()->getStockItem()->getIsQtyDecimal()) {
$integerQty = 0;
}
}
$selections[] = [
'selection_id' => $productLink->getId(),
'option_id' => $productLink->getOptionId(),
'product_id' => $linkedProduct->getId(),
'name' => $linkedProduct->getName(),
'sku' => $linkedProduct->getSku(),
'is_default' => ($productLink->getIsDefault()) ? '1' : '0',
'selection_price_value' => $productLink->getPrice(),
'selection_price_type' => $productLink->getPriceType(),
'selection_qty' => $integerQty ? (int)$productLink->getQty() : $productLink->getQty(),
'selection_can_change_qty' => $productLink->getCanChangeQuantity(),
'selection_qty_is_integer' => (bool)$integerQty,
'position' => $productLink->getPosition(),
'delete' => '',
];
}
$data[$modelId][BundlePanel::CODE_BUNDLE_OPTIONS][BundlePanel::CODE_BUNDLE_OPTIONS][] = [
'position' => $option->getPosition(),
'option_id' => $option->getOptionId(),
'title' => $option->getTitle(),
'default_title' => $option->getDefaultTitle(),
'type' => $option->getType(),
'required' => ($option->getRequired()) ? '1' : '0',
'bundle_selections' => $selections,
];
}
}

return $data;
}
}

 

-----
If Issue Solved, Click Kudos and Accept As solutions.
Sohel Rana, 7x Magento 2, 2x Magento 1 Certified

Re: Help please creating a custom product type extended from bundle product type in magento 2?

Hi, the add option button appears, but The product didn't save.
"the attribute "price" must be set" i solved by updating di.xml
Backend working fine..

Now frontend display of new product type is not working same as bundle product type..
which files i need to update for frontend display of New product type..

Re: Help please creating a custom product type extended from bundle product type in magento 2?

thanks, we need to override etc/di.xml too... & it works perfectly.. 

Re: Help please creating a custom product type extended from bundle product type in magento 2?

The product didn't save."the attribute "price" must be set"

How do you fix this issue ? can you provide me code to fix this. thanks Smiley Very Happy

Re: Help please creating a custom product type extended from bundle product type in magento 2?

Hi there @arslantS and @Sohel  great post, this really help me a lot!

But I'm stuck in this issue that you mentioned:
The "Price" attribute value is empty. Set the attribute and try again.

Could you help me and share the changes you've made in etc/di.xml

I've tryed to make some changes in etc/di.xml, but nothing works to allowed me to save my custom product type extending bundle type.. 

One more doubt, please @Sohel could you guide me how can I create a custom frontend template for this custom product type.. we need that for this custom product type we have a different layout page, totally different as default bundle product page, but with all funcionalities as default bundle.

Just one more doubt, can I create a custom product extending bundle product type and sell this products by recurring paymments, like weekly, montly or annualy?

Re: Help please creating a custom product type extended from bundle product type in magento 2?

Hi @fabox237 

Did you find out the solution to this problem? Can you share how?

Thanks in advance.