first of all: prefer events to rewrites, prefer rewrites to monkey patches and never touch the core unless you are a Magento core developer :)
So said, let's decompose the problem into two sub problems:
- Let's identify the event we can observe to add the free Y product if user buy X product.
- Let's Make sure that an automatically added free Y product can coexist with a manually added (and non free) Y product; in other words: differentiate quote items referring to the same product.
Identify the event
The most appropriate event is sales_quote_collect_totals_before
because it is called not only when an item is manually added by the user to the cart but also when the item is added in a programmatic manner, for example when the user logs in and the quote merge occurs.
We should pay attention to the fact that this event is called not only when adding a product to cart, so we should keep track of the fact that we already added the gift.
Differentiate quote items
How Magento determines whether two quote items are the same?
- They have the same
product_id
- They have the same
parent_id
(if any)
- They have the same custom options
As far as I understand from your description, the free Y and the non-free Y have the same product_id
and are simple products (so they don't have a parent_id
).
Thus we will differentiate them by adding dynamic custom options to the quote item representing the gift.
Implementation steps
Here are the steps needed to implement our solution.
Activate a custom module
Create the following {magento_install_dir}/app/etc/modules/Yourname_Yourmodule.xml
file:
<?xml version="1.0"?>
<config>
<modules>
<Yourname_Yourmodule>
<active>true</active>
<codePool>local</codePool>
</Yourname_Yourmodule>
</modules>
</config>
Note: substitute Yourname
with your vendor name and Yourmodule
with your module name.
Note 2: if you intend to distribute the extension prefer the community
code pool.
Define the module configuration
Create the following {magento_install_dir}/app/code/local/Yourname/Yourmodule/etc/config.xml
file:
<?xml version="1.0"?>
<config>
<modules>
<Yourname_Yourmodule>
<version>0.1.0</version>
</Yourname_Yourmodule>
</modules>
<global>
<models>
<yourname_yourmodule>
<class>Yourname_Yourmodule_Model</class>
<resourceModel>yourname_yourmodule_resource</resourceModel>
</yourname_yourmodule>
<yourname_yourmodule_resource>
<class>Yourname_Yourmodule_Model_Resource</class>
</yourname_yourmodule_resource>
</models>
<helpers>
<yourname_yourmodule>
<class>Yourname_Yourmodule_Helper</class>
</yourname_yourmodule>
</helpers>
</global>
<frontend>
<events>
<sales_quote_collect_totals_before>
<observers>
<yourname_yourmodule>
<type>singleton</type>
<class>yourname_yourmodule/observer_addFreeProductObserver</class>
<method>execute</method>
</yourname_yourmodule>
</observers>
</sales_quote_collect_totals_before>
</events>
</frontend>
<default>
<!-- Default configuration values -->
<yourname_yourmodule>
<general>
<active>1</active>
<rewarding_product_id>418</rewarding_product_id>
<rewarded_product_id>906</rewarded_product_id>
</general>
</yourname_yourmodule>
</default>
</config>
Note 1: I'm observing the event only in frontend area; if the store manager creates an order in the Admin Panel, our logic won't be applied.
Note 2: I'm using Magento 2 convention for observers: a class associated to each event whose name ends with Observer
suffix and with a single responsibility performed by the execute()
method.
Note 3: even if I'm not providing system.xml
for module configuration I set module configuration default values through the <default>
XML node.
Implement the helper
Our helper is a simple gateway towards system configuration.
Create the following {magento_install_dir}/app/code/local/Yourname/Yourmodule/Helper/Data.php
file:
<?php
class Yourname_Yourmodule_Helper_Data extends Mage_Core_Helper_Abstract
{
/**
* XML Paths for General configuration constants
*/
const XML_PATH_IS_ACTIVE = 'yourname_yourmodule/general/active';
const XML_PATH_REWARDING_PRODUCT_ID = 'yourname_yourmodule/general/rewarding_product_id';
const XML_PATH_FREE_PRODUCT_ID = 'yourname_yourmodule/general/free_product_id';
public function isActive()
{
return Mage::getStoreConfigFlag(self::XML_PATH_IS_ACTIVE);
}
public function getRewardingProductId()
{
return Mage::getStoreConfig(self::XML_PATH_REWARDING_PRODUCT_ID);
}
public function getFreeProductId()
{
return Mage::getStoreConfig(self::XML_PATH_FREE_PRODUCT_ID);
}
}
Implement the observer
Here are the logical steps of our observer:
- Check whether the module is activated, otherwise do nothing
- If X is not in cart remove Y (if present)
- If both X and free Y are in cart, update Y's quantity according to X's
- If the X product is in cart but the free Y is not, add free Y
Create the following {magento_install_dir}/app/code/local/Yourname/Yourmodule/Model/Observer/AddFreeProductObserver.php
file:
<?php
class Yourname_Yourmodule_Model_Observer_AddFreeProductObserver
{
public function execute(Varien_Event_Observer $observer)
{
// Check whether the module is activated, otherwise do nothing
/** @var Yourname_Yourmodule_Helper_Data $helper */
$helper = Mage::helper('yourname_yourmodule');
if (!$helper->isActive()) {
return;
}
// Check whether rewarding X product is in cart, otherwise do nothing
$rewardingProductId = $helper->getRewardingProductId();
$rewardingProductName = '';
$rewardingProductItemQty = 0;
$freeProductId = $helper->getFreeProductId();
$freeProductItemId = null;
/** @var Mage_Sales_Model_Quote $quote */
$quote = $observer
->getQuote()
->setIsSuperMode(true); // note: this ignores stock checks
/** @var Mage_Sales_Model_Quote_Item $item */
foreach ($quote->getAllVisibleItems() as $item) {
if ($item->getProductId() == $rewardingProductId) {
$rewardingProductName = $item->getName();
$rewardingProductItemQty = $item->getQty();
continue;
}
if ($item->getProductId() == $freeProductId) {
/** @var Mage_Sales_Model_Quote_Item_Option $customOptionValue */
if ($customOptionValue = $item->getOptionByCode('additional_options')) {
$value = $customOptionValue->getValue() ? unserialize($customOptionValue->getValue()) : array();
if (isset($value['free_message'])) {
$freeProductItemId = $item->getId();
}
}
continue;
}
}
if (!$rewardingProductItemQty) {
// If the free Y product is in cart, remove it
if ($freeProductItemId) {
$quote->removeItem($freeProductItemId);
}
return;
}
// If free Y is already in cart update Y's quantity according to X's
if ($freeProductItemId) {
$quote
->getItemById($freeProductItemId)
->setQty($rewardingProductItemQty);
return;
}
// The X product is in cart but the free Y is not: add free Y
$freeProduct = Mage::getModel('catalog/product')->load($freeProductId);
if ($freeProduct->getId()) {
$customOptionValue = $freeProduct->getCustomOption('additional_option');
if ($customOptionValue) {
$customOptionValue = unserialize($customOptionValue);
} else {
$customOptionValue = array();
}
if (isset($customOptionValue['free_message'])) {
return;
}
$customOptionValue = array_merge(
$customOptionValue,
array(
'free_message' => array(
'label' => $helper->__('Free'),
'value' => $helper->__('One free each %s', $rewardingProductName),
)
)
);
$freeProduct->addCustomOption('additional_options', serialize($customOptionValue));
$quote
->addProductAdvanced($freeProduct, 1)
->setCustomPrice(0)
->setOriginalCustomPrice(0);
}
}
}
Conclusion
I've not tested this solution too much, consider it a guiding example and do your tests to be sure it works the way you desire.
Hope it helps.
Best Answer
Please Check https://github.com/lillik/magento2-price-decimal Extension. I think it'll help you.