diff --git a/app/code/Magento/GroupedProduct/Api/Data/GroupedOptionsInterface.php b/app/code/Magento/GroupedProduct/Api/Data/GroupedOptionsInterface.php new file mode 100644 index 0000000000000..9278ef168eb0d --- /dev/null +++ b/app/code/Magento/GroupedProduct/Api/Data/GroupedOptionsInterface.php @@ -0,0 +1,45 @@ +objectFactory = $objectFactory; + $this->productOptionExtensionFactory = $productOptionExtensionFactory; + $this->productOptionFactory = $productOptionFactory; + } + + /** + * Converts the grouped_options request data into the same format as native frontend add-to-cart + * + * @param CartItemInterface $cartItem + * @return DataObject|null + */ + public function convertToBuyRequest(CartItemInterface $cartItem): ?DataObject + { + if ($cartItem->getProductOption() + && $cartItem->getProductOption()->getExtensionAttributes() + && $cartItem->getProductOption()->getExtensionAttributes()->getGroupedOptions() + ) { + $groupedOptions = $cartItem->getProductOption()->getExtensionAttributes()->getGroupedOptions(); + $this->groupedOptions = $groupedOptions; + + return $this->objectFactory->create($this->getConvertedData($groupedOptions)); + } + + return null; + } + + /** + * Returns grouped_options converted to super_group data + * + * @param GroupedOptionsInterface[] $groupedOptions + * @return array + * @throws LocalizedException + */ + private function getConvertedData(array $groupedOptions): array + { + $requestData = []; + foreach ($groupedOptions as $item) { + /** @var GroupedOptionsInterface $item */ + if ($item->getQty() === null || $item->getId() === null) { + throw new LocalizedException(__('Please specify id and qty for grouped options.')); + } + $requestData[self::SUPER_GROUP_CODE][$item->getId()] = $item->getQty(); + } + + return $requestData; + } + + /** + * Option processor + * + * @param CartItemInterface $cartItem + * @return CartItemInterface + */ + public function processOptions(CartItemInterface $cartItem): CartItemInterface + { + if (empty($this->groupedOptions) || $cartItem->getProductType() !== Grouped::TYPE_CODE) { + return $cartItem; + } + + $extension = $this->productOptionExtensionFactory->create() + ->setGroupedOptions($this->groupedOptions); + if (!$cartItem->getProductOption()) { + $cartItem->setProductOption($this->productOptionFactory->create()); + } + $cartItem->getProductOption()->setExtensionAttributes($extension); + + return $cartItem; + } +} diff --git a/app/code/Magento/GroupedProduct/Model/Quote/Item/GroupedOptions.php b/app/code/Magento/GroupedProduct/Model/Quote/Item/GroupedOptions.php new file mode 100644 index 0000000000000..3b249a7b5d548 --- /dev/null +++ b/app/code/Magento/GroupedProduct/Model/Quote/Item/GroupedOptions.php @@ -0,0 +1,79 @@ +id = $id; + $this->qty = $qty; + $this->extensionAttributes = $extensionAttributes; + } + + /** + * @inheritDoc + */ + public function getId(): ?int + { + return $this->id; + } + + /** + * @inheritDoc + */ + public function getQty(): ?int + { + return $this->qty; + } + + /** + * @inheritDoc + */ + public function setExtensionAttributes(GroupedOptionsExtensionInterface $extensionAttributes): void + { + $this->extensionAttributes = $extensionAttributes; + } + + /** + * @inheritDoc + */ + public function getExtensionAttributes(): ?GroupedOptionsExtensionInterface + { + return $this->extensionAttributes; + } +} diff --git a/app/code/Magento/GroupedProduct/etc/di.xml b/app/code/Magento/GroupedProduct/etc/di.xml index 924d2d1fc9669..8b58ac08ebd31 100644 --- a/app/code/Magento/GroupedProduct/etc/di.xml +++ b/app/code/Magento/GroupedProduct/etc/di.xml @@ -6,6 +6,8 @@ */ --> + + @@ -105,6 +107,13 @@ + + + + Magento\GroupedProduct\Model\Quote\Item\CartItemProcessor\Proxy + + + diff --git a/app/code/Magento/GroupedProduct/etc/extension_attributes.xml b/app/code/Magento/GroupedProduct/etc/extension_attributes.xml index 14ff9821025c4..b614a1277de76 100644 --- a/app/code/Magento/GroupedProduct/etc/extension_attributes.xml +++ b/app/code/Magento/GroupedProduct/etc/extension_attributes.xml @@ -9,4 +9,8 @@ + + + + diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartAddingItemsTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartAddingItemsTest.php index 7900ae45e2f3d..bb2a4e68212cf 100644 --- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartAddingItemsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartAddingItemsTest.php @@ -7,6 +7,11 @@ namespace Magento\Quote\Api; +use Magento\Catalog\Model\ResourceModel\Product as ProductResource; +use Magento\Framework\Webapi\Rest\Request; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; use Magento\TestFramework\TestCase\WebapiAbstract; /** @@ -15,13 +20,22 @@ class CartAddingItemsTest extends WebapiAbstract { /** - * @var \Magento\TestFramework\ObjectManager + * @var ObjectManager */ protected $objectManager; + /** + * @var ProductResource + */ + private $productResource; + + /** + * @inheritDoc + */ protected function setUp(): void { - $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->objectManager = Bootstrap::getObjectManager(); + $this->productResource = $this->objectManager->get(ProductResource::class); } /** @@ -36,9 +50,9 @@ public function testPriceForCreatingQuoteFromEmptyCart() $this->_markTestAsRestOnly(); // Get customer ID token - /** @var \Magento\Integration\Api\CustomerTokenServiceInterface $customerTokenService */ + /** @var CustomerTokenServiceInterface $customerTokenService */ $customerTokenService = $this->objectManager->create( - \Magento\Integration\Api\CustomerTokenServiceInterface::class + CustomerTokenServiceInterface::class ); $token = $customerTokenService->createCustomerAccessToken( 'customer_one_address@test.com', @@ -49,7 +63,7 @@ public function testPriceForCreatingQuoteFromEmptyCart() $serviceInfo = [ 'rest' => [ 'resourcePath' => '/V1/carts/mine', - 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, + 'httpMethod' => Request::HTTP_METHOD_POST, 'token' => $token ] ]; @@ -58,13 +72,6 @@ public function testPriceForCreatingQuoteFromEmptyCart() $this->assertGreaterThan(0, $quoteId); // Adding item to the cart - $serviceInfoForAddingProduct = [ - 'rest' => [ - 'resourcePath' => '/V1/carts/mine/items', - 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, - 'token' => $token - ] - ]; $requestData = [ 'cartItem' => [ 'quote_id' => $quoteId, @@ -72,7 +79,7 @@ public function testPriceForCreatingQuoteFromEmptyCart() 'qty' => 1 ] ]; - $item = $this->_webApiCall($serviceInfoForAddingProduct, $requestData); + $item = $this->_webApiCall($this->getServiceInfoAddToCart($token), $requestData); $this->assertNotEmpty($item); $this->assertEquals(10, $item['price']); @@ -80,7 +87,7 @@ public function testPriceForCreatingQuoteFromEmptyCart() $serviceInfoForGettingPaymentInfo = [ 'rest' => [ 'resourcePath' => '/V1/carts/mine/payment-information', - 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET, + 'httpMethod' => Request::HTTP_METHOD_GET, 'token' => $token ] ]; @@ -92,4 +99,137 @@ public function testPriceForCreatingQuoteFromEmptyCart() $quote->load($quoteId); $quote->delete(); } + + /** + * Test qty for cart after adding grouped product with custom qty. + * + * @magentoApiDataFixture Magento/GroupedProduct/_files/product_grouped_with_simple.php + * @magentoApiDataFixture Magento/Customer/_files/customer_one_address.php + * @return void + */ + public function testAddToCartGroupedCustomQuantity(): void + { + $this->_markTestAsRestOnly(); + + $firstProductId = $this->productResource->getIdBySku('simple_11'); + $secondProductId = $this->productResource->getIdBySku('simple_22'); + $qtyData = [$firstProductId => 2, $secondProductId => 4]; + + // Get customer ID token + /** @var CustomerTokenServiceInterface $customerTokenService */ + $customerTokenService = $this->objectManager->create(CustomerTokenServiceInterface::class); + $token = $customerTokenService->createCustomerAccessToken( + 'customer_one_address@test.com', + 'password' + ); + + // Creating empty cart for registered customer. + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => '/V1/carts/mine', + 'httpMethod' => Request::HTTP_METHOD_POST, + 'token' => $token + ] + ]; + + $quoteId = $this->_webApiCall($serviceInfo, ['customerId' => 999]); // customerId 999 will get overridden + $this->assertGreaterThan(0, $quoteId); + + // Adding item to the cart + $productOptionData = [ + 'extension_attributes' => [ + 'grouped_options' => [ + ['id' => $firstProductId, 'qty' => $qtyData[$firstProductId]], + ['id' => $secondProductId, 'qty' => $qtyData[$secondProductId]], + ] + ] + ]; + $requestData = [ + 'cartItem' => [ + 'quote_id' => $quoteId, + 'sku' => 'grouped', + 'qty' => 1, + 'product_option' => $productOptionData + ] + ]; + $response = $this->_webApiCall($this->getServiceInfoAddToCart($token), $requestData); + $this->assertArrayHasKey('product_option', $response); + $this->assertEquals($response['product_option'], $productOptionData); + + /** @var CartRepositoryInterface $cartRepository */ + $cartRepository = $this->objectManager->get(CartRepositoryInterface::class); + $quote = $cartRepository->get($quoteId); + + foreach ($quote->getAllItems() as $item) { + $this->assertEquals($qtyData[$item->getProductId()], $item->getQty()); + } + } + + /** + * Test adding grouped product when qty for grouped_options not specified. + * + * @magentoApiDataFixture Magento/GroupedProduct/_files/product_grouped_with_simple.php + * @magentoApiDataFixture Magento/Customer/_files/customer_one_address.php + * @return void + */ + public function testAddToCartGroupedCustomQuantityNotAllParamsSpecified(): void + { + $this->_markTestAsRestOnly(); + + $productId = $this->productResource->getIdBySku('simple_11'); + + // Get customer ID token + /** @var CustomerTokenServiceInterface $customerTokenService */ + $customerTokenService = $this->objectManager->create(CustomerTokenServiceInterface::class); + $token = $customerTokenService->createCustomerAccessToken( + 'customer_one_address@test.com', + 'password' + ); + + // Creating empty cart for registered customer. + $serviceInfo = [ + 'rest' => ['resourcePath' => '/V1/carts/mine', 'httpMethod' => Request::HTTP_METHOD_POST, 'token' => $token] + ]; + + $quoteId = $this->_webApiCall($serviceInfo, ['customerId' => 999]); // customerId 999 will get overridden + $this->assertGreaterThan(0, $quoteId); + + // Adding item to the cart + $requestData = [ + 'cartItem' => [ + 'quote_id' => $quoteId, + 'sku' => 'grouped', + 'qty' => 1, + 'product_option' => [ + 'extension_attributes' => [ + 'grouped_options' => [ + ['id' => $productId], + ] + ] + ] + ] + ]; + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Please specify id and qty for grouped options.'); + + $this->_webApiCall($this->getServiceInfoAddToCart($token), $requestData); + } + + /** + * Returns service info add to cart + * + * @param string $token + * @return array + */ + private function getServiceInfoAddToCart(string $token): array + { + return [ + 'rest' => [ + 'resourcePath' => '/V1/carts/mine/items', + 'httpMethod' => Request::HTTP_METHOD_POST, + 'token' => $token + ] + ]; + } }