setCode(self::COLLECTOR_TYPE_CODE); $this->eventManager = $eventManager; $this->calculator = $validator; $this->storeManager = $storeManager; $this->priceCurrency = $priceCurrency; } /** * Collect address discount amount * * @param \Magento\Quote\Model\Quote $quote * @param \Magento\Quote\Api\Data\ShippingAssignmentInterface $shippingAssignment * @param \Magento\Quote\Model\Quote\Address\Total $total * @return $this * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function collect( \Magento\Quote\Model\Quote $quote, \Magento\Quote\Api\Data\ShippingAssignmentInterface $shippingAssignment, \Magento\Quote\Model\Quote\Address\Total $total ) { parent::collect($quote, $shippingAssignment, $total); $store = $this->storeManager->getStore($quote->getStoreId()); $address = $shippingAssignment->getShipping()->getAddress(); $this->calculator->reset($address); $items = $shippingAssignment->getItems(); if (!count($items)) { return $this; } $eventArgs = [ 'website_id' => $store->getWebsiteId(), 'customer_group_id' => $quote->getCustomerGroupId(), 'coupon_code' => $quote->getCouponCode(), ]; $this->calculator->init($store->getWebsiteId(), $quote->getCustomerGroupId(), $quote->getCouponCode()); $this->calculator->initTotals($items, $address); $address->setDiscountDescription([]); $items = $this->calculator->sortItemsByPriority($items, $address); /** @var \Magento\Quote\Model\Quote\Item $item */ foreach ($items as $item) { if ($item->getNoDiscount() || !$this->calculator->canApplyDiscount($item)) { $item->setDiscountAmount(0); $item->setBaseDiscountAmount(0); // ensure my children are zeroed out if ($item->getHasChildren() && $item->isChildrenCalculated()) { foreach ($item->getChildren() as $child) { $child->setDiscountAmount(0); $child->setBaseDiscountAmount(0); } } continue; } // to determine the child item discount, we calculate the parent if ($item->getParentItem()) { continue; } $eventArgs['item'] = $item; $this->eventManager->dispatch('sales_quote_address_discount_item', $eventArgs); if ($item->getHasChildren() && $item->isChildrenCalculated()) { $this->calculator->process($item); $this->distributeDiscount($item); foreach ($item->getChildren() as $child) { $eventArgs['item'] = $child; $this->eventManager->dispatch('sales_quote_address_discount_item', $eventArgs); $this->aggregateItemDiscount($child, $total); } } else { $this->calculator->process($item); $this->aggregateItemDiscount($item, $total); } } $this->calculator->prepareDescription($address); $total->setDiscountDescription($address->getDiscountDescription()); $total->setSubtotalWithDiscount($total->getSubtotal() + $total->getDiscountAmount()); $total->setBaseSubtotalWithDiscount($total->getBaseSubtotal() + $total->getBaseDiscountAmount()); $address->setDiscountAmount($total->getDiscountAmount()); $address->setBaseDiscountAmount($total->getBaseDiscountAmount()); return $this; } /** * Aggregate item discount information to total data and related properties * * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item * @param \Magento\Quote\Model\Quote\Address\Total $total * @return $this */ protected function aggregateItemDiscount( \Magento\Quote\Model\Quote\Item\AbstractItem $item, \Magento\Quote\Model\Quote\Address\Total $total ) { $total->addTotalAmount($this->getCode(), -$item->getDiscountAmount()); $total->addBaseTotalAmount($this->getCode(), -$item->getBaseDiscountAmount()); return $this; } /** * Distribute discount at parent item to children items * * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item * @return $this */ protected function distributeDiscount(\Magento\Quote\Model\Quote\Item\AbstractItem $item) { $parentBaseRowTotal = $item->getBaseRowTotal(); $keys = [ 'discount_amount', 'base_discount_amount', 'original_discount_amount', 'base_original_discount_amount', ]; $roundingDelta = []; foreach ($keys as $key) { //Initialize the rounding delta to a tiny number to avoid floating point precision problem $roundingDelta[$key] = 0.0000001; } foreach ($item->getChildren() as $child) { $ratio = $parentBaseRowTotal != 0 ? $child->getBaseRowTotal() / $parentBaseRowTotal : 0; foreach ($keys as $key) { if (!$item->hasData($key)) { continue; } $value = $item->getData($key) * $ratio; $roundedValue = $this->priceCurrency->round($value + $roundingDelta[$key]); $roundingDelta[$key] += $value - $roundedValue; $child->setData($key, $roundedValue); } } foreach ($keys as $key) { $item->setData($key, 0); } return $this; } /** * Add discount total information to address * * @param \Magento\Quote\Model\Quote $quote * @param \Magento\Quote\Model\Quote\Address\Total $total * @return array|null * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function fetch(\Magento\Quote\Model\Quote $quote, \Magento\Quote\Model\Quote\Address\Total $total) { $result = null; $amount = $total->getDiscountAmount(); if ($amount != 0) { $description = $total->getDiscountDescription(); $result = [ 'code' => $this->getCode(), 'title' => strlen($description) ? __('Discount (%1)', $description) : __('Discount'), 'value' => $amount ]; } return $result; } }