DemacMedia/Magento-Multi-Location-Inventory

View on GitHub
app/code/community/Demac/MultiLocationInventory/Model/CatalogInventory/Observer.php

Summary

Maintainability
F
3 days
Test Coverage
<?php
/**
 * Class Demac_MultiLocationInventory_Model_CatalogInventory_Observer
 */
class Demac_MultiLocationInventory_Model_CatalogInventory_Observer extends Mage_CatalogInventory_Model_Observer
{
    /**
     * Check product inventory data when quote item quantity declaring
     *
     * Contains only minor changes so that instanceof allows our overridden class as well.
     *
     * @param  Varien_Event_Observer $observer
     *
     * @return Mage_CatalogInventory_Model_Observer
     */
    public function checkQuoteItemQty($observer)
    {
        $quoteItem = $observer->getEvent()->getItem();
        /* @var $quoteItem Mage_Sales_Model_Quote_Item */
        if(!$quoteItem || !$quoteItem->getProductId() || !$quoteItem->getQuote()
            || $quoteItem->getQuote()->getIsSuperMode()
        ) {
            return $this;
        }

        /**
         * Get Qty
         */
        $qty = $quoteItem->getQty();

        /**
         * Check if product in stock. For composite products check base (parent) item stosk status
         */
        $stockItem = $quoteItem->getProduct()->getStockItem();

        $parentStockItem = false;
        if($quoteItem->getParentItem()) {
            $parentStockItem = $quoteItem->getParentItem()->getProduct()->getStockItem();
        }
        if($stockItem) {
            if(!$stockItem->getIsInStock() || ($parentStockItem && !$parentStockItem->getIsInStock())) {
                $quoteItem->addErrorInfo(
                    'cataloginventory',
                    Mage_CatalogInventory_Helper_Data::ERROR_QTY,
                    Mage::helper('cataloginventory')->__('This product is currently out of stock.')
                );
                $quoteItem->getQuote()->addErrorInfo(
                    'stock',
                    'cataloginventory',
                    Mage_CatalogInventory_Helper_Data::ERROR_QTY,
                    Mage::helper('cataloginventory')->__('Some of the products are currently out of stock.')
                );

                return $this;
            } else {
                // Delete error from item and its quote, if it was set due to item out of stock
                $this->_removeErrorsFromQuoteAndItem($quoteItem, Mage_CatalogInventory_Helper_Data::ERROR_QTY);
            }
        }

        /**
         * Check item for options
         */
        $options = $quoteItem->getQtyOptions();
        if($options && $qty > 0) {
            $qty = $quoteItem->getProduct()->getTypeInstance(true)->prepareQuoteItemQty($qty, $quoteItem->getProduct());
            $quoteItem->setData('qty', $qty);

            if($stockItem) {
                $result = $stockItem->checkQtyIncrements($qty);
                if($result->getHasError()) {
                    $quoteItem->addErrorInfo(
                        'cataloginventory',
                        Mage_CatalogInventory_Helper_Data::ERROR_QTY_INCREMENTS,
                        $result->getMessage()
                    );

                    $quoteItem->getQuote()->addErrorInfo(
                        $result->getQuoteMessageIndex(),
                        'cataloginventory',
                        Mage_CatalogInventory_Helper_Data::ERROR_QTY_INCREMENTS,
                        $result->getQuoteMessage()
                    );
                } else {
                    // Delete error from item and its quote, if it was set due to qty problems
                    $this->_removeErrorsFromQuoteAndItem(
                        $quoteItem,
                        Mage_CatalogInventory_Helper_Data::ERROR_QTY_INCREMENTS
                    );
                }
            }

            $quoteItemHasErrors = false;
            foreach ($options as $option) {
                $optionValue = $option->getValue();
                /* @var $option Mage_Sales_Model_Quote_Item_Option */
                $optionQty         = $qty * $optionValue;
                $increaseOptionQty = ($quoteItem->getQtyToAdd() ? $quoteItem->getQtyToAdd() : $qty) * $optionValue;

                $stockItem = $option->getProduct()->getStockItem();

                if($quoteItem->getProductType() == Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE) {
                    $stockItem->setProductName($quoteItem->getName());
                }

                /* @var $stockItem Mage_CatalogInventory_Model_Stock_Item */
                if(!$stockItem instanceof Mage_CatalogInventory_Model_Stock_Item && !$stockItem instanceof Demac_MultiLocationInventory_Model_CatalogInventory_Stock_Item) {
                    Mage::throwException(
                        Mage::helper('cataloginventory')->__('The stock item for Product in option is not valid.')
                    );
                }

                /**
                 * define that stock item is child for composite product
                 */
                $stockItem->setIsChildItem(true);
                /**
                 * don't check qty increments value for option product
                 */
                $stockItem->setSuppressCheckQtyIncrements(true);

                $qtyForCheck = $this->_getQuoteItemQtyForCheck(
                    $option->getProduct()->getId(),
                    $quoteItem->getId(),
                    $increaseOptionQty
                );

                $result = $stockItem->checkQuoteItemQty($optionQty, $qtyForCheck, $optionValue);

                if(!is_null($result->getItemIsQtyDecimal())) {
                    $option->setIsQtyDecimal($result->getItemIsQtyDecimal());
                }

                if($result->getHasQtyOptionUpdate()) {
                    $option->setHasQtyOptionUpdate(true);
                    $quoteItem->updateQtyOption($option, $result->getOrigQty());
                    $option->setValue($result->getOrigQty());
                    /**
                     * if option's qty was updates we also need to update quote item qty
                     */
                    $quoteItem->setData('qty', intval($qty));
                }
                if(!is_null($result->getMessage())) {
                    $option->setMessage($result->getMessage());
                    $quoteItem->setMessage($result->getMessage());
                }
                if(!is_null($result->getItemBackorders())) {
                    $option->setBackorders($result->getItemBackorders());
                }

                if($result->getHasError()) {
                    $option->setHasError(true);
                    $quoteItemHasErrors = true;

                    $quoteItem->addErrorInfo(
                        'cataloginventory',
                        Mage_CatalogInventory_Helper_Data::ERROR_QTY,
                        $result->getMessage()
                    );

                    $quoteItem->getQuote()->addErrorInfo(
                        $result->getQuoteMessageIndex(),
                        'cataloginventory',
                        Mage_CatalogInventory_Helper_Data::ERROR_QTY,
                        $result->getQuoteMessage()
                    );
                } elseif(!$quoteItemHasErrors) {
                    // Delete error from item and its quote, if it was set due to qty lack
                    $this->_removeErrorsFromQuoteAndItem($quoteItem, Mage_CatalogInventory_Helper_Data::ERROR_QTY);
                }

                $stockItem->unsIsChildItem();
            }
        } else {
            /* @var $stockItem Mage_CatalogInventory_Model_Stock_Item */
            if(!$stockItem instanceof Mage_CatalogInventory_Model_Stock_Item && !$stockItem instanceof Demac_MultiLocationInventory_Model_CatalogInventory_Stock_Item) {
                Mage::throwException(Mage::helper('cataloginventory')->__('The stock item for Product is not valid.'));
            }

            /**
             * When we work with subitem (as subproduct of bundle or configurable product)
             */
            if($quoteItem->getParentItem()) {
                $rowQty = $quoteItem->getParentItem()->getQty() * $qty;
                /**
                 * we are using 0 because original qty was processed
                 */
                $qtyForCheck = $this->_getQuoteItemQtyForCheck(
                    $quoteItem->getProduct()->getId(),
                    $quoteItem->getId(),
                    0
                );
            } else {
                $increaseQty = $quoteItem->getQtyToAdd() ? $quoteItem->getQtyToAdd() : $qty;
                $rowQty      = $qty;
                $qtyForCheck = $this->_getQuoteItemQtyForCheck(
                    $quoteItem->getProduct()->getId(),
                    $quoteItem->getId(),
                    $increaseQty
                );
            }

            $productTypeCustomOption = $quoteItem->getProduct()->getCustomOption('product_type');
            if(!is_null($productTypeCustomOption)) {
                // Check if product related to current item is a part of grouped product
                if($productTypeCustomOption->getValue() == Mage_Catalog_Model_Product_Type_Grouped::TYPE_CODE) {
                    $stockItem->setProductName($quoteItem->getProduct()->getName());
                    $stockItem->setIsChildItem(true);
                }
            }

            $result = $stockItem->checkQuoteItemQty($rowQty, $qtyForCheck, $qty);

            if($stockItem->hasIsChildItem()) {
                $stockItem->unsIsChildItem();
            }

            if(!is_null($result->getItemIsQtyDecimal())) {
                $quoteItem->setIsQtyDecimal($result->getItemIsQtyDecimal());
                if($quoteItem->getParentItem()) {
                    $quoteItem->getParentItem()->setIsQtyDecimal($result->getItemIsQtyDecimal());
                }
            }

            /**
             * Just base (parent) item qty can be changed
             * qty of child products are declared just during add process
             * exception for updating also managed by product type
             */
            if($result->getHasQtyOptionUpdate()
                && (!$quoteItem->getParentItem()
                    || $quoteItem->getParentItem()->getProduct()->getTypeInstance(true)
                        ->getForceChildItemQtyChanges($quoteItem->getParentItem()->getProduct())
                )
            ) {
                $quoteItem->setData('qty', $result->getOrigQty());
            }

            if(!is_null($result->getItemUseOldQty())) {
                $quoteItem->setUseOldQty($result->getItemUseOldQty());
            }
            if(!is_null($result->getMessage())) {
                $quoteItem->setMessage($result->getMessage());
            }

            if(!is_null($result->getItemBackorders())) {
                $quoteItem->setBackorders($result->getItemBackorders());
            }

            if($result->getHasError()) {
                $quoteItem->addErrorInfo(
                    'cataloginventory',
                    Mage_CatalogInventory_Helper_Data::ERROR_QTY,
                    $result->getMessage()
                );

                $quoteItem->getQuote()->addErrorInfo(
                    $result->getQuoteMessageIndex(),
                    'cataloginventory',
                    Mage_CatalogInventory_Helper_Data::ERROR_QTY,
                    $result->getQuoteMessage()
                );
            } else {
                // Delete error from item and its quote, if it was set due to qty lack
                $this->_removeErrorsFromQuoteAndItem($quoteItem, Mage_CatalogInventory_Helper_Data::ERROR_QTY);
            }
        }

        return $this;
    }


