<?php /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\SalesRule\Model; use Magento\Quote\Model\Quote\Address; use Magento\SalesRule\Model\Quote\ChildrenValidationLocator; use Magento\Framework\App\ObjectManager; use Magento\SalesRule\Model\Rule\Action\Discount\CalculatorFactory; /** * Class RulesApplier * @package Magento\SalesRule\Model\Validator */ class RulesApplier { /** * Application Event Dispatcher * * @var \Magento\Framework\Event\ManagerInterface */ protected $_eventManager; /** * @var \Magento\SalesRule\Model\Utility */ protected $validatorUtility; /** * @var ChildrenValidationLocator */ private $childrenValidationLocator; /** * @var CalculatorFactory */ private $calculatorFactory; /** * @param \Magento\SalesRule\Model\Rule\Action\Discount\CalculatorFactory $calculatorFactory * @param \Magento\Framework\Event\ManagerInterface $eventManager * @param \Magento\SalesRule\Model\Utility $utility * @param ChildrenValidationLocator|null $childrenValidationLocator */ public function __construct( \Magento\SalesRule\Model\Rule\Action\Discount\CalculatorFactory $calculatorFactory, \Magento\Framework\Event\ManagerInterface $eventManager, \Magento\SalesRule\Model\Utility $utility, ChildrenValidationLocator $childrenValidationLocator = null ) { $this->calculatorFactory = $calculatorFactory; $this->validatorUtility = $utility; $this->_eventManager = $eventManager; $this->childrenValidationLocator = $childrenValidationLocator ?: ObjectManager::getInstance()->get(ChildrenValidationLocator::class); } /** * Apply rules to current order item * * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item * @param \Magento\SalesRule\Model\ResourceModel\Rule\Collection $rules * @param bool $skipValidation * @param mixed $couponCode * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function applyRules($item, $rules, $skipValidation, $couponCode) { $address = $item->getAddress(); $appliedRuleIds = []; /* @var $rule \Magento\SalesRule\Model\Rule */ foreach ($rules as $rule) { if (!$this->validatorUtility->canProcessRule($rule, $address)) { continue; } if (!$skipValidation && !$rule->getActions()->validate($item)) { if (!$this->childrenValidationLocator->isChildrenValidationRequired($item)) { continue; } $childItems = $item->getChildren(); $isContinue = true; if (!empty($childItems)) { foreach ($childItems as $childItem) { if ($rule->getActions()->validate($childItem)) { $isContinue = false; } } } if ($isContinue) { continue; } } $this->applyRule($item, $rule, $address, $couponCode); $appliedRuleIds[$rule->getRuleId()] = $rule->getRuleId(); if ($rule->getStopRulesProcessing()) { break; } } return $appliedRuleIds; } /** * Add rule discount description label to address object * * @param Address $address * @param \Magento\SalesRule\Model\Rule $rule * @return $this */ public function addDiscountDescription($address, $rule) { $description = $address->getDiscountDescriptionArray(); $ruleLabel = $rule->getStoreLabel($address->getQuote()->getStore()); $label = ''; if ($ruleLabel) { $label = $ruleLabel; } else { if (strlen($address->getCouponCode())) { $label = $address->getCouponCode(); } } if (strlen($label)) { $description[$rule->getId()] = $label; } $address->setDiscountDescriptionArray($description); return $this; } /** * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item * @param \Magento\SalesRule\Model\Rule $rule * @param \Magento\Quote\Model\Quote\Address $address * @param mixed $couponCode * @return $this */ protected function applyRule($item, $rule, $address, $couponCode) { $discountData = $this->getDiscountData($item, $rule); $this->setDiscountData($discountData, $item); $this->maintainAddressCouponCode($address, $rule, $couponCode); $this->addDiscountDescription($address, $rule); return $this; } /** * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item * @param \Magento\SalesRule\Model\Rule $rule * @return \Magento\SalesRule\Model\Rule\Action\Discount\Data */ protected function getDiscountData($item, $rule) { $qty = $this->validatorUtility->getItemQty($item, $rule); $discountCalculator = $this->calculatorFactory->create($rule->getSimpleAction()); $qty = $discountCalculator->fixQuantity($qty, $rule); $discountData = $discountCalculator->calculate($rule, $item, $qty); $this->eventFix($discountData, $item, $rule, $qty); $this->validatorUtility->deltaRoundingFix($discountData, $item); /** * We can't use row total here because row total not include tax * Discount can be applied on price included tax */ $this->validatorUtility->minFix($discountData, $item, $qty); return $discountData; } /** * @param \Magento\SalesRule\Model\Rule\Action\Discount\Data $discountData * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item * @return $this */ protected function setDiscountData($discountData, $item) { $item->setDiscountAmount($discountData->getAmount()); $item->setBaseDiscountAmount($discountData->getBaseAmount()); $item->setOriginalDiscountAmount($discountData->getOriginalAmount()); $item->setBaseOriginalDiscountAmount($discountData->getBaseOriginalAmount()); return $this; } /** * Set coupon code to address if $rule contains validated coupon * * @param Address $address * @param \Magento\SalesRule\Model\Rule $rule * @param mixed $couponCode * @return $this */ public function maintainAddressCouponCode($address, $rule, $couponCode) { /* Rule is a part of rules collection, which includes only rules with 'No Coupon' type or with validated coupon. As a result, if rule uses coupon code(s) ('Specific' or 'Auto' Coupon Type), it always contains validated coupon */ if ($rule->getCouponType() != \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON) { $address->setCouponCode($couponCode); } return $this; } /** * Fire event to allow overwriting of discount amounts * * @param \Magento\SalesRule\Model\Rule\Action\Discount\Data $discountData * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item * @param \Magento\SalesRule\Model\Rule $rule * @param float $qty * @return $this */ protected function eventFix( \Magento\SalesRule\Model\Rule\Action\Discount\Data $discountData, \Magento\Quote\Model\Quote\Item\AbstractItem $item, \Magento\SalesRule\Model\Rule $rule, $qty ) { $quote = $item->getQuote(); $address = $item->getAddress(); $this->_eventManager->dispatch( 'salesrule_validator_process', [ 'rule' => $rule, 'item' => $item, 'address' => $address, 'quote' => $quote, 'qty' => $qty, 'result' => $discountData ] ); return $this; } /** * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item * @param int[] $appliedRuleIds * @return $this */ public function setAppliedRuleIds(\Magento\Quote\Model\Quote\Item\AbstractItem $item, array $appliedRuleIds) { $address = $item->getAddress(); $quote = $item->getQuote(); $item->setAppliedRuleIds(join(',', $appliedRuleIds)); $address->setAppliedRuleIds($this->validatorUtility->mergeIds($address->getAppliedRuleIds(), $appliedRuleIds)); $quote->setAppliedRuleIds($this->validatorUtility->mergeIds($quote->getAppliedRuleIds(), $appliedRuleIds)); return $this; } }