Skip to content

Commit 35e4ae6

Browse files
authored
Merge pull request #7171 from magento-l3/ACP2E-10
ACP2E-10: Incorrect Discount: Two Cart rules with and without coupon
2 parents 0af4e97 + 26658c0 commit 35e4ae6

File tree

15 files changed

+508
-300
lines changed

15 files changed

+508
-300
lines changed

app/code/Magento/SalesRule/Helper/CartFixedDiscount.php

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,31 @@ public function getDiscountAmount(
8989
);
9090
}
9191

92+
/**
93+
* Get discount amount for item calculated proportionally based on already applied discount
94+
*
95+
* @param float $ruleDiscount
96+
* @param float $qty
97+
* @param float $baseItemPrice
98+
* @param float $baseItemDiscountAmount
99+
* @param float $baseRuleTotalsDiscount
100+
* @param string $discountType
101+
* @return float
102+
*/
103+
public function getDiscountedAmountProportionally(
104+
float $ruleDiscount,
105+
float $qty,
106+
float $baseItemPrice,
107+
float $baseItemDiscountAmount,
108+
float $baseRuleTotalsDiscount,
109+
string $discountType
110+
): float {
111+
$baseItemPriceTotal = $baseItemPrice * $qty - $baseItemDiscountAmount;
112+
$ratio = $baseItemPriceTotal / $baseRuleTotalsDiscount;
113+
$discountAmount = $this->deltaPriceRound->round($ruleDiscount * $ratio, $discountType);
114+
return $discountAmount;
115+
}
116+
92117
/**
93118
* Get shipping discount amount
94119
*
@@ -186,10 +211,6 @@ public function getBaseRuleTotals(
186211
$baseRuleTotals = ($quote->getIsMultiShipping() && $isMultiShipping) ?
187212
$this->getQuoteTotalsForMultiShipping($quote) :
188213
$this->getQuoteTotalsForRegularShipping($address, $baseRuleTotals);
189-
} else {
190-
if ($quote->getIsMultiShipping() && $isMultiShipping) {
191-
$baseRuleTotals = $quote->getBaseSubtotal();
192-
}
193214
}
194215
return (float) $baseRuleTotals;
195216
}

app/code/Magento/SalesRule/Model/Quote/Discount.php

Lines changed: 86 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Magento\Quote\Api\Data\AddressInterface;
1212
use Magento\Quote\Api\Data\ShippingAssignmentInterface;
1313
use Magento\Quote\Model\Quote;
14+
use Magento\Quote\Model\Quote\Address;
1415
use Magento\Quote\Model\Quote\Address\Total;
1516
use Magento\Quote\Model\Quote\Address\Total\AbstractTotal;
1617
use Magento\Quote\Model\Quote\Item;
@@ -20,8 +21,10 @@
2021
use Magento\SalesRule\Api\Data\RuleDiscountInterfaceFactory;
2122
use Magento\SalesRule\Model\Data\RuleDiscount;
2223
use Magento\SalesRule\Model\Discount\PostProcessorFactory;
24+
use Magento\SalesRule\Model\Rule;
2325
use Magento\SalesRule\Model\Validator;
2426
use Magento\Store\Model\StoreManagerInterface;
27+
use Magento\SalesRule\Model\RulesApplier;
2528

2629
/**
2730
* Discount totals calculation model.
@@ -66,21 +69,33 @@ class Discount extends AbstractTotal
6669
*/
6770
private $discountDataInterfaceFactory;
6871

