5

But instead build new line items. For instance if it's the same product just list it twice on the cart page.

enter image description here

As you can see in the picture: the "Portia Capri-29-Blue" have qty 2. But instead of it to increment in the qty box just list each one as a seperate line item.

And for the reason why. Here's what I'm trying to accomplish. using the b2b features in m2 commerce 2.2.3 I've created a way for the roles of buyers to add products to a cart and then save their cart. when their cart gets saved i've sent their cart back to the managers account. If he has all his buyers saved carts in his account. buyer A, buyer B, buyer C. let's say buyer A and buyer C have the same product in their cart. Well when i'm invoicing back to each buyers account how can I know which buyers had the same product if it just increments and merges.

I hope this makes sense and any help would be greatly appreciated. Thank you.

2
  • Did you check my answer? Commented Jul 21, 2018 at 17:08
  • 1
    Thanks everyone for participating and I'll be making a decision shortly. Everyone's answers were very helpful.
    – getData
    Commented Jul 26, 2018 at 22:09

3 Answers 3

11
+100

If every time want to add a separate product in cart, then you can pluginize representProduct method located in Magento\Quote\Model\Quote\Item

Your [Vendor]/[Module]/etc/di.xml looks like below

<?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='Magento\Quote\Model\Quote\Item'>
        <plugin name='beforeDispatch' type='[Vendor]\[Module]\Plugin\Model\Quote\ItemPlugin' sortOrder='99'/>
    </type>    
</config>

and your [Vendor]\[Module]\Plugin\Model\Quote\ItemPlugin.php looks like below

<?php

namespace [Vendor]\[Module]\Plugin\Model\Quote;
class ItemPlugin 
{
    
    public function afterRepresentProduct(\Magento\Quote\Model\Quote\Item $subject, $result)
    {
        if ($yourCondition) { // if there is no condition return always false
             $result = false;
        }
        return $result;
    }
}
2
  • It only works when add to cart from product detail page... What to do if the product is updated from minicart ?
    – aravind
    Commented Oct 4, 2019 at 12:50
  • If a product is updated from minicart need to add as new line item instead on updating quantity how to do that?
    – aravind
    Commented Oct 4, 2019 at 12:50
4

You can list all the products with same sku in the same row by overriding quote item model's representProduct() function , which defines how the product will be represented in cart during the creation of quote item itself.

Just add a preference for Magento\Quote\Model\Quote\Item in your di.xml as following ;

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Magento\Quote\Model\Quote\Item" type="Vendor\Module\Model\Quote\Quote\Item" />   
</config>

and in your Vendor\Module\Model\Quote\Quote\Item add following code ;

<?php
namespace Vendor\Module\Model\Quote\Quote;

use Magento\Framework\Api\AttributeValueFactory;
use Magento\Framework\Api\ExtensionAttributesFactory;
use Magento\Quote\Api\Data\CartItemInterface;

/**
 * Sales Quote Item Model
 *
 * @method \Magento\Quote\Model\ResourceModel\Quote\Item _getResource()
 * @method \Magento\Quote\Model\ResourceModel\Quote\Item getResource()
 * @method string getCreatedAt()
 * @method \Magento\Quote\Model\Quote\Item setCreatedAt(string $value)
 * @method string getUpdatedAt()
 * @method \Magento\Quote\Model\Quote\Item setUpdatedAt(string $value)
 * @method int getStoreId()
 * @method \Magento\Quote\Model\Quote\Item setStoreId(int $value)
 * @method int getParentItemId()
 * @method \Magento\Quote\Model\Quote\Item setParentItemId(int $value)
 * @method int getIsVirtual()
 * @method \Magento\Quote\Model\Quote\Item setIsVirtual(int $value)
 * @method string getDescription()
 * @method \Magento\Quote\Model\Quote\Item setDescription(string $value)
 * @method string getAdditionalData()
 * @method \Magento\Quote\Model\Quote\Item setAdditionalData(string $value)
 * @method int getFreeShipping()
 * @method \Magento\Quote\Model\Quote\Item setFreeShipping(int $value)
 * @method int getIsQtyDecimal()
 * @method \Magento\Quote\Model\Quote\Item setIsQtyDecimal(int $value)
 * @method int getNoDiscount()
 * @method \Magento\Quote\Model\Quote\Item setNoDiscount(int $value)
 * @method float getWeight()
 * @method \Magento\Quote\Model\Quote\Item setWeight(float $value)
 * @method float getBasePrice()
 * @method \Magento\Quote\Model\Quote\Item setBasePrice(float $value)
 * @method float getCustomPrice()
 * @method float getTaxPercent()
 * @method \Magento\Quote\Model\Quote\Item setTaxPercent(float $value)
 * @method \Magento\Quote\Model\Quote\Item setTaxAmount(float $value)
 * @method \Magento\Quote\Model\Quote\Item setBaseTaxAmount(float $value)
 * @method \Magento\Quote\Model\Quote\Item setRowTotal(float $value)
 * @method \Magento\Quote\Model\Quote\Item setBaseRowTotal(float $value)
 * @method float getRowTotalWithDiscount()
 * @method \Magento\Quote\Model\Quote\Item setRowTotalWithDiscount(float $value)
 * @method float getRowWeight()
 * @method \Magento\Quote\Model\Quote\Item setRowWeight(float $value)
 * @method float getBaseTaxBeforeDiscount()
 * @method \Magento\Quote\Model\Quote\Item setBaseTaxBeforeDiscount(float $value)
 * @method float getTaxBeforeDiscount()
 * @method \Magento\Quote\Model\Quote\Item setTaxBeforeDiscount(float $value)
 * @method float getOriginalCustomPrice()
 * @method \Magento\Quote\Model\Quote\Item setOriginalCustomPrice(float $value)
 * @method string getRedirectUrl()
 * @method \Magento\Quote\Model\Quote\Item setRedirectUrl(string $value)
 * @method float getBaseCost()
 * @method \Magento\Quote\Model\Quote\Item setBaseCost(float $value)
 * @method \Magento\Quote\Model\Quote\Item setPriceInclTax(float $value)
 * @method float getBasePriceInclTax()
 * @method \Magento\Quote\Model\Quote\Item setBasePriceInclTax(float $value)
 * @method \Magento\Quote\Model\Quote\Item setRowTotalInclTax(float $value)
 * @method float getBaseRowTotalInclTax()
 * @method \Magento\Quote\Model\Quote\Item setBaseRowTotalInclTax(float $value)
 * @method int getGiftMessageId()
 * @method \Magento\Quote\Model\Quote\Item setGiftMessageId(int $value)
 * @method string getWeeeTaxApplied()
 * @method \Magento\Quote\Model\Quote\Item setWeeeTaxApplied(string $value)
 * @method float getWeeeTaxAppliedAmount()
 * @method \Magento\Quote\Model\Quote\Item setWeeeTaxAppliedAmount(float $value)
 * @method float getWeeeTaxAppliedRowAmount()
 * @method \Magento\Quote\Model\Quote\Item setWeeeTaxAppliedRowAmount(float $value)
 * @method float getBaseWeeeTaxAppliedAmount()
 * @method \Magento\Quote\Model\Quote\Item setBaseWeeeTaxAppliedAmount(float $value)
 * @method float getBaseWeeeTaxAppliedRowAmnt()
 * @method \Magento\Quote\Model\Quote\Item setBaseWeeeTaxAppliedRowAmnt(float $value)
 * @method float getWeeeTaxDisposition()
 * @method \Magento\Quote\Model\Quote\Item setWeeeTaxDisposition(float $value)
 * @method float getWeeeTaxRowDisposition()
 * @method \Magento\Quote\Model\Quote\Item setWeeeTaxRowDisposition(float $value)
 * @method float getBaseWeeeTaxDisposition()
 * @method \Magento\Quote\Model\Quote\Item setBaseWeeeTaxDisposition(float $value)
 * @method float getBaseWeeeTaxRowDisposition()
 * @method \Magento\Quote\Model\Quote\Item setBaseWeeeTaxRowDisposition(float $value)
 * @method float getDiscountTaxCompensationAmount()
 * @method \Magento\Quote\Model\Quote\Item setDiscountTaxCompensationAmount(float $value)
 * @method float getBaseDiscountTaxCompensationAmount()
 * @method \Magento\Quote\Model\Quote\Item setBaseDiscountTaxCompensationAmount(float $value)
 * @method null|bool getHasConfigurationUnavailableError()
 * @method \Magento\Quote\Model\Quote\Item setHasConfigurationUnavailableError(bool $value)
 * @method \Magento\Quote\Model\Quote\Item unsHasConfigurationUnavailableError()
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
 * @SuppressWarnings(PHPMD.ExcessivePublicCount)
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
 */
