I need to apply discount on another different product Y based on X Product Step Qty. So that why I was created custom simple action in sales rule. I created custom action class for calculation discount.
see my code below I share
Vendor\Module\etc\adminhtml\di.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\SalesRule\Model\Rule\Metadata\ValueProvider">
<plugin name="salesrule-plugin" type="Vendor\Module\Plugin\Rule\Metadata\ValueProvider" sortOrder="1" />
</type>
</config>
Vendor\Module\Plugin\Rule\Metadata\ValueProvider
<?php
namespace Vendor\Module\Plugin\Rule\Metadata;
class ValueProvider {
public function afterGetMetadataValues(
\Magento\SalesRule\Model\Rule\Metadata\ValueProvider $subject,
$result
) {
$applyOptions = [
'label' => __('Custom'),
'value' => [
[
'label' => 'Buy X Get next Y with M% discount',
'value' => 'buy_x_get_next_y_with_percent',
]
],
];
array_push($result['actions']['children']['simple_action']['arguments']['data']['config']['options'], $applyOptions);
return $result;
}
}
vendor\module\etc\di.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\SalesRule\Model\Rule\Action\Discount\CalculatorFactory">
<arguments>
<argument name="discountRules" xsi:type="array">
<item name="buy_x_get_next_y_with_percent" xsi:type="string">\Vendor\Module\Model\Rule\Action\Discount\BuyXGetNextYWithPercent</item>
</argument>
</arguments>
</type>
</config>
Vendor\Module\Model\Rule\Action\Discount\BuyXGetNextYWithPercent
<?php
namespace Vendor\Module\Model\Rule\Action\Discount;
use Magento\SalesRule\Model\Rule\Action\Discount\AbstractDiscount;
/**
* Buy X Get next Y with M% discount (buy_x_get_next_y_with_percent)
*
* e.g. Rule :- Buy 3 Britannia Bourbon, Get 2 Good Day with 10% off
*
* Applicable Cases :-
* * case 1 :- Bourbon = 3 & good day = 2
* * case 2 :- Bourbon = 3 & good day = 7
* * case 3 :- Bourbon = 4 & good day = 2
* * case 4 :- Bourbon = 5 & good day = 3
* * case 5 :- Bourbon = 6 & good day = 4
* * case 6 :- Bourbon = 6 & good day = 15
* * case 7 :- Bourbon = 7 & good day = 4
*
*
* Not Applicable cases (discount = 0) :-
* * case 1 :- Bourbon = 2 & good day = 2
* * case 2 :- Bourbon = 3 & good day = 1
* * case 3 :- Bourbon = 6 & good day = 2
* * case 4 :- Bourbon = 6 & good day = 3
*
*/
class BuyXGetNextYWithPercent extends AbstractDiscount
{
/**
* Calculate Buy X Get next Y with M% discount amount
*
* @param \Magento\SalesRule\Model\Rule $rule
* @param \Magento\Quote\Model\Quote\Item\AbstractItem $item
* @param float $qty
* @return \Magento\SalesRule\Model\Rule\Action\Discount\Data
*/
public function calculate($rule, $item, $qty)
{
$rulePercent = min(100, $rule->getDiscountAmount());
$discountData = $this->_calculate($rule, $item, $qty, $rulePercent);
return $discountData;
}
/**
* @param \Magento\SalesRule\Model\Rule $rule
* @param \Magento\Quote\Model\Quote\Item\AbstractItem $item
* @param float $qty
* @param float $rulePercent
* @return Data
*/
protected function _calculate($rule, $item, $qty, $rulePercent)
{
/** @var \Magento\SalesRule\Model\Rule\Action\Discount\Data $discountData */
$discountData = $this->discountFactory->create();
$skus = [];
$conditions = $rule->getConditions();
if($conditions && gettype($conditions) === 'object')
{
$conditions = $conditions->getData();
if (isset($conditions['conditions'])) {
$conditions = $conditions['conditions'];
foreach ($conditions as $condition) {
if (isset($condition['conditions'])) {
$productConditions = $condition['conditions'];
foreach ($productConditions as $productCondition) {
if (isset($productCondition['attribute']) && $productCondition['attribute'] == "sku" && isset($productCondition['value'])) {
$skuValues = $productCondition['value'];
$skuValues = explode(",",$skuValues);
foreach ($skuValues as $skuValue) {
$skus[] = $skuValue;
}
}
}
}
}
}
}
$uniqSkus = array_unique($skus);
if(count($uniqSkus) > 0) {
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
/** @var \Magento\Checkout\Model\Session $checkoutSession */
$checkoutSession = $objectManager->get(\Magento\Checkout\Model\Session::class);
$quote = $checkoutSession->getQuote();
/** @var Magento\Quote\Model\Quote\Item\AbstractItem $quoteI */
foreach($quote->getItems() as $quoteI) {
if(in_array($quoteI->getSku(), $uniqSkus)) {
$getApplicableStepQty[] = $this->getApplicableDiscount($quoteI->getTotalQty(), $rule);
}
}
$applicableQty = min($getApplicableStepQty) * $rule->getYProductDiscountQty();
$maxDiscountQty = $rule->getDiscountQty();
if(! $maxDiscountQty) {
// if max qty is not set or zero
$applicableQty = min($applicableQty, $item->getTotalQty());
} else {
$applicableQty = min($applicableQty, $maxDiscountQty);
}
if($applicableQty > 0 && $item->getTotalQty() >= $applicableQty) {
$itemPrice = $this->validator->getItemPrice($item);
$baseItemPrice = $this->validator->getItemBasePrice($item);
$itemOriginalPrice = $this->validator->getItemOriginalPrice($item);
$baseItemOriginalPrice = $this->validator->getItemBaseOriginalPrice($item);
$_rulePct = $rulePercent / 100;
$discountData->setAmount(($applicableQty * $itemPrice - $item->getDiscountAmount()) * $_rulePct);
$discountData->setBaseAmount(($applicableQty * $baseItemPrice - $item->getBaseDiscountAmount()) * $_rulePct);
$discountData->setOriginalAmount(($applicableQty * $itemOriginalPrice - $item->getDiscountAmount()) * $_rulePct);
$discountData->setBaseOriginalAmount(
($applicableQty * $baseItemOriginalPrice - $item->getBaseDiscountAmount()) * $_rulePct
);
if ($maxDiscountQty >= $applicableQty || !$maxDiscountQty) {
$discountPercent = min(100, $item->getDiscountPercent() + $rulePercent);
$item->setDiscountPercent($discountPercent);
}
}
}
return $discountData;
}
/**
* get applicable qty for given qty
* @param float $qty
* @param \Magento\SalesRule\Model\Rule $rule
* @return float
*/
private function getApplicableDiscount($qty, $rule)
{
$step = $rule->getDiscountStep();
if ($step) {
$qty = floor($qty / $step);
}
return $qty;
}
}
Vendor\Module\view\adminhtml\ui_component\sales_rule_form.xml
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
<fieldset name="actions" sortOrder="30">
<field name="y_product_discount_qty" formElement="input">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="source" xsi:type="string">sales_rule</item>
<item name="sortOrder" xsi:type="number">4</item>
</item>
</argument>
<settings>
<validation>
<rule name="required-entry" xsi:type="boolean">true</rule>
<rule name="validate-number" xsi:type="boolean">true</rule>
<rule name="validate-greater-than-zero" xsi:type="boolean">true</rule>
</validation>
<dataType>text</dataType>
<label translate="true">Discount Qty (Get Y)</label>
<dataScope>y_product_discount_qty</dataScope>
</settings>
</field>
</fieldset>
</form>
now, In admin panel create cart price rule as same as below snapshot.
-: Issue :-
Magneto throw error of Exception #0 (LogicException): Infinite loop detected, review the trace for the looping path
at BuyXGetNextYWithPercent.php:91 line which is $checkoutSession->getQuote()
.
-: what i need :- I need to get quote data in Sales Rule custom action class without any Infinite loop error.
How can we get quote data without any issue in cart price rule?
OR
Please help me about how to discount apply on another product Y based on X Product Step Qty