Skip to content

Commit

Permalink
Merge pull request #7171 from magento-l3/ACP2E-10
Browse files Browse the repository at this point in the history
ACP2E-10: Incorrect Discount: Two Cart rules with and without coupon
  • Loading branch information
dhorytskyi authored Nov 10, 2021
2 parents 0af4e97 + 26658c0 commit 35e4ae6
Show file tree
Hide file tree
Showing 15 changed files with 508 additions and 300 deletions.
29 changes: 25 additions & 4 deletions app/code/Magento/SalesRule/Helper/CartFixedDiscount.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,31 @@ public function getDiscountAmount(
);
}

/**
* Get discount amount for item calculated proportionally based on already applied discount
*
* @param float $ruleDiscount
* @param float $qty
* @param float $baseItemPrice
* @param float $baseItemDiscountAmount
* @param float $baseRuleTotalsDiscount
* @param string $discountType
* @return float
*/
public function getDiscountedAmountProportionally(
float $ruleDiscount,
float $qty,
float $baseItemPrice,
float $baseItemDiscountAmount,
float $baseRuleTotalsDiscount,
string $discountType
): float {
$baseItemPriceTotal = $baseItemPrice * $qty - $baseItemDiscountAmount;
$ratio = $baseItemPriceTotal / $baseRuleTotalsDiscount;
$discountAmount = $this->deltaPriceRound->round($ruleDiscount * $ratio, $discountType);
return $discountAmount;
}

/**
* Get shipping discount amount
*
Expand Down Expand Up @@ -186,10 +211,6 @@ public function getBaseRuleTotals(
$baseRuleTotals = ($quote->getIsMultiShipping() && $isMultiShipping) ?
$this->getQuoteTotalsForMultiShipping($quote) :
$this->getQuoteTotalsForRegularShipping($address, $baseRuleTotals);
} else {
if ($quote->getIsMultiShipping() && $isMultiShipping) {
$baseRuleTotals = $quote->getBaseSubtotal();
}
}
return (float) $baseRuleTotals;
}
Expand Down
137 changes: 86 additions & 51 deletions app/code/Magento/SalesRule/Model/Quote/Discount.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Magento\Quote\Api\Data\AddressInterface;
use Magento\Quote\Api\Data\ShippingAssignmentInterface;
use Magento\Quote\Model\Quote;
use Magento\Quote\Model\Quote\Address;
use Magento\Quote\Model\Quote\Address\Total;
use Magento\Quote\Model\Quote\Address\Total\AbstractTotal;
use Magento\Quote\Model\Quote\Item;
Expand All @@ -20,8 +21,10 @@
use Magento\SalesRule\Api\Data\RuleDiscountInterfaceFactory;
use Magento\SalesRule\Model\Data\RuleDiscount;
use Magento\SalesRule\Model\Discount\PostProcessorFactory;
use Magento\SalesRule\Model\Rule;
use Magento\SalesRule\Model\Validator;
use Magento\Store\Model\StoreManagerInterface;
use Magento\SalesRule\Model\RulesApplier;

/**
* Discount totals calculation model.
Expand Down Expand Up @@ -66,21 +69,33 @@ class Discount extends AbstractTotal
*/
private $discountDataInterfaceFactory;

/**
* @var RulesApplier|null
*/
private $rulesApplier;

/**
* @var array
*/
private $addressDiscountAggregator = [];

