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 ;
1720
1821class CartQuantityValidator implements CartQuantityValidatorInterface
1922{
2023 /**
21- * @var CartItemRepositoryInterface
22- */
23- private $ cartItemRepository ;
24-
25- /**
26- * @var StockRegistryInterface
24+ * @var array
2725 */
28- private $ stockRegistry ;
26+ private array $ cumulativeQty = [] ;
2927
3028 /**
29+ * CartQuantityValidator Constructor
30+ *
3131 * @param CartItemRepositoryInterface $cartItemRepository
3232 * @param StockRegistryInterface $stockRegistry
33+ * @param Config $config
34+ * @param ProductRepositoryInterface $productRepository
3335 */
3436 public function __construct (
35- CartItemRepositoryInterface $ cartItemRepository ,
36- StockRegistryInterface $ stockRegistry
37+ private readonly CartItemRepositoryInterface $ cartItemRepository ,
38+ private readonly StockRegistryInterface $ stockRegistry ,
39+ private readonly Config $ config ,
40+ private readonly ProductRepositoryInterface $ productRepository
3741 ) {
38- $ this ->cartItemRepository = $ cartItemRepository ;
39- $ this ->stockRegistry = $ stockRegistry ;
4042 }
4143
4244 /**
@@ -49,28 +51,166 @@ public function __construct(
4951 public function validateFinalCartQuantities (CartInterface $ customerCart , CartInterface $ guestCart ): bool
5052 {
5153 $ modified = false ;
52- /** @var CartItemInterface $guestCartItem */
54+ $ this ->cumulativeQty = [];
55+
5356 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- }
57+ foreach ($ customerCart ->getAllVisibleItems () as $ customerCartItem ) {
58+ if (!$ customerCartItem ->compare ($ guestCartItem )) {
59+ continue ;
60+ }
61+
62+ if ($ this ->config ->getCartMergePreference () === Config::CART_PREFERENCE_CUSTOMER ) {
63+ $ this ->safeDeleteCartItem ((int ) $ guestCart ->getId (), (int ) $ guestCartItem ->getItemId ());
64+ $ modified = true ;
65+ continue ;
66+ }
67+
68+ $ sku = $ this ->getSkuFromItem ($ customerCartItem );
69+ $ product = $ this ->getProduct ((int ) $ customerCartItem ->getProduct ()->getId ());
70+ $ isAvailable = $ customerCartItem ->getChildren ()
71+ ? $ this ->validateCompositeProductQty ($ product , $ guestCartItem , $ customerCartItem )
72+ : $ this ->validateProductQty ($ product , $ sku , $ guestCartItem ->getQty (), $ customerCartItem ->getQty ());
73+
74+ if ($ this ->config ->getCartMergePreference () === Config::CART_PREFERENCE_GUEST ) {
75+ $ this ->safeDeleteCartItem ((int ) $ customerCart ->getId (), (int ) $ customerCartItem ->getItemId ());
76+ $ modified = true ;
77+ }
78+
79+ if (!$ isAvailable ) {
80+ $ this ->safeDeleteCartItem ((int ) $ guestCart ->getId (), (int ) $ guestCartItem ->getItemId ());
81+ $ modified = true ;
7182 }
7283 }
7384 }
85+
7486 return $ modified ;
7587 }
88+
89+ /**
90+ * Get SKU from Cart Item
91+ *
92+ * @param CartItemInterface $item
93+ * @return string
94+ */
95+ private function getSkuFromItem (CartItemInterface $ item ): string
96+ {
97+ return $ item ->getProduct ()->getOptions ()
98+ ? $ this ->getProduct ((int ) $ item ->getProduct ()->getId ())->getSku ()
99+ : $ item ->getProduct ()->getSku ();
100+ }
101+
102+ /**
103+ * Get current cart item quantity based on merge preference
104+ *
105+ * @param float $guestQty
106+ * @param float $customerQty
107+ * @return float
108+ */
109+ private function getCurrentCartItemQty (float $ guestQty , float $ customerQty ): float
110+ {
111+ return match ($ this ->config ->getCartMergePreference ()) {
112+ Config::CART_PREFERENCE_CUSTOMER => $ customerQty ,
113+ Config::CART_PREFERENCE_GUEST => $ guestQty ,
114+ default => $ guestQty + $ customerQty
115+ };
116+ }
117+
118+ /**
119+ * Validate product stock availability
120+ *
121+ * @param ProductInterface $product
122+ * @param string $sku
123+ * @param float $guestQty
124+ * @param float $customerQty
125+ * @return bool
126+ */
127+ private function validateProductQty (
128+ ProductInterface $ product ,
129+ string $ sku ,
130+ float $ guestQty ,
131+ float $ customerQty
132+ ): bool {
133+ $ salableQty = $ this ->stockRegistry ->getStockStatus (
134+ $ product ->getId (),
135+ $ product ->getStore ()->getWebsiteId ()
136+ )->getQty ();
137+
138+ $ this ->cumulativeQty [$ sku ] ??= 0 ;
139+ $ this ->cumulativeQty [$ sku ] += $ this ->getCurrentCartItemQty ($ guestQty , $ customerQty );
140+
141+ return $ salableQty >= $ this ->cumulativeQty [$ sku ];
142+ }
143+
144+ /**
145+ * Validate composite product quantities
146+ *
147+ * @param ProductInterface $productInterface
148+ * @param Item $guestCartItem
149+ * @param Item $customerCartItem
150+ * @return bool
151+ */
152+ private function validateCompositeProductQty (
153+ ProductInterface $ productInterface ,
154+ Item $ guestCartItem ,
155+ Item $ customerCartItem
156+ ): bool {
157+ $ guestChildItems = $ this ->retrieveChildItems ($ guestCartItem );
158+ foreach ($ customerCartItem ->getChildren () as $ customerChildItem ) {
159+ $ childProduct = $ customerChildItem ->getProduct ()->getOptions ()
160+ ? $ this ->getProduct ((int ) $ customerChildItem ->getProduct ()->getId ())
161+ : $ customerChildItem ->getProduct ();
162+ $ sku = $ childProduct ->getSku ();
163+ $ customerItemQty = $ customerCartItem ->getQty () * $ customerChildItem ->getQty ();
164+ $ guestItemQty = $ guestCartItem ->getQty () * $ guestChildItems [$ sku ]->getQty ();
165+
166+ if (!$ this ->validateProductQty ($ childProduct , $ sku , $ guestItemQty , $ customerItemQty )) {
167+ return false ;
168+ }
169+ }
170+
171+ return true ;
172+ }
173+
174+ /**
175+ * Get product by ID
176+ *
177+ * @param int $productId
178+ * @return ProductInterface
179+ * @throws NoSuchEntityException
180+ */
181+ private function getProduct (int $ productId ): ProductInterface
182+ {
183+ return $ this ->productRepository ->getById ($ productId );
184+ }
185+
186+ /**
187+ * Retrieve child items from a quote item
188+ *
189+ * @param Item $quoteItem
190+ * @return Item[]
191+ */
192+ private function retrieveChildItems (Item $ quoteItem ): array
193+ {
194+ $ childItems = [];
195+ foreach ($ quoteItem ->getChildren () as $ childItem ) {
196+ $ childItems [$ childItem ->getProduct ()->getSku ()] = $ childItem ;
197+ }
198+ return $ childItems ;
199+ }
200+
201+ /**
202+ * Safely delete a cart item by ID
203+ *
204+ * @param int $cartId
205+ * @param int $itemId
206+ * @return void
207+ */
208+ private function safeDeleteCartItem (int $ cartId , int $ itemId ): void
209+ {
210+ try {
211+ $ this ->cartItemRepository ->deleteById ($ cartId , $ itemId );
212+ } catch (NoSuchEntityException |CouldNotSaveException $ e ) { // phpcs:ignore
213+ // Optionally log the error.
214+ }
215+ }
76216}
0 commit comments