1
1
<?php
2
2
/**
3
- * Copyright © Magento, Inc. All rights reserved.
4
- * See COPYING.txt for license details .
3
+ * Copyright 2014 Adobe
4
+ * All Rights Reserved .
5
5
*/
6
+
6
7
namespace Magento \SalesRule \Model ;
7
8
8
9
use Magento \Framework \Event \ManagerInterface ;
10
+ use Magento \Framework \Pricing \PriceCurrencyInterface ;
9
11
use Magento \Quote \Model \Quote \Address ;
10
12
use Magento \Quote \Model \Quote \Item \AbstractItem ;
11
13
use Magento \SalesRule \Model \Data \RuleDiscount ;
@@ -72,6 +74,11 @@ class RulesApplier
72
74
*/
73
75
private $ discountAggregator ;
74
76
77
+ /**
78
+ * @var PriceCurrencyInterface
79
+ */
80
+ private $ priceCurrency ;
81
+
75
82
/**
76
83
* @param CalculatorFactory $calculatorFactory
77
84
* @param ManagerInterface $eventManager
@@ -81,6 +88,7 @@ class RulesApplier
81
88
* @param RuleDiscountInterfaceFactory|null $discountInterfaceFactory
82
89
* @param DiscountDataInterfaceFactory|null $discountDataInterfaceFactory
83
90
* @param SelectRuleCoupon|null $selectRuleCoupon
91
+ * @param PriceCurrencyInterface|null $priceCurrency
84
92
*/
85
93
public function __construct (
86
94
CalculatorFactory $ calculatorFactory ,
@@ -90,7 +98,8 @@ public function __construct(
90
98
?DataFactory $ discountDataFactory = null ,
91
99
?RuleDiscountInterfaceFactory $ discountInterfaceFactory = null ,
92
100
?DiscountDataInterfaceFactory $ discountDataInterfaceFactory = null ,
93
- ?SelectRuleCoupon $ selectRuleCoupon = null
101
+ ?SelectRuleCoupon $ selectRuleCoupon = null ,
102
+ ?PriceCurrencyInterface $ priceCurrency = null
94
103
) {
95
104
$ this ->calculatorFactory = $ calculatorFactory ;
96
105
$ this ->validatorUtility = $ utility ;
@@ -104,6 +113,7 @@ public function __construct(
104
113
?: ObjectManager::getInstance ()->get (DiscountDataInterfaceFactory::class);
105
114
$ this ->selectRuleCoupon = $ selectRuleCoupon
106
115
?: ObjectManager::getInstance ()->get (SelectRuleCoupon::class);
116
+ $ this ->priceCurrency = $ priceCurrency ?: ObjectManager::getInstance ()->get (PriceCurrencyInterface::class);
107
117
}
108
118
109
119
/**
@@ -237,21 +247,28 @@ protected function applyRule($item, $rule, $address, array $couponCodes = [])
237
247
{
238
248
if ($ item ->getChildren () && $ item ->isChildrenCalculated ()) {
239
249
$ cloneItem = clone $ item ;
240
-
241
- $ applyToChildren = false ;
242
- foreach ($ item ->getChildren () as $ childItem ) {
243
- if ($ rule ->getActions ()->validate ($ childItem )) {
244
- $ discountData = $ this ->getDiscountData ($ childItem , $ rule , $ address , $ couponCodes );
245
- $ this ->setDiscountData ($ discountData , $ childItem );
246
- $ applyToChildren = true ;
247
- }
248
- }
249
250
/**
250
- * validate without children
251
+ * Validates item without children to check whether the rule can be applied to the item itself
252
+ * If the rule can be applied to the item, the discount is applied to the item itself and
253
+ * distributed among its children
251
254
*/
252
- if (!$ applyToChildren && $ rule ->getActions ()->validate ($ cloneItem )) {
255
+ if ($ rule ->getActions ()->validate ($ cloneItem )) {
256
+ // Aggregate discount data from children
257
+ $ discountData = $ this ->getDiscountDataFromChildren ($ item );
258
+ $ this ->setDiscountData ($ discountData , $ item );
259
+ // Calculate discount data based on parent item
253
260
$ discountData = $ this ->getDiscountData ($ item , $ rule , $ address , $ couponCodes );
261
+ $ this ->distributeDiscount ($ discountData , $ item );
262
+ // reset discount data in parent item after distributing discount to children
263
+ $ discountData = $ this ->discountFactory ->create ();
254
264
$ this ->setDiscountData ($ discountData , $ item );
265
+ } else {
266
+ foreach ($ item ->getChildren () as $ childItem ) {
267
+ if ($ rule ->getActions ()->validate ($ childItem )) {
268
+ $ discountData = $ this ->getDiscountData ($ childItem , $ rule , $ address , $ couponCodes );
269
+ $ this ->setDiscountData ($ discountData , $ childItem );
270
+ }
271
+ }
255
272
}
256
273
} else {
257
274
$ discountData = $ this ->getDiscountData ($ item , $ rule , $ address , $ couponCodes );
@@ -264,6 +281,63 @@ protected function applyRule($item, $rule, $address, array $couponCodes = [])
264
281
return $ this ;
265
282
}
266
283
284
+ /**
285
+ * Get discount data from children
286
+ *
287
+ * @param AbstractItem $item
288
+ * @return Data
289
+ */
290
+ private function getDiscountDataFromChildren (AbstractItem $ item ): Data
291
+ {
292
+ $ discountData = $ this ->discountFactory ->create ();
293
+
294
+ foreach ($ item ->getChildren () as $ child ) {
295
+ $ discountData ->setAmount ($ discountData ->getAmount () + $ child ->getDiscountAmount ());
296
+ $ discountData ->setBaseAmount ($ discountData ->getBaseAmount () + $ child ->getBaseDiscountAmount ());
297
+ $ discountData ->setOriginalAmount ($ discountData ->getOriginalAmount () + $ child ->getOriginalDiscountAmount ());
298
+ $ discountData ->setBaseOriginalAmount (
299
+ $ discountData ->getBaseOriginalAmount () + $ child ->getBaseOriginalDiscountAmount ()
300
+ );
301
+ }
302
+
303
+ return $ discountData ;
304
+ }
305
+
306
+ /**
307
+ * Distributes discount applied from parent item to its children items
308
+ *
309
+ * This method originates from \Magento\SalesRule\Model\Quote\Discount::distributeDiscount()
310
+ *
311
+ * @param Data $discountData
312
+ * @param AbstractItem $item
313
+ * @see \Magento\SalesRule\Model\Quote\Discount::distributeDiscount()
314
+ */
315
+ private function distributeDiscount (Data $ discountData , AbstractItem $ item ): void
316
+ {
317
+ $ data = [
318
+ 'discount_amount ' => $ discountData ->getAmount () - $ item ->getDiscountAmount (),
319
+ 'base_discount_amount ' => $ discountData ->getBaseAmount () - $ item ->getBaseDiscountAmount (),
320
+ ];
321
+
322
+ $ parentBaseRowTotal = max (0 , $ item ->getBaseRowTotal () - $ item ->getBaseDiscountAmount ());
323
+ $ keys = array_keys ($ data );
324
+ $ roundingDelta = [];
325
+ foreach ($ keys as $ key ) {
326
+ //Initialize the rounding delta to a tiny number to avoid floating point precision problem
327
+ $ roundingDelta [$ key ] = 0.0000001 ;
328
+ }
329
+ foreach ($ item ->getChildren () as $ child ) {
330
+ $ childBaseRowTotalWithDiscount = max (0 , $ child ->getBaseRowTotal () - $ child ->getBaseDiscountAmount ());
331
+ $ ratio = min (1 , $ parentBaseRowTotal != 0 ? $ childBaseRowTotalWithDiscount / $ parentBaseRowTotal : 0 );
332
+ foreach ($ keys as $ key ) {
333
+ $ value = $ data [$ key ] * $ ratio ;
334
+ $ roundedValue = $ this ->priceCurrency ->round ($ value + $ roundingDelta [$ key ]);
335
+ $ roundingDelta [$ key ] += $ value - $ roundedValue ;
336
+ $ child ->setData ($ key , $ child ->getData ($ key ) + $ roundedValue );
337
+ }
338
+ }
339
+ }
340
+
267
341
/**
268
342
* Get discount Data
269
343
*
0 commit comments