Discount.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\SalesRule\Model\Quote;
  7. /**
  8. * Discount totals calculation model.
  9. */
  10. class Discount extends \Magento\Quote\Model\Quote\Address\Total\AbstractTotal
  11. {
  12. const COLLECTOR_TYPE_CODE = 'discount';
  13. /**
  14. * Discount calculation object
  15. *
  16. * @var \Magento\SalesRule\Model\Validator
  17. */
  18. protected $calculator;
  19. /**
  20. * Core event manager proxy
  21. *
  22. * @var \Magento\Framework\Event\ManagerInterface
  23. */
  24. protected $eventManager = null;
  25. /**
  26. * @var \Magento\Store\Model\StoreManagerInterface
  27. */
  28. protected $storeManager;
  29. /**
  30. * @var \Magento\Framework\Pricing\PriceCurrencyInterface
  31. */
  32. protected $priceCurrency;
  33. /**
  34. * @param \Magento\Framework\Event\ManagerInterface $eventManager
  35. * @param \Magento\Store\Model\StoreManagerInterface $storeManager
  36. * @param \Magento\SalesRule\Model\Validator $validator
  37. * @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency
  38. */
  39. public function __construct(
  40. \Magento\Framework\Event\ManagerInterface $eventManager,
  41. \Magento\Store\Model\StoreManagerInterface $storeManager,
  42. \Magento\SalesRule\Model\Validator $validator,
  43. \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency
  44. ) {
  45. $this->setCode(self::COLLECTOR_TYPE_CODE);
  46. $this->eventManager = $eventManager;
  47. $this->calculator = $validator;
  48. $this->storeManager = $storeManager;
  49. $this->priceCurrency = $priceCurrency;
  50. }
  51. /**
  52. * Collect address discount amount
  53. *
  54. * @param \Magento\Quote\Model\Quote $quote
  55. * @param \Magento\Quote\Api\Data\ShippingAssignmentInterface $shippingAssignment
  56. * @param \Magento\Quote\Model\Quote\Address\Total $total
  57. * @return $this
  58. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  59. */
  60. public function collect(
  61. \Magento\Quote\Model\Quote $quote,
  62. \Magento\Quote\Api\Data\ShippingAssignmentInterface $shippingAssignment,
  63. \Magento\Quote\Model\Quote\Address\Total $total
  64. ) {
  65. parent::collect($quote, $shippingAssignment, $total);
  66. $store = $this->storeManager->getStore($quote->getStoreId());
  67. $address = $shippingAssignment->getShipping()->getAddress();
  68. $this->calculator->reset($address);
  69. $items = $shippingAssignment->getItems();
  70. if (!count($items)) {
  71. return $this;
  72. }
  73. $eventArgs = [
  74. 'website_id' => $store->getWebsiteId(),
  75. 'customer_group_id' => $quote->getCustomerGroupId(),
  76. 'coupon_code' => $quote->getCouponCode(),
  77. ];
  78. $this->calculator->init($store->getWebsiteId(), $quote->getCustomerGroupId(), $quote->getCouponCode());
  79. $this->calculator->initTotals($items, $address);
  80. $address->setDiscountDescription([]);
  81. $items = $this->calculator->sortItemsByPriority($items, $address);
  82. /** @var \Magento\Quote\Model\Quote\Item $item */
  83. foreach ($items as $item) {
  84. if ($item->getNoDiscount() || !$this->calculator->canApplyDiscount($item)) {
  85. $item->setDiscountAmount(0);
  86. $item->setBaseDiscountAmount(0);
  87. // ensure my children are zeroed out
  88. if ($item->getHasChildren() && $item->isChildrenCalculated()) {
  89. foreach ($item->getChildren() as $child) {
  90. $child->setDiscountAmount(0);
  91. $child->setBaseDiscountAmount(0);
  92. }
  93. }
  94. continue;
  95. }
  96. // to determine the child item discount, we calculate the parent
  97. if ($item->getParentItem()) {
  98. continue;
  99. }
  100. $eventArgs['item'] = $item;
  101. $this->eventManager->dispatch('sales_quote_address_discount_item', $eventArgs);
  102. if ($item->getHasChildren() && $item->isChildrenCalculated()) {
  103. $this->calculator->process($item);
  104. $this->distributeDiscount($item);
  105. foreach ($item->getChildren() as $child) {
  106. $eventArgs['item'] = $child;
  107. $this->eventManager->dispatch('sales_quote_address_discount_item', $eventArgs);
  108. $this->aggregateItemDiscount($child, $total);
  109. }
  110. } else {
  111. $this->calculator->process($item);
  112. $this->aggregateItemDiscount($item, $total);
  113. }
  114. }
  115. $this->calculator->prepareDescription($address);
  116. $total->setDiscountDescription($address->getDiscountDescription());
  117. $total->setSubtotalWithDiscount($total->getSubtotal() + $total->getDiscountAmount());
  118. $total->setBaseSubtotalWithDiscount($total->getBaseSubtotal() + $total->getBaseDiscountAmount());
  119. $address->setDiscountAmount($total->getDiscountAmount());
  120. $address->setBaseDiscountAmount($total->getBaseDiscountAmount());
  121. return $this;
  122. }
  123. /**
  124. * Aggregate item discount information to total data and related properties
  125. *
  126. * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item
  127. * @param \Magento\Quote\Model\Quote\Address\Total $total
  128. * @return $this
  129. */
  130. protected function aggregateItemDiscount(
  131. \Magento\Quote\Model\Quote\Item\AbstractItem $item,
  132. \Magento\Quote\Model\Quote\Address\Total $total
  133. ) {
  134. $total->addTotalAmount($this->getCode(), -$item->getDiscountAmount());
  135. $total->addBaseTotalAmount($this->getCode(), -$item->getBaseDiscountAmount());
  136. return $this;
  137. }
  138. /**
  139. * Distribute discount at parent item to children items
  140. *
  141. * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item
  142. * @return $this
  143. */
  144. protected function distributeDiscount(\Magento\Quote\Model\Quote\Item\AbstractItem $item)
  145. {
  146. $parentBaseRowTotal = $item->getBaseRowTotal();
  147. $keys = [
  148. 'discount_amount',
  149. 'base_discount_amount',
  150. 'original_discount_amount',
  151. 'base_original_discount_amount',
  152. ];
  153. $roundingDelta = [];
  154. foreach ($keys as $key) {
  155. //Initialize the rounding delta to a tiny number to avoid floating point precision problem
  156. $roundingDelta[$key] = 0.0000001;
  157. }
  158. foreach ($item->getChildren() as $child) {
  159. $ratio = $parentBaseRowTotal != 0 ? $child->getBaseRowTotal() / $parentBaseRowTotal : 0;
  160. foreach ($keys as $key) {
  161. if (!$item->hasData($key)) {
  162. continue;
  163. }
  164. $value = $item->getData($key) * $ratio;
  165. $roundedValue = $this->priceCurrency->round($value + $roundingDelta[$key]);
  166. $roundingDelta[$key] += $value - $roundedValue;
  167. $child->setData($key, $roundedValue);
  168. }
  169. }
  170. foreach ($keys as $key) {
  171. $item->setData($key, 0);
  172. }
  173. return $this;
  174. }
  175. /**
  176. * Add discount total information to address
  177. *
  178. * @param \Magento\Quote\Model\Quote $quote
  179. * @param \Magento\Quote\Model\Quote\Address\Total $total
  180. * @return array|null
  181. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  182. */
  183. public function fetch(\Magento\Quote\Model\Quote $quote, \Magento\Quote\Model\Quote\Address\Total $total)
  184. {
  185. $result = null;
  186. $amount = $total->getDiscountAmount();
  187. if ($amount != 0) {
  188. $description = $total->getDiscountDescription();
  189. $result = [
  190. 'code' => $this->getCode(),
  191. 'title' => strlen($description) ? __('Discount (%1)', $description) : __('Discount'),
  192. 'value' => $amount
  193. ];
  194. }
  195. return $result;
  196. }
  197. }