11<?php
22/**
3- * Copyright © Magento, Inc. All rights reserved.
4- * See COPYING.txt for license details .
3+ * Copyright 2019 Adobe
4+ * All Rights Reserved .
55 */
66declare (strict_types=1 );
77
88namespace Magento \QuoteGraphQl \Model \Cart \MergeCarts ;
99
10-
10+ use Magento \Catalog \Api \Data \ProductInterface ;
11+ use Magento \Catalog \Api \ProductRepositoryInterface ;
1112use Magento \CatalogInventory \Api \StockRegistryInterface ;
13+ use Magento \Checkout \Model \Config ;
1214use Magento \Framework \Exception \CouldNotSaveException ;
1315use Magento \Framework \Exception \NoSuchEntityException ;
1416use Magento \Quote \Api \CartItemRepositoryInterface ;
1517use Magento \Quote \Api \Data \CartInterface ;
1618use Magento \Quote \Api \Data \CartItemInterface ;
19+ use Magento \Quote \Model \Quote \Item ;
20+ use Psr \Log \LoggerInterface ;
1721
1822class CartQuantityValidator implements CartQuantityValidatorInterface
1923{
2024 /**
21- * @var CartItemRepositoryInterface
22- */
23- private $ cartItemRepository ;
24-
25- /**
26- * @var StockRegistryInterface
25+ * @var array
2726 */
28- private $ stockRegistry ;
27+ private array $ cumulativeQty = [] ;
2928
3029 /**
30+ * CartQuantityValidator Constructor
31+ *
3132 * @param CartItemRepositoryInterface $cartItemRepository
3233 * @param StockRegistryInterface $stockRegistry
34+ * @param Config $config
35+ * @param ProductRepositoryInterface $productRepository
36+ * @param LoggerInterface $logger
3337 */
3438 public function __construct (
35- CartItemRepositoryInterface $ cartItemRepository ,
36- StockRegistryInterface $ stockRegistry
39+ private readonly CartItemRepositoryInterface $ cartItemRepository ,
40+ private readonly StockRegistryInterface $ stockRegistry ,
41+ private readonly Config $ config ,
42+ private readonly ProductRepositoryInterface $ productRepository ,
43+ private readonly LoggerInterface $ logger
3744 ) {
38- $ this ->cartItemRepository = $ cartItemRepository ;
39- $ this ->stockRegistry = $ stockRegistry ;
4045 }
4146
4247 /**
@@ -45,32 +50,171 @@ public function __construct(
4550 * @param CartInterface $customerCart
4651 * @param CartInterface $guestCart
4752 * @return bool
53+ * @throws NoSuchEntityException
4854 */
4955 public function validateFinalCartQuantities (CartInterface $ customerCart , CartInterface $ guestCart ): bool
5056 {
5157 $ modified = false ;
52- /** @var CartItemInterface $guestCartItem */
58+ $ this ->cumulativeQty = [];
59+
5360 foreach ($ guestCart ->getAllVisibleItems () as $ guestCartItem ) {
54- foreach ($ customerCart ->getAllItems () as $ customerCartItem ) {
55- if ($ customerCartItem ->compare ($ guestCartItem )) {
56- $ product = $ customerCartItem ->getProduct ();
57- $ stockCurrentQty = $ this ->stockRegistry ->getStockStatus (
58- $ product ->getId (),
59- $ product ->getStore ()->getWebsiteId ()
60- )->getQty ();
61- if ($ stockCurrentQty < $ guestCartItem ->getQty () + $ customerCartItem ->getQty ()) {
62- try {
63- $ this ->cartItemRepository ->deleteById ($ guestCart ->getId (), $ guestCartItem ->getItemId ());
64- $ modified = true ;
65- } catch (NoSuchEntityException $ e ) {
66- continue ;
67- } catch (CouldNotSaveException $ e ) {
68- continue ;
69- }
70- }
61+ foreach ($ customerCart ->getAllVisibleItems () as $ customerCartItem ) {
62+ if (!$ customerCartItem ->compare ($ guestCartItem )) {
63+ continue ;
64+ }
65+
66+ if ($ this ->config ->getCartMergePreference () === Config::CART_PREFERENCE_CUSTOMER ) {
67+ $ this ->safeDeleteCartItem ((int ) $ guestCart ->getId (), (int ) $ guestCartItem ->getItemId ());
68+ $ modified = true ;
69+ continue ;
70+ }
71+
72+ $ sku = $ this ->getSkuFromItem ($ customerCartItem );
73+ $ product = $ this ->getProduct ((int ) $ customerCartItem ->getProduct ()->getId ());
74+ $ isAvailable = $ customerCartItem ->getChildren ()
75+ ? $ this ->isCompositeProductQtyValid ($ guestCartItem , $ customerCartItem )
76+ : $ this ->isProductQtyValid ($ product , $ sku , $ guestCartItem ->getQty (), $ customerCartItem ->getQty ());
77+
78+ if ($ this ->config ->getCartMergePreference () === Config::CART_PREFERENCE_GUEST ) {
79+ $ this ->safeDeleteCartItem ((int ) $ customerCart ->getId (), (int ) $ customerCartItem ->getItemId ());
80+ $ modified = true ;
81+ }
82+
83+ if (!$ isAvailable ) {
84+ $ this ->safeDeleteCartItem ((int ) $ guestCart ->getId (), (int ) $ guestCartItem ->getItemId ());
85+ $ modified = true ;
7186 }
7287 }
7388 }
89+
7490 return $ modified ;
7591 }
92+
93+ /**
94+ * Get SKU from Cart Item
95+ *
96+ * @param CartItemInterface $item
97+ * @return string
98+ * @throws NoSuchEntityException
99+ */
100+ private function getSkuFromItem (CartItemInterface $ item ): string
101+ {
102+ return $ item ->getProduct ()->getOptions ()
103+ ? $ this ->getProduct ((int ) $ item ->getProduct ()->getId ())->getSku ()
104+ : $ item ->getProduct ()->getSku ();
105+ }
106+
107+ /**
108+ * Get current cart item quantity based on merge preference
109+ *
110+ * @param float $guestQty
111+ * @param float $customerQty
112+ * @return float
113+ */
114+ private function getCurrentCartItemQty (float $ guestQty , float $ customerQty ): float
115+ {
116+ return match ($ this ->config ->getCartMergePreference ()) {
117+ Config::CART_PREFERENCE_CUSTOMER => $ customerQty ,
118+ Config::CART_PREFERENCE_GUEST => $ guestQty ,
119+ default => $ guestQty + $ customerQty
120+ };
121+ }
122+
123+ /**
124+ * Validate product stock availability
125+ *
126+ * @param ProductInterface $product
127+ * @param string $sku
128+ * @param float $guestQty
129+ * @param float $customerQty
130+ * @return bool
131+ */
132+ private function isProductQtyValid (
133+ ProductInterface $ product ,
134+ string $ sku ,
135+ float $ guestQty ,
136+ float $ customerQty
137+ ): bool {
138+ $ salableQty = $ this ->stockRegistry ->getStockStatus (
139+ $ product ->getId (),
140+ $ product ->getStore ()->getWebsiteId ()
141+ )->getQty ();
142+
143+ $ this ->cumulativeQty [$ sku ] ??= 0 ;
144+ $ this ->cumulativeQty [$ sku ] += $ this ->getCurrentCartItemQty ($ guestQty , $ customerQty );
145+
146+ return $ salableQty >= $ this ->cumulativeQty [$ sku ];
147+ }
148+
149+ /**
150+ * Validate composite product quantities
151+ *
152+ * @param Item $guestCartItem
153+ * @param Item $customerCartItem
154+ * @return bool
155+ * @throws NoSuchEntityException
156+ */
157+ private function isCompositeProductQtyValid (
158+ Item $ guestCartItem ,
159+ Item $ customerCartItem
160+ ): bool {
161+ $ guestChildItems = $ this ->retrieveChildItems ($ guestCartItem );
162+ foreach ($ customerCartItem ->getChildren () as $ customerChildItem ) {
163+ $ childProduct = $ customerChildItem ->getProduct ()->getOptions ()
164+ ? $ this ->getProduct ((int ) $ customerChildItem ->getProduct ()->getId ())
165+ : $ customerChildItem ->getProduct ();
166+ $ sku = $ childProduct ->getSku ();
167+ $ customerItemQty = $ customerCartItem ->getQty () * $ customerChildItem ->getQty ();
168+ $ guestItemQty = $ guestCartItem ->getQty () * $ guestChildItems [$ sku ]->getQty ();
169+
170+ if (!$ this ->isProductQtyValid ($ childProduct , $ sku , $ guestItemQty , $ customerItemQty )) {
171+ return false ;
172+ }
173+ }
174+
175+ return true ;
176+ }
177+
178+ /**
179+ * Get product by ID
180+ *
181+ * @param int $productId
182+ * @return ProductInterface
183+ * @throws NoSuchEntityException
184+ */
185+ private function getProduct (int $ productId ): ProductInterface
186+ {
187+ return $ this ->productRepository ->getById ($ productId );
188+ }
189+
190+ /**
191+ * Retrieve child items from a quote item
192+ *
193+ * @param Item $quoteItem
194+ * @return Item[]
195+ */
196+ private function retrieveChildItems (Item $ quoteItem ): array
197+ {
198+ $ childItems = [];
199+ foreach ($ quoteItem ->getChildren () as $ childItem ) {
200+ $ childItems [$ childItem ->getProduct ()->getSku ()] = $ childItem ;
201+ }
202+ return $ childItems ;
203+ }
204+
205+ /**
206+ * Safely delete a cart item by ID
207+ *
208+ * @param int $cartId
209+ * @param int $itemId
210+ * @return void
211+ */
212+ private function safeDeleteCartItem (int $ cartId , int $ itemId ): void
213+ {
214+ try {
215+ $ this ->cartItemRepository ->deleteById ($ cartId , $ itemId );
216+ } catch (NoSuchEntityException |CouldNotSaveException $ e ) {
217+ $ this ->logger ->error ($ e );
218+ }
219+ }
76220}
0 commit comments