11<?php
22/**
3- * Copyright © Magento, Inc. All rights reserved.
4- * See COPYING.txt for license details .
3+ * Copyright 2014 Adobe
4+ * All Rights Reserved .
55 */
6+
67namespace Magento \SalesRule \Model ;
78
89use Magento \Framework \Event \ManagerInterface ;
10+ use Magento \Framework \Pricing \PriceCurrencyInterface ;
911use Magento \Quote \Model \Quote \Address ;
1012use Magento \Quote \Model \Quote \Item \AbstractItem ;
1113use Magento \SalesRule \Model \Data \RuleDiscount ;
@@ -72,6 +74,11 @@ class RulesApplier
7274 */
7375 private $ discountAggregator ;
7476
77+ /**
78+ * @var PriceCurrencyInterface
79+ */
80+ private $ priceCurrency ;
81+
7582 /**
7683 * @param CalculatorFactory $calculatorFactory
7784 * @param ManagerInterface $eventManager
@@ -81,6 +88,7 @@ class RulesApplier
8188 * @param RuleDiscountInterfaceFactory|null $discountInterfaceFactory
8289 * @param DiscountDataInterfaceFactory|null $discountDataInterfaceFactory
8390 * @param SelectRuleCoupon|null $selectRuleCoupon
91+ * @param PriceCurrencyInterface|null $priceCurrency
8492 */
8593 public function __construct (
8694 CalculatorFactory $ calculatorFactory ,
@@ -90,7 +98,8 @@ public function __construct(
9098 ?DataFactory $ discountDataFactory = null ,
9199 ?RuleDiscountInterfaceFactory $ discountInterfaceFactory = null ,
92100 ?DiscountDataInterfaceFactory $ discountDataInterfaceFactory = null ,
93- ?SelectRuleCoupon $ selectRuleCoupon = null
101+ ?SelectRuleCoupon $ selectRuleCoupon = null ,
102+ ?PriceCurrencyInterface $ priceCurrency = null
94103 ) {
95104 $ this ->calculatorFactory = $ calculatorFactory ;
96105 $ this ->validatorUtility = $ utility ;
@@ -104,6 +113,7 @@ public function __construct(
104113 ?: ObjectManager::getInstance ()->get (DiscountDataInterfaceFactory::class);
105114 $ this ->selectRuleCoupon = $ selectRuleCoupon
106115 ?: ObjectManager::getInstance ()->get (SelectRuleCoupon::class);
116+ $ this ->priceCurrency = $ priceCurrency ?: ObjectManager::getInstance ()->get (PriceCurrencyInterface::class);
107117 }
108118
109119 /**
@@ -237,21 +247,28 @@ protected function applyRule($item, $rule, $address, array $couponCodes = [])
237247 {
238248 if ($ item ->getChildren () && $ item ->isChildrenCalculated ()) {
239249 $ 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- }
249250 /**
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
251254 */
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
253260 $ 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 ();
254264 $ 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+ }
255272 }
256273 } else {
257274 $ discountData = $ this ->getDiscountData ($ item , $ rule , $ address , $ couponCodes );
@@ -264,6 +281,63 @@ protected function applyRule($item, $rule, $address, array $couponCodes = [])
264281 return $ this ;
265282 }
266283
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+
267341 /**
268342 * Get discount Data
269343 *
0 commit comments