/**
* @param ManagerInterface $eventManager
* @param StoreManagerInterface $storeManager
* @param Validator $validator
* @param PriceCurrencyInterface $priceCurrency
* @param RuleDiscountInterfaceFactory|null $discountInterfaceFactory
* @param DiscountDataInterfaceFactory|null $discountDataInterfaceFactory
* @param RulesApplier|null $rulesApplier
*/
public function __construct(
ManagerInterface $eventManager,
StoreManagerInterface $storeManager,
Validator $validator,
PriceCurrencyInterface $priceCurrency,
RuleDiscountInterfaceFactory $discountInterfaceFactory = null,
DiscountDataInterfaceFactory $discountDataInterfaceFactory = null
DiscountDataInterfaceFactory $discountDataInterfaceFactory = null,
RulesApplier $rulesApplier = null
) {
$this->setCode(self::COLLECTOR_TYPE_CODE);
$this->eventManager = $eventManager;
Expand All @@ -91,6 +106,8 @@ public function __construct(
?: ObjectManager::getInstance()->get(RuleDiscountInterfaceFactory::class);
$this->discountDataInterfaceFactory = $discountDataInterfaceFactory
?: ObjectManager::getInstance()->get(DiscountDataInterfaceFactory::class);
$this->rulesApplier = $rulesApplier
?: ObjectManager::getInstance()->get(RulesApplier::class);
}

/**
Expand All @@ -102,88 +119,106 @@ public function __construct(
* @return $this
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function collect(
Quote $quote,
ShippingAssignmentInterface $shippingAssignment,
Total $total
) {
parent::collect($quote, $shippingAssignment, $total);

$store = $this->storeManager->getStore($quote->getStoreId());
/** @var Address $address */
$address = $shippingAssignment->getShipping()->getAddress();

if ($quote->currentPaymentWasSet()) {
$address->setPaymentMethod($quote->getPayment()->getMethod());
}

$this->calculator->reset($address);

$items = $shippingAssignment->getItems();
if (!count($items)) {
$itemsAggregate = [];
foreach ($shippingAssignment->getItems() as $item) {
$itemId = $item->getId();
$itemsAggregate[$itemId] = $item;
}
$items = [];
foreach ($quote->getAllAddresses() as $quoteAddress) {
foreach ($quoteAddress->getAllItems() as $item) {
$items[] = $item;
}
}
if (!$items || !$itemsAggregate) {
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);
$address->getExtensionAttributes()->setDiscounts([]);
$addressDiscountAggregator = [];

/** @var Item $item */
$this->addressDiscountAggregator = [];
$address->setCartFixedRules([]);
$quote->setCartFixedRules([]);
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);
}
$this->rulesApplier->setAppliedRuleIds($item, []);
if ($item->getExtensionAttributes()) {
$item->getExtensionAttributes()->setDiscounts(null);
}
$item->setDiscountAmount(0);
$item->setBaseDiscountAmount(0);
$item->setDiscountPercent(0);
if ($item->getChildren() && $item->isChildrenCalculated()) {
foreach ($item->getChildren() as $child) {
$child->setDiscountAmount(0);
$child->setBaseDiscountAmount(0);
$child->setDiscountPercent(0);
}
}
}
$this->calculator->init($store->getWebsiteId(), $quote->getCustomerGroupId(), $quote->getCouponCode());
$this->calculator->initTotals($items, $address);
$items = $this->calculator->sortItemsByPriority($items, $address);
$rules = $this->calculator->getRules($address);
/** @var Rule $rule */
foreach ($rules as $rule) {
/** @var Item $item */
foreach ($items as $item) {
if ($item->getNoDiscount() || !$this->calculator->canApplyDiscount($item) || $item->getParentItem()) {
continue;
}
$eventArgs['item'] = $item;
$this->eventManager->dispatch('sales_quote_address_discount_item', $eventArgs);
$this->calculator->process($item, $rule);
}
$appliedRuleIds = $quote->getAppliedRuleIds() ? explode(',', $quote->getAppliedRuleIds()) : [];
if ($rule->getStopRulesProcessing() && in_array($rule->getId(), $appliedRuleIds)) {
break;
}
$this->calculator->initTotals($items, $address);
}
foreach ($items as $item) {
if (!isset($itemsAggregate[$item->getId()])) {
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);
} elseif ($item->getHasChildren() && $item->isChildrenCalculated()) {
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->aggregateItemDiscount($item, $total);
if ($item->getExtensionAttributes()) {
$this->aggregateDiscountPerRule($item, $address, $addressDiscountAggregator);
$this->aggregateDiscountPerRule($item, $address);
}
}

$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;
}

Expand Down Expand Up @@ -273,13 +308,11 @@ public function fetch(Quote $quote, Total $total)
*
* @param AbstractItem $item
* @param AddressInterface $address
* @param array $addressDiscountAggregator
* @return void
*/
private function aggregateDiscountPerRule(
AbstractItem $item,
AddressInterface $address,
array &$addressDiscountAggregator
AddressInterface $address
) {
$discountBreakdown = $item->getExtensionAttributes()->getDiscounts();
if ($discountBreakdown) {
Expand All @@ -288,15 +321,17 @@ private function aggregateDiscountPerRule(
$discount = $value->getDiscountData();
$ruleLabel = $value->getRuleLabel();
$ruleID = $value->getRuleID();
if (isset($addressDiscountAggregator[$ruleID])) {
if (isset($this->addressDiscountAggregator[$ruleID])) {
/** @var RuleDiscount $cartDiscount */
$cartDiscount = $addressDiscountAggregator[$ruleID];
$cartDiscount = $this->addressDiscountAggregator[$ruleID];
$discountData = $cartDiscount->getDiscountData();
$discountData->setBaseAmount($discountData->getBaseAmount()+$discount->getBaseAmount());
$discountData->setAmount($discountData->getAmount()+$discount->getAmount());
$discountData->setOriginalAmount($discountData->getOriginalAmount()+$discount->getOriginalAmount());
$discountData->setBaseAmount($discountData->getBaseAmount() + $discount->getBaseAmount());
$discountData->setAmount($discountData->getAmount() + $discount->getAmount());
$discountData->setOriginalAmount(
$discountData->getOriginalAmount() + $discount->getOriginalAmount()
);
$discountData->setBaseOriginalAmount(
$discountData->getBaseOriginalAmount()+$discount->getBaseOriginalAmount()
$discountData->getBaseOriginalAmount() + $discount->getBaseOriginalAmount()
);
} else {
$data = [
Expand All @@ -313,10 +348,10 @@ private function aggregateDiscountPerRule(
];
/** @var RuleDiscount $cartDiscount */
$cartDiscount = $this->discountInterfaceFactory->create(['data' => $data]);
$addressDiscountAggregator[$ruleID] = $cartDiscount;
$this->addressDiscountAggregator[$ruleID] = $cartDiscount;
}
}
}
$address->getExtensionAttributes()->setDiscounts(array_values($addressDiscountAggregator));
$address->getExtensionAttributes()->setDiscounts(array_values($this->addressDiscountAggregator));
}
}
Loading

0 comments on commit 35e4ae6

Please sign in to comment.