Magento Product Import Problem – Stock Update Issue Resolved

By 9th November 2012 September 5th, 2017 Development
Magento Product Import Problem - Stock Update Issue Resolved

The Magento product import is a handy tool used to update and create products from a CSV file. A problem exists however with this import that can be quite damaging to the stock data for all the products referenced in the import file. To clarify the import process in question, it is accessed through the following menu item in the Magento admin panel.


The problem:

When an import is run, the stock data for the product on the first line of the CSV is replicated for all other products in the file, regardless of what inventory data you set in the import file itself. The obvious issue is that this can cause the quantity’s of products to change, the stock status and more, which can be very damaging to business activity.

This issue is evident in all community editions and enterprise version 1.12. It is thought that it is fixed in later editions and also in Magento2.

Lets pretend we don’t know the fix (as seen on Magento2) and take a look at the code.

File: app/code/core/Mage/ImportExport/Model/Import/Entity/Product.php
Class: Mage_ImportExport_Model_Import_Entity_Product

Navigate to the file above in your Magento install, then search for the following function:

protected function _saveStockItem()

This function is used to save the stock data for items in the import. The part we are interested in is the point that the stock data is saved. You will notice a foreach loop, this is looping through all the products in the import and saving the stock data, so the problem must be inside here.

<br />foreach ($bunch as $rowNum => $rowData) {<br /> if (!$this->isRowAllowedToImport($rowData, $rowNum)) {<br /> continue;<br /> }<br /> // only SCOPE_DEFAULT can contain stock data<br /> if (self::SCOPE_DEFAULT != $this->getRowScope($rowData)) {<br /> continue;<br /> }<br /><br /> $row['product_id'] = $this->_newSku[$rowData[self::COL_SKU]]['entity_id'];<br /> $row['stock_id'] = 1;<br /><br /> /** @var $stockItem Mage_CatalogInventory_Model_Stock_Item */<br /> $stockItem = Mage::getModel('cataloginventory/stock_item');<br /> $stockItem->loadByProduct($row['product_id']);<br /> $existStockData = $stockItem->getData();<br /><br /> $row = array_merge(<br /> $defaultStockData,<br /> array_intersect_key($existStockData, $defaultStockData),<br /> array_intersect_key($rowData, $defaultStockData),<br /> $row<br /> );<br /><br /> $stockItem->setData($row);<br /><br /> if ($helper->isQty($this->_newSku[$rowData[self::COL_SKU]]['type_id'])) {<br /> if ($stockItem->verifyNotification()) {<br /> $stockItem->setLowStockDate(Mage::app()->getLocale()<br /> ->date(null, null, null, false)<br /> ->toString(Varien_Date::DATETIME_INTERNAL_FORMAT)<br /> );<br /> }<br /> $stockItem->setStockStatusChangedAutomatically((int) !$stockItem->verifyStock());<br /> } else {<br /> $stockItem->setQty(0);<br /> }<br /> $stockData[] = $stockItem->unsetOldData()->getData();<br />}<br />

The first thing to notice is the $row variable, with values being set on it without it being instantiated. This is not usual practise with arrays so lets investigate this variable further.

The $row variable is next used in an array_merge where default stock data is merged / overwritten by existing stock data, which is overwritten with stockdata found in the import and then finally overwritten by $row again.

Heres the problem, after the first cycle, $row is set as a full stock data array, and we can see that its going to overwrite all future stock data no matter what is set for that product.

So now we have found the problem, lets build a solution. The required fix for this issue is to properly instatiate the $row variable inside the foreach loop. This will ensure that we are not carrying any old stock data for previous products.

<br />foreach ($bunch as $rowNum => $rowData) {<br /> if (!$this->isRowAllowedToImport($rowData, $rowNum)) {<br /> continue;<br /> }<br /> // only SCOPE_DEFAULT can contain stock data<br /> if (self::SCOPE_DEFAULT != $this->getRowScope($rowData)) {<br /> continue;<br /> }<br /><br /> // Instantiate $row variable correctly.<br /> $row = array();<br /><br /> $row['product_id'] = $this->_newSku[$rowData[self::COL_SKU]]['entity_id'];<br /> $row['stock_id'] = 1;<br /><br /> /** @var $stockItem Mage_CatalogInventory_Model_Stock_Item */<br /> $stockItem = Mage::getModel('cataloginventory/stock_item');<br /> $stockItem->loadByProduct($row['product_id']);<br /> $existStockData = $stockItem->getData();<br /><br /> $row = array_merge(<br /> $defaultStockData,<br /> array_intersect_key($existStockData, $defaultStockData),<br /> array_intersect_key($rowData, $defaultStockData),<br /> $row<br /> );<br /><br /> $stockItem->setData($row);<br /><br /> if ($helper->isQty($this->_newSku[$rowData[self::COL_SKU]]['type_id'])) {<br /> if ($stockItem->verifyNotification()) {<br /> $stockItem->setLowStockDate(Mage::app()->getLocale()<br /> ->date(null, null, null, false)<br /> ->toString(Varien_Date::DATETIME_INTERNAL_FORMAT)<br /> );<br /> }<br /> $stockItem->setStockStatusChangedAutomatically((int) !$stockItem->verifyStock());<br /> } else {<br /> $stockItem->setQty(0);<br /> }<br /> $stockData[] = $stockItem->unsetOldData()->getData();<br />}<br />

Now we have the fix in place. The LogicSpot team have put together a module which you can download and paste on your Magento installation which will solve this problem.

Download the fix here.