72+
/**
73+
* @var RulesApplier|null
74+
*/
75+
private $rulesApplier;
76+
77+
/**
78+
* @var array
79+
*/
80+
private $addressDiscountAggregator = [];
81+
6982
/**
7083
* @param ManagerInterface $eventManager
7184
* @param StoreManagerInterface $storeManager
7285
* @param Validator $validator
7386
* @param PriceCurrencyInterface $priceCurrency
7487
* @param RuleDiscountInterfaceFactory|null $discountInterfaceFactory
7588
* @param DiscountDataInterfaceFactory|null $discountDataInterfaceFactory
89+
* @param RulesApplier|null $rulesApplier
7690
*/
7791
public function __construct(
7892
ManagerInterface $eventManager,
7993
StoreManagerInterface $storeManager,
8094
Validator $validator,
8195
PriceCurrencyInterface $priceCurrency,
8296
RuleDiscountInterfaceFactory $discountInterfaceFactory = null,
83-
DiscountDataInterfaceFactory $discountDataInterfaceFactory = null
97+
DiscountDataInterfaceFactory $discountDataInterfaceFactory = null,
98+
RulesApplier $rulesApplier = null
8499
) {
85100
$this->setCode(self::COLLECTOR_TYPE_CODE);
86101
$this->eventManager = $eventManager;
@@ -91,6 +106,8 @@ public function __construct(
91106
?: ObjectManager::getInstance()->get(RuleDiscountInterfaceFactory::class);
92107
$this->discountDataInterfaceFactory = $discountDataInterfaceFactory
93108
?: ObjectManager::getInstance()->get(DiscountDataInterfaceFactory::class);
109+
$this->rulesApplier = $rulesApplier
110+
?: ObjectManager::getInstance()->get(RulesApplier::class);
94111
}
95112

96113
/**
@@ -102,88 +119,106 @@ public function __construct(
102119
* @return $this
103120
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
104121
* @SuppressWarnings(PHPMD.NPathComplexity)
122+
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
105123
*/
106124
public function collect(
107125
Quote $quote,
108126
ShippingAssignmentInterface $shippingAssignment,
109127
Total $total
110128
) {
111129
parent::collect($quote, $shippingAssignment, $total);
112-
113130
$store = $this->storeManager->getStore($quote->getStoreId());
131+
/** @var Address $address */
114132
$address = $shippingAssignment->getShipping()->getAddress();
115-
116133
if ($quote->currentPaymentWasSet()) {
117134
$address->setPaymentMethod($quote->getPayment()->getMethod());
118135
}
119-
120136
$this->calculator->reset($address);
121-
122-
$items = $shippingAssignment->getItems();
123-
if (!count($items)) {
137+
$itemsAggregate = [];
138+
foreach ($shippingAssignment->getItems() as $item) {
139+
$itemId = $item->getId();
140+
$itemsAggregate[$itemId] = $item;
141+
}
142+
$items = [];
143+
foreach ($quote->getAllAddresses() as $quoteAddress) {
144+
foreach ($quoteAddress->getAllItems() as $item) {
145+
$items[] = $item;
146+
}
147+
}
148+
if (!$items || !$itemsAggregate) {
124149
return $this;
125150
}
126-
127151
$eventArgs = [
128152
'website_id' => $store->getWebsiteId(),
129153
'customer_group_id' => $quote->getCustomerGroupId(),
130154
'coupon_code' => $quote->getCouponCode(),
131155
];
132-
133-
$this->calculator->init($store->getWebsiteId(), $quote->getCustomerGroupId(), $quote->getCouponCode());
134-
$this->calculator->initTotals($items, $address);
135-
136156
$address->setDiscountDescription([]);
137-
$items = $this->calculator->sortItemsByPriority($items, $address);
138157
$address->getExtensionAttributes()->setDiscounts([]);
139-
$addressDiscountAggregator = [];
140-
141-
/** @var Item $item */
158+
$this->addressDiscountAggregator = [];
159+
$address->setCartFixedRules([]);
160+
$quote->setCartFixedRules([]);
142161
foreach ($items as $item) {
143-
if ($item->getNoDiscount() || !$this->calculator->canApplyDiscount($item)) {
144-
$item->setDiscountAmount(0);
145-
$item->setBaseDiscountAmount(0);
146-
147-
// ensure my children are zeroed out
148-
if ($item->getHasChildren() && $item->isChildrenCalculated()) {
149-
foreach ($item->getChildren() as $child) {
150-
$child->setDiscountAmount(0);
151-
$child->setBaseDiscountAmount(0);
152-
}
162+
$this->rulesApplier->setAppliedRuleIds($item, []);
163+
if ($item->getExtensionAttributes()) {
164+
$item->getExtensionAttributes()->setDiscounts(null);
165+
}
166+
$item->setDiscountAmount(0);
167+
$item->setBaseDiscountAmount(0);
168+
$item->setDiscountPercent(0);
169+
if ($item->getChildren() && $item->isChildrenCalculated()) {
170+
foreach ($item->getChildren() as $child) {
171+
$child->setDiscountAmount(0);
172+
$child->setBaseDiscountAmount(0);
173+
$child->setDiscountPercent(0);
174+
}
175+
}
176+
}
177+
$this->calculator->init($store->getWebsiteId(), $quote->getCustomerGroupId(), $quote->getCouponCode());
178+
$this->calculator->initTotals($items, $address);
179+
$items = $this->calculator->sortItemsByPriority($items, $address);
180+
$rules = $this->calculator->getRules($address);
181+
/** @var Rule $rule */
182+
foreach ($rules as $rule) {
183+
/** @var Item $item */
184+
foreach ($items as $item) {
185+
if ($item->getNoDiscount() || !$this->calculator->canApplyDiscount($item) || $item->getParentItem()) {
186+
continue;
153187
}
188+
$eventArgs['item'] = $item;
189+
$this->eventManager->dispatch('sales_quote_address_discount_item', $eventArgs);
190+
$this->calculator->process($item, $rule);
191+
}
192+
$appliedRuleIds = $quote->getAppliedRuleIds() ? explode(',', $quote->getAppliedRuleIds()) : [];
193+
if ($rule->getStopRulesProcessing() && in_array($rule->getId(), $appliedRuleIds)) {
194+
break;
195+
}
196+
$this->calculator->initTotals($items, $address);
197+
}
198+
foreach ($items as $item) {
199+
if (!isset($itemsAggregate[$item->getId()])) {
154200
continue;
155201
}
156-
// to determine the child item discount, we calculate the parent
157202
if ($item->getParentItem()) {
158203
continue;
159-
}
160-
161-
$eventArgs['item'] = $item;
162-
$this->eventManager->dispatch('sales_quote_address_discount_item', $eventArgs);
163-
164-
if ($item->getHasChildren() && $item->isChildrenCalculated()) {
165-
$this->calculator->process($item);
204+
} elseif ($item->getHasChildren() && $item->isChildrenCalculated()) {
166205
foreach ($item->getChildren() as $child) {
167206
$eventArgs['item'] = $child;
168207
$this->eventManager->dispatch('sales_quote_address_discount_item', $eventArgs);
169208
$this->aggregateItemDiscount($child, $total);
170209
}
171-
} else {
172-
$this->calculator->process($item);
173-
$this->aggregateItemDiscount($item, $total);
174210
}
211+
$this->aggregateItemDiscount($item, $total);
175212
if ($item->getExtensionAttributes()) {
176-
$this->aggregateDiscountPerRule($item, $address, $addressDiscountAggregator);
213+
$this->aggregateDiscountPerRule($item, $address);
177214
}
178215
}
179-
180216
$this->calculator->prepareDescription($address);
181217
$total->setDiscountDescription($address->getDiscountDescription());
182218
$total->setSubtotalWithDiscount($total->getSubtotal() + $total->getDiscountAmount());
183219
$total->setBaseSubtotalWithDiscount($total->getBaseSubtotal() + $total->getBaseDiscountAmount());
184220
$address->setDiscountAmount($total->getDiscountAmount());
185221
$address->setBaseDiscountAmount($total->getBaseDiscountAmount());
186-
187222
return $this;
188223
}
189224

@@ -273,13 +308,11 @@ public function fetch(Quote $quote, Total $total)
273308
*
274309
* @param AbstractItem $item
275310
* @param AddressInterface $address
276-
* @param array $addressDiscountAggregator
277311
* @return void
278312
*/
279313
private function aggregateDiscountPerRule(
280314
AbstractItem $item,
281-
AddressInterface $address,
282-
array &$addressDiscountAggregator
315+
AddressInterface $address
283316
) {
284317
$discountBreakdown = $item->getExtensionAttributes()->getDiscounts();
285318
if ($discountBreakdown) {
@@ -288,15 +321,17 @@ private function aggregateDiscountPerRule(
288321
$discount = $value->getDiscountData();
289322
$ruleLabel = $value->getRuleLabel();
290323
$ruleID = $value->getRuleID();
291-
if (isset($addressDiscountAggregator[$ruleID])) {
324+
if (isset($this->addressDiscountAggregator[$ruleID])) {
292325
/** @var RuleDiscount $cartDiscount */
293-
$cartDiscount = $addressDiscountAggregator[$ruleID];
326+
$cartDiscount = $this->addressDiscountAggregator[$ruleID];
294327
$discountData = $cartDiscount->getDiscountData();
295-
$discountData->setBaseAmount($discountData->getBaseAmount()+$discount->getBaseAmount());
296-
$discountData->setAmount($discountData->getAmount()+$discount->getAmount());
297-
$discountData->setOriginalAmount($discountData->getOriginalAmount()+$discount->getOriginalAmount());
328+
$discountData->setBaseAmount($discountData->getBaseAmount() + $discount->getBaseAmount());
329+
$discountData->setAmount($discountData->getAmount() + $discount->getAmount());
330+
$discountData->setOriginalAmount(
331+
$discountData->getOriginalAmount() + $discount->getOriginalAmount()
332+
);
298333
$discountData->setBaseOriginalAmount(
299-
$discountData->getBaseOriginalAmount()+$discount->getBaseOriginalAmount()
334+
$discountData->getBaseOriginalAmount() + $discount->getBaseOriginalAmount()
300335
);
301336
} else {
302337
$data = [
@@ -313,10 +348,10 @@ private function aggregateDiscountPerRule(
313348
];
314349
/** @var RuleDiscount $cartDiscount */
315350
$cartDiscount = $this->discountInterfaceFactory->create(['data' => $data]);
316-
$addressDiscountAggregator[$ruleID] = $cartDiscount;
351+
$this->addressDiscountAggregator[$ruleID] = $cartDiscount;
317352
}
318353
}
319354
}
320-
$address->getExtensionAttributes()->setDiscounts(array_values($addressDiscountAggregator));
355+
$address->getExtensionAttributes()->setDiscounts(array_values($this->addressDiscountAggregator));
321356
}
322357
}

0 commit comments

Comments
 (0)