*/ abstract class Mage_Sales_Model_Quote_Item_Abstract extends Mage_Core_Model_Abstract implements Mage_Catalog_Model_Product_Configuration_Item_Interface { /** * Parent item for sub items for bundle product, configurable product, etc. * * @var Mage_Sales_Model_Quote_Item_Abstract */ protected $_parentItem = null; /** * Children items in bundle product, configurable product, etc. * * @var array */ protected $_children = array(); /** * * @var array */ protected $_messages = array(); /** * Retrieve Quote instance * * @return Mage_Sales_Model_Quote */ abstract function getQuote(); /** * Retrieve product model object associated with item * * @return Mage_Catalog_Model_Product */ public function getProduct() { $product = $this->_getData('product'); if ($product === null && $this->getProductId()) { $product = Mage::getModel('catalog/product') ->setStoreId($this->getQuote()->getStoreId()) ->load($this->getProductId()); $this->setProduct($product); } /** * Reset product final price because it related to custom options */ $product->setFinalPrice(null); if (is_array($this->_optionsByCode)) { $product->setCustomOptions($this->_optionsByCode); } return $product; } /** * Returns special download params (if needed) for custom option with type = 'file' * Needed to implement Mage_Catalog_Model_Product_Configuration_Item_Interface. * Return null, as quote item needs no additional configuration. * * @return null|Varien_Object */ public function getFileDownloadParams() { return null; } /** * Specify parent item id before saving data * * @return Mage_Sales_Model_Quote_Item_Abstract */ protected function _beforeSave() { parent::_beforeSave(); if ($this->getParentItem()) { $this->setParentItemId($this->getParentItem()->getId()); } return $this; } /** * Set parent item * * @param Mage_Sales_Model_Quote_Item $parentItem * @return Mage_Sales_Model_Quote_Item */ public function setParentItem($parentItem) { if ($parentItem) { $this->_parentItem = $parentItem; $parentItem->addChild($this); } return $this; } /** * Get parent item * * @return Mage_Sales_Model_Quote_Item */ public function getParentItem() { return $this->_parentItem; } /** * Get chil items * * @return array */ public function getChildren() { return $this->_children; } /** * Add child item * * @param Mage_Sales_Model_Quote_Item_Abstract $child * @return Mage_Sales_Model_Quote_Item_Abstract */ public function addChild($child) { $this->setHasChildren(true); $this->_children[] = $child; return $this; } /** * Adds message(s) for quote item. Duplicated messages are not added. * * @param mixed $messages * @return Mage_Sales_Model_Quote_Item_Abstract */ public function setMessage($messages) { $messagesExists = $this->getMessage(false); if (!is_array($messages)) { $messages = array($messages); } foreach ($messages as $message) { if (!in_array($message, $messagesExists)) { $this->addMessage($message); } } return $this; } /** * Add message of quote item to array of messages * * @param string $message * @return Mage_Sales_Model_Quote_Item_Abstract */ public function addMessage($message) { $this->_messages[] = $message; return $this; } /** * Get messages array of quote item * * @param bool $string flag for converting messages to string * @return array|string */ public function getMessage($string = true) { if ($string) { return join("\n", $this->_messages); } return $this->_messages; } /** * Removes message by text * * @param string $text * @return Mage_Sales_Model_Quote_Item_Abstract */ public function removeMessageByText($text) { foreach ($this->_messages as $key => $message) { if ($message == $text) { unset($this->_messages[$key]); } } return $this; } /** * Clears all messages * * @return Mage_Sales_Model_Quote_Item_Abstract */ public function clearMessage() { $this->unsMessage(); // For older compatibility, when we kept message inside data array $this->_messages = array(); return $this; } /** * Retrieve store model object * * @return Mage_Core_Model_Store */ public function getStore() { return $this->getQuote()->getStore(); } /** * Checking item data * * @return Mage_Sales_Model_Quote_Item_Abstract */ public function checkData() { $this->setHasError(false); $this->clearMessage(); $qty = $this->_getData('qty'); try { $this->setQty($qty); } catch (Mage_Core_Exception $e) { $this->setHasError(true); $this->setMessage($e->getMessage()); } catch (Exception $e) { $this->setHasError(true); $this->setMessage(Mage::helper('sales')->__('Item qty declaration error.')); } try { $this->getProduct()->getTypeInstance(true)->checkProductBuyState($this->getProduct()); } catch (Mage_Core_Exception $e) { $this->setHasError(true) ->setMessage($e->getMessage()); $this->getQuote()->setHasError(true) ->addMessage(Mage::helper('sales')->__('Some of the products below do not have all the required options.')); } catch (Exception $e) { $this->setHasError(true) ->setMessage(Mage::helper('sales')->__('Item options declaration error.')); $this->getQuote()->setHasError(true) ->addMessage(Mage::helper('sales')->__('Items options declaration error.')); } if ($this->getProduct()->getHasError()) { $this->setHasError(true) ->setMessage(Mage::helper('sales')->__('Some of the selected options are not currently available.')); $this->getQuote()->setHasError(true) ->addMessage($this->getProduct()->getMessage(), 'options'); } if ($this->getHasConfigurationUnavailableError()) { $this->setHasError(true) ->setMessage(Mage::helper('sales')->__('Selected option(s) or their combination is not currently available.')); $this->getQuote()->setHasError(true) ->addMessage(Mage::helper('sales')->__('Some item options or their combination are not currently available.'), 'unavailable-configuration'); $this->unsHasConfigurationUnavailableError(); } return $this; } /** * Get original (not related with parent item) item quantity * * @return int|float */ public function getQty() { return $this->_getData('qty'); } /** * Get total item quantity (include parent item relation) * * @return int|float */ public function getTotalQty() { if ($this->getParentItem()) { return $this->getQty()*$this->getParentItem()->getQty(); } return $this->getQty(); } /** * Calculate item row total price * * @return Mage_Sales_Model_Quote_Item */ public function calcRowTotal() { $qty = $this->getTotalQty(); // Round unit price before multiplying to prevent losing 1 cent on subtotal $total = $this->getStore()->roundPrice($this->getCalculationPriceOriginal()) * $qty; $baseTotal = $this->getStore()->roundPrice($this->getBaseCalculationPriceOriginal()) * $qty; $this->setRowTotal($this->getStore()->roundPrice($total)); $this->setBaseRowTotal($this->getStore()->roundPrice($baseTotal)); return $this; } /** * Get item price used for quote calculation process. * This method get custom price (if it is defined) or original product final price * * @return float */ public function getCalculationPrice() { $price = $this->_getData('calculation_price'); if (is_null($price)) { if ($this->hasCustomPrice()) { $price = $this->getCustomPrice(); } else { $price = $this->getConvertedPrice(); } $this->setData('calculation_price', $price); } return $price; } /** * Get item price used for quote calculation process. * This method get original custom price applied before tax calculation * * @return float */ public function getCalculationPriceOriginal() { $price = $this->_getData('calculation_price'); if (is_null($price)) { if ($this->hasOriginalCustomPrice()) { $price = $this->getOriginalCustomPrice(); } else { $price = $this->getConvertedPrice(); } $this->setData('calculation_price', $price); } return $price; } /** * Get calculation price used for quote calculation in base currency. * * @return float */ public function getBaseCalculationPrice() { if (!$this->hasBaseCalculationPrice()) { if ($this->hasCustomPrice()) { $price = (float) $this->getCustomPrice(); if ($price) { $rate = $this->getStore()->convertPrice($price) / $price; $price = $price / $rate; } } else { $price = $this->getPrice(); } $this->setBaseCalculationPrice($price); } return $this->_getData('base_calculation_price'); } /** * Get original calculation price used for quote calculation in base currency. * * @return float */ public function getBaseCalculationPriceOriginal() { if (!$this->hasBaseCalculationPrice()) { if ($this->hasOriginalCustomPrice()) { $price = (float) $this->getOriginalCustomPrice(); if ($price) { $rate = $this->getStore()->convertPrice($price) / $price; $price = $price / $rate; } } else { $price = $this->getPrice(); } $this->setBaseCalculationPrice($price); } return $this->_getData('base_calculation_price'); } /** * Get whether the item is nominal * TODO: fix for multishipping checkout * * @return bool */ public function isNominal() { if (!$this->hasData('is_nominal')) { $this->setData('is_nominal', $this->getProduct() ? '1' == $this->getProduct()->getIsRecurring() : false); } return $this->_getData('is_nominal'); } /** * Data getter for 'is_nominal' * Used for converting item to order item * * @return int */ public function getIsNominal() { return (int)$this->isNominal(); } /** * Get original price (retrieved from product) for item. * Original price value is in quote selected currency * * @return float */ public function getOriginalPrice() { $price = $this->_getData('original_price'); if (is_null($price)) { $price = $this->getStore()->convertPrice($this->getBaseOriginalPrice()); $this->setData('original_price', $price); } return $price; } /** * Set original price to item (calculation price will be refreshed too) * * @param float $price * @return Mage_Sales_Model_Quote_Item_Abstract */ public function setOriginalPrice($price) { return $this->setData('original_price', $price); } /** * Get Original item price (got from product) in base website currency * * @return float */ public function getBaseOriginalPrice() { return $this->_getData('base_original_price'); } /** * Specify custom item price (used in case whe we have apply not product price to item) * * @param float $value * @return Mage_Sales_Model_Quote_Item_Abstract */ public function setCustomPrice($value) { $this->setCalculationPrice($value); $this->setBaseCalculationPrice(null); return $this->setData('custom_price', $value); } /** * Get item price. Item price currency is website base currency. * * @return decimal */ public function getPrice() { return $this->_getData('price'); } /** * Specify item price (base calculation price and converted price will be refreshed too) * * @param float $value * @return Mage_Sales_Model_Quote_Item_Abstract */ public function setPrice($value) { $this->setBaseCalculationPrice(null); $this->setConvertedPrice(null); return $this->setData('price', $value); } /** * Get item price converted to quote currency * @return float */ public function getConvertedPrice() { $price = $this->_getData('converted_price'); if (is_null($price)) { $price = $this->getStore()->convertPrice($this->getPrice()); $this->setData('converted_price', $price); } return $price; } /** * Set new value for converted price * @param float $value * @return Mage_Sales_Model_Quote_Item_Abstract */ public function setConvertedPrice($value) { $this->setCalculationPrice(null); $this->setData('converted_price', $value); return $this; } /** * Clone quote item * * @return Mage_Sales_Model_Quote_Item */ public function __clone() { $this->setId(null); $this->_parentItem = null; $this->_children = array(); $this->_messages = array(); return $this; } /** * Checking if there children calculated or parent item * when we have parent quote item and its children * * @return bool */ public function isChildrenCalculated() { if ($this->getParentItem()) { $calculate = $this->getParentItem()->getProduct()->getPriceType(); } else { $calculate = $this->getProduct()->getPriceType(); } if ((null !== $calculate) && (int)$calculate === Mage_Catalog_Model_Product_Type_Abstract::CALCULATE_CHILD) { return true; } return false; } /** * Checking can we ship product separatelly (each child separately) * or each parent product item can be shipped only like one item * * @return bool */ public function isShipSeparately() { if ($this->getParentItem()) { $shipmentType = $this->getParentItem()->getProduct()->getShipmentType(); } else { $shipmentType = $this->getProduct()->getShipmentType(); } if ((null !== $shipmentType) && (int)$shipmentType === Mage_Catalog_Model_Product_Type_Abstract::SHIPMENT_SEPARATELY) { return true; } return false; } /** * Calculate item tax amount * * @deprecated logic moved to tax totals calculation model * @return Mage_Sales_Model_Quote_Item */ public function calcTaxAmount() { $store = $this->getStore(); if (!Mage::helper('tax')->priceIncludesTax($store)) { if (Mage::helper('tax')->applyTaxAfterDiscount($store)) { $rowTotal = $this->getRowTotalWithDiscount(); $rowBaseTotal = $this->getBaseRowTotalWithDiscount(); } else { $rowTotal = $this->getRowTotal(); $rowBaseTotal = $this->getBaseRowTotal(); } $taxPercent = $this->getTaxPercent()/100; $this->setTaxAmount($store->roundPrice($rowTotal * $taxPercent)); $this->setBaseTaxAmount($store->roundPrice($rowBaseTotal * $taxPercent)); $rowTotal = $this->getRowTotal(); $rowBaseTotal = $this->getBaseRowTotal(); $this->setTaxBeforeDiscount($store->roundPrice($rowTotal * $taxPercent)); $this->setBaseTaxBeforeDiscount($store->roundPrice($rowBaseTotal * $taxPercent)); } else { if (Mage::helper('tax')->applyTaxAfterDiscount($store)) { $totalBaseTax = $this->getBaseTaxAmount(); $totalTax = $this->getTaxAmount(); if ($totalTax && $totalBaseTax) { $totalTax -= $this->getDiscountAmount() * ($this->getTaxPercent() / 100); $totalBaseTax -= $this->getBaseDiscountAmount() * ($this->getTaxPercent() / 100); $this->setBaseTaxAmount($store->roundPrice($totalBaseTax)); $this->setTaxAmount($store->roundPrice($totalTax)); } } } if (Mage::helper('tax')->discountTax($store) && !Mage::helper('tax')->applyTaxAfterDiscount($store)) { if ($this->getDiscountPercent()) { $baseTaxAmount = $this->getBaseTaxBeforeDiscount(); $taxAmount = $this->getTaxBeforeDiscount(); $baseDiscountDisposition = $baseTaxAmount/100*$this->getDiscountPercent(); $discountDisposition = $taxAmount/100*$this->getDiscountPercent(); $this->setDiscountAmount($this->getDiscountAmount()+$discountDisposition); $this->setBaseDiscountAmount($this->getBaseDiscountAmount()+$baseDiscountDisposition); } } return $this; } /** * Get item tax amount * * @deprecated * @return decimal */ public function getTaxAmount() { return $this->_getData('tax_amount'); } /** * Get item base tax amount * * @deprecated * @return decimal */ public function getBaseTaxAmount() { return $this->_getData('base_tax_amount'); } /** * Get item price (item price always exclude price) * * @deprecated * @return decimal */ protected function _calculatePrice($value, $saveTaxes = true) { $store = $this->getQuote()->getStore(); if (Mage::helper('tax')->priceIncludesTax($store)) { $bAddress = $this->getQuote()->getBillingAddress(); $sAddress = $this->getQuote()->getShippingAddress(); $address = $this->getAddress(); if ($address) { switch ($address->getAddressType()) { case Mage_Sales_Model_Quote_Address::TYPE_BILLING: $bAddress = $address; break; case Mage_Sales_Model_Quote_Address::TYPE_SHIPPING: $sAddress = $address; break; } } if ($this->getProduct()->getIsVirtual()) { $sAddress = $bAddress; } $priceExcludingTax = Mage::helper('tax')->getPrice( $this->getProduct()->setTaxPercent(null), $value, false, $sAddress, $bAddress, $this->getQuote()->getCustomerTaxClassId(), $store ); $priceIncludingTax = Mage::helper('tax')->getPrice( $this->getProduct()->setTaxPercent(null), $value, true, $sAddress, $bAddress, $this->getQuote()->getCustomerTaxClassId(), $store ); if ($saveTaxes) { $qty = $this->getQty(); if ($this->getParentItem()) { $qty = $qty*$this->getParentItem()->getQty(); } if (Mage::helper('tax')->displayCartPriceInclTax($store)) { $rowTotal = $value*$qty; $rowTotalExcTax = Mage::helper('tax')->getPrice( $this->getProduct()->setTaxPercent(null), $rowTotal, false, $sAddress, $bAddress, $this->getQuote()->getCustomerTaxClassId(), $store ); $rowTotalIncTax = Mage::helper('tax')->getPrice( $this->getProduct()->setTaxPercent(null), $rowTotal, true, $sAddress, $bAddress, $this->getQuote()->getCustomerTaxClassId(), $store ); $totalBaseTax = $rowTotalIncTax-$rowTotalExcTax; $this->setRowTotalExcTax($rowTotalExcTax); } else { $taxAmount = $priceIncludingTax - $priceExcludingTax; $this->setTaxPercent($this->getProduct()->getTaxPercent()); $totalBaseTax = $taxAmount*$qty; } $totalTax = $this->getStore()->convertPrice($totalBaseTax); $this->setTaxBeforeDiscount($totalTax); $this->setBaseTaxBeforeDiscount($totalBaseTax); $this->setTaxAmount($totalTax); $this->setBaseTaxAmount($totalBaseTax); } $value = $priceExcludingTax; } return $value; } }