Skip to content

magento/magento2#39873: addProductsToCart mutation doesn't work as expect with a given parent_sku #39950

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: 2.4-develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?php
/**
* Copyright 2025 Adobe
* All Rights Reserved.
*/
declare(strict_types=1);

namespace Magento\QuoteGraphQl\Model\CartItem\Precursor;

use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\GraphQl\Model\Query\ContextInterface;
use Magento\QuoteGraphQl\Model\CartItem\PrecursorInterface;

/**
* Handles parent-child relationship for configurable products
*/
class ConfigurableProductPrecursor implements PrecursorInterface
{
/**
* @var array
*/
private array $errors = [];

/**
* @var ProductRepositoryInterface
*/
private ProductRepositoryInterface $productRepository;

/**
* @param ProductRepositoryInterface $productRepository
*/
public function __construct(
ProductRepositoryInterface $productRepository
) {
$this->productRepository = $productRepository;
}

/**
* Process cart item data to handle parent_sku for configurable products
*
* @param array $cartItemData
* @param ContextInterface $context
* @return array
*/
public function process(array $cartItemData, ContextInterface $context): array
{
$processedCartItemData = [];

foreach ($cartItemData as $cartItemIndex => $cartItem) {
if (!isset($cartItem['parent_sku'])) {
$processedCartItemData[$cartItemIndex] = $cartItem;
continue;
}

try {
$childProduct = $this->productRepository->get($cartItem['sku']);
$parentProduct = $this->productRepository->get($cartItem['parent_sku']);

if ($parentProduct->getTypeId() !== Configurable::TYPE_CODE) {
$this->errors[] = [
'message' => sprintf('Product %s is not a configurable product', $cartItem['parent_sku']),
'code' => 'UNDEFINED'
];
$processedCartItemData[$cartItemIndex] = $cartItem;
continue;
}

$configurableOptions = $this->getConfigurableOptions($parentProduct, $childProduct);

if (empty($configurableOptions)) {
$this->errors[] = [
'message' => sprintf('Could not match child product %s with parent %s', $cartItem['sku'], $cartItem['parent_sku']),
'code' => 'UNDEFINED'
];
$processedCartItemData[$cartItemIndex] = $cartItem;
continue;
}

$parentCartItem = [
'sku' => $cartItem['parent_sku'],
'quantity' => $cartItem['quantity'],
'selected_options' => array_merge($configurableOptions, $cartItem['selected_options'] ?? []),
'entered_options' => $cartItem['entered_options'] ?? [],
'parent_sku' => null
];

$processedCartItemData[] = $parentCartItem;
unset($cartItemData[$cartItemIndex]);

} catch (NoSuchEntityException $e) {
$this->errors[] = [
'message' => $e->getMessage(),
'code' => 'UNDEFINED'
];
$processedCartItemData[$cartItemIndex] = $cartItem;
}
}

return $processedCartItemData;
}

/**
* Get configurable option IDs for the simple product
*
* @param ProductInterface $parentProduct
* @param ProductInterface $childProduct
* @return array
*/
private function getConfigurableOptions(ProductInterface $parentProduct, ProductInterface $childProduct): array
{
$selectedOptions = [];

/** @var Configurable $configurableType */
$configurableType = $parentProduct->getTypeInstance();
$attributes = $configurableType->getConfigurableAttributes($parentProduct);

$childProductData = $childProduct->getData();

foreach ($attributes as $attribute) {
$attributeId = $attribute->getProductAttribute()->getAttributeId();
$attributeCode = $attribute->getProductAttribute()->getAttributeCode();

if (!isset($childProductData[$attributeCode])) {
continue;
}

$optionId = $childProductData[$attributeCode];
$selectedOptions[] = base64_encode("configurable/$attributeId/$optionId");
}

return $selectedOptions;
}

/**
* Return collected errors
*
* @return array
*/
public function getErrors(): array
{
return $this->errors;
}
}
7 changes: 7 additions & 0 deletions app/code/Magento/QuoteGraphQl/etc/graphql/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,11 @@
</argument>
</arguments>
</type>
<type name="Magento\QuoteGraphQl\Model\CartItem\PrecursorComposite">
<arguments>
<argument name="precursors" xsi:type="array">
<item name="configurableProductPrecursor" xsi:type="object">Magento\QuoteGraphQl\Model\CartItem\Precursor\ConfigurableProductPrecursor</item>
</argument>
</arguments>
</type>
</config>