Skip to content

Instantly share code, notes, and snippets.

@gistya
Created April 23, 2014 01:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gistya/11199766 to your computer and use it in GitHub Desktop.
Save gistya/11199766 to your computer and use it in GitHub Desktop.
BundlePricesPatchFileChanges.diff: add local overrides for files to fix the bulde price method. still haven't made any changes *to* these files yet though
From 72bbed942cd0d3ab8e70233f6b986ee4c517fb9a Mon Sep 17 00:00:00 2001
From: Gistya Eusebio <gistya@gmail.com>
Date: Tue, 22 Apr 2014 18:07:32 -0700
Subject: [PATCH] add local overrides for files to fix the bulde price method.
still haven't made any changes *to* these files yet though.
---
.../Product/Edit/Tab/Attributes/Special.php | 43 +
app/code/local/Mage/Bundle/Model/Product/Price.php | 975 +++++++++++++++++++++
.../Mage/Bundle/Model/Resource/Indexer/Price.php | 698 +++++++++++++++
.../Mage/Bundle/Model/Resource/Price/Index.php | 856 ++++++++++++++++++
4 files changed, 2572 insertions(+)
create mode 100644 app/code/local/Mage/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Attributes/Special.php
create mode 100644 app/code/local/Mage/Bundle/Model/Product/Price.php
create mode 100644 app/code/local/Mage/Bundle/Model/Resource/Indexer/Price.php
create mode 100644 app/code/local/Mage/Bundle/Model/Resource/Price/Index.php
diff --git a/app/code/local/Mage/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Attributes/Special.php b/app/code/local/Mage/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Attributes/Special.php
new file mode 100644
index 0000000..3d6ff4f
--- /dev/null
+++ b/app/code/local/Mage/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Attributes/Special.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * Magento
+ *
+ * NOTICE OF LICENSE
+ *
+ * This source file is subject to the Open Software License (OSL 3.0)
+ * that is bundled with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://opensource.org/licenses/osl-3.0.php
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@magentocommerce.com so we can send you a copy immediately.
+ *
+ * DISCLAIMER
+ *
+ * Do not edit or add to this file if you wish to upgrade Magento to newer
+ * versions in the future. If you wish to customize Magento for your
+ * needs please refer to http://www.magentocommerce.com for more information.
+ *
+ * @category Mage
+ * @package Mage_Bundle
+ * @copyright Copyright (c) 2013 Magento Inc. (http://www.magentocommerce.com)
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
+ */
+
+/**
+ * Bundle Special Price Attribute Block
+ *
+ * @category Mage
+ * @package Mage_Bundle
+ * @author Magento Core Team <core@magentocommerce.com>
+ */
+class Mage_Bundle_Block_Adminhtml_Catalog_Product_Edit_Tab_Attributes_Special extends Mage_Adminhtml_Block_Catalog_Form_Renderer_Fieldset_Element
+{
+ public function getElementHtml()
+ {
+ $html = '<input id="'.$this->getElement()->getHtmlId().'" name="'.$this->getElement()->getName()
+ .'" value="'.$this->getElement()->getEscapedValue().'" '.$this->getElement()->serialize($this->getElement()->getHtmlAttributes()).'/>'."\n"
+ .'<strong>[%]</strong>';;
+ return $html;
+ }
+}
diff --git a/app/code/local/Mage/Bundle/Model/Product/Price.php b/app/code/local/Mage/Bundle/Model/Product/Price.php
new file mode 100644
index 0000000..b69b184
--- /dev/null
+++ b/app/code/local/Mage/Bundle/Model/Product/Price.php
@@ -0,0 +1,975 @@
+<?php
+/**
+ * Magento
+ *
+ * NOTICE OF LICENSE
+ *
+ * This source file is subject to the Open Software License (OSL 3.0)
+ * that is bundled with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://opensource.org/licenses/osl-3.0.php
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@magentocommerce.com so we can send you a copy immediately.
+ *
+ * DISCLAIMER
+ *
+ * Do not edit or add to this file if you wish to upgrade Magento to newer
+ * versions in the future. If you wish to customize Magento for your
+ * needs please refer to http://www.magentocommerce.com for more information.
+ *
+ * @category Mage
+ * @package Mage_Bundle
+ * @copyright Copyright (c) 2013 Magento Inc. (http://www.magentocommerce.com)
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
+ */
+
+/**
+ * Bundle Price Model
+ *
+ * @category Mage
+ * @package Mage_Bundle
+ * @author Magento Core Team <core@magentocommerce.com>
+ */
+class Mage_Bundle_Model_Product_Price extends Mage_Catalog_Model_Product_Type_Price
+{
+ /**
+ * Fixed price type
+ */
+ const PRICE_TYPE_FIXED = 1;
+
+ /**
+ * Dynamic price type
+ */
+ const PRICE_TYPE_DYNAMIC = 0;
+
+ /**
+ * Flag which indicates - is min/max prices have been calculated by index
+ *
+ * @var bool
+ */
+ protected $_isPricesCalculatedByIndex;
+
+ /**
+ * Is min/max prices have been calculated by index
+ *
+ * @return bool
+ */
+ public function getIsPricesCalculatedByIndex()
+ {
+ return $this->_isPricesCalculatedByIndex;
+ }
+
+ /**
+ * Return product base price
+ *
+ * @param Mage_Catalog_Model_Product $product
+ * @return string
+ */
+ public function getPrice($product)
+ {
+ if ($product->getPriceType() == self::PRICE_TYPE_FIXED) {
+ return $product->getData('price');
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Get Total price for Bundle items
+ *
+ * @param Mage_Catalog_Model_Product $product
+ * @param null|float $qty
+ * @return float
+ */
+ public function getTotalBundleItemsPrice($product, $qty = null)
+ {
+ $price = 0.0;
+ if ($product->hasCustomOptions()) {
+ $customOption = $product->getCustomOption('bundle_selection_ids');
+ if ($customOption) {
+ $selectionIds = unserialize($customOption->getValue());
+ $selections = $product->getTypeInstance(true)->getSelectionsByIds($selectionIds, $product);
+ $selections->addTierPriceData();
+ Mage::dispatchEvent('prepare_catalog_product_collection_prices', array(
+ 'collection' => $selections,
+ 'store_id' => $product->getStoreId(),
+ ));
+ foreach ($selections->getItems() as $selection) {
+ if ($selection->isSalable()) {
+ $selectionQty = $product->getCustomOption('selection_qty_' . $selection->getSelectionId());
+ if ($selectionQty) {
+ $price += $this->getSelectionFinalTotalPrice($product, $selection, $qty,
+ $selectionQty->getValue());
+ }
+ }
+ }
+ }
+ }
+ return $price;
+ }
+
+ /**
+ * Get product final price
+ *
+ * @param double $qty
+ * @param Mage_Catalog_Model_Product $product
+ * @return double
+ */
+ public function getFinalPrice($qty = null, $product)
+ {
+ if (is_null($qty) && !is_null($product->getCalculatedFinalPrice())) {
+ return $product->getCalculatedFinalPrice();
+ }
+
+ $finalPrice = $this->getBasePrice($product, $qty);
+ $product->setFinalPrice($finalPrice);
+ Mage::dispatchEvent('catalog_product_get_final_price', array('product' => $product, 'qty' => $qty));
+ $finalPrice = $product->getData('final_price');
+
+ $finalPrice = $this->_applyOptionsPrice($product, $qty, $finalPrice);
+ $finalPrice += $this->getTotalBundleItemsPrice($product, $qty);
+
+ $product->setFinalPrice($finalPrice);
+ return max(0, $product->getData('final_price'));
+ }
+
+ /**
+ * Returns final price of a child product
+ *
+ * @param Mage_Catalog_Model_Product $product
+ * @param float $productQty
+ * @param Mage_Catalog_Model_Product $childProduct
+ * @param float $childProductQty
+ * @return decimal
+ */
+ public function getChildFinalPrice($product, $productQty, $childProduct, $childProductQty)
+ {
+ return $this->getSelectionFinalTotalPrice($product, $childProduct, $productQty, $childProductQty, false);
+ }
+
+ /**
+ * Retrieve Price
+ *
+ * @deprecated after 1.5.1.0
+ * @see Mage_Bundle_Model_Product_Price::getTotalPrices()
+ *
+ * @param Mage_Catalog_Model_Product $product
+ * @param string $which
+ * @return decimal|array
+ */
+ public function getPrices($product, $which = null)
+ {
+ return $this->getTotalPrices($product, $which);
+ }
+
+ /**
+ * Retrieve Prices depending on tax
+ *
+ * @deprecated after 1.5.1.0
+ * @see Mage_Bundle_Model_Product_Price::getTotalPrices()
+ *
+ * @param Mage_Catalog_Model_Product $product
+ * @param string $which
+ * @param bool|null $includeTax
+ * @return decimal|array
+ */
+ public function getPricesDependingOnTax($product, $which = null, $includeTax = null)
+ {
+ return $this->getTotalPrices($product, $which, $includeTax);
+ }
+
+ /**
+ * Retrieve Price considering tier price
+ *
+ * @param Mage_Catalog_Model_Product $product
+ * @param string|null $which
+ * @param bool|null $includeTax
+ * @param bool $takeTierPrice
+ * @return decimal|array
+ */
+ public function getTotalPrices($product, $which = null, $includeTax = null, $takeTierPrice = true)
+ {
+ $isPriceFixedType = ($product->getPriceType() == self::PRICE_TYPE_FIXED);
+ $this->_isPricesCalculatedByIndex = ($product->getData('min_price') && $product->getData('max_price'));
+ $taxHelper = $this->_getHelperData('tax');
+
+ if ($this->_isPricesCalculatedByIndex && !$includeTax) {
+ $minimalPrice = $taxHelper->getPrice($product, $product->getData('min_price'), $includeTax,
+ null, null, null, null, null, false);
+ $maximalPrice = $taxHelper->getPrice($product, $product->getData('max_price'), $includeTax,
+ null, null, null, null, null, false);
+ } else {
+ /**
+ * Check if product price is fixed
+ */
+ $finalPrice = $product->getFinalPrice();
+ if ($isPriceFixedType) {
+ $minimalPrice = $maximalPrice = $taxHelper->getPrice($product, $finalPrice, $includeTax,
+ null, null, null, null, null, false);
+ } else { // PRICE_TYPE_DYNAMIC
+ $minimalPrice = $maximalPrice = 0;
+ }
+
+ $minimalPrice += $this->_getMinimalBundleOptionsPrice($product, $includeTax, $takeTierPrice);
+ $maximalPrice += $this->_getMaximalBundleOptionsPrice($product, $includeTax, $takeTierPrice);
+
+ $customOptions = $product->getOptions();
+ if ($isPriceFixedType && $customOptions) {
+ foreach ($customOptions as $customOption) {
+ /* @var $customOption Mage_Catalog_Model_Product_Option */
+ $minimalPrice += $taxHelper->getPrice(
+ $product,
+ $this->_getMinimalCustomOptionPrice($customOption),
+ $includeTax);
+ $maximalPrice += $taxHelper->getPrice(
+ $product,
+ $this->_getMaximalCustomOptionPrice($customOption),
+ $includeTax);
+ }
+ }
+ }
+
+ $minimalPrice = $product->getStore()->roundPrice($minimalPrice);
+ $maximalPrice = $product->getStore()->roundPrice($maximalPrice);
+
+ if ('max' == $which) {
+ return $maximalPrice;
+ } elseif ('min' == $which) {
+ return $minimalPrice;
+ }
+
+ return array($minimalPrice, $maximalPrice);
+ }
+
+ /**
+ * Get minimal possible price for bundle option
+ *
+ * @param Mage_Catalog_Model_Product $product
+ * @param bool|null $includeTax
+ * @param bool $takeTierPrice
+ * @return int|mixed
+ */
+ protected function _getMinimalBundleOptionsPrice($product, $includeTax, $takeTierPrice)
+ {
+ $options = $this->getOptions($product);
+ $minimalPrice = 0;
+ $minimalPriceWithTax = 0;
+ $hasRequiredOptions = $this->_hasRequiredOptions($product);
+ $selectionMinimalPrices = array();
+ $selectionMinimalPricesWithTax = array();
+
+ if (!$options) {
+ return $minimalPrice;
+ }
+
+ foreach ($options as $option) {
+ /* @var $option Mage_Bundle_Model_Option */
+ $selectionPrices = $this->_getSelectionPrices($product, $option, $takeTierPrice, $includeTax);
+ $selectionPricesWithTax = $this->_getSelectionPrices($product, $option, $takeTierPrice, true);
+
+ if (count($selectionPrices)) {
+ $selectionMinPrice = is_array($selectionPrices) ? min($selectionPrices) : $selectionPrices;
+ $selectMinPriceWithTax = is_array($selectionPricesWithTax) ?
+ min($selectionPricesWithTax) : $selectionPricesWithTax;
+ if ($option->getRequired()) {
+ $minimalPrice += $selectionMinPrice;
+ $minimalPriceWithTax += $selectMinPriceWithTax;
+ } elseif (!$hasRequiredOptions) {
+ $selectionMinimalPrices[] = $selectionMinPrice;
+ $selectionMinimalPricesWithTax[] = $selectMinPriceWithTax;
+ }
+ }
+ }
+ // condition is TRUE when all product options are NOT required
+ if (!$hasRequiredOptions) {
+ $minimalPrice = min($selectionMinimalPrices);
+ $minimalPriceWithTax = min($selectionMinimalPricesWithTax);
+ }
+
+ $taxConfig = $this->_getHelperData('tax')->getConfig();
+
+ //In the case of total base calculation we round the tax first and
+ //deduct the tax from the price including tax
+ if ($taxConfig->priceIncludesTax($product->getStore())
+ && Mage_Tax_Model_Calculation::CALC_TOTAL_BASE ==
+ $taxConfig->getAlgorithm($product->getStore())
+ && ($minimalPriceWithTax > $minimalPrice)
+ ) {
+ //We convert the value to string to maintain the precision
+ $tax = (String)($minimalPriceWithTax - $minimalPrice);
+ $roundedTax = $this->_getApp()->getStore()->roundPrice($tax);
+ $minimalPrice = $minimalPriceWithTax - $roundedTax;
+ }
+ return $minimalPrice;
+ }
+
+ /**
+ * Get maximal possible price for bundle option
+ *
+ * @param Mage_Catalog_Model_Product $product
+ * @param bool|null $includeTax
+ * @param bool $takeTierPrice
+ * @return float
+ */
+ protected function _getMaximalBundleOptionsPrice($product, $includeTax, $takeTierPrice)
+ {
+ $maximalPrice = 0;
+ $options = $this->getOptions($product);
+
+ if (!$options) {
+ return $maximalPrice;
+ }
+
+ foreach ($options as $option) {
+ $selectionPrices = $this->_getSelectionPrices($product, $option, $takeTierPrice, $includeTax);
+ if (count($selectionPrices)) {
+ $maximalPrice += ($option->isMultiSelection())
+ ? array_sum($selectionPrices)
+ : max($selectionPrices);
+ }
+ }
+ return $maximalPrice;
+ }
+
+ /**
+ * Get all prices for bundle option selection
+ *
+ * @param Mage_Catalog_Model_Product $product
+ * @param Mage_Bundle_Model_Option $option
+ * @param bool $takeTierPrice
+ * @param bool|null $includeTax
+ * @return array
+ */
+ protected function _getSelectionPrices($product, $option, $takeTierPrice, $includeTax)
+ {
+ $selectionPrices = array();
+ $taxHelper = $this->_getHelperData('tax');
+ $taxCalcMethod = $taxHelper->getConfig()->getAlgorithm($product->getStore());
+ $isPriceFixedType = ($product->getPriceType() == self::PRICE_TYPE_FIXED);
+
+ $selections = $option->getSelections();
+ if (!$selections) {
+ return $selectionPrices;
+ }
+
+ foreach ($selections as $selection) {
+ /* @var $selection Mage_Bundle_Model_Selection */
+ if (!$selection->isSalable()) {
+ /**
+ * @todo CatalogInventory Show out of stock Products
+ */
+ continue;
+ }
+
+ $item = $isPriceFixedType ? $product : $selection;
+
+ $selectionUnitPrice = $this->getSelectionFinalTotalPrice(
+ $product, $selection, 1, null, false, $takeTierPrice);
+ $selectionQty = $selection->getSelectionQty();
+ if ($isPriceFixedType || $taxCalcMethod == Mage_Tax_Model_Calculation::CALC_TOTAL_BASE) {
+ $selectionPrice = $selectionQty * $taxHelper->getPrice($item, $selectionUnitPrice, $includeTax,
+ null, null, null, null, null, false);
+ $selectionPrices[] = $selectionPrice;
+ } else if ($taxCalcMethod == Mage_Tax_Model_Calculation::CALC_ROW_BASE) {
+ $selectionPrice = $taxHelper->getPrice($item, $selectionUnitPrice * $selectionQty, $includeTax);
+ $selectionPrices[] = $selectionPrice;
+ } else { //dynamic price and Mage_Tax_Model_Calculation::CALC_UNIT_BASE
+ $selectionPrice = $taxHelper->getPrice($item, $selectionUnitPrice, $includeTax) * $selectionQty;
+ $selectionPrices[] = $selectionPrice;
+ }
+ }
+ return $selectionPrices;
+ }
+
+ /**
+ * Calculate Minimal price of bundle (counting all required options)
+ *
+ * @param Mage_Catalog_Model_Product $product
+ * @return decimal
+ */
+ public function getMinimalPrice($product)
+ {
+ return $this->getPricesTierPrice($product, 'min');
+ }
+
+ /**
+ * Calculate maximal price of bundle
+ *
+ * @param Mage_Catalog_Model_Product $product
+ * @return decimal
+ */
+ public function getMaximalPrice($product)
+ {
+ return $this->getPricesTierPrice($product, 'max');
+ }
+
+ /**
+ * Get Options with attached Selections collection
+ *
+ * @param Mage_Catalog_Model_Product $product
+ * @return Mage_Bundle_Model_Mysql4_Option_Collection
+ */
+ public function getOptions($product)
+ {
+ $product->getTypeInstance(true)
+ ->setStoreFilter($product->getStoreId(), $product);
+
+ $optionCollection = $product->getTypeInstance(true)
+ ->getOptionsCollection($product);
+
+ $selectionCollection = $product->getTypeInstance(true)
+ ->getSelectionsCollection(
+ $product->getTypeInstance(true)->getOptionsIds($product),
+ $product
+ );
+
+ return $optionCollection->appendSelections($selectionCollection, false, false);
+ }
+
+ /**
+ * Calculate price of selection
+ *
+ * @deprecated after 1.6.2.0
+ * @see Mage_Bundle_Model_Product_Price::getSelectionFinalTotalPrice()
+ *
+ * @param Mage_Catalog_Model_Product $bundleProduct
+ * @param Mage_Catalog_Model_Product $selectionProduct
+ * @param float|null $selectionQty
+ * @param null|bool $multiplyQty Whether to multiply selection's price by its quantity
+ * @return float
+ */
+ public function getSelectionPrice($bundleProduct, $selectionProduct, $selectionQty = null, $multiplyQty = true)
+ {
+ return $this->getSelectionFinalTotalPrice($bundleProduct, $selectionProduct, 0, $selectionQty, $multiplyQty);
+ }
+
+ /**
+ * Calculate selection price for front view (with applied special of bundle)
+ *
+ * @param Mage_Catalog_Model_Product $bundleProduct
+ * @param Mage_Catalog_Model_Product $selectionProduct
+ * @param decimal $qty
+ * @return decimal
+ */
+ public function getSelectionPreFinalPrice($bundleProduct, $selectionProduct, $qty = null)
+ {
+ return $this->getSelectionPrice($bundleProduct, $selectionProduct, $qty);
+ }
+
+ /**
+ * Calculate final price of selection
+ *
+ * @deprecated after 1.5.1.0
+ * @see Mage_Bundle_Model_Product_Price::getSelectionFinalTotalPrice()
+ *
+ * @param Mage_Catalog_Model_Product $bundleProduct
+ * @param Mage_Catalog_Model_Product $selectionProduct
+ * @param decimal $bundleQty
+ * @param decimal $selectionQty
+ * @param bool $multiplyQty
+ * @return decimal
+ */
+ public function getSelectionFinalPrice($bundleProduct, $selectionProduct, $bundleQty, $selectionQty = null,
+ $multiplyQty = true)
+ {
+ return $this->getSelectionFinalTotalPrice($bundleProduct, $selectionProduct, $bundleQty, $selectionQty,
+ $multiplyQty);
+ }
+
+ /**
+ * Calculate final price of selection
+ * with take into account tier price
+ *
+ * @param Mage_Catalog_Model_Product $bundleProduct
+ * @param Mage_Catalog_Model_Product $selectionProduct
+ * @param float $bundleQty
+ * @param float $selectionQty
+ * @param bool $multiplyQty
+ * @param bool $takeTierPrice
+ * @return float
+ */
+ public function getSelectionFinalTotalPrice($bundleProduct, $selectionProduct, $bundleQty, $selectionQty,
+ $multiplyQty = true, $takeTierPrice = true)
+ {
+ if (is_null($selectionQty)) {
+ $selectionQty = $selectionProduct->getSelectionQty();
+ }
+
+ if ($bundleProduct->getPriceType() == self::PRICE_TYPE_DYNAMIC) {
+ $price = $selectionProduct->getFinalPrice($takeTierPrice ? $selectionQty : 1);
+ } else {
+ if ($selectionProduct->getSelectionPriceType()) { // percent
+ $product = clone $bundleProduct;
+ $product->setFinalPrice($this->getPrice($product));
+ Mage::dispatchEvent(
+ 'catalog_product_get_final_price',
+ array('product' => $product, 'qty' => $bundleQty)
+ );
+ $price = $product->getData('final_price') * ($selectionProduct->getSelectionPriceValue() / 100);
+
+ } else { // fixed
+ $price = $selectionProduct->getSelectionPriceValue();
+ }
+ }
+
+ $price = $this->getLowestPrice($bundleProduct, $price, $bundleQty);
+
+ if ($multiplyQty) {
+ $price *= $selectionQty;
+ }
+
+ return $price;
+ }
+
+ /**
+ * Returns the lowest price after applying any applicable bundle discounts
+ *
+ * @param Mage_Catalog_Model_Product $bundleProduct
+ * @param float|string $price
+ * @param int $bundleQty
+ * @return float
+ */
+ public function getLowestPrice($bundleProduct, $price, $bundleQty = 1)
+ {
+ $price *= 1;
+ return min($this->_getApp()->getStore()->roundPrice($price),
+ $this->_applyGroupPrice($bundleProduct, $price),
+ $this->_applyTierPrice($bundleProduct, $bundleQty, $price),
+ $this->_applySpecialPrice($bundleProduct, $price)
+ );
+ }
+
+ /**
+ * Apply group price for bundle product
+ *
+ * @param Mage_Catalog_Model_Product $product
+ * @param float $finalPrice
+ * @return float
+ */
+ protected function _applyGroupPrice($product, $finalPrice)
+ {
+ $result = $finalPrice;
+ $groupPrice = $product->getGroupPrice();
+
+ if (is_numeric($groupPrice)) {
+ $groupPrice = $finalPrice - ($finalPrice * ($groupPrice / 100));
+ $groupPrice = $this->_getApp()->getStore()->roundPrice($groupPrice);
+ $result = min($finalPrice, $groupPrice);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get product group price
+ *
+ * @param Mage_Catalog_Model_Product $product
+ * @return float|null
+ */
+ public function getGroupPrice($product)
+ {
+ $groupPrices = $product->getData('group_price');
+
+ if (is_null($groupPrices)) {
+ $attribute = $product->getResource()->getAttribute('group_price');
+ if ($attribute) {
+ $attribute->getBackend()->afterLoad($product);
+ $groupPrices = $product->getData('group_price');
+ }
+ }
+
+ if (is_null($groupPrices) || !is_array($groupPrices)) {
+ return null;
+ }
+
+ $customerGroup = $this->_getCustomerGroupId($product);
+
+ $matchedPrice = 0;
+
+ foreach ($groupPrices as $groupPrice) {
+ if ($groupPrice['cust_group'] == $customerGroup && $groupPrice['website_price'] > $matchedPrice) {
+ $matchedPrice = $groupPrice['website_price'];
+ break;
+ }
+ }
+
+ return $matchedPrice;
+ }
+
+ /**
+ * Apply tier price for bundle
+ *
+ * @param Mage_Catalog_Model_Product $product
+ * @param decimal $qty
+ * @param decimal $finalPrice
+ * @return decimal
+ */
+ protected function _applyTierPrice($product, $qty, $finalPrice)
+ {
+ if (is_null($qty)) {
+ return $finalPrice;
+ }
+
+ $tierPrice = $product->getTierPrice($qty);
+
+ if (is_numeric($tierPrice)) {
+ $tierPrice = $finalPrice - ($finalPrice * ($tierPrice / 100));
+ $tierPrice = $this->_getApp()->getStore()->roundPrice($tierPrice);
+ $finalPrice = min($finalPrice, $tierPrice);
+ }
+
+ return $finalPrice;
+ }
+
+ /**
+ * Get product tier price by qty
+ *
+ * @param decimal $qty
+ * @param Mage_Catalog_Model_Product $product
+ * @return decimal
+ */
+ public function getTierPrice($qty = null, $product)
+ {
+ $allGroups = Mage_Customer_Model_Group::CUST_GROUP_ALL;
+ $prices = $product->getData('tier_price');
+
+ if (is_null($prices)) {
+ $attribute = $product->getResource()->getAttribute('tier_price');
+ if ($attribute) {
+ $attribute->getBackend()->afterLoad($product);
+ $prices = $product->getData('tier_price');
+ }
+ }
+
+ if (is_null($prices) || !is_array($prices)) {
+ if (!is_null($qty)) {
+ return 0;
+ }
+ return array(array(
+ 'price' => 0,
+ 'website_price' => 0,
+ 'price_qty' => 1,
+ 'cust_group' => $allGroups
+ ));
+ }
+
+ $custGroup = $this->_getCustomerGroupId($product);
+ if ($qty) {
+ $prevQty = 1;
+ $prevPrice = 0;
+ $prevGroup = $allGroups;
+
+ foreach ($prices as $price) {
+ if ($price['cust_group'] != $custGroup && $price['cust_group'] != $allGroups) {
+ // tier not for current customer group nor is for all groups
+ continue;
+ }
+ if ($qty < $price['price_qty']) {
+ // tier is higher than product qty
+ continue;
+ }
+ if ($price['price_qty'] < $prevQty) {
+ // higher tier qty already found
+ continue;
+ }
+ if ($price['price_qty'] == $prevQty && $prevGroup != $allGroups && $price['cust_group'] == $allGroups) {
+ // found tier qty is same as current tier qty but current tier group is ALL_GROUPS
+ continue;
+ }
+
+ if ($price['website_price'] > $prevPrice) {
+ $prevPrice = $price['website_price'];
+ $prevQty = $price['price_qty'];
+ $prevGroup = $price['cust_group'];
+ }
+ }
+
+ return $prevPrice;
+ } else {
+ $qtyCache = array();
+ foreach ($prices as $i => $price) {
+ if ($price['cust_group'] != $custGroup && $price['cust_group'] != $allGroups) {
+ unset($prices[$i]);
+ } else if (isset($qtyCache[$price['price_qty']])) {
+ $j = $qtyCache[$price['price_qty']];
+ if ($prices[$j]['website_price'] < $price['website_price']) {
+ unset($prices[$j]);
+ $qtyCache[$price['price_qty']] = $i;
+ } else {
+ unset($prices[$i]);
+ }
+ } else {
+ $qtyCache[$price['price_qty']] = $i;
+ }
+ }
+ }
+
+ return ($prices) ? $prices : array();
+ }
+
+ /**
+ * Calculate product price based on special price data and price rules
+ *
+ * @param float $basePrice
+ * @param float $specialPrice
+ * @param string $specialPriceFrom
+ * @param string $specialPriceTo
+ * @param float|null|false $rulePrice
+ * @param mixed $wId
+ * @param mixed $gId
+ * @param null|int $productId
+ * @return float
+ */
+ public static function calculatePrice($basePrice, $specialPrice, $specialPriceFrom, $specialPriceTo,
+ $rulePrice = false, $wId = null, $gId = null, $productId = null)
+ {
+ $resource = Mage::getResourceSingleton('bundle/bundle');
+ $selectionResource = Mage::getResourceSingleton('bundle/selection');
+ $productPriceTypeId = Mage::getSingleton('eav/entity_attribute')->getIdByCode(
+ Mage_Catalog_Model_Product::ENTITY,
+ 'price_type'
+ );
+
+ if ($wId instanceof Mage_Core_Model_Store) {
+ $store = $wId->getId();
+ $wId = $wId->getWebsiteId();
+ } else {
+ $store = Mage::app()->getStore($wId)->getId();
+ $wId = Mage::app()->getStore($wId)->getWebsiteId();
+ }
+
+ if (!$gId) {
+ $gId = Mage::getSingleton('customer/session')->getCustomerGroupId();
+ } else if ($gId instanceof Mage_Customer_Model_Group) {
+ $gId = $gId->getId();
+ }
+
+ if (!isset(self::$attributeCache[$productId]['price_type'])) {
+ $attributes = $resource->getAttributeData($productId, $productPriceTypeId, $store);
+ self::$attributeCache[$productId]['price_type'] = $attributes;
+ } else {
+ $attributes = self::$attributeCache[$productId]['price_type'];
+ }
+
+ $options = array(0);
+ $results = $resource->getSelectionsData($productId);
+
+ if (!$attributes || !$attributes[0]['value']) { //dynamic
+ foreach ($results as $result) {
+ if (!$result['product_id']) {
+ continue;
+ }
+
+ if ($result['selection_can_change_qty'] && $result['type'] != 'multi'
+ && $result['type'] != 'checkbox'
+ ) {
+ $qty = 1;
+ } else {
+ $qty = $result['selection_qty'];
+ }
+
+ $result['final_price'] = $selectionResource->getPriceFromIndex($result['product_id'], $qty, $store,
+ $gId);
+
+ $selectionPrice = $result['final_price'] * $qty;
+
+ if (isset($options[$result['option_id']])) {
+ $options[$result['option_id']] = min($options[$result['option_id']], $selectionPrice);
+ } else {
+ $options[$result['option_id']] = $selectionPrice;
+ }
+ }
+ $basePrice = array_sum($options);
+ } else {
+ foreach ($results as $result) {
+ if (!$result['product_id']) {
+ continue;
+ }
+ if ($result['selection_price_type']) {
+ $selectionPrice = $basePrice * $result['selection_price_value'] / 100;
+ } else {
+ $selectionPrice = $result['selection_price_value'];
+ }
+
+ if ($result['selection_can_change_qty'] && $result['type'] != 'multi'
+ && $result['type'] != 'checkbox'
+ ) {
+ $qty = 1;
+ } else {
+ $qty = $result['selection_qty'];
+ }
+
+ $selectionPrice = $selectionPrice * $qty;
+
+ if (isset($options[$result['option_id']])) {
+ $options[$result['option_id']] = min($options[$result['option_id']], $selectionPrice);
+ } else {
+ $options[$result['option_id']] = $selectionPrice;
+ }
+ }
+
+ $basePrice = $basePrice + array_sum($options);
+ }
+
+ $finalPrice = self::calculateSpecialPrice($basePrice, $specialPrice, $specialPriceFrom, $specialPriceTo,
+ $store);
+
+ /**
+ * adding customer defined options price
+ */
+ $customOptions = Mage::getResourceSingleton('catalog/product_option_collection')->reset();
+ $customOptions->addFieldToFilter('is_require', '1')
+ ->addProductToFilter($productId)
+ ->addPriceToResult($store, 'price')
+ ->addValuesToResult();
+
+ foreach ($customOptions as $customOption) {
+ $values = $customOption->getValues();
+ if ($values) {
+ $prices = array();
+ foreach ($values as $value) {
+ $prices[] = $value->getPrice();
+ }
+ if (count($prices)) {
+ $finalPrice += min($prices);
+ }
+ } else {
+ $finalPrice += $customOption->getPrice();
+ }
+ }
+
+ if ($rulePrice === false) {
+ $rulePrice = Mage::getResourceModel('catalogrule/rule')
+ ->getRulePrice(Mage::app()->getLocale()->storeTimeStamp($store), $wId, $gId, $productId);
+ }
+
+ if ($rulePrice !== null && $rulePrice !== false) {
+ $finalPrice = min($finalPrice, $rulePrice);
+ }
+
+ $finalPrice = max($finalPrice, 0);
+
+ return $finalPrice;
+ }
+
+ /**
+ * Calculate and apply special price
+ *
+ * @param float $finalPrice
+ * @param float $specialPrice
+ * @param string $specialPriceFrom
+ * @param string $specialPriceTo
+ * @param mixed $store
+ * @return float
+ */
+ public static function calculateSpecialPrice($finalPrice, $specialPrice, $specialPriceFrom, $specialPriceTo,
+ $store = null)
+ {
+ if (!is_null($specialPrice) && $specialPrice != false) {
+ if (Mage::app()->getLocale()->isStoreDateInInterval($store, $specialPriceFrom, $specialPriceTo)) {
+ $specialPrice = Mage::app()->getStore()->roundPrice($finalPrice * $specialPrice / 100);
+ $finalPrice = min($finalPrice, $specialPrice);
+ }
+ }
+
+ return $finalPrice;
+ }
+
+ /**
+ * Check is group price value fixed or percent of original price
+ *
+ * @return bool
+ */
+ public function isGroupPriceFixed()
+ {
+ return false;
+ }
+
+ /**
+ * Get data helper
+ *
+ * @param string $name
+ * @return Mage_Core_Helper_Abstract
+ */
+ protected function _getHelperData($name)
+ {
+ return Mage::helper($name);
+ }
+
+ /**
+ * Get Magento App instance
+ *
+ * @return Mage_Core_Model_App
+ */
+ protected function _getApp()
+ {
+ return Mage::app();
+ }
+
+ /**
+ * Check if product has required options
+ *
+ * @param Mage_Catalog_Model_Product $product
+ * @return bool
+ */
+ protected function _hasRequiredOptions($product)
+ {
+ $options = $this->getOptions($product);
+ if ($options) {
+ foreach ($options as $option) {
+ if ($option->getRequired()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get minimum possible price of custom options
+ *
+ * @param Mage_Catalog_Model_Product_Option $option
+ * @return float
+ */
+ protected function _getMinimalCustomOptionPrice($option)
+ {
+ $prices = $this->_getCustomOptionValuesPrices($option);
+ $minimalOptionPrice = ($prices) ? min($prices) : (float)$option->getPrice(true);
+ $minimalPrice = ($option->getIsRequire()) ? $minimalOptionPrice : 0;
+ return $minimalPrice;
+ }
+
+ /**
+ * Get maximum possible price of custom options
+ *
+ * @param Mage_Catalog_Model_Product_Option $option
+ * @return float
+ */
+ protected function _getMaximalCustomOptionPrice($option)
+ {
+ $prices = $this->_getCustomOptionValuesPrices($option);
+ $maximalOptionPrice = ($option->isMultipleType()) ? array_sum($prices) : max($prices);
+ $maximalPrice = ($prices) ? $maximalOptionPrice : (float)($option->getPrice(true));
+ return $maximalPrice;
+ }
+
+ /**
+ * Get all custom option values prices
+ *
+ * @param Mage_Catalog_Model_Product_Option $option
+ * @return array
+ */
+ protected function _getCustomOptionValuesPrices($option)
+ {
+ $values = $option->getValues();
+ $prices = array();
+ if ($values) {
+ foreach ($values as $value) {
+ /* @var $value Mage_Catalog_Model_Product_Option_Value */
+ $prices[] = $value->getPrice(true);
+ }
+ }
+ return $prices;
+ }
+}
diff --git a/app/code/local/Mage/Bundle/Model/Resource/Indexer/Price.php b/app/code/local/Mage/Bundle/Model/Resource/Indexer/Price.php
new file mode 100644
index 0000000..0fe3f7d
--- /dev/null
+++ b/app/code/local/Mage/Bundle/Model/Resource/Indexer/Price.php
@@ -0,0 +1,698 @@
+<?php
+/**
+ * Magento
+ *
+ * NOTICE OF LICENSE
+ *
+ * This source file is subject to the Open Software License (OSL 3.0)
+ * that is bundled with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://opensource.org/licenses/osl-3.0.php
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@magentocommerce.com so we can send you a copy immediately.
+ *
+ * DISCLAIMER
+ *
+ * Do not edit or add to this file if you wish to upgrade Magento to newer
+ * versions in the future. If you wish to customize Magento for your
+ * needs please refer to http://www.magentocommerce.com for more information.
+ *
+ * @category Mage
+ * @package Mage_Bundle
+ * @copyright Copyright (c) 2013 Magento Inc. (http://www.magentocommerce.com)
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
+ */
+
+
+/**
+ * Bundle products Price indexer resource model
+ *
+ * @category Mage
+ * @package Mage_Bundle
+ * @author Magento Core Team <core@magentocommerce.com>
+ */
+class Mage_Bundle_Model_Resource_Indexer_Price extends Mage_Catalog_Model_Resource_Product_Indexer_Price_Default
+{
+ /**
+ * Reindex temporary (price result data) for all products
+ *
+ * @return Mage_Bundle_Model_Resource_Indexer_Price
+ */
+ public function reindexAll()
+ {
+ $this->useIdxTable(true);
+
+ $this->beginTransaction();
+ try {
+ $this->_prepareBundlePrice();
+ $this->commit();
+ } catch (Exception $e) {
+ $this->rollBack();
+ throw $e;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Reindex temporary (price result data) for defined product(s)
+ *
+ * @param int|array $entityIds
+ * @return Mage_Bundle_Model_Resource_Indexer_Price
+ */
+ public function reindexEntity($entityIds)
+ {
+ $this->_prepareBundlePrice($entityIds);
+
+ return $this;
+ }
+
+ /**
+ * Retrieve temporary price index table name for fixed bundle products
+ *
+ * @return string
+ */
+ protected function _getBundlePriceTable()
+ {
+ if ($this->useIdxTable()) {
+ return $this->getTable('bundle/price_indexer_idx');
+ }
+ return $this->getTable('bundle/price_indexer_tmp');
+ }
+
+ /**
+ * Retrieve table name for temporary bundle selection prices index
+ *
+ * @return string
+ */
+ protected function _getBundleSelectionTable()
+ {
+ if ($this->useIdxTable()) {
+ return $this->getTable('bundle/selection_indexer_idx');
+ }
+ return $this->getTable('bundle/selection_indexer_tmp');
+ }
+
+ /**
+ * Retrieve table name for temporary bundle option prices index
+ *
+ * @return string
+ */
+ protected function _getBundleOptionTable()
+ {
+ if ($this->useIdxTable()) {
+ return $this->getTable('bundle/option_indexer_idx');
+ }
+ return $this->getTable('bundle/option_indexer_tmp');
+ }
+
+ /**
+ * Prepare temporary price index table for fixed bundle products
+ *
+ * @return Mage_Bundle_Model_Resource_Indexer_Price
+ */
+ protected function _prepareBundlePriceTable()
+ {
+ $this->_getWriteAdapter()->delete($this->_getBundlePriceTable());
+ return $this;
+ }
+
+ /**
+ * Prepare table structure for temporary bundle selection prices index
+ *
+ * @return Mage_Bundle_Model_Resource_Indexer_Price
+ */
+ protected function _prepareBundleSelectionTable()
+ {
+ $this->_getWriteAdapter()->delete($this->_getBundleSelectionTable());
+ return $this;
+ }
+
+ /**
+ * Prepare table structure for temporary bundle option prices index
+ *
+ * @return Mage_Bundle_Model_Resource_Indexer_Price
+ */
+ protected function _prepareBundleOptionTable()
+ {
+ $this->_getWriteAdapter()->delete($this->_getBundleOptionTable());
+ return $this;
+ }
+
+ /**
+ * Prepare temporary price index data for bundle products by price type
+ *
+ * @param int $priceType
+ * @param int|array $entityIds the entity ids limitatation
+ * @return Mage_Bundle_Model_Resource_Indexer_Price
+ */
+ protected function _prepareBundlePriceByType($priceType, $entityIds = null)
+ {
+ $write = $this->_getWriteAdapter();
+ $table = $this->_getBundlePriceTable();
+
+ $select = $write->select()
+ ->from(array('e' => $this->getTable('catalog/product')), array('entity_id'))
+ ->join(
+ array('cg' => $this->getTable('customer/customer_group')),
+ '',
+ array('customer_group_id')
+ );
+ $this->_addWebsiteJoinToSelect($select, true);
+ $this->_addProductWebsiteJoinToSelect($select, 'cw.website_id', 'e.entity_id');
+ $select->columns('website_id', 'cw')
+ ->join(
+ array('cwd' => $this->_getWebsiteDateTable()),
+ 'cw.website_id = cwd.website_id',
+ array()
+ )
+ ->joinLeft(
+ array('tp' => $this->_getTierPriceIndexTable()),
+ 'tp.entity_id = e.entity_id AND tp.website_id = cw.website_id'
+ . ' AND tp.customer_group_id = cg.customer_group_id',
+ array()
+ )
+ ->joinLeft(
+ array('gp' => $this->_getGroupPriceIndexTable()),
+ 'gp.entity_id = e.entity_id AND gp.website_id = cw.website_id'
+ . ' AND gp.customer_group_id = cg.customer_group_id',
+ array()
+ )
+ ->where('e.type_id=?', $this->getTypeId());
+
+ // add enable products limitation
+ $statusCond = $write->quoteInto('=?', Mage_Catalog_Model_Product_Status::STATUS_ENABLED);
+ $this->_addAttributeToSelect($select, 'status', 'e.entity_id', 'cs.store_id', $statusCond, true);
+ if (Mage::helper('core')->isModuleEnabled('Mage_Tax')) {
+ $taxClassId = $this->_addAttributeToSelect($select, 'tax_class_id', 'e.entity_id', 'cs.store_id');
+ } else {
+ $taxClassId = new Zend_Db_Expr('0');
+ }
+
+ if ($priceType == Mage_Bundle_Model_Product_Price::PRICE_TYPE_DYNAMIC) {
+ $select->columns(array('tax_class_id' => new Zend_Db_Expr('0')));
+ } else {
+ $select->columns(
+ array('tax_class_id' => $write->getCheckSql($taxClassId . ' IS NOT NULL', $taxClassId, 0))
+ );
+ }
+
+ $priceTypeCond = $write->quoteInto('=?', $priceType);
+ $this->_addAttributeToSelect($select, 'price_type', 'e.entity_id', 'cs.store_id', $priceTypeCond);
+
+ $price = $this->_addAttributeToSelect($select, 'price', 'e.entity_id', 'cs.store_id');
+ $specialPrice = $this->_addAttributeToSelect($select, 'special_price', 'e.entity_id', 'cs.store_id');
+ $specialFrom = $this->_addAttributeToSelect($select, 'special_from_date', 'e.entity_id', 'cs.store_id');
+ $specialTo = $this->_addAttributeToSelect($select, 'special_to_date', 'e.entity_id', 'cs.store_id');
+ $curentDate = new Zend_Db_Expr('cwd.website_date');
+
+ $specialExpr = $write->getCheckSql(
+ $write->getCheckSql(
+ $specialFrom . ' IS NULL',
+ '1',
+ $write->getCheckSql(
+ $specialFrom . ' <= ' . $curentDate,
+ '1',
+ '0'
+ )
+ ) . " > 0 AND ".
+ $write->getCheckSql(
+ $specialTo . ' IS NULL',
+ '1',
+ $write->getCheckSql(
+ $specialTo . ' >= ' . $curentDate,
+ '1',
+ '0'
+ )
+ )
+ . " > 0 AND {$specialPrice} > 0 AND {$specialPrice} < 100 ",
+ $specialPrice,
+ '0'
+ );
+
+ $groupPriceExpr = $write->getCheckSql(
+ 'gp.price IS NOT NULL AND gp.price > 0 AND gp.price < 100',
+ 'gp.price',
+ '0'
+ );
+
+ $tierExpr = new Zend_Db_Expr("tp.min_price");
+
+ if ($priceType == Mage_Bundle_Model_Product_Price::PRICE_TYPE_FIXED) {
+ $finalPrice = $write->getCheckSql(
+ $specialExpr . ' > 0',
+ 'ROUND(' . $price . ' * (' . $specialExpr . ' / 100), 2)',
+ $price
+ );
+ $tierPrice = $write->getCheckSql(
+ $tierExpr . ' IS NOT NULL',
+ 'ROUND(' . $price . ' - ' . '(' . $price . ' * (' . $tierExpr . ' / 100)), 2)',
+ 'NULL'
+ );
+ $groupPrice = $write->getCheckSql(
+ $groupPriceExpr . ' > 0',
+ 'ROUND(' . $price . ' - ' . '(' . $price . ' * (' . $groupPriceExpr . ' / 100)), 2)',
+ 'NULL'
+ );
+ $finalPrice = $write->getCheckSql(
+ "{$groupPrice} IS NOT NULL AND {$groupPrice} < {$finalPrice}",
+ $groupPrice,
+ $finalPrice
+ );
+ } else {
+ $finalPrice = new Zend_Db_Expr("0");
+ $tierPrice = $write->getCheckSql($tierExpr . ' IS NOT NULL', '0', 'NULL');
+ $groupPrice = $write->getCheckSql($groupPriceExpr . ' > 0', $groupPriceExpr, 'NULL');
+ }
+
+ $select->columns(array(
+ 'price_type' => new Zend_Db_Expr($priceType),
+ 'special_price' => $specialExpr,
+ 'tier_percent' => $tierExpr,
+ 'orig_price' => $write->getCheckSql($price . ' IS NULL', '0', $price),
+ 'price' => $finalPrice,
+ 'min_price' => $finalPrice,
+ 'max_price' => $finalPrice,
+ 'tier_price' => $tierPrice,
+ 'base_tier' => $tierPrice,
+ 'group_price' => $groupPrice,
+ 'base_group_price' => $groupPrice,
+ 'group_price_percent' => new Zend_Db_Expr('gp.price'),
+ ));
+
+ if (!is_null($entityIds)) {
+ $select->where('e.entity_id IN(?)', $entityIds);
+ }
+
+ /**
+ * Add additional external limitation
+ */
+ Mage::dispatchEvent('catalog_product_prepare_index_select', array(
+ 'select' => $select,
+ 'entity_field' => new Zend_Db_Expr('e.entity_id'),
+ 'website_field' => new Zend_Db_Expr('cw.website_id'),
+ 'store_field' => new Zend_Db_Expr('cs.store_id')
+ ));
+
+ $query = $select->insertFromSelect($table);
+ $write->query($query);
+
+ return $this;
+ }
+
+ /**
+ * Calculate fixed bundle product selections price
+ *
+ * @return Mage_Bundle_Model_Resource_Indexer_Price
+ */
+ protected function _calculateBundleOptionPrice()
+ {
+ $write = $this->_getWriteAdapter();
+
+ $this->_prepareBundleSelectionTable();
+ $this->_calculateBundleSelectionPrice(Mage_Bundle_Model_Product_Price::PRICE_TYPE_FIXED);
+ $this->_calculateBundleSelectionPrice(Mage_Bundle_Model_Product_Price::PRICE_TYPE_DYNAMIC);
+
+ $this->_prepareBundleOptionTable();
+
+ $select = $write->select()
+ ->from(
+ array('i' => $this->_getBundleSelectionTable()),
+ array('entity_id', 'customer_group_id', 'website_id', 'option_id')
+ )
+ ->group(array('entity_id', 'customer_group_id', 'website_id', 'option_id', 'is_required', 'group_type'))
+ ->columns(array(
+ 'min_price' => $write->getCheckSql('i.is_required = 1', 'MIN(i.price)', '0'),
+ 'alt_price' => $write->getCheckSql('i.is_required = 0', 'MIN(i.price)', '0'),
+ 'max_price' => $write->getCheckSql('i.group_type = 1', 'SUM(i.price)', 'MAX(i.price)'),
+ 'tier_price' => $write->getCheckSql('i.is_required = 1', 'MIN(i.tier_price)', '0'),
+ 'alt_tier_price' => $write->getCheckSql('i.is_required = 0', 'MIN(i.tier_price)', '0'),
+ 'group_price' => $write->getCheckSql('i.is_required = 1', 'MIN(i.group_price)', '0'),
+ 'alt_group_price' => $write->getCheckSql('i.is_required = 0', 'MIN(i.group_price)', '0'),
+ ));
+
+ $query = $select->insertFromSelect($this->_getBundleOptionTable());
+ $write->query($query);
+
+ $this->_prepareDefaultFinalPriceTable();
+
+ $minPrice = new Zend_Db_Expr($write->getCheckSql(
+ 'SUM(io.min_price) = 0',
+ 'MIN(io.alt_price)',
+ 'SUM(io.min_price)'
+ ) . ' + i.price');
+ $maxPrice = new Zend_Db_Expr("SUM(io.max_price) + i.price");
+ $tierPrice = $write->getCheckSql(
+ 'MIN(i.tier_percent) IS NOT NULL',
+ $write->getCheckSql(
+ 'SUM(io.tier_price) = 0',
+ 'SUM(io.alt_tier_price)',
+ 'SUM(io.tier_price)'
+ ) . ' + MIN(i.tier_price)',
+ 'NULL'
+ );
+ $groupPrice = $write->getCheckSql(
+ 'MIN(i.group_price_percent) IS NOT NULL',
+ $write->getCheckSql(
+ 'SUM(io.group_price) = 0',
+ 'SUM(io.alt_group_price)',
+ 'SUM(io.group_price)'
+ ) . ' + MIN(i.group_price)',
+ 'NULL'
+ );
+
+ $select = $write->select()
+ ->from(
+ array('io' => $this->_getBundleOptionTable()),
+ array('entity_id', 'customer_group_id', 'website_id')
+ )
+ ->join(
+ array('i' => $this->_getBundlePriceTable()),
+ 'i.entity_id = io.entity_id AND i.customer_group_id = io.customer_group_id'
+ . ' AND i.website_id = io.website_id',
+ array()
+ )
+ ->group(array('io.entity_id', 'io.customer_group_id', 'io.website_id',
+ 'i.tax_class_id', 'i.orig_price', 'i.price'))
+ ->columns(array('i.tax_class_id',
+ 'orig_price' => 'i.orig_price',
+ 'price' => 'i.price',
+ 'min_price' => $minPrice,
+ 'max_price' => $maxPrice,
+ 'tier_price' => $tierPrice,
+ 'base_tier' => 'MIN(i.base_tier)',
+ 'group_price' => $groupPrice,
+ 'base_group_price' => 'MIN(i.base_group_price)',
+ ));
+
+ $query = $select->insertFromSelect($this->_getDefaultFinalPriceTable());
+ $write->query($query);
+
+ return $this;
+ }
+
+ /**
+ * Calculate bundle product selections price by product type
+ *
+ * @param int $priceType
+ * @return Mage_Bundle_Model_Resource_Indexer_Price
+ */
+ protected function _calculateBundleSelectionPrice($priceType)
+ {
+ $write = $this->_getWriteAdapter();
+
+ if ($priceType == Mage_Bundle_Model_Product_Price::PRICE_TYPE_FIXED) {
+
+ $selectionPriceValue = $write->getCheckSql(
+ 'bsp.selection_price_value IS NULL',
+ 'bs.selection_price_value',
+ 'bsp.selection_price_value'
+ );
+ $selectionPriceType = $write->getCheckSql(
+ 'bsp.selection_price_type IS NULL',
+ 'bs.selection_price_type',
+ 'bsp.selection_price_type'
+ );
+ $priceExpr = new Zend_Db_Expr(
+ $write->getCheckSql(
+ $selectionPriceType . ' = 1',
+ 'ROUND(i.price * (' . $selectionPriceValue . ' / 100),2)',
+ $write->getCheckSql(
+ 'i.special_price > 0 AND i.special_price < 100',
+ 'ROUND(' . $selectionPriceValue . ' * (i.special_price / 100),2)',
+ $selectionPriceValue
+ )
+ ) . '* bs.selection_qty'
+ );
+
+ $tierExpr = $write->getCheckSql(
+ 'i.base_tier IS NOT NULL',
+ $write->getCheckSql(
+ $selectionPriceType .' = 1',
+ 'ROUND(i.base_tier - (i.base_tier * (' . $selectionPriceValue . ' / 100)),2)',
+ $write->getCheckSql(
+ 'i.tier_percent > 0',
+ 'ROUND(' . $selectionPriceValue
+ . ' - (' . $selectionPriceValue . ' * (i.tier_percent / 100)),2)',
+ $selectionPriceValue
+ )
+ ) . ' * bs.selection_qty',
+ 'NULL'
+ );
+
+ $groupExpr = $write->getCheckSql(
+ 'i.base_group_price IS NOT NULL',
+ $write->getCheckSql(
+ $selectionPriceType .' = 1',
+ $priceExpr,
+ $write->getCheckSql(
+ 'i.group_price_percent > 0',
+ 'ROUND(' . $selectionPriceValue
+ . ' - (' . $selectionPriceValue . ' * (i.group_price_percent / 100)),2)',
+ $selectionPriceValue
+ )
+ ) . ' * bs.selection_qty',
+ 'NULL'
+ );
+ $priceExpr = new Zend_Db_Expr(
+ $write->getCheckSql("{$groupExpr} < {$priceExpr}", $groupExpr, $priceExpr)
+ );
+ } else {
+ $priceExpr = new Zend_Db_Expr(
+ $write->getCheckSql(
+ 'i.special_price > 0 AND i.special_price < 100',
+ 'ROUND(idx.min_price * (i.special_price / 100), 2)',
+ 'idx.min_price'
+ ) . ' * bs.selection_qty'
+ );
+ $tierExpr = $write->getCheckSql(
+ 'i.base_tier IS NOT NULL',
+ 'ROUND(idx.min_price * (i.base_tier / 100), 2)* bs.selection_qty',
+ 'NULL'
+ );
+ $groupExpr = $write->getCheckSql(
+ 'i.base_group_price IS NOT NULL',
+ 'ROUND(idx.min_price * (i.base_group_price / 100), 2)* bs.selection_qty',
+ 'NULL'
+ );
+ $groupPriceExpr = new Zend_Db_Expr(
+ $write->getCheckSql(
+ 'i.base_group_price IS NOT NULL AND i.base_group_price > 0 AND i.base_group_price < 100',
+ 'ROUND(idx.min_price - idx.min_price * (i.base_group_price / 100), 2)',
+ 'idx.min_price'
+ ) . ' * bs.selection_qty'
+ );
+ $priceExpr = new Zend_Db_Expr(
+ $write->getCheckSql("{$groupPriceExpr} < {$priceExpr}", $groupPriceExpr, $priceExpr)
+ );
+ }
+
+ $select = $write->select()
+ ->from(
+ array('i' => $this->_getBundlePriceTable()),
+ array('entity_id', 'customer_group_id', 'website_id')
+ )
+ ->join(
+ array('bo' => $this->getTable('bundle/option')),
+ 'bo.parent_id = i.entity_id',
+ array('option_id')
+ )
+ ->join(
+ array('bs' => $this->getTable('bundle/selection')),
+ 'bs.option_id = bo.option_id',
+ array('selection_id')
+ )
+ ->joinLeft(
+ array('bsp' => $this->getTable('bundle/selection_price')),
+ 'bs.selection_id = bsp.selection_id AND bsp.website_id = i.website_id',
+ array('')
+ )
+ ->join(
+ array('idx' => $this->getIdxTable()),
+ 'bs.product_id = idx.entity_id AND i.customer_group_id = idx.customer_group_id'
+ . ' AND i.website_id = idx.website_id',
+ array()
+ )
+ ->join(
+ array('e' => $this->getTable('catalog/product')),
+ 'bs.product_id = e.entity_id AND e.required_options=0',
+ array()
+ )
+ ->where('i.price_type=?', $priceType)
+ ->columns(array(
+ 'group_type' => $write->getCheckSql(
+ "bo.type = 'select' OR bo.type = 'radio'",
+ '0',
+ '1'
+ ),
+ 'is_required' => 'bo.required',
+ 'price' => $priceExpr,
+ 'tier_price' => $tierExpr,
+ 'group_price' => $groupExpr,
+ ));
+
+ $query = $select->insertFromSelect($this->_getBundleSelectionTable());
+ $write->query($query);
+
+ return $this;
+ }
+
+ /**
+ * Prepare temporary index price for bundle products
+ *
+ * @param int|array $entityIds the entity ids limitation
+ * @return Mage_Bundle_Model_Resource_Indexer_Price
+ */
+ protected function _prepareBundlePrice($entityIds = null)
+ {
+ $this->_prepareTierPriceIndex($entityIds);
+ $this->_prepareGroupPriceIndex($entityIds);
+ $this->_prepareBundlePriceTable();
+ $this->_prepareBundlePriceByType(Mage_Bundle_Model_Product_Price::PRICE_TYPE_FIXED, $entityIds);
+ $this->_prepareBundlePriceByType(Mage_Bundle_Model_Product_Price::PRICE_TYPE_DYNAMIC, $entityIds);
+
+ /**
+ * Add possibility modify prices from external events
+ */
+ $select = $this->_getWriteAdapter()->select()
+ ->join(array('wd' => $this->_getWebsiteDateTable()),
+ 'i.website_id = wd.website_id',
+ array()
+ );
+ Mage::dispatchEvent('prepare_catalog_product_price_index_table', array(
+ 'index_table' => array('i' => $this->_getBundlePriceTable()),
+ 'select' => $select,
+ 'entity_id' => 'i.entity_id',
+ 'customer_group_id' => 'i.customer_group_id',
+ 'website_id' => 'i.website_id',
+ 'website_date' => 'wd.website_date',
+ 'update_fields' => array('price', 'min_price', 'max_price')
+ ));
+
+ $this->_calculateBundleOptionPrice();
+ $this->_applyCustomOption();
+
+ $this->_movePriceDataToIndexTable();
+
+ return $this;
+ }
+
+ /**
+ * Prepare percentage tier price for bundle products
+ *
+ * @see Mage_Catalog_Model_Resource_Product_Indexer_Price::_prepareTierPriceIndex
+ *
+ * @param int|array $entityIds
+ * @return Mage_Bundle_Model_Resource_Indexer_Price
+ */
+ protected function _prepareTierPriceIndex($entityIds = null)
+ {
+ $adapter = $this->_getWriteAdapter();
+
+ // remove index by bundle products
+ $select = $adapter->select()
+ ->from(array('i' => $this->_getTierPriceIndexTable()), null)
+ ->join(
+ array('e' => $this->getTable('catalog/product')),
+ 'i.entity_id=e.entity_id',
+ array()
+ )
+ ->where('e.type_id=?', $this->getTypeId());
+ $query = $select->deleteFromSelect('i');
+ $adapter->query($query);
+
+ $select = $adapter->select()
+ ->from(
+ array('tp' => $this->getValueTable('catalog/product', 'tier_price')),
+ array('entity_id')
+ )
+ ->join(
+ array('e' => $this->getTable('catalog/product')),
+ 'tp.entity_id=e.entity_id',
+ array()
+ )
+ ->join(
+ array('cg' => $this->getTable('customer/customer_group')),
+ 'tp.all_groups = 1 OR (tp.all_groups = 0 AND tp.customer_group_id = cg.customer_group_id)',
+ array('customer_group_id')
+ )
+ ->join(
+ array('cw' => $this->getTable('core/website')),
+ 'tp.website_id = 0 OR tp.website_id = cw.website_id',
+ array('website_id')
+ )
+ ->where('cw.website_id != 0')
+ ->where('e.type_id=?', $this->getTypeId())
+ ->columns(new Zend_Db_Expr('MIN(tp.value)'))
+ ->group(array('tp.entity_id', 'cg.customer_group_id', 'cw.website_id'));
+
+ if (!empty($entityIds)) {
+ $select->where('tp.entity_id IN(?)', $entityIds);
+ }
+
+ $query = $select->insertFromSelect($this->_getTierPriceIndexTable());
+ $adapter->query($query);
+
+ return $this;
+ }
+
+ /**
+ * Prepare percentage group price for bundle products
+ *
+ * @see Mage_Catalog_Model_Resource_Product_Indexer_Price::_prepareGroupPriceIndex
+ *
+ * @param int|array $entityIds
+ * @return Mage_Bundle_Model_Resource_Indexer_Price
+ */
+ protected function _prepareGroupPriceIndex($entityIds = null)
+ {
+ $adapter = $this->_getWriteAdapter();
+
+ // remove index by bundle products
+ $select = $adapter->select()
+ ->from(array('i' => $this->_getGroupPriceIndexTable()), null)
+ ->join(
+ array('e' => $this->getTable('catalog/product')),
+ 'i.entity_id=e.entity_id',
+ array()
+ )
+ ->where('e.type_id=?', $this->getTypeId());
+ $query = $select->deleteFromSelect('i');
+ $adapter->query($query);
+
+ $select = $adapter->select()
+ ->from(
+ array('gp' => $this->getValueTable('catalog/product', 'group_price')),
+ array('entity_id')
+ )
+ ->join(
+ array('e' => $this->getTable('catalog/product')),
+ 'gp.entity_id=e.entity_id',
+ array()
+ )
+ ->join(
+ array('cg' => $this->getTable('customer/customer_group')),
+ 'gp.all_groups = 1 OR (gp.all_groups = 0 AND gp.customer_group_id = cg.customer_group_id)',
+ array('customer_group_id')
+ )
+ ->join(
+ array('cw' => $this->getTable('core/website')),
+ 'gp.website_id = 0 OR gp.website_id = cw.website_id',
+ array('website_id')
+ )
+ ->where('cw.website_id != 0')
+ ->where('e.type_id=?', $this->getTypeId())
+ ->columns(new Zend_Db_Expr('MIN(gp.value)'))
+ ->group(array('gp.entity_id', 'cg.customer_group_id', 'cw.website_id'));
+
+ if (!empty($entityIds)) {
+ $select->where('gp.entity_id IN(?)', $entityIds);
+ }
+
+ $query = $select->insertFromSelect($this->_getGroupPriceIndexTable());
+ $adapter->query($query);
+
+ return $this;
+ }
+}
diff --git a/app/code/local/Mage/Bundle/Model/Resource/Price/Index.php b/app/code/local/Mage/Bundle/Model/Resource/Price/Index.php
new file mode 100644
index 0000000..f892ef7
--- /dev/null
+++ b/app/code/local/Mage/Bundle/Model/Resource/Price/Index.php
@@ -0,0 +1,856 @@
+<?php
+/**
+ * Magento
+ *
+ * NOTICE OF LICENSE
+ *
+ * This source file is subject to the Open Software License (OSL 3.0)
+ * that is bundled with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://opensource.org/licenses/osl-3.0.php
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@magentocommerce.com so we can send you a copy immediately.
+ *
+ * DISCLAIMER
+ *
+ * Do not edit or add to this file if you wish to upgrade Magento to newer
+ * versions in the future. If you wish to customize Magento for your
+ * needs please refer to http://www.magentocommerce.com for more information.
+ *
+ * @category Mage
+ * @package Mage_Bundle
+ * @copyright Copyright (c) 2013 Magento Inc. (http://www.magentocommerce.com)
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
+ */
+
+
+/**
+ * Bundle Product Price Index Resource model
+ *
+ * @category Mage
+ * @package Mage_Bundle
+ * @author Magento Core Team <core@magentocommerce.com>
+ */
+class Mage_Bundle_Model_Resource_Price_Index extends Mage_Core_Model_Resource_Db_Abstract
+{
+ /**
+ * EAV attributes cache
+ *
+ * @var array
+ */
+ protected $_attributes = array();
+
+ /**
+ * Websites cache
+ *
+ * @var array
+ */
+ protected $_websites;
+
+ /**
+ * Customer Groups cache
+ *
+ * @var array
+ */
+ protected $_customerGroups;
+
+ /**
+ * Initialize connection and define main table
+ *
+ */
+ protected function _construct()
+ {
+ $this->_init('bundle/price_index', 'entity_id');
+ }
+
+ /**
+ * Retrieve attribute object
+ *
+ * @param string $attributeCode
+ * @return Mage_Catalog_Model_Resource_Eav_Attribute
+ */
+ protected function _getAttribute($attributeCode)
+ {
+ if (!isset($this->_attributes[$attributeCode])) {
+ $this->_attributes[$attributeCode] = Mage::getSingleton('catalog/config')
+ ->getAttribute(Mage_Catalog_Model_Product::ENTITY, $attributeCode);
+ }
+ return $this->_attributes[$attributeCode];
+ }
+
+ /**
+ * Retrieve websites collection array
+ *
+ * @return array
+ */
+ protected function _getWebsites()
+ {
+ if (is_null($this->_websites)) {
+ $this->_websites = Mage::app()->getWebsites(false);
+ }
+ return $this->_websites;
+ }
+
+ /**
+ * Retrieve customer groups collection array
+ *
+ * @return array
+ */
+ protected function _getCustomerGroups()
+ {
+ if (is_null($this->_customerGroups)) {
+ $this->_customerGroups = array();
+ foreach (Mage::getModel('customer/group')->getCollection() as $group) {
+ $this->_customerGroups[$group->getId()] = $group;
+ }
+ }
+ return $this->_customerGroups;
+ }
+
+ /**
+ * Retrieve product ids array by product condition
+ *
+ * @param Mage_Core_Model_Product|Mage_Catalog_Model_Product_Condition_Interface|array|int $product
+ * @param int $lastEntityId
+ * @param int $limit
+ * @return array
+ */
+ public function getProducts($product = null, $lastEntityId = 0, $limit = 100)
+ {
+
+ $select = $this->_getReadAdapter()->select()
+ ->from(
+ array('e' => $this->getTable('catalog/product')),
+ array('entity_id')
+ )
+ ->where('e.type_id=?', Mage_Catalog_Model_Product_Type::TYPE_BUNDLE);
+ if ($product instanceof Mage_Catalog_Model_Product) {
+ $select->where('e.entity_id=?', $product->getId());
+ } elseif ($product instanceof Mage_Catalog_Model_Product_Condition_Interface) {
+ $value = new Zend_Db_Expr($product->getIdsSelect($this->_getReadAdapter()));
+ $select->where('e.entity_id IN(?)', $value);
+ } elseif (is_numeric($product) || is_array($product)) {
+ $select->where('e.entity_id IN(?)', $product);
+ }
+
+ $priceType = $this->_getAttribute('price_type');
+ $priceTypeAlias = 't_' . $priceType->getAttributeCode();
+ $joinConds = array(
+ $priceTypeAlias . '.attribute_id=:attribute_id',
+ $priceTypeAlias . '.store_id=0',
+ $priceTypeAlias . '.entity_id=e.entity_id'
+ );
+
+ $select->joinLeft(
+ array($priceTypeAlias => $priceType->getBackend()->getTable()),
+ join(' AND ', $joinConds),
+ array('price_type' => $priceTypeAlias . '.value')
+ );
+
+ $select->where('e.entity_id>:last_entity_id', $lastEntityId)
+ ->order('e.entity_id')
+ ->limit($limit);
+ $bind = array(
+ 'attribute_id' => $priceType->getAttributeId(),
+ 'last_entity_id' => $lastEntityId
+ );
+ return $this->_getReadAdapter()->fetchPairs($select, $bind);
+ }
+
+ /**
+ * Reindex Bundle product Price Index
+ *
+ * @param Mage_Core_Model_Product|Mage_Catalog_Model_Product_Condition_Interface|array|int $products
+ * @return Mage_Bundle_Model_Resource_Price_Index
+ */
+ public function reindex($products = null)
+ {
+ $lastEntityId = 0;
+ while (true) {
+ $productsData = $this->getProducts($products, $lastEntityId);
+ if (!$productsData) {
+ break;
+ }
+
+ foreach ($productsData as $productId => $priceType) {
+ $this->_reindexProduct($productId, $priceType);
+ $lastEntityId = $productId;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Reindex product price
+ *
+ * @param int $productId
+ * @param int $priceType
+ * @return Mage_Bundle_Model_Resource_Price_Index
+ */
+ protected function _reindexProduct($productId, $priceType)
+ {
+ $options = $this->getSelections($productId);
+ $selectionProducts = array();
+ foreach ($options as $option) {
+ foreach ($option['selections'] as $selection) {
+ $selectionProducts[$selection['product_id']] = $selection['product_id'];
+ }
+ }
+
+ $priceIndex = array();
+ if ($priceType == Mage_Bundle_Model_Product_Price::PRICE_TYPE_DYNAMIC) {
+ // load selection product prices from index for dynamic bundle
+ $priceIndex = $this->getProductsPriceFromIndex($selectionProducts);
+ }
+
+ foreach ($this->_getWebsites() as $website) {
+ if (!$website->getDefaultStore()) {
+ continue;
+ }
+ $salableStatus = $this->getProductsSalableStatus($selectionProducts, $website);
+ $priceData = $this->getProductsPriceData($productId, $website);
+ $priceData = $priceData[$productId];
+
+ /* @var $website Mage_Core_Model_Website */
+ foreach ($this->_getCustomerGroups() as $group) {
+ /* @var $group Mage_Customer_Model_Group */
+ if ($priceType == Mage_Bundle_Model_Product_Price::PRICE_TYPE_FIXED) {
+ $basePrice = $this->_getBasePrice($productId, $priceData, $website, $group);
+ $customOptions = $this->getCustomOptions($productId, $website);
+ } elseif ($priceType == Mage_Bundle_Model_Product_Price::PRICE_TYPE_DYNAMIC) {
+ $basePrice = 0;
+ }
+
+ list($minPrice, $maxPrice) = $this->_calculateBundleSelections($options, $salableStatus,
+ $productId, $priceType, $basePrice, $priceData, $priceIndex, $website, $group
+ );
+
+ if ($priceType == Mage_Bundle_Model_Product_Price::PRICE_TYPE_FIXED) {
+ list($minPrice, $maxPrice) =
+ $this->_calculateCustomOptions($customOptions, $basePrice, $minPrice, $maxPrice);
+ }
+
+ $this->_savePriceIndex($productId, $website->getId(), $group->getId(), $minPrice, $maxPrice);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Save price index
+ *
+ * @param int $productId
+ * @param int $websiteId
+ * @param int $groupId
+ * @param float $minPrice
+ * @param float $maxPrice
+ * @return Mage_Bundle_Model_Resource_Price_Index
+ */
+ protected function _savePriceIndex($productId, $websiteId, $groupId, $minPrice, $maxPrice)
+ {
+ $adapter = $this->_getWriteAdapter();
+ $adapter->beginTransaction();
+ $bind = array($productId, $websiteId, $groupId, $minPrice, $maxPrice);
+ $adapter->insertOnDuplicate($this->getMainTable(), $bind, array('min_price', 'max_price'));
+ $adapter->commit();
+
+ return $this;
+ }
+
+ /**
+ * Retrieve bundle options with selections and prices by product
+ *
+ * @param int $productId
+ * @return array
+ */
+ public function getSelections($productId)
+ {
+ $options = array();
+ $read = $this->_getReadAdapter();
+ $select = $read->select()
+ ->from(
+ array('option_table' => $this->getTable('bundle/option')),
+ array('option_id', 'required', 'type')
+ )
+ ->join(
+ array('selection_table' => $this->getTable('bundle/selection')),
+ 'selection_table.option_id=option_table.option_id',
+ array('selection_id', 'product_id', 'selection_price_type',
+ 'selection_price_value', 'selection_qty', 'selection_can_change_qty')
+ )
+ ->join(
+ array('e' => $this->getTable('catalog/product')),
+ 'e.entity_id=selection_table.product_id AND e.required_options=0',
+ array()
+ )
+ ->where('option_table.parent_id=:product_id');
+
+ $query = $read->query($select, array('product_id' => $productId));
+ while ($row = $query->fetch()) {
+ if (!isset($options[$row['option_id']])) {
+ $options[$row['option_id']] = array(
+ 'option_id' => $row['option_id'],
+ 'required' => $row['required'],
+ 'type' => $row['type'],
+ 'selections' => array()
+ );
+ }
+ $options[$row['option_id']]['selections'][$row['selection_id']] = array(
+ 'selection_id' => $row['selection_id'],
+ 'product_id' => $row['product_id'],
+ 'price_type' => $row['selection_price_type'],
+ 'price_value' => $row['selection_price_value'],
+ 'qty' => $row['selection_qty'],
+ 'can_change_qty' => $row['selection_can_change_qty']
+ );
+ }
+
+ return $options;
+ }
+
+ /**
+ * Retrieve salable product statuses
+ *
+ * @param int|array $products
+ * @param Mage_Core_Model_Website $website
+ * @return array
+ */
+ public function getProductsSalableStatus($products, Mage_Core_Model_Website $website)
+ {
+ $read = $this->_getReadAdapter();
+ $productsData = array();
+ $select = $read->select()
+ ->from(array('e' => $this->getTable('catalog/product')), 'entity_id')
+ ->where('e.entity_id IN(?)', $products);
+ // add belong to website
+ $select->joinLeft(
+ array('pw' => $this->getTable('catalog/product_website')),
+ 'e.entity_id=pw.product_id AND pw.website_id=:website_id',
+ array('pw.website_id')
+ );
+
+ $store = $website->getDefaultStore();
+
+ // add product status
+ $status = $this->_getAttribute('status');
+ if ($status->isScopeGlobal()) {
+ $select->join(
+ array('t_status' => $status->getBackend()->getTable()),
+ 'e.entity_id=t_status.entity_id'
+ . ' AND t_status.attribute_id=:status_attribute_id'
+ . ' AND t_status.store_id=0',
+ array('status' => 't_status.value')
+ );
+ } else {
+
+ $statusField = $read->getCheckSql(
+ 't2_status.value_id > 0',
+ 't2_status.value',
+ 't1_status.value'
+ );
+
+ $statusTable = $status->getBackend()->getTable();
+ $select->join(
+ array('t1_status' => $statusTable),
+ 'e.entity_id=t1_status.entity_id'
+ . ' AND t1_status.attribute_id=:status_attribute_id'
+ . ' AND t1_status.store_id=0',
+ array('status' => $statusField)
+ )
+ ->joinLeft(
+ array('t2_status' => $statusTable),
+ 't1_status.entity_id = t2_status.entity_id'
+ . ' AND t1_status.attribute_id = t2_status.attribute_id'
+ . ' AND t2_status.store_id=:store_id',
+ array()
+ );
+ }
+
+ $bind = array(
+ 'status_attribute_id' => $status->getAttributeId(),
+ 'website_id' => $website->getId(),
+ 'store_id' => $store->getId()
+ );
+
+ Mage::dispatchEvent('catalog_product_prepare_index_select', array(
+ 'website' => $website,
+ 'select' => $select,
+ 'bind' => $bind
+ ));
+
+ $query = $read->query($select, $bind);
+ while ($row = $query->fetch()) {
+ $salable = isset($row['salable']) ? $row['salable'] : true;
+ $website = $row['website_id'] > 0 ? true : false;
+ $status = $row['status'];
+
+ $productsData[$row['entity_id']] = $salable && $status && $website;
+ }
+
+ return $productsData;
+ }
+
+ /**
+ * Retrieve Selection Product price from Price Index
+ * Return index key {entity_id}-{website_id}-{customer_group_id}
+ *
+ * @param int|array $productIds
+ * @return array
+ */
+ public function getProductsPriceFromIndex($productIds)
+ {
+ $price = $this->_getAttribute('price');
+ $read = $this->_getReadAdapter();
+ $key = $read->getConcatSql(array('entity_id', 'customer_group_id', 'website_id'), '-');
+
+ $select = $read->select()
+ ->from(
+ array('price_index' => $this->getTable('catalogindex/price')),
+ array('index_key' => $key, 'value')
+ )
+ ->where('entity_id IN(?)', $productIds)
+ ->where('attribute_id= :attribute_id');
+ $index = $read->fetchPairs($select, array('attribute_id' => $price->getAttributeId()));
+ return $index;
+ }
+
+ /**
+ * Retrieve product(s) price data
+ *
+ * @param int|array $products
+ * @param Mage_Core_Model_Website $website
+ * @return array
+ */
+ public function getProductsPriceData($products, Mage_Core_Model_Website $website)
+ {
+ $productsData = array();
+ $read = $this->_getReadAdapter();
+ $select = $read->select()
+ ->from(array('e' => $this->getTable('catalog/product')), 'entity_id')
+ ->where('e.entity_id IN(?)', $products);
+
+ $this->_addAttributeDataToSelect($select, 'price', $website);
+ $this->_addAttributeDataToSelect($select, 'special_price', $website);
+ $this->_addAttributeDataToSelect($select, 'special_from_date', $website);
+ $this->_addAttributeDataToSelect($select, 'special_to_date', $website);
+
+ $query = $read->query($select);
+ while ($row = $query->fetch()) {
+ $productsData[$row['entity_id']] = array(
+ 'price' => $row['price'],
+ 'special_price' => $row['special_price'],
+ 'special_from_date' => $row['special_from_date'],
+ 'special_to_date' => $row['special_to_date']
+ );
+ }
+
+ return $productsData;
+ }
+
+ /**
+ * Add attribute data to select
+ *
+ * @param Varien_Db_Select $select
+ * @param string $attributeCode
+ * @param Mage_Core_Model_Website $website
+ * @return Mage_Bundle_Model_Resource_Price_Index
+ */
+ protected function _addAttributeDataToSelect(Varien_Db_Select $select, $attributeCode,
+ Mage_Core_Model_Website $website)
+ {
+ $attribute = $this->_getAttribute($attributeCode);
+ $store = $website->getDefaultStore();
+ if ($attribute->isScopeGlobal()) {
+ $table = 't_' . $attribute->getAttributeCode();
+ $select->joinLeft(
+ array($table => $attribute->getBackend()->getTable()),
+ "e.entity_id={$table}.entity_id"
+ . " AND {$table}.attribute_id={$attribute->getAttributeId()}"
+ . " AND {$table}.store_id=0",
+ array($attribute->getAttributeCode() => $table . '.value')
+ );
+ } else {
+ $tableName = $attribute->getBackend()->getTable();
+ $tableGlobal = 't1_' . $attribute->getAttributeCode();
+ $tableStore = 't2_' . $attribute->getAttributeCode();
+
+ $attributeCond = $this->getReadConnection()->getCheckSql(
+ $tableStore . '.value_id > 0',
+ $tableStore . '.value',
+ $tableGlobal . '.value'
+ );
+ $select->joinLeft(
+ array($tableGlobal => $tableName),
+ "e.entity_id = {$tableGlobal}.entity_id"
+ . " AND {$tableGlobal}.attribute_id = {$attribute->getAttributeId()}"
+ . " AND {$tableGlobal}.store_id = 0",
+ array($attribute->getAttributeCode() => $attributeCond)
+ )
+ ->joinLeft(
+ array($tableStore => $tableName),
+ "{$tableGlobal}.entity_id = {$tableStore}.entity_id"
+ . " AND {$tableGlobal}.attribute_id = {$tableStore}.attribute_id"
+ . " AND {$tableStore}.store_id = " . $store->getId(),
+ array()
+ );
+ }
+ return $this;
+ }
+
+ /**
+ * Retrieve fixed bundle base price (with special price and rules)
+ *
+ * @param int $productId
+ * @param array $priceData
+ * @param Mage_Core_Model_Website $website
+ * @param Mage_Customer_Model_Group $customerGroup
+ * @return float
+ */
+ protected function _getBasePrice($productId, array $priceData, $website, $customerGroup)
+ {
+ $store = $website->getDefaultStore();
+ $storeTimeStamp = Mage::app()->getLocale()->storeTimeStamp($store);
+ $finalPrice = $this->_calculateSpecialPrice($priceData['price'], $priceData, $website);
+
+ $rulePrice = Mage::getResourceModel('catalogrule/rule')
+ ->getRulePrice($storeTimeStamp, $website->getId(), $customerGroup->getId(), $productId);
+
+ if ($rulePrice !== null && $rulePrice !== false) {
+ $finalPrice = min($finalPrice, $rulePrice);
+ }
+
+ return $finalPrice;
+ }
+
+ /**
+ * Retrieve custom options for product
+ *
+ * @param int $productId
+ * @param Mage_Core_Model_Website $website
+ * @return array
+ */
+ public function getCustomOptions($productId, Mage_Core_Model_Website $website)
+ {
+ $options = array();
+ $store = $website->getDefaultStore();
+ $price = $this->_getAttribute('price');
+ $adapter = $this->_getReadAdapter();
+
+ $bind = array(
+ ':product_id' => $productId,
+ ':store_id' => $store->getId(),
+
+ );
+ $select = $adapter->select()
+ ->from(
+ array('option_table' => $this->getTable('catalog/product_option')),
+ array('option_id', 'is_require', 'type')
+ )
+ ->where('option_table.product_id=:product_id');
+
+ if ($price->isScopeGlobal()) {
+ $select->join(
+ array('price_table' => $this->getTable('catalog/product_option_price')),
+ 'option_table.option_id = price_table.option_id' .
+ ' AND price_table.store_id = 0',
+ array('value_id' => 'option_price_id', 'price', 'price_type')
+ );
+ } else {
+ $valueIdCond = $adapter->getCheckSql(
+ 'price_store_table.option_price_id IS NOT NULL',
+ 'price_store_table.option_price_id',
+ 'price_global_table.option_price_id'
+ );
+ $priceCond = $adapter->getCheckSql(
+ 'price_store_table.price IS NOT NULL',
+ 'price_store_table.price',
+ 'price_global_table.price'
+ );
+ $priceTypeCond = $adapter->getCheckSql(
+ 'price_store_table.price_type IS NOT NULL',
+ 'price_store_table.price_type',
+ 'price_global_table.price_type'
+ );
+
+ $select
+ ->join(
+ array('price_global_table' => $this->getTable('catalog/product_option_price')),
+ 'option_table.option_id=price_global_table.option_id' .
+ ' AND price_global_table.store_id=0',
+ array(
+ 'value_id' => $valueIdCond,
+ 'price' => $priceCond,
+ 'price_type' => $priceTypeCond
+ ))
+ ->joinLeft(
+ array('price_store_table' => $this->getTable('catalog/product_option_price')),
+ 'option_table.option_id = price_store_table.option_id' .
+ ' AND price_store_table.store_id=:store_id',
+ array()
+ );
+ }
+
+ $query = $adapter->query($select, $bind);
+ while ($row = $query->fetch()) {
+ if (!isset($options[$row['option_id']])) {
+ $options[$row['option_id']] = array(
+ 'option_id' => $row['option_id'],
+ 'is_require' => $row['is_require'],
+ 'type' => $row['type'],
+ 'values' => array()
+ );
+ }
+ $options[$row['option_id']]['values'][$row['value_id']] = array(
+ 'price_type' => $row['price_type'],
+ 'price_value' => $row['price']
+ );
+ }
+
+ $select = $adapter->select()
+ ->from(
+ array('option_table' => $this->getTable('catalog/product_option')),
+ array('option_id', 'is_require', 'type')
+ )
+ ->join(
+ array('type_table' => $this->getTable('catalog/product_option_type_value')),
+ 'option_table.option_id=type_table.option_id',
+ array()
+ )
+ ->where('option_table.product_id=:product_id');
+
+ if ($price->isScopeGlobal()) {
+ $select->join(
+ array('price_table' => $this->getTable('catalog/product_option_type_price')),
+ 'type_table.option_type_id=price_table.option_type_id' .
+ ' AND price_table.store_id=0',
+ array('value_id' => 'option_type_id', 'price', 'price_type')
+ );
+ } else {
+ $select
+ ->join(
+ array('price_global_table' => $this->getTable('catalog/product_option_type_price')),
+ 'type_table.option_type_id=price_global_table.option_type_id' .
+ ' AND price_global_table.store_id=0',
+ array(
+ 'value_id' => $valueIdCond,
+ 'price' => $priceCond,
+ 'price_type' => $priceTypeCond
+ )
+ )
+ ->joinLeft(
+ array('price_store_table' => $this->getTable('catalog/product_option_type_price')),
+ 'type_table.option_type_id=price_store_table.option_type_id' .
+ ' AND price_store_table.store_id=:store_id',
+ array()
+ );
+ }
+
+ $query = $adapter->query($select, $bind);
+ while ($row = $query->fetch()) {
+ if (!isset($options[$row['option_id']])) {
+ $options[$row['option_id']] = array(
+ 'option_id' => $row['option_id'],
+ 'is_require' => $row['is_require'],
+ 'type' => $row['type'],
+ 'values' => array()
+ );
+ }
+ $options[$row['option_id']]['values'][$row['value_id']] = array(
+ 'price_type' => $row['price_type'],
+ 'price_value' => $row['price']
+ );
+ }
+
+ return $options;
+ }
+
+ /**
+ * Calculate custom options price
+ * Return array with indexes(0 -> min_price, 1 -> max_price)
+ *
+ * @param array $options
+ * @param float $basePrice
+ * @param float $minPrice
+ * @param float $maxPrice
+ * @return array
+ */
+ public function _calculateCustomOptions(array $options, $basePrice, $minPrice, $maxPrice)
+ {
+ foreach ($options as $option) {
+ $optionPrices = array();
+ foreach ($option['values'] as $value) {
+ if ($value['price_type'] == 'percent') {
+ $valuePrice = $basePrice * $value['price_value'] / 100;
+ } else {
+ $valuePrice = $value['price_value'];
+ }
+ $optionPrices[] = $valuePrice;
+ }
+ if ($option['is_require']) {
+ $minPrice += min($optionPrices);
+ }
+ $multiTypes = array(
+ Mage_Catalog_Model_Product_Option::OPTION_TYPE_DROP_DOWN,
+ Mage_Catalog_Model_Product_Option::OPTION_TYPE_CHECKBOX,
+ Mage_Catalog_Model_Product_Option::OPTION_TYPE_MULTIPLE
+ );
+ if ($optionPrices) {
+ if (in_array($option['type'], $multiTypes)) {
+ $maxPrice += array_sum($optionPrices);
+ } else {
+ $maxPrice += max($optionPrices);
+ }
+ }
+ }
+
+ return array($minPrice, $maxPrice);
+ }
+
+ /**
+ * Calculate minimal and maximal price for bundle selections
+ * Return array with prices (0 -> min_price, 1 -> max_price)
+ *
+ * @param array $options
+ * @param array $salableStatus
+ * @param int $productId
+ * @param int $priceType
+ * @param float $basePrice
+ * @param array $priceData
+ * @param array $priceIndex
+ * @param Mage_Core_Model_Website $website
+ * @param Mage_Customer_Model_Group $group
+ * @return array
+ */
+ public function _calculateBundleSelections(array $options, array $salableStatus, $productId, $priceType, $basePrice,
+ $priceData, $priceIndex, $website, $group)
+ {
+ $minPrice = $maxPrice = $basePrice;
+ $optPrice = 0;
+
+ foreach ($options as $option) {
+ $optionPrices = array();
+ foreach ($option['selections'] as $selection) {
+ if (!$selection['product_id']) {
+ continue;
+ }
+
+ if (!$salableStatus[$selection['product_id']]) {
+ continue;
+ }
+
+ if ($priceType == Mage_Bundle_Model_Product_Price::PRICE_TYPE_FIXED) {
+ $basePrice = $this->_getBasePrice($productId, $priceData, $website, $group);
+ }
+
+ // calculate selection price
+ if ($priceType == Mage_Bundle_Model_Product_Price::PRICE_TYPE_DYNAMIC) {
+ $priceIndexKey = join('-', array(
+ $selection['product_id'],
+ $website->getId(),
+ $group->getId()
+ ));
+
+ $selectionPrice = isset($priceIndex[$priceIndexKey]) ? $priceIndex[$priceIndexKey] : 0;
+ $selectionPrice = $this->_calculateSpecialPrice($selectionPrice, $priceData, $website);
+ } else {
+ if ($selection['price_type']) { // percent
+ $selectionPrice = $basePrice * $selection['price_value'] / 100;
+ } else {
+ $selectionPrice = $this->_calculateSpecialPrice($selection['price_value'],
+ $priceData, $website);
+ }
+ }
+
+ // calculate selection qty
+ if ($selection['can_change_qty'] && $option['type'] != 'multi' && $option['type'] != 'checkbox') {
+ $qty = 1;
+ } else {
+ $qty = $selection['qty'];
+ }
+
+ $selectionPrice = $selectionPrice * $qty;
+ $optionPrices[$selection['selection_id']] = $selectionPrice;
+ }
+
+ if ($optionPrices) {
+ if ($option['required']) {
+ $minPrice += min($optionPrices);
+ } else {
+ $optPrice = $optPrice && $optPrice < min($optionPrices) ? $optPrice : min($optionPrices);
+ }
+ if (in_array($option['type'], array('multi', 'checkbox'))) {
+ $maxPrice += array_sum($optionPrices);
+ } else {
+ $maxPrice += max($optionPrices);
+ }
+ }
+ }
+
+ if ($minPrice == 0) {
+ $minPrice = $optPrice;
+ }
+ return array($minPrice, $maxPrice);
+ }
+
+ /**
+ * Apply special price
+ *
+ * @param float $finalPrice
+ * @param array $priceData
+ * @param Mage_Core_Model_Website $website
+ * @return float
+ */
+ public function _calculateSpecialPrice($finalPrice, array $priceData, Mage_Core_Model_Website $website)
+ {
+ $store = $website->getDefaultStore();
+ $specialPrice = $priceData['special_price'];
+
+ if (!is_null($specialPrice) && $specialPrice != false) {
+ if (Mage::app()->getLocale()->isStoreDateInInterval($store, $priceData['special_from_date'],
+ $priceData['special_to_date'])) {
+ $specialPrice = ($finalPrice * $specialPrice) / 100;
+ $finalPrice = min($finalPrice, $specialPrice);
+ }
+ }
+
+ return $finalPrice;
+ }
+
+ /**
+ * Retrieve price index for products
+ *
+ * @param int|array $productIds
+ * @param int $websiteId
+ * @param int $groupId
+ * @return array
+ */
+ public function loadPriceIndex($productIds, $websiteId, $groupId)
+ {
+ $prices = array();
+ $adapter = $this->_getReadAdapter();
+ $select = $adapter->select()
+ ->from(
+ array('pi' => $this->getMainTable()),
+ array('entity_id', 'min_price', 'max_price')
+ )
+ ->where('entity_id IN(?)', $productIds)
+ ->where('website_id=:website_id')
+ ->where('customer_group_id=:group_id');
+ $bind = array(
+ 'website_id' => $websiteId,
+ 'group_id' => $groupId
+ );
+ $query = $adapter->query($select, $bind);
+ while ($row = $query->fetch()) {
+ $prices[$row['entity_id']] = array(
+ 'min_price' => $row['min_price'],
+ 'max_price' => $row['max_price']
+ );
+ }
+
+ return $prices;
+ }
+}
--
1.8.4.2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment