Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 11 additions & 21 deletions app/code/Magento/SalesRule/Model/Quote/Discount.php
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ public function collect(
$child->setDiscountPercent(0);
}
}
$item->getAddress()->setDiscountAmount(0);
$item->getAddress()->setBaseDiscountAmount(0);
}
$this->calculator->initFromQuote($quote);
Expand All @@ -189,6 +190,8 @@ public function collect(
$itemsToApplyRules = $items;
$rules = $this->calculator->getRules($address);
$address->setBaseDiscountAmount(0);
$address->setDiscountAmount(0);
$ruleTotalDiscount = $ruleBaseTotalDiscount = 0;
/** @var Rule $rule */
foreach ($rules as $rule) {
/** @var Item $item */
Expand Down Expand Up @@ -219,27 +222,12 @@ public function collect(
if ($rule->getStopRulesProcessing() && in_array($rule->getId(), $appliedRuleIds)) {
unset($itemsToApplyRules[$key]);
}

$ruleBaseTotalDiscount += $item->getBaseDiscountAmount();
$ruleTotalDiscount += $item->getDiscountAmount();
}
$baseDiscountAmount = 0;
$discountAmount = 0;
// $itemsAggregate are items specific to the current shipping address
foreach ($itemsAggregate as $item) {
if ($item->getParentItem()) {
continue;
}
if ($item->getChildren() && $item->isChildrenCalculated()) {
foreach ($item->getChildren() as $child) {
$baseDiscountAmount += $child->getBaseDiscountAmount();
$discountAmount += $child->getDiscountAmount();
}
}
$baseDiscountAmount += $item->getBaseDiscountAmount();
$discountAmount += $item->getDiscountAmount();
}
$address->setBaseDiscountAmount(-$baseDiscountAmount);
$address->setDiscountAmount(-$discountAmount);
$address->setBaseSubtotalWithDiscount($address->getBaseSubtotal() - $baseDiscountAmount);
$address->setSubtotalWithDiscount($address->getSubtotal() - $discountAmount);
$address->setBaseDiscountAmount($ruleBaseTotalDiscount);
$address->setDiscountAmount($ruleTotalDiscount);
}
$this->calculator->initTotals($items, $address);
foreach ($items as $item) {
Expand All @@ -254,12 +242,14 @@ public function collect(
$this->eventManager->dispatch('sales_quote_address_discount_item', $eventArgs);
$this->aggregateItemDiscount($child, $total);
}
} else {
$this->aggregateItemDiscount($item, $total);
}
$this->aggregateItemDiscount($item, $total);
if ($item->getExtensionAttributes()) {
$this->aggregateDiscountPerRule($item, $address);
}
}

$this->calculator->prepareDescription($address);
$total->setDiscountDescription($address->getDiscountDescription());
$total->setSubtotalWithDiscount($total->getSubtotal() + $total->getDiscountAmount());
Expand Down
85 changes: 55 additions & 30 deletions app/code/Magento/SalesRule/Model/RulesApplier.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ class RulesApplier
*/
private $priceCurrency;

/**
* @var DataFactory
*/
private $aggregateDiscount = null;

/**
* @param CalculatorFactory $calculatorFactory
* @param ManagerInterface $eventManager
Expand Down Expand Up @@ -247,29 +252,31 @@ protected function applyRule($item, $rule, $address, array $couponCodes = [])
{
if ($item->getChildren() && $item->isChildrenCalculated()) {
$cloneItem = clone $item;
$canApplytoParent = $rule->getActions()->validate($cloneItem);
$canApplyChild = false;
/**
* Validates item without children to check whether the rule can be applied to the item itself
* If the rule can be applied to the item, the discount is applied to the item itself and
* distributed among its children
*/
if ($rule->getActions()->validate($cloneItem)) {
// Aggregate discount data from children
$discountData = $this->getDiscountDataFromChildren($item);
$this->setDiscountData($discountData, $item);
// Calculate discount data based on parent item
$discountData = $this->getDiscountData($item, $rule, $address, $couponCodes);
$this->distributeDiscount($discountData, $item);
// reset discount data in parent item after distributing discount to children
$discountData = $this->discountFactory->create();
$this->setDiscountData($discountData, $item);
} else {
foreach ($item->getChildren() as $childItem) {
if ($rule->getActions()->validate($childItem)) {
$discountData = $this->getDiscountData($childItem, $rule, $address, $couponCodes);
$this->setDiscountData($discountData, $childItem);
}
foreach ($item->getChildren() as $childItem) {
if ($canApplytoParent || $rule->getActions()->validate($childItem)) {
// setting it to true if it was false and child in action conditions validated true
$canApplyChild = true;
// Cloning the items to find out rule specific discount
$childClone = clone $childItem;
$discountData = $this->getDiscountData($childItem, $rule, $address, $couponCodes);
$this->setDiscountData($discountData, $childItem);
$this->setAggregateDiscountData($childItem, $childClone);
}
}
/**
* apply discount to parent item if parent or children is valid
*/
if ($canApplytoParent || $canApplyChild) {
$discountData = $this->getDiscountDataFromChildren();
$this->setDiscountData($discountData, $item);
}
} else {
$discountData = $this->getDiscountData($item, $rule, $address, $couponCodes);
$this->setDiscountData($discountData, $item);
Expand All @@ -282,25 +289,43 @@ protected function applyRule($item, $rule, $address, array $couponCodes = [])
}

/**
* Get discount data from children
* Set Sum of child items discount data
*
* @param AbstractItem $item
* @return Data
* @param AbstractItem $childClone
* @return void
*/
private function getDiscountDataFromChildren(AbstractItem $item): Data
private function setAggregateDiscountData(AbstractItem $item, AbstractItem $childClone)
{
$discountData = $this->discountFactory->create();

foreach ($item->getChildren() as $child) {
$discountData->setAmount($discountData->getAmount() + $child->getDiscountAmount());
$discountData->setBaseAmount($discountData->getBaseAmount() + $child->getBaseDiscountAmount());
$discountData->setOriginalAmount($discountData->getOriginalAmount() + $child->getOriginalDiscountAmount());
$discountData->setBaseOriginalAmount(
$discountData->getBaseOriginalAmount() + $child->getBaseOriginalDiscountAmount()
);
}
$this->aggregateDiscount ??= $this->discountFactory->create();
$this->aggregateDiscount->setAmount(
($this->aggregateDiscount->getAmount() - $childClone->getDiscountAmount())
+ $item->getDiscountAmount()
);
$this->aggregateDiscount->setBaseAmount(
($this->aggregateDiscount->getBaseAmount()-$childClone->getBaseDiscountAmount())
+ $item->getBaseDiscountAmount()
);
$this->aggregateDiscount->setOriginalAmount(
($this->aggregateDiscount->getOriginalAmount()
- $childClone->getOriginalDiscountAmount())
+ $item->getOriginalDiscountAmount()
);
$this->aggregateDiscount->setBaseOriginalAmount(
($this->aggregateDiscount->getBaseOriginalAmount()
- $childClone->getBaseOriginalDiscountAmount())
+ $item->getBaseOriginalDiscountAmount()
);
}

return $discountData;
/**
* Get Aggregated discount data
*
* @return Data
*/
private function getDiscountDataFromChildren(): Data
{
return $this->aggregateDiscount;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ public function testCollectItemNoDiscount(): void
$itemNoDiscount->expects($this->any())->method('getExtensionAttributes')->willReturn($itemExtension);
$itemNoDiscount->expects($this->any())->method('getId')->willReturn(1);
$itemNoDiscount->expects($this->once())->method('getNoDiscount')->willReturn(true);
$itemNoDiscount->expects($this->once())->method('getAddress')->willReturn($this->addressMock);
$itemNoDiscount->expects($this->any())->method('getAddress')->willReturn($this->addressMock);
$this->validatorMock->expects($this->once())->method('sortItemsByPriority')
->with([$itemNoDiscount], $this->addressMock)
->willReturnArgument(0);
Expand Down Expand Up @@ -256,7 +256,7 @@ public function testCollectItemHasParent(): void
$itemWithParentId->expects($this->any())->method('getId')->willReturn(1);
$itemWithParentId->expects($this->any())->method('getParentItem')->willReturn(true);
$itemWithParentId->expects($this->any())->method('getExtensionAttributes')->willReturn(false);
$itemWithParentId->expects($this->once())->method('getAddress')->willReturn($this->addressMock);
$itemWithParentId->expects($this->any())->method('getAddress')->willReturn($this->addressMock);

$this->validatorMock->expects($this->any())->method('canApplyDiscount')->willReturn(true);
$this->validatorMock->expects($this->any())->method('sortItemsByPriority')
Expand Down Expand Up @@ -339,7 +339,7 @@ public function testCollectItemHasNoChildren(): void
$itemWithChildren->expects($this->any())->method('getParentItem')->willReturn(false);
$itemWithChildren->expects($this->once())->method('getHasChildren')->willReturn(false);
$itemWithChildren->expects($this->any())->method('getId')->willReturn(2);
$itemWithChildren->expects($this->once())->method('getAddress')->willReturn($this->addressMock);
$itemWithChildren->expects($this->any())->method('getAddress')->willReturn($this->addressMock);

$this->validatorMock->expects($this->any())->method('canApplyDiscount')->willReturn(true);
$this->validatorMock->expects($this->once())->method('sortItemsByPriority')
Expand Down Expand Up @@ -459,7 +459,7 @@ public function testCollectAddressBaseDiscountAmountIncludingItemChildren(): voi
$item->expects($this->any())->method('getId')->willReturn(1);
$item->expects($this->any())->method('getParentItem')->willReturn(false);
$item->expects($this->any())->method('getExtensionAttributes')->willReturn(false);
$item->expects($this->once())->method('getAddress')->willReturn($this->addressMock);
$item->expects($this->any())->method('getAddress')->willReturn($this->addressMock);
$child = $this->getMockBuilder(Item::class)
->addMethods(['getBaseDiscountAmount'])
->disableOriginalConstructor()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ public function testBundleProductDynamicPriceWithBundleDiscountAndChildDiscount(
{
$discounts = [
// bundle with dynamic price does not have discount on its own, instead it's distributed to children
'bundle' => 0,
'bundle' => 5.90,
// rule1 = (20/100 * 21.98) * (5.99 / 21.98) = 1.198
// rule2 = 1.50
// D = rule1 + rule1 = 1.198 + 1.50 = 2.698 ~ 2.70
Expand Down Expand Up @@ -324,7 +324,7 @@ public function testBundleProductDynamicPriceWithChildDiscountAndBundleDiscount(
{
$discounts = [
// bundle with dynamic price does not have discount on its own, instead it's distributed to children
'bundle' => 0,
'bundle' => 5.60,
// rule2 = 1.50
// rule1 = (20/100 * (21.98 - 1.50) * ((5.99 - 1.50) / (21.98 - 1.50)) = 0.898
// D = rule2 + rule1 = 1.50 + 0.898 = 2.398 ~ 2.40
Expand Down Expand Up @@ -360,7 +360,7 @@ public static function bundleProductWithDynamicPriceAndCartPriceRuleDataProvider
'bundle_cc',
[
// bundle with dynamic price does not have discount on its own, instead it's distributed to children
'bundle' => 0,
'bundle' => 10.99,
'simple1' => 3,
'simple2' => 7.99,
],
Expand All @@ -369,7 +369,7 @@ public static function bundleProductWithDynamicPriceAndCartPriceRuleDataProvider
[
'simple1_cc',
[
'bundle' => 0,
'bundle' => 3,
'simple1' => 3,
'simple2' => 0,
],
Expand All @@ -378,7 +378,7 @@ public static function bundleProductWithDynamicPriceAndCartPriceRuleDataProvider
[
'simple2_cc',
[
'bundle' => 0,
'bundle' => 8,
'simple1' => 0,
'simple2' => 8,
],
Expand Down