RulesApplier.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\SalesRule\Model;
  7. use Magento\Quote\Model\Quote\Address;
  8. use Magento\SalesRule\Model\Quote\ChildrenValidationLocator;
  9. use Magento\Framework\App\ObjectManager;
  10. use Magento\SalesRule\Model\Rule\Action\Discount\CalculatorFactory;
  11. /**
  12. * Class RulesApplier
  13. * @package Magento\SalesRule\Model\Validator
  14. */
  15. class RulesApplier
  16. {
  17. /**
  18. * Application Event Dispatcher
  19. *
  20. * @var \Magento\Framework\Event\ManagerInterface
  21. */
  22. protected $_eventManager;
  23. /**
  24. * @var \Magento\SalesRule\Model\Utility
  25. */
  26. protected $validatorUtility;
  27. /**
  28. * @var ChildrenValidationLocator
  29. */
  30. private $childrenValidationLocator;
  31. /**
  32. * @var CalculatorFactory
  33. */
  34. private $calculatorFactory;
  35. /**
  36. * @param \Magento\SalesRule\Model\Rule\Action\Discount\CalculatorFactory $calculatorFactory
  37. * @param \Magento\Framework\Event\ManagerInterface $eventManager
  38. * @param \Magento\SalesRule\Model\Utility $utility
  39. * @param ChildrenValidationLocator|null $childrenValidationLocator
  40. */
  41. public function __construct(
  42. \Magento\SalesRule\Model\Rule\Action\Discount\CalculatorFactory $calculatorFactory,
  43. \Magento\Framework\Event\ManagerInterface $eventManager,
  44. \Magento\SalesRule\Model\Utility $utility,
  45. ChildrenValidationLocator $childrenValidationLocator = null
  46. ) {
  47. $this->calculatorFactory = $calculatorFactory;
  48. $this->validatorUtility = $utility;
  49. $this->_eventManager = $eventManager;
  50. $this->childrenValidationLocator = $childrenValidationLocator
  51. ?: ObjectManager::getInstance()->get(ChildrenValidationLocator::class);
  52. }
  53. /**
  54. * Apply rules to current order item
  55. *
  56. * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item
  57. * @param \Magento\SalesRule\Model\ResourceModel\Rule\Collection $rules
  58. * @param bool $skipValidation
  59. * @param mixed $couponCode
  60. * @return array
  61. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  62. */
  63. public function applyRules($item, $rules, $skipValidation, $couponCode)
  64. {
  65. $address = $item->getAddress();
  66. $appliedRuleIds = [];
  67. /* @var $rule \Magento\SalesRule\Model\Rule */
  68. foreach ($rules as $rule) {
  69. if (!$this->validatorUtility->canProcessRule($rule, $address)) {
  70. continue;
  71. }
  72. if (!$skipValidation && !$rule->getActions()->validate($item)) {
  73. if (!$this->childrenValidationLocator->isChildrenValidationRequired($item)) {
  74. continue;
  75. }
  76. $childItems = $item->getChildren();
  77. $isContinue = true;
  78. if (!empty($childItems)) {
  79. foreach ($childItems as $childItem) {
  80. if ($rule->getActions()->validate($childItem)) {
  81. $isContinue = false;
  82. }
  83. }
  84. }
  85. if ($isContinue) {
  86. continue;
  87. }
  88. }
  89. $this->applyRule($item, $rule, $address, $couponCode);
  90. $appliedRuleIds[$rule->getRuleId()] = $rule->getRuleId();
  91. if ($rule->getStopRulesProcessing()) {
  92. break;
  93. }
  94. }
  95. return $appliedRuleIds;
  96. }
  97. /**
  98. * Add rule discount description label to address object
  99. *
  100. * @param Address $address
  101. * @param \Magento\SalesRule\Model\Rule $rule
  102. * @return $this
  103. */
  104. public function addDiscountDescription($address, $rule)
  105. {
  106. $description = $address->getDiscountDescriptionArray();
  107. $ruleLabel = $rule->getStoreLabel($address->getQuote()->getStore());
  108. $label = '';
  109. if ($ruleLabel) {
  110. $label = $ruleLabel;
  111. } else {
  112. if (strlen($address->getCouponCode())) {
  113. $label = $address->getCouponCode();
  114. }
  115. }
  116. if (strlen($label)) {
  117. $description[$rule->getId()] = $label;
  118. }
  119. $address->setDiscountDescriptionArray($description);
  120. return $this;
  121. }
  122. /**
  123. * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item
  124. * @param \Magento\SalesRule\Model\Rule $rule
  125. * @param \Magento\Quote\Model\Quote\Address $address
  126. * @param mixed $couponCode
  127. * @return $this
  128. */
  129. protected function applyRule($item, $rule, $address, $couponCode)
  130. {
  131. $discountData = $this->getDiscountData($item, $rule);
  132. $this->setDiscountData($discountData, $item);
  133. $this->maintainAddressCouponCode($address, $rule, $couponCode);
  134. $this->addDiscountDescription($address, $rule);
  135. return $this;
  136. }
  137. /**
  138. * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item
  139. * @param \Magento\SalesRule\Model\Rule $rule
  140. * @return \Magento\SalesRule\Model\Rule\Action\Discount\Data
  141. */
  142. protected function getDiscountData($item, $rule)
  143. {
  144. $qty = $this->validatorUtility->getItemQty($item, $rule);
  145. $discountCalculator = $this->calculatorFactory->create($rule->getSimpleAction());
  146. $qty = $discountCalculator->fixQuantity($qty, $rule);
  147. $discountData = $discountCalculator->calculate($rule, $item, $qty);
  148. $this->eventFix($discountData, $item, $rule, $qty);
  149. $this->validatorUtility->deltaRoundingFix($discountData, $item);
  150. /**
  151. * We can't use row total here because row total not include tax
  152. * Discount can be applied on price included tax
  153. */
  154. $this->validatorUtility->minFix($discountData, $item, $qty);
  155. return $discountData;
  156. }
  157. /**
  158. * @param \Magento\SalesRule\Model\Rule\Action\Discount\Data $discountData
  159. * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item
  160. * @return $this
  161. */
  162. protected function setDiscountData($discountData, $item)
  163. {
  164. $item->setDiscountAmount($discountData->getAmount());
  165. $item->setBaseDiscountAmount($discountData->getBaseAmount());
  166. $item->setOriginalDiscountAmount($discountData->getOriginalAmount());
  167. $item->setBaseOriginalDiscountAmount($discountData->getBaseOriginalAmount());
  168. return $this;
  169. }
  170. /**
  171. * Set coupon code to address if $rule contains validated coupon
  172. *
  173. * @param Address $address
  174. * @param \Magento\SalesRule\Model\Rule $rule
  175. * @param mixed $couponCode
  176. * @return $this
  177. */
  178. public function maintainAddressCouponCode($address, $rule, $couponCode)
  179. {
  180. /*
  181. Rule is a part of rules collection, which includes only rules with 'No Coupon' type or with validated coupon.
  182. As a result, if rule uses coupon code(s) ('Specific' or 'Auto' Coupon Type), it always contains validated coupon
  183. */
  184. if ($rule->getCouponType() != \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON) {
  185. $address->setCouponCode($couponCode);
  186. }
  187. return $this;
  188. }
  189. /**
  190. * Fire event to allow overwriting of discount amounts
  191. *
  192. * @param \Magento\SalesRule\Model\Rule\Action\Discount\Data $discountData
  193. * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item
  194. * @param \Magento\SalesRule\Model\Rule $rule
  195. * @param float $qty
  196. * @return $this
  197. */
  198. protected function eventFix(
  199. \Magento\SalesRule\Model\Rule\Action\Discount\Data $discountData,
  200. \Magento\Quote\Model\Quote\Item\AbstractItem $item,
  201. \Magento\SalesRule\Model\Rule $rule,
  202. $qty
  203. ) {
  204. $quote = $item->getQuote();
  205. $address = $item->getAddress();
  206. $this->_eventManager->dispatch(
  207. 'salesrule_validator_process',
  208. [
  209. 'rule' => $rule,
  210. 'item' => $item,
  211. 'address' => $address,
  212. 'quote' => $quote,
  213. 'qty' => $qty,
  214. 'result' => $discountData
  215. ]
  216. );
  217. return $this;
  218. }
  219. /**
  220. * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item
  221. * @param int[] $appliedRuleIds
  222. * @return $this
  223. */
  224. public function setAppliedRuleIds(\Magento\Quote\Model\Quote\Item\AbstractItem $item, array $appliedRuleIds)
  225. {
  226. $address = $item->getAddress();
  227. $quote = $item->getQuote();
  228. $item->setAppliedRuleIds(join(',', $appliedRuleIds));
  229. $address->setAppliedRuleIds($this->validatorUtility->mergeIds($address->getAppliedRuleIds(), $appliedRuleIds));
  230. $quote->setAppliedRuleIds($this->validatorUtility->mergeIds($quote->getAppliedRuleIds(), $appliedRuleIds));
  231. return $this;
  232. }
  233. }