class Item extends \Magento\Quote\Model\Quote\Item
{
    /**
     * Prefix of model events names
     *
     * @var string
     */
    protected $_eventPrefix = 'sales_quote_item';

    /**
     * Parameter name in event
     *
     * In observe method you can use $observer->getEvent()->getObject() in this case
     *
     * @var string
     */
    protected $_eventObject = 'item';

    /**
     * Quote model object
     *
     * @var \Magento\Quote\Model\Quote
     */
    protected $_quote;

    /**
     * Item options array
     *
     * @var array
     */
    protected $_options = [];

    /**
     * Item options by code cache
     *
     * @var array
     */
    protected $_optionsByCode = [];

    /**
     * Not Represent options
     *
     * @var array
     */
    protected $_notRepresentOptions = ['info_buyRequest'];

    /**
     * Flag stating that options were successfully saved
     *
     */
    protected $_flagOptionsSaved;

    /**
     * Array of errors associated with this quote item
     *
     * @var \Magento\Sales\Model\Status\ListStatus
     */
    protected $_errorInfos;

    /**
     * @var \Magento\Framework\Locale\FormatInterface
     */
    protected $_localeFormat;

    /**
     * @var \Magento\Quote\Model\Quote\Item\OptionFactory
     */
    protected $_itemOptionFactory;

    /**
     * @var \Magento\Quote\Model\Quote\Item\Compare
     */
    protected $quoteItemCompare;

    /**
     * @var \Magento\CatalogInventory\Api\StockRegistryInterface
     */
    protected $stockRegistry;

    protected $scopeConfig;

    /**
     * @param \Magento\Framework\Model\Context $context
     * @param \Magento\Framework\Registry $registry
     * @param ExtensionAttributesFactory $extensionFactory
     * @param AttributeValueFactory $customAttributeFactory
     * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
     * @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency
     * @param \Magento\Sales\Model\Status\ListFactory $statusListFactory
     * @param \Magento\Framework\Locale\FormatInterface $localeFormat
     * @param Item\OptionFactory $itemOptionFactory
     * @param Item\Compare $quoteItemCompare
     * @param \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry
     * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
     * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
     * @param array $data
     *
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
     */
    public function __construct(
        \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
        \Magento\Framework\Model\Context $context,
        \Magento\Framework\Registry $registry,
        ExtensionAttributesFactory $extensionFactory,
        AttributeValueFactory $customAttributeFactory,
        \Magento\Catalog\Api\ProductRepositoryInterface $productRepository,
        \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency,
        \Magento\Sales\Model\Status\ListFactory $statusListFactory,
        \Magento\Framework\Locale\FormatInterface $localeFormat,
        \Magento\Quote\Model\Quote\Item\OptionFactory $itemOptionFactory,
        \Magento\Quote\Model\Quote\Item\Compare $quoteItemCompare,
        \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry,
        \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
        \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
        array $data = []
    ) {
        $this->scopeConfig = $scopeConfig;
        // $this->registry = $registry;
        // $this->extensionFactory = $extensionFactory;
        // $this->customAttributeFactory = $customAttributeFactory;
        // $this->productRepository = $productRepository;
        // $this->priceCurrency = $priceCurrency;
        $this->_errorInfos = $statusListFactory->create();
        $this->_localeFormat = $localeFormat;
        $this->_itemOptionFactory = $itemOptionFactory;
        $this->quoteItemCompare = $quoteItemCompare;
        $this->stockRegistry = $stockRegistry;
        $this->resource = $resource;
        $this->resourceCollection = $resourceCollection;
        parent::__construct(
            $context,
            $registry,
            $extensionFactory,
            $customAttributeFactory,
            $productRepository,
            $priceCurrency,
            $statusListFactory,
            $localeFormat,
            $itemOptionFactory,
            $quoteItemCompare,
            $stockRegistry,
            $resource,
            $resourceCollection,
            $data
        );
    }

    public function representProduct($product)
    {
        $itemProduct = $this->getProduct();

        if (!$product || $itemProduct->getId() == $product->getId()) {
            return true;
        }

        if (!$product || $itemProduct->getId() != $product->getId()) {
            return false;
        }

        /**
         * Check maybe product is planned to be a child of some quote item - in this case we limit search
         * only within same parent item
         */
        $stickWithinParent = $product->getStickWithinParent();
        if ($stickWithinParent) {
            if ($this->getParentItem() !== $stickWithinParent) {
                return false;
            }
        }

        // Check options
        $itemOptions = $this->getOptionsByCode();
        $productOptions = $product->getCustomOptions();

        if (!$this->compareOptions($itemOptions, $productOptions)) {
            return false;
        }
        if (!$this->compareOptions($productOptions, $itemOptions)) {
            return false;
        }
    }
}

For explaination I have just added following condition in representProduct() function

if (!$product || $itemProduct->getId() == $product->getId()) {
            return true;
        }

Please note that the this will apply to new quote items created and not the old ones that are already present in cart.

Feel free to ask any questions if required.

1
1

Same sku/product in cart can vary by options (like a fake custom option). When the product is added to cart (Add to cart button), you need to send an unique parameter for each type of customer or whatever. Don't forget about full page cache. You need some JavaScript that will modify the payload of add to cart buttons for each type of product. The JS implementation is up to you (using sections.xml or overriding existing Magento 2 JS components or Jquery_Ui widgets, ..).

See: \Magento\Checkout\Model\Cart::addProduct($productInfo, $requestInfo = null) If $requestInfo is an array, you're parameter will be there.

$requestInfo = [
..
'lorem' => 'ipsum'
..
];

Further Magento knows to associate ('represent') a product by the combination of sku and $requestInfo.

See: \Magento\Quote\Model\Quote\Item::representProduct($product) and \Magento\Quote\Model\Quote::getItemByProduct

/**
 * Retrieve quote item by product id
 *
 * @param   \Magento\Catalog\Model\Product $product
 * @return  \Magento\Quote\Model\Quote\Item|bool
 */
public function getItemByProduct($product)
{
    foreach ($this->getAllItems() as $item) {
        if ($item->representProduct($product)) {
            return $item;
        }
    }
    return false;
}

Don't think to the $product function's parameter as a product pulled out from catalog. $product is a representation of catalog product model in cart 'context'. It contains the pre-configured options of the product added in cart with 'Add to Cart' button through the action class: \Magento\Checkout\Controller\Cart\Add::execute();

..
$params = $this->getRequest()->getParams();
..
$this->cart->addProduct($product, $params);
..
$this->cart->save();
..

You're new parameter that identifies uniquely something ca fit very well in $params. For debugging purpose log $params in add to cart action. Than add products of various types to cart. It will be more clear to reverse engineer.

This is not related to your questions. If you will try to add the same product to cart multiple times from the same script (action) in a single request/call, you'll have to full load the product model each time you call $cart->addProduct(). As you know by know, the selected options of the product for cart are set inside the $product object during $this->cart->addProduct($product, $params).

2
  • @getData If you don't agree with the answer you can at least say why. Maybe you should investigate why in cart the same configurable product with different options is split in different quote items. Commented Jul 24, 2018 at 10:55
  • I did not disagree with your answer. I'm still reviewing everything. Thanks for adding your answer to the thread @obscure.
    – getData
    Commented Jul 24, 2018 at 20:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.