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
Solved! Go to Solution.
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;
}
}
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;
}
}
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..
thanks, we need to override etc/di.xml too... & it works perfectly..
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
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?