*/ class Mage_Sales_Model_Order_Item extends Mage_Core_Model_Abstract { const STATUS_PENDING = 1; // No items shipped, invoiced, canceled, refunded nor backordered const STATUS_SHIPPED = 2; // When qty ordered - [qty canceled + qty returned] = qty shipped const STATUS_INVOICED = 9; // When qty ordered - [qty canceled + qty returned] = qty invoiced const STATUS_BACKORDERED = 3; // When qty ordered - [qty canceled + qty returned] = qty backordered const STATUS_CANCELED = 5; // When qty ordered = qty canceled const STATUS_PARTIAL = 6; // If [qty shipped or(max of two) qty invoiced + qty canceled + qty returned] // < qty ordered const STATUS_MIXED = 7; // All other combinations const STATUS_REFUNDED = 8; // When qty ordered = qty refunded const STATUS_RETURNED = 4; // When qty ordered = qty returned // not used at the moment protected $_eventPrefix = 'sales_order_item'; protected $_eventObject = 'item'; protected static $_statuses = null; /** * Order instance * * @var Mage_Sales_Model_Order */ protected $_order = null; protected $_parentItem = null; protected $_children = array(); /** * Init resource model */ protected function _construct() { $this->_init('sales/order_item'); } /** * Init mapping array of short fields to * its full names * * @return Varien_Object */ protected function _initOldFieldsMap() { $this->_oldFieldsMap = Mage::helper('sales')->getOldFieldMap('order_item'); return $this; } /** * Prepare data before save * * @return Mage_Sales_Model_Order_Item */ protected function _beforeSave() { parent::_beforeSave(); if (!$this->getOrderId() && $this->getOrder()) { $this->setOrderId($this->getOrder()->getId()); } if ($this->getParentItem()) { $this->setParentItemId($this->getParentItem()->getId()); } return $this; } /** * Set parent item * * @param Mage_Sales_Model_Order_Item $item * @return Mage_Sales_Model_Order_Item */ public function setParentItem($item) { if ($item) { $this->_parentItem = $item; $item->setHasChildren(true); $item->addChildItem($this); } return $this; } /** * Get parent item * * @return Mage_Sales_Model_Order_Item || null */ public function getParentItem() { return $this->_parentItem; } /** * Check item invoice availability * * @return bool */ public function canInvoice() { return $this->getQtyToInvoice()>0; } /** * Check item ship availability * * @return bool */ public function canShip() { return $this->getQtyToShip()>0; } /** * Check item refund availability * * @return bool */ public function canRefund() { return $this->getQtyToRefund()>0; } /** * Retrieve item qty available for ship * * @return float|integer */ public function getQtyToShip() { if ($this->isDummy(true)) { return 0; } return $this->getSimpleQtyToShip(); } /** * Retrieve item qty available for ship * * @return float|integer */ public function getSimpleQtyToShip() { $qty = $this->getQtyOrdered() - $this->getQtyShipped() - $this->getQtyRefunded() - $this->getQtyCanceled(); return max($qty, 0); } /** * Retrieve item qty available for invoice * * @return float|integer */ public function getQtyToInvoice() { if ($this->isDummy()) { return 0; } $qty = $this->getQtyOrdered() - $this->getQtyInvoiced() - $this->getQtyCanceled(); return max($qty, 0); } /** * Retrieve item qty available for refund * * @return float|integer */ public function getQtyToRefund() { if ($this->isDummy()) { return 0; } return max($this->getQtyInvoiced()-$this->getQtyRefunded(), 0); } /** * Retrieve item qty available for cancel * * @return float|integer */ public function getQtyToCancel() { if ($this->getProductType() == Mage_Catalog_Model_Product_Type::TYPE_BUNDLE) { $qtyToCancel = $this->getQtyToCancelBundle(); } elseif ($this->getParentItem() && $this->getParentItem()->getProductType() == Mage_Catalog_Model_Product_Type::TYPE_BUNDLE ) { $qtyToCancel = $this->getQtyToCancelBundleItem(); } else { $qtyToCancel = min($this->getQtyToInvoice(), $this->getQtyToShip()); } return max($qtyToCancel, 0); } /** * Retrieve Bundle item qty available for cancel * getQtyToInvoice() will always deliver 0 for Bundle * * @return float|integer */ public function getQtyToCancelBundle() { if ($this->isDummy()) { $qty = $this->getQtyOrdered() - $this->getQtyInvoiced() - $this->getQtyCanceled(); return min(max($qty, 0), $this->getQtyToShip()); } return min($this->getQtyToInvoice(), $this->getQtyToShip()); } /** * Retrieve Bundle child item qty available for cancel * getQtyToShip() always returns 0 for BundleItems that ship together * * @return float|integer */ public function getQtyToCancelBundleItem() { if ($this->isDummy(true)) { return min($this->getQtyToInvoice(), $this->getSimpleQtyToShip()); } return min($this->getQtyToInvoice(), $this->getQtyToShip()); } /** * Declare order * * @param Mage_Sales_Model_Order $order * @return Mage_Sales_Model_Order_Item */ public function setOrder(Mage_Sales_Model_Order $order) { $this->_order = $order; $this->setOrderId($order->getId()); return $this; } /** * Retrieve order model object * * @return Mage_Sales_Model_Order */ public function getOrder() { if (is_null($this->_order) && ($orderId = $this->getOrderId())) { $order = Mage::getModel('sales/order'); $order->load($orderId); $this->setOrder($order); } return $this->_order; } /** * Retrieve item status identifier * * @return int */ public function getStatusId() { $backordered = (float)$this->getQtyBackordered(); if (!$backordered && $this->getHasChildren()) { $backordered = (float)$this->_getQtyChildrenBackordered(); } $canceled = (float)$this->getQtyCanceled(); $invoiced = (float)$this->getQtyInvoiced(); $ordered = (float)$this->getQtyOrdered(); $refunded = (float)$this->getQtyRefunded(); $shipped = (float)$this->getQtyShipped(); $actuallyOrdered = $ordered - $canceled - $refunded; if (!$invoiced && !$shipped && !$refunded && !$canceled && !$backordered) { return self::STATUS_PENDING; } if ($shipped && $invoiced && ($actuallyOrdered == $shipped)) { return self::STATUS_SHIPPED; } if ($invoiced && !$shipped && ($actuallyOrdered == $invoiced)) { return self::STATUS_INVOICED; } if ($backordered && ($actuallyOrdered == $backordered) ) { return self::STATUS_BACKORDERED; } if ($refunded && $ordered == $refunded) { return self::STATUS_REFUNDED; } if ($canceled && $ordered == $canceled) { return self::STATUS_CANCELED; } if (max($shipped, $invoiced) < $actuallyOrdered) { return self::STATUS_PARTIAL; } return self::STATUS_MIXED; } /** * Retrieve backordered qty of children items * * @return float|null */ protected function _getQtyChildrenBackordered() { $backordered = null; foreach ($this->_children as $childItem) { $backordered += (float)$childItem->getQtyBackordered(); } return $backordered; } /** * Retrieve status * * @return string */ public function getStatus() { return $this->getStatusName($this->getStatusId()); } /** * Retrieve status name * * @return string */ public static function getStatusName($statusId) { if (is_null(self::$_statuses)) { self::getStatuses(); } if (isset(self::$_statuses[$statusId])) { return self::$_statuses[$statusId]; } return Mage::helper('sales')->__('Unknown Status'); } /** * Cancel order item * * @return Mage_Sales_Model_Order_Item */ public function cancel() { if ($this->getStatusId() !== self::STATUS_CANCELED) { Mage::dispatchEvent('sales_order_item_cancel', array('item'=>$this)); $this->setQtyCanceled($this->getQtyToCancel()); $this->setTaxCanceled( $this->getTaxCanceled() + $this->getBaseTaxAmount() * $this->getQtyCanceled() / $this->getQtyOrdered() ); $this->setHiddenTaxCanceled( $this->getHiddenTaxCanceled() + $this->getHiddenTaxAmount() * $this->getQtyCanceled() / $this->getQtyOrdered() ); } return $this; } /** * Retrieve order item statuses array * * @return array */ public static function getStatuses() { if (is_null(self::$_statuses)) { self::$_statuses = array( //self::STATUS_PENDING => Mage::helper('sales')->__('Pending'), self::STATUS_PENDING => Mage::helper('sales')->__('Ordered'), self::STATUS_SHIPPED => Mage::helper('sales')->__('Shipped'), self::STATUS_INVOICED => Mage::helper('sales')->__('Invoiced'), self::STATUS_BACKORDERED => Mage::helper('sales')->__('Backordered'), self::STATUS_RETURNED => Mage::helper('sales')->__('Returned'), self::STATUS_REFUNDED => Mage::helper('sales')->__('Refunded'), self::STATUS_CANCELED => Mage::helper('sales')->__('Canceled'), self::STATUS_PARTIAL => Mage::helper('sales')->__('Partial'), self::STATUS_MIXED => Mage::helper('sales')->__('Mixed'), ); } return self::$_statuses; } /** * Redeclare getter for back compatibility * * @return float */ public function getOriginalPrice() { $price = $this->getData('original_price'); if (is_null($price)) { return $this->getPrice(); } return $price; } /** * Set product options * * @param array $options * @return Mage_Sales_Model_Order_Item */ public function setProductOptions(array $options) { $this->setData('product_options', serialize($options)); return $this; } /** * Get product options array * * @return array */ public function getProductOptions() { if ($options = $this->_getData('product_options')) { return unserialize($options); } return array(); } /** * Get product options array by code. * If code is null return all options * * @param string $code * @return array */ public function getProductOptionByCode($code=null) { $options = $this->getProductOptions(); if (is_null($code)) { return $options; } if (isset($options[$code])) { return $options[$code]; } return null; } /** * Return real product type of item or NULL if item is not composite * * @return string | null */ public function getRealProductType() { if ($productType = $this->getProductOptionByCode('real_product_type')) { return $productType; } return null; } /** * Adds child item to this item * * @param Mage_Sales_Model_Order_Item $item */ public function addChildItem($item) { if ($item instanceof Mage_Sales_Model_Order_Item) { $this->_children[] = $item; } else if (is_array($item)) { $this->_children = array_merge($this->_children, $item); } } /** * Return chilgren items of this item * * @return array */ public function getChildrenItems() { return $this->_children; } /** * Return checking of what calculation * type was for this product * * @return bool */ public function isChildrenCalculated() { if ($parentItem = $this->getParentItem()) { $options = $parentItem->getProductOptions(); } else { $options = $this->getProductOptions(); } if (isset($options['product_calculations']) && $options['product_calculations'] == Mage_Catalog_Model_Product_Type_Abstract::CALCULATE_CHILD) { return true; } return false; } /** * Check if discount has to be applied to parent item * * @return bool */ public function getForceApplyDiscountToParentItem() { if ($this->getParentItem()) { $product = $this->getParentItem()->getProduct(); } else { $product = $this->getProduct(); } return $product->getTypeInstance()->getForceApplyDiscountToParentItem(); } /** * Return checking of what shipment * type was for this product * * @return bool */ public function isShipSeparately() { if ($parentItem = $this->getParentItem()) { $options = $parentItem->getProductOptions(); } else { $options = $this->getProductOptions(); } if (isset($options['shipment_type']) && $options['shipment_type'] == Mage_Catalog_Model_Product_Type_Abstract::SHIPMENT_SEPARATELY) { return true; } return false; } /** * This is Dummy item or not * if $shipment is true then we checking this for shipping situation if not * then we checking this for calculation * * @param bool $shipment * @return bool */ public function isDummy($shipment = false){ if ($shipment) { if ($this->getHasChildren() && $this->isShipSeparately()) { return true; } if ($this->getHasChildren() && !$this->isShipSeparately()) { return false; } if ($this->getParentItem() && $this->isShipSeparately()) { return false; } if ($this->getParentItem() && !$this->isShipSeparately()) { return true; } } else { if ($this->getHasChildren() && $this->isChildrenCalculated()) { return true; } if ($this->getHasChildren() && !$this->isChildrenCalculated()) { return false; } if ($this->getParentItem() && $this->isChildrenCalculated()) { return false; } if ($this->getParentItem() && !$this->isChildrenCalculated()) { return true; } } return false; } /** * Returns formatted buy request - object, holding request received from * product view page with keys and options for configured product * * @return Varien_Object */ public function getBuyRequest() { $option = $this->getProductOptionByCode('info_buyRequest'); if (!$option) { $option = array(); } $buyRequest = new Varien_Object($option); $buyRequest->setQty($this->getQtyOrdered() * 1); return $buyRequest; } /** * Retrieve product * * @return Mage_Catalog_Model_Product */ public function getProduct() { if (!$this->getData('product')) { $product = Mage::getModel('catalog/product')->load($this->getProductId()); $this->setProduct($product); } return $this->getData('product'); } /** * Get the discount amount applied on weee in base * * @return float */ public function getBaseDiscountAppliedForWeeeTax() { $weeeTaxAppliedAmounts = unserialize($this->getWeeeTaxApplied()); $totalDiscount = 0; if (!is_array($weeeTaxAppliedAmounts)) { return $totalDiscount; } foreach ($weeeTaxAppliedAmounts as $weeeTaxAppliedAmount) { if (isset($weeeTaxAppliedAmount['total_base_weee_discount'])) { return $weeeTaxAppliedAmount['total_base_weee_discount']; } else { $totalDiscount += isset($weeeTaxAppliedAmount['base_weee_discount']) ? $weeeTaxAppliedAmount['base_weee_discount'] : 0; } } return $totalDiscount; } /** * Get the discount amount applied on Weee * * @return float */ public function getDiscountAppliedForWeeeTax() { $weeeTaxAppliedAmounts = unserialize($this->getWeeeTaxApplied()); $totalDiscount = 0; if (!is_array($weeeTaxAppliedAmounts)) { return $totalDiscount; } foreach ($weeeTaxAppliedAmounts as $weeeTaxAppliedAmount) { if (isset($weeeTaxAppliedAmount['total_weee_discount'])) { return $weeeTaxAppliedAmount['total_weee_discount']; } else { $totalDiscount += isset($weeeTaxAppliedAmount['weee_discount']) ? $weeeTaxAppliedAmount['weee_discount'] : 0; } } return $totalDiscount; } }