    private $inventoryToRestock = 0;

    /**
     * Return creditmemo items qty to stock..
     *
     * @param Varien_Event_Observer $observer
     */
    public function refundOrderInventory($observer)
    {
        /* @var $creditmemo Mage_Sales_Model_Order_Creditmemo */
        $creditmemo = $observer->getEvent()->getCreditmemo();
        $items      = array();
        foreach ($creditmemo->getAllItems() as $item) {
            /* @var $item Mage_Sales_Model_Order_Creditmemo_Item */
            $return = false;
            if($item->hasBackToStock()) {
                if($item->getBackToStock() && $item->getQty()) {
                    $return = true;
                }
            } elseif(Mage::helper('cataloginventory')->isAutoReturnEnabled()) {
                $return = true;
            }
            if($return) {
                $parentOrderId = $item->getOrderItem()->getParentItemId();
                /* @var $parentItem Mage_Sales_Model_Order_Creditmemo_Item */
                $parentItem = $parentOrderId ? $creditmemo->getItemByOrderId($parentOrderId) : false;
                $qty        = $parentItem ? ($parentItem->getQty() * $item->getQty()) : $item->getQty();

                $this->inventoryToRestock = $qty;

                $quoteItemId = $item->getOrderItem()->getQuoteItemId();
                $productId   = $item->getProductId();

                $locationIds = Mage::helper('demac_multilocationinventory/location')->getPrioritizedLocationsForOrderQuoteItem($orderId, $quoteItemId);


                $sourceCollection = Mage::getModel('demac_multilocationinventory/order_stock_source')
                    ->getCollection()
                    ->addFieldToSelect(array('id', 'location_id', 'qty'))
                    ->addFieldToFilter('sales_quote_item_id', $quoteItemId);
                $sourceCollection
                    ->getSelect()
                    ->order('id ASC');
                $this->inventoryToRestock = $qty;
                Mage::getModel('demac_multilocationinventory/resource_iterator')->walk(
                    $sourceCollection->getSelect(),
                    array(
                        array($this, '_loopCancelItem')
                    ),
                    array(
                        'invoker'       => $this,
                        'product_id'    => $productId,
                        'quote_item_id' => $quoteItemId
                    )
                );

                Mage::getModel('demac_multilocationinventory/indexer')->reindex($productId);
            }
        }
    }


    /**
     * Cancel order item
     *
     * @param   Varien_Event_Observer $observer
     *
     * @return  Mage_CatalogInventory_Model_Observer
     */
    public function cancelOrderItem($observer)
    {
        $item = $observer->getEvent()->getItem();

        $children = $item->getChildrenItems();
        $qty      = $item->getQtyOrdered() - max($item->getQtyShipped(), $item->getQtyInvoiced()) - $item->getQtyCanceled();

        $quoteItemId = $item->getQuoteItemId();

        $productId = $item->getProductId();

        if($item->getQuoteItemId() && $qty && empty($children) && $qty) {
            $sourceCollection = Mage::getModel('demac_multilocationinventory/order_stock_source')
                ->getCollection()
                ->addFieldToSelect(array('id', 'location_id', 'qty'))
                ->addFieldToFilter('sales_quote_item_id', $quoteItemId);
            $sourceCollection
                ->getSelect()
                ->order('id ASC');
            $this->inventoryToRestock = $qty;
            Mage::getModel('demac_multilocationinventory/resource_iterator')->walk(
                $sourceCollection->getSelect(),
                array(
                    array($this, '_loopCancelItem')
                ),
                array(
                    'invoker'       => $this,
                    'product_id'    => $productId,
                    'quote_item_id' => $quoteItemId
                )
            );
        }

        Mage::getModel('demac_multilocationinventory/indexer')->reindex($productId);

        return $this;
    }

    /**
     * Return refunded items to stock (iterator method)
     *
     * @param $args Iterator data: invoker / product_id / quote_item_id / row
     *
     * @return bool
     */
    public function _loopCancelItem($args)
    {
        $productId = $args['product_id'];

        $row = $args['row'];

        $locationId      = $row['location_id'];
        $qtyFromLocation = $row['qty'];

        $stockItem = Mage::getModel('demac_multilocationinventory/stock')->loadByProduct($locationId, $productId);

        if($this->inventoryToRestock < $qtyFromLocation) {
            $orderStockSource = Mage::getModel('demac_multilocationinventory/order_stock_source')->load($row['id']);
            $orderStockSource->setQty($orderStockSource->getQty() - $this->inventoryToRestock);
            $orderStockSource->save();
            $stockItem->setQty($stockItem->getQty() + $this->inventoryToRestock);
            if($stockItem->getQty() > 0) {
                $stockItem->setIsInStock(1);
            }
            $stockItem->save();
            $this->inventoryToRestock = 0;

            //return false to exit loop
            return false;
        } else {
            $orderStockSource = Mage::getModel('demac_multilocationinventory/order_stock_source')->load($row['id']);
            $orderStockSource->setQty($orderStockSource->getQty() - $qtyFromLocation);
            $orderStockSource->save();
            $stockItem->setQty($stockItem->getQty() + $qtyFromLocation);
            if($stockItem->getQty() > 0) {
                $stockItem->setIsInStock(1);
            }
            $stockItem->save();
            $this->inventoryToRestock -= $qtyFromLocation;
        }
    }

}