Skip to content

Commit a8f9956

Browse files
committed
ACP2E-10: Incorrect Discount: Two Cart rules with and without coupon
1 parent bab395a commit a8f9956

File tree

10 files changed

+334
-293
lines changed

10 files changed

+334
-293
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: 83 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,103 @@ 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+
$item->setDiscountAmount(0);
164+
$item->setBaseDiscountAmount(0);
165+
$item->setDiscountPercent(0);
166+
if ($item->getChildren() && $item->isChildrenCalculated()) {
167+
foreach ($item->getChildren() as $child) {
168+
$child->setDiscountAmount(0);
169+
$child->setBaseDiscountAmount(0);
170+
$child->setDiscountPercent(0);
171+
}
172+
}
173+
}
174+
$this->calculator->init($store->getWebsiteId(), $quote->getCustomerGroupId(), $quote->getCouponCode());
175+
$this->calculator->initTotals($items, $address);
176+
$items = $this->calculator->sortItemsByPriority($items, $address);
177+
$rules = $this->calculator->getRules($address);
178+
/** @var Rule $rule */
179+
foreach ($rules as $rule) {
180+
/** @var Item $item */
181+
foreach ($items as $item) {
182+
if ($item->getNoDiscount() || !$this->calculator->canApplyDiscount($item) || $item->getParentItem()) {
183+
continue;
153184
}
185+
$eventArgs['item'] = $item;
186+
$this->eventManager->dispatch('sales_quote_address_discount_item', $eventArgs);
187+
$this->calculator->process($item, $rule);
188+
}
189+
$appliedRuleIds = $quote->getAppliedRuleIds() ? explode(',', $quote->getAppliedRuleIds()) : [];
190+
if ($rule->getStopRulesProcessing() && in_array($rule->getId(), $appliedRuleIds)) {
191+
break;
192+
}
193+
$this->calculator->initTotals($items, $address);
194+
}
195+
foreach ($items as $item) {
196+
if (!isset($itemsAggregate[$item->getId()])) {
154197
continue;
155198
}
156-
// to determine the child item discount, we calculate the parent
157199
if ($item->getParentItem()) {
158200
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);
201+
} elseif ($item->getHasChildren() && $item->isChildrenCalculated()) {
166202
foreach ($item->getChildren() as $child) {
167203
$eventArgs['item'] = $child;
168204
$this->eventManager->dispatch('sales_quote_address_discount_item', $eventArgs);
169205
$this->aggregateItemDiscount($child, $total);
170206
}
171-
} else {
172-
$this->calculator->process($item);
173-
$this->aggregateItemDiscount($item, $total);
174207
}
208+
$this->aggregateItemDiscount($item, $total);
175209
if ($item->getExtensionAttributes()) {
176-
$this->aggregateDiscountPerRule($item, $address, $addressDiscountAggregator);
210+
$this->aggregateDiscountPerRule($item, $address);
177211
}
178212
}
179-
180213
$this->calculator->prepareDescription($address);
181214
$total->setDiscountDescription($address->getDiscountDescription());
182215
$total->setSubtotalWithDiscount($total->getSubtotal() + $total->getDiscountAmount());
183216
$total->setBaseSubtotalWithDiscount($total->getBaseSubtotal() + $total->getBaseDiscountAmount());
184217
$address->setDiscountAmount($total->getDiscountAmount());
185218
$address->setBaseDiscountAmount($total->getBaseDiscountAmount());
186-
187219
return $this;
188220
}
189221

@@ -273,13 +305,11 @@ public function fetch(Quote $quote, Total $total)
273305
*
274306
* @param AbstractItem $item
275307
* @param AddressInterface $address
276-
* @param array $addressDiscountAggregator
277308
* @return void
278309
*/
279310
private function aggregateDiscountPerRule(
280311
AbstractItem $item,
281-
AddressInterface $address,
282-
array &$addressDiscountAggregator
312+
AddressInterface $address
283313
) {
284314
$discountBreakdown = $item->getExtensionAttributes()->getDiscounts();
285315
if ($discountBreakdown) {
@@ -288,15 +318,17 @@ private function aggregateDiscountPerRule(
288318
$discount = $value->getDiscountData();
289319
$ruleLabel = $value->getRuleLabel();
290320
$ruleID = $value->getRuleID();
291-
if (isset($addressDiscountAggregator[$ruleID])) {
321+
if (isset($this->addressDiscountAggregator[$ruleID])) {
292322
/** @var RuleDiscount $cartDiscount */
293-
$cartDiscount = $addressDiscountAggregator[$ruleID];
323+
$cartDiscount = $this->addressDiscountAggregator[$ruleID];
294324
$discountData = $cartDiscount->getDiscountData();
295-
$discountData->setBaseAmount($discountData->getBaseAmount()+$discount->getBaseAmount());
296-
$discountData->setAmount($discountData->getAmount()+$discount->getAmount());
297-
$discountData->setOriginalAmount($discountData->getOriginalAmount()+$discount->getOriginalAmount());
325+
$discountData->setBaseAmount($discountData->getBaseAmount() + $discount->getBaseAmount());
326+
$discountData->setAmount($discountData->getAmount() + $discount->getAmount());
327+
$discountData->setOriginalAmount(
328+
$discountData->getOriginalAmount() + $discount->getOriginalAmount()
329+
);
298330
$discountData->setBaseOriginalAmount(
299-
$discountData->getBaseOriginalAmount()+$discount->getBaseOriginalAmount()
331+
$discountData->getBaseOriginalAmount() + $discount->getBaseOriginalAmount()
300332
);
301333
} else {
302334
$data = [
@@ -313,10 +345,10 @@ private function aggregateDiscountPerRule(
313345
];
314346
/** @var RuleDiscount $cartDiscount */
315347
$cartDiscount = $this->discountInterfaceFactory->create(['data' => $data]);
316-
$addressDiscountAggregator[$ruleID] = $cartDiscount;
348+
$this->addressDiscountAggregator[$ruleID] = $cartDiscount;
317349
}
318350
}
319351
}
320-
$address->getExtensionAttributes()->setDiscounts(array_values($addressDiscountAggregator));
352+
$address->getExtensionAttributes()->setDiscounts(array_values($this->addressDiscountAggregator));
321353
}
322354
}

0 commit comments

Comments
 (0)