diff --git a/vendor/magento/module-inventory-quote-graph-ql/Model/Cart/MergeCarts/CartQuantityValidator.php b/vendor/magento/module-inventory-quote-graph-ql/Model/Cart/MergeCarts/CartQuantityValidator.php index 635106ae87cb..13b3f17f62a7 100644 --- a/vendor/magento/module-inventory-quote-graph-ql/Model/Cart/MergeCarts/CartQuantityValidator.php +++ b/vendor/magento/module-inventory-quote-graph-ql/Model/Cart/MergeCarts/CartQuantityValidator.php @@ -12,6 +12,7 @@ use Magento\Quote\Api\CartItemRepositoryInterface; use Magento\Quote\Api\Data\CartInterface; use Magento\Quote\Api\Data\CartItemInterface; +use Magento\Quote\Model\Quote\Item; use Magento\QuoteGraphQl\Model\Cart\MergeCarts\CartQuantityValidatorInterface; use Magento\InventorySalesApi\Api\GetProductSalableQtyInterface; use Magento\InventoryCatalog\Model\GetStockIdForCurrentWebsite; @@ -33,6 +34,11 @@ class CartQuantityValidator implements CartQuantityValidatorInterface */ private $getStockIdForCurrentWebsite; + /** + * @var array + */ + private $cumulativeQty = []; + /** * @param CartItemRepositoryInterface $cartItemRepository * @param GetProductSalableQtyInterface $getProductSalableQty @@ -59,13 +65,22 @@ public function validateFinalCartQuantities(CartInterface $customerCart, CartInt { $modified = false; $stockId = $this->getStockIdForCurrentWebsite->execute(); + $this->cumulativeQty = []; + /** @var CartItemInterface $guestCartItem */ foreach ($guestCart->getAllVisibleItems() as $guestCartItem) { foreach ($customerCart->getAllItems() as $customerCartItem) { if ($customerCartItem->compare($guestCartItem)) { - $product = $customerCartItem->getProduct(); - $productSalableQty = $this->getProductSalableQty->execute($product->getSku(), $stockId); - if ($productSalableQty < $guestCartItem->getQty() + $customerCartItem->getQty()) { + $enoughQty = $customerCartItem->getChildren() + ? $this->validateCompositeProductQty($stockId, $guestCartItem, $customerCartItem) + : $this->validateProductQty( + $stockId, + $customerCartItem->getProduct()->getSku(), + $guestCartItem->getQty(), + $customerCartItem->getQty() + ); + + if (!$enoughQty) { try { $this->cartItemRepository->deleteById($guestCart->getId(), $guestCartItem->getItemId()); $modified = true; @@ -78,6 +93,65 @@ public function validateFinalCartQuantities(CartInterface $customerCart, CartInt } } } + $this->cumulativeQty = []; + return $modified; } + + /** + * Validate product stock availability + * + * @param int $stockId + * @param string $sku + * @param float $guestItemQty + * @param float $customerItemQty + * @return bool + */ + private function validateProductQty(int $stockId, string $sku, float $guestItemQty, float $customerItemQty): bool + { + $salableQty = $this->getProductSalableQty->execute($sku, $stockId); + $this->cumulativeQty[$sku] ??= 0; + $this->cumulativeQty[$sku] += $guestItemQty + $customerItemQty; + + return $salableQty >= $this->cumulativeQty[$sku]; + } + + /** + * Validate composite product stock availability + * + * @param int $stockId + * @param Item $guestCartItem + * @param Item $customerCartItem + * @return bool + */ + private function validateCompositeProductQty(int $stockId, Item $guestCartItem, Item $customerCartItem): bool + { + $guestChildItems = $this->retrieveChildItems($guestCartItem); + foreach ($customerCartItem->getChildren() as $customerChildItem) { + $sku = $customerChildItem->getProduct()->getSku(); + $customerItemQty = $customerCartItem->getQty() * $customerChildItem->getQty(); + $guestItemQty = $guestCartItem->getQty() * $guestChildItems[$sku]->getQty(); + if (!$this->validateProductQty($stockId, $sku, $guestItemQty, $customerItemQty)) { + return false; + } + } + + return true; + } + + /** + * Retrieve child quote items mapped by sku + * + * @param Item $quoteItem + * @return array + */ + private function retrieveChildItems(Item $quoteItem): array + { + $childItems = []; + foreach ($quoteItem->getChildren() as $childItem) { + $childItems[$childItem->getProduct()->getSku()] = $childItem; + } + + return $childItems; + } }