Strona głównaBlogHow to Fix Overselling Issues in Magento

Styczeń 26, 2017 Technologie

How to Fix Overselling Issues in Magento

If your eCommerce store is based on Magento that is integrated with the ERP system, you have probably encountered the above problem before. The problem manifests itself when Magento allows the customer to create the order with a configurable product (e.g. a shoe model) without creating a simple product record (specific shoe size).

This error does not have any impact on the order display in Magento’s Admin Panel; nevertheless, it might affect the external app integration – it is not recommended to exclude a simple product from the API response. Moreover, the problem might cause overselling of products, as the actual number of products in the system is not properly reduced after a purchase.

Explanation of the problem

You can find the source of the problem in the following file: app/code/core/Mage/Sales/Model/Resource/Quote/Item/Collection.php, and to be more precise, in the method: assignProducts

protected function _assignProducts()
{
Varien_Profiler::start(‚QUOTE:’.__METHOD__);
$productIds = array();
foreach ($this as $item) {
$productIds[] = (int)$item->getProductId();
}
$this->_productIds = array_merge($this->_productIds, $productIds);
$productCollection = Mage::getModel(‚catalog/product’)->getCollection()
->setStoreId($this->getStoreId())
->addIdFilter($this->_productIds)
->addAttributeToSelect(Mage::getSingleton(‚sales/quote_config’)->getProductAttributes())
->addOptionsToResult()
->addStoreFilter()
->addUrlRewrite()
->addTierPriceData();
Mage::dispatchEvent(‚prepare_catalog_product_collection_prices’, array(
‚collection’ => $productCollection,
‚store_id’ => $this->getStoreId(),
));
Mage::dispatchEvent(‚sales_quote_item_collection_products_after_load’, array(
‚product_collection’ => $productCollection
));
$recollectQuote = false;
foreach ($this as $item) {
$product = $productCollection->getItemById($item->getProductId());
if ($product) {
$product->setCustomOptions(array());
$qtyOptions = array();
$optionProductIds = array();
foreach ($item->getOptions() as $option) {
/**
* Call type-specific logic for product associated with quote item
*/
$product->getTypeInstance(true)->assignProductToOption(
$productCollection->getItemById($option->getProductId()),
$option,
$product
);
if (is_object($option->getProduct()) && $option->getProduct()->getId() != $product->getId()) {
$optionProductIds[$option->getProduct()->getId()] = $option->getProduct()->getId();
}
}
if ($optionProductIds) {
foreach ($optionProductIds as $optionProductId) {
$qtyOption = $item->getOptionByCode(‚product_qty_’ . $optionProductId);
if ($qtyOption) {
$qtyOptions[$optionProductId] = $qtyOption;
}
}
}
$item->setQtyOptions($qtyOptions)->setProduct($product);
} else {
$item->isDeleted(true);
$recollectQuote = true;
}
$item->checkData();
}
if ($recollectQuote && $this->_quote) {
$this->_quote->collectTotals();
}
Varien_Profiler::stop(‚QUOTE:’.__METHOD__);
return $this;
}
view raw
php
hosted with ❤ by GitHub

After adding a configurable product to the shopping cart, everything is correct. When we take a closer look at the records in the database in the sales_quote_item table, every single configurable cart item should have two records assigned.

SELECT item_id, quote_id, product_id, parent_item_id, product_type, sku, name, price FROM sales_flat_quote_item WHERE quote_id = 5582 ORDER BY item_id DESC;
view raw
php
hosted with ❤ by GitHub

However, if we go to the Admin Panel, hide the simple product that has been added to the shopping cart by changing the status to Disabled, and then we refresh the shopping cart, some modifications will occur. The simple product will disappear from the database and a customer will be asked to choose a different product size.

It happens because inactive products are not indexed in the catalog_product_flat table and thus a given condition cannot be met in terms of previously chosen size:

foreach ($this as $item) {
$product = $productCollection->getItemById($item->getProductId());
if ($product) {
// ….
} else {
$item->isDeleted(true); // simple product is removed
$recollectQuote = true;
}
$item->checkData();
}
view raw
php
hosted with ❤ by GitHub

To reproduce the error, you have to unblock previously chosen size. After refreshing the shopping cart, the product alert will not be visible any longer. The simple product will not be recreated in the database and a customer will be able to order previously chosen size.

By following all the steps in the shopping cart and after making the order, the quantity of the product will not be reduced, as the simple product does not exist, and the configurable product does not have any amount.

How to solve it

Solution to this problem is quite easy. If the simple product is not present in the code, you have to recreate it. This can be done in the following way:

<?php
class Module_Sales_Model_Resource_Quote_Item_Collection extends
Mage_Sales_Model_Resource_Quote_Item_Collection{
/**
* Add products to items and item options
*
* @return Mage_Sales_Model_Resource_Quote_Item_Collection
*/
protected function _assignProducts()
{
parent::_assignProducts();
foreach($this->getItemsByColumnValue(product_type, configurable) as $item){
if(!$this->getItemByColumnValue(parent_item_id, $item->getId())){
if($option = $item->getOptionByCode(info_buyRequest)){
$buyRequest = new Varien_Object(unserialize($option->getValue()));
$buyRequest->unsetData(reset_count);
try{
$this->_quote->updateItem((int)$item->getId(), $buyRequest);
}
catch(Exception $e){
// product out of stock
}
}
}
}
return $this;
}
}
view raw
Collection.php
hosted with ❤ by GitHub

Summary

As presented above, the problem is quite specific and difficult to recreate. It can occur with the introduction of a very popular product. In this case, an unwary administrator can block and unblock the product added to the shopping carts by a large number of customers, which leads to data inconsistency error in sales_quote_item table.

Kacper, Senior Magento Developer at Polcode