diff --git a/app/code/Magento/CatalogGraphQl/Plugin/ProductAttributeSortInput.php b/app/code/Magento/CatalogGraphQl/Plugin/ProductAttributeSortInput.php
new file mode 100644
index 000000000000..ac1a2279b771
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Plugin/ProductAttributeSortInput.php
@@ -0,0 +1,95 @@
+getSortFieldsOrder($info, $args['sort']);
+ }
+ return [$field, $context, $info, $value, $args];
+ }
+
+ /**
+ * Get sort fields in the original order
+ *
+ * @param ResolveInfo $info
+ * @param array $sortFields
+ * @return array
+ * @throws \Exception
+ */
+ private function getSortFieldsOrder(ResolveInfo $info, array $sortFields)
+ {
+ $sortFieldsOriginal = [];
+ Visitor::visit(
+ $info->operation,
+ [
+ 'enter' => [
+ NodeKind::ARGUMENT => function (Node $node) use (&$sortFieldsOriginal, $sortFields) {
+ if ($node->name->value === 'sort') {
+ Visitor::visit(
+ $node->value,
+ [
+ 'enter' => [
+ NodeKind::OBJECT_FIELD =>
+ function (Node $node) use (&$sortFieldsOriginal, $sortFields) {
+ if (isset($sortFields[$node->name->value])) {
+ $sortFieldsOriginal[$node->name->value] =
+ $sortFields[$node->name->value];
+ }
+ }
+ ]
+ ]
+ );
+ return Visitor::stop();
+ }
+ }
+ ]
+ ]
+ );
+ return $sortFieldsOriginal;
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml
index a6fbced9b42c..bdae2e16ff84 100644
--- a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml
+++ b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml
@@ -283,4 +283,12 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CustomerImportExport/Model/Import/AbstractCustomer.php b/app/code/Magento/CustomerImportExport/Model/Import/AbstractCustomer.php
index 8fb08cecc54c..1b5674b85fe4 100644
--- a/app/code/Magento/CustomerImportExport/Model/Import/AbstractCustomer.php
+++ b/app/code/Magento/CustomerImportExport/Model/Import/AbstractCustomer.php
@@ -6,6 +6,8 @@
namespace Magento\CustomerImportExport\Model\Import;
+use Magento\Customer\Model\Config\Share;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Validator\EmailAddress;
use Magento\Framework\Validator\ValidateException;
use Magento\Framework\Validator\ValidatorChain;
@@ -87,6 +89,11 @@ abstract class AbstractCustomer extends \Magento\ImportExport\Model\Import\Entit
*/
protected $masterAttributeCode = '_email';
+ /**
+ * @var Share
+ */
+ private $configShare;
+
/**
* @param \Magento\Framework\Stdlib\StringUtils $string
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
@@ -99,6 +106,7 @@ abstract class AbstractCustomer extends \Magento\ImportExport\Model\Import\Entit
* @param \Magento\Eav\Model\Config $eavConfig
* @param \Magento\CustomerImportExport\Model\ResourceModel\Import\Customer\StorageFactory $storageFactory
* @param array $data
+ * @param Share|null $configShare
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -112,7 +120,8 @@ public function __construct(
\Magento\ImportExport\Model\Export\Factory $collectionFactory,
\Magento\Eav\Model\Config $eavConfig,
\Magento\CustomerImportExport\Model\ResourceModel\Import\Customer\StorageFactory $storageFactory,
- array $data = []
+ array $data = [],
+ ?Share $configShare = null
) {
$this->_storageFactory = $storageFactory;
parent::__construct(
@@ -127,7 +136,7 @@ public function __construct(
$eavConfig,
$data
);
-
+ $this->configShare = $configShare ?? ObjectManager::getInstance()->get(Share::class);
$this->addMessageTemplate(self::ERROR_WEBSITE_IS_EMPTY, __('Please specify a website.'));
$this->addMessageTemplate(
self::ERROR_EMAIL_IS_EMPTY,
@@ -174,6 +183,11 @@ protected function _initCustomers(array $data)
protected function _getCustomerId($email, $websiteCode)
{
$email = strtolower(trim($email));
+
+ if ($this->configShare->isGlobalScope()) {
+ return $this->_customerStorage->getCustomerIdByEmail($email);
+ }
+
if (isset($this->_websiteCodeToId[$websiteCode])) {
$websiteId = $this->_websiteCodeToId[$websiteCode];
return $this->_customerStorage->getCustomerId($email, $websiteId);
diff --git a/app/code/Magento/CustomerImportExport/Model/Import/Address.php b/app/code/Magento/CustomerImportExport/Model/Import/Address.php
index 17a2b3678d9c..c8d33ff14dd2 100644
--- a/app/code/Magento/CustomerImportExport/Model/Import/Address.php
+++ b/app/code/Magento/CustomerImportExport/Model/Import/Address.php
@@ -6,6 +6,7 @@
namespace Magento\CustomerImportExport\Model\Import;
+use Magento\Customer\Model\Config\Share;
use Magento\Customer\Model\ResourceModel\Address\Attribute\Source\CountryWithWebsites as CountryWithWebsitesSource;
use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
use Magento\Framework\App\ObjectManager;
@@ -272,7 +273,8 @@ class Address extends AbstractCustomer
* @param array $data
* @param CountryWithWebsitesSource|null $countryWithWebsites
* @param AddressStorage|null $addressStorage
- * @param Processor $indexerProcessor
+ * @param Processor|null $indexerProcessor
+ * @param Share|null $configShare
*
* @SuppressWarnings(PHPMD.NPathComplexity)
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
@@ -297,7 +299,8 @@ public function __construct(
array $data = [],
?CountryWithWebsitesSource $countryWithWebsites = null,
?AddressStorage $addressStorage = null,
- ?Processor $indexerProcessor = null
+ ?Processor $indexerProcessor = null,
+ ?Share $configShare = null
) {
$this->_customerFactory = $customerFactory;
$this->_addressFactory = $addressFactory;
@@ -325,7 +328,8 @@ public function __construct(
$collectionFactory,
$eavConfig,
$storageFactory,
- $data
+ $data,
+ $configShare
);
$this->_entityTable = isset(
diff --git a/app/code/Magento/CustomerImportExport/Model/ResourceModel/Import/Customer/Storage.php b/app/code/Magento/CustomerImportExport/Model/ResourceModel/Import/Customer/Storage.php
index 21a2252257f7..0c16e2010fe5 100644
--- a/app/code/Magento/CustomerImportExport/Model/ResourceModel/Import/Customer/Storage.php
+++ b/app/code/Magento/CustomerImportExport/Model/ResourceModel/Import/Customer/Storage.php
@@ -5,6 +5,7 @@
*/
namespace Magento\CustomerImportExport\Model\ResourceModel\Import\Customer;
+use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Customer\Model\ResourceModel\Customer\Collection as CustomerCollection;
use Magento\Customer\Model\ResourceModel\Customer\CollectionFactory as CustomerCollectionFactory;
use Magento\Framework\DataObject;
@@ -29,6 +30,11 @@ class Storage
*/
protected $_customerIds = [];
+ /**
+ * @var array
+ */
+ private $customerIdsByEmail = [];
+
/**
* Number of items to fetch from db in one query
*
@@ -60,12 +66,19 @@ class Storage
*/
private $customerStoreIds = [];
+ /**
+ * @var CustomerRepositoryInterface
+ */
+ private $customerRepository;
+
/**
* @param CustomerCollectionFactory $collectionFactory
+ * @param CustomerRepositoryInterface $customerRepository
* @param array $data
*/
public function __construct(
CustomerCollectionFactory $collectionFactory,
+ CustomerRepositoryInterface $customerRepository,
array $data = []
) {
$this->_customerCollection = isset(
@@ -73,6 +86,7 @@ public function __construct(
) ? $data['customer_collection'] : $collectionFactory->create();
$this->_pageSize = isset($data['page_size']) ? (int) $data['page_size'] : 0;
$this->customerCollectionFactory = $collectionFactory;
+ $this->customerRepository = $customerRepository;
}
/**
@@ -130,7 +144,8 @@ public function addCustomerByArray(array $customer): Storage
/**
* Add customer to array
*
- * @deprecated 100.3.0 @see addCustomerByArray
+ * @deprecated 100.3.0
+ * @see addCustomerByArray
* @param DataObject $customer
* @return $this
*/
@@ -164,6 +179,25 @@ public function getCustomerId(string $email, int $websiteId)
return false;
}
+ /**
+ * Find customer ID by email.
+ *
+ * @param string $email
+ * @return bool|int
+ */
+ public function getCustomerIdByEmail(string $email)
+ {
+ if (!isset($this->customerIdsByEmail[$email])) {
+ try {
+ $this->customerIdsByEmail[$email] = $this->customerRepository->get($email)->getId();
+ } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
+ $this->customerIdsByEmail[$email] = false;
+ }
+ }
+
+ return $this->customerIdsByEmail[$email];
+ }
+
/**
* Get previously loaded customer id.
*
diff --git a/app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/AddressTest.php b/app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/AddressTest.php
index c86ba7966113..be66976ac7ca 100644
--- a/app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/AddressTest.php
+++ b/app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/AddressTest.php
@@ -9,6 +9,7 @@
use Magento\Customer\Model\Address\Validator\Postcode;
use Magento\Customer\Model\AddressFactory;
+use Magento\Customer\Model\Config\Share;
use Magento\Customer\Model\CustomerFactory;
use Magento\Customer\Model\Indexer\Processor;
use Magento\Customer\Model\ResourceModel\Address\Attribute as AddressAttribute;
@@ -149,6 +150,16 @@ class AddressTest extends TestCase
*/
private $countryWithWebsites;
+ /**
+ * @var Share|MockObject
+ */
+ private $configShare;
+
+ /**
+ * @var Storage
+ */
+ private $customerStorage;
+
/**
* Init entity adapter model
*/
@@ -171,6 +182,7 @@ protected function setUp(): void
->method('getAllOptions')
->willReturn([]);
+ $this->configShare = $this->createMock(Share::class);
$this->_model = $this->_getModelMock();
$this->errorAggregator = $this->createPartialMock(
ProcessingErrorAggregator::class,
@@ -198,7 +210,7 @@ protected function _getModelDependencies()
->getMock();
$connection = $this->createMock(\stdClass::class);
$attributeCollection = $this->_createAttrCollectionMock();
- $customerStorage = $this->_createCustomerStorageMock();
+ $this->customerStorage = $this->_createCustomerStorageMock();
$customerEntity = $this->_createCustomerEntityMock();
$addressCollection = new Collection(
$this->createMock(EntityFactory::class)
@@ -222,7 +234,7 @@ protected function _getModelDependencies()
'bunch_size' => 1,
'attribute_collection' => $attributeCollection,
'entity_type_id' => 1,
- 'customer_storage' => $customerStorage,
+ 'customer_storage' => $this->customerStorage,
'customer_entity' => $customerEntity,
'address_collection' => $addressCollection,
'entity_table' => 'not_used',
@@ -388,7 +400,8 @@ protected function _getModelMock()
$this->_getModelDependencies(),
$this->countryWithWebsites,
$this->createMock(\Magento\CustomerImportExport\Model\ResourceModel\Import\Address\Storage::class),
- $this->createMock(Processor::class)
+ $this->createMock(Processor::class),
+ $this->configShare
);
$property = new \ReflectionProperty($modelMock, '_availableBehaviors');
@@ -447,6 +460,37 @@ public function testValidateRowForUpdate(array $rowData, array $errors, $isValid
{
$this->_model->setParameters(['behavior' => Import::BEHAVIOR_ADD_UPDATE]);
+ $this->configShare->expects($this->once())
+ ->method('isGlobalScope')
+ ->willReturn(false);
+
+ if ($isValid) {
+ $this->assertTrue($this->_model->validateRow($rowData, 0));
+ } else {
+ $this->assertFalse($this->_model->validateRow($rowData, 0));
+ }
+ }
+
+ /**
+ * @dataProvider validateRowForUpdateDataProvider
+ *
+ * @param array $rowData
+ * @param array $errors
+ * @param boolean $isValid
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function testValidateRowForUpdateGlobalCustomer(array $rowData, array $errors, $isValid = false)
+ {
+ $this->_model->setParameters(['behavior' => Import::BEHAVIOR_ADD_UPDATE]);
+
+ $this->configShare->expects($this->once())
+ ->method('isGlobalScope')
+ ->willReturn(true);
+
+ $this->customerStorage->expects($this->once())
+ ->method('getCustomerIdByEmail')
+ ->willReturn(1);
+
if ($isValid) {
$this->assertTrue($this->_model->validateRow($rowData, 0));
} else {
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Filter/Builder/Range.php b/app/code/Magento/Elasticsearch/SearchAdapter/Filter/Builder/Range.php
index a2cab32ea4e6..5cad2317ce9b 100644
--- a/app/code/Magento/Elasticsearch/SearchAdapter/Filter/Builder/Range.php
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Filter/Builder/Range.php
@@ -26,6 +26,8 @@ public function __construct(
}
/**
+ * Add the range filters
+ *
* @param RequestFilterInterface|RangeFilterRequest $filter
* @return array
*/
@@ -33,10 +35,10 @@ public function buildFilter(RequestFilterInterface $filter)
{
$filterQuery = [];
$fieldName = $this->fieldMapper->getFieldName($filter->getField());
- if ($filter->getFrom()) {
+ if ($filter->getFrom() !== null) {
$filterQuery['range'][$fieldName]['gte'] = $filter->getFrom();
}
- if ($filter->getTo()) {
+ if ($filter->getTo() !== null) {
$filterQuery['range'][$fieldName]['lte'] = $filter->getTo();
}
return [$filterQuery];
diff --git a/app/code/Magento/Quote/Model/Cart/AddProductsToCart.php b/app/code/Magento/Quote/Model/Cart/AddProductsToCart.php
index c3da61f0bd87..471b895aa94f 100644
--- a/app/code/Magento/Quote/Model/Cart/AddProductsToCart.php
+++ b/app/code/Magento/Quote/Model/Cart/AddProductsToCart.php
@@ -157,7 +157,8 @@ private function addItemToCart(Quote $cart, Data\CartItem $cartItem, int $cartIt
$cartItemPosition
);
} else {
- $product = $this->productReader->getProductBySku($sku);
+ $productBySku = $this->productReader->getProductBySku($sku);
+ $product = isset($productBySku) ? clone $productBySku : null;
if (!$product || !$product->isSaleable() || !$product->isAvailable()) {
$errors[] = $this->error->create(
__('Could not find a product with SKU "%sku"', ['sku' => $sku])->render(),
diff --git a/app/code/Magento/SalesRule/Model/Coupon/Quote/UpdateCouponUsages.php b/app/code/Magento/SalesRule/Model/Coupon/Quote/UpdateCouponUsages.php
index f2a1cff4c5b9..236006bc521b 100644
--- a/app/code/Magento/SalesRule/Model/Coupon/Quote/UpdateCouponUsages.php
+++ b/app/code/Magento/SalesRule/Model/Coupon/Quote/UpdateCouponUsages.php
@@ -72,5 +72,6 @@ public function execute(CartInterface $quote, bool $increment): void
$this->couponUsagePublisher->publish($updateInfo);
$this->processor->updateCustomerRulesUsages($updateInfo);
+ $this->processor->updateCouponUsages($updateInfo);
}
}
diff --git a/app/code/Magento/SalesRule/Model/Coupon/UpdateCouponUsages.php b/app/code/Magento/SalesRule/Model/Coupon/UpdateCouponUsages.php
index 3ae4ec80f537..7255b455c90a 100644
--- a/app/code/Magento/SalesRule/Model/Coupon/UpdateCouponUsages.php
+++ b/app/code/Magento/SalesRule/Model/Coupon/UpdateCouponUsages.php
@@ -12,6 +12,7 @@
use Magento\SalesRule\Model\Coupon\Usage\Processor as CouponUsageProcessor;
use Magento\SalesRule\Model\Coupon\Usage\UpdateInfo;
use Magento\SalesRule\Model\Coupon\Usage\UpdateInfoFactory;
+use Magento\SalesRule\Model\Service\CouponUsagePublisher;
/**
* Updates the coupon usages
@@ -28,16 +29,25 @@ class UpdateCouponUsages
*/
private $updateInfoFactory;
+ /**
+ * @var CouponUsagePublisher
+ */
+ private $couponUsagePublisher;
+
/**
* @param CouponUsageProcessor $couponUsageProcessor
* @param UpdateInfoFactory $updateInfoFactory
+ * @param ?CouponUsagePublisher $couponUsagePublisher
*/
public function __construct(
CouponUsageProcessor $couponUsageProcessor,
- UpdateInfoFactory $updateInfoFactory
+ UpdateInfoFactory $updateInfoFactory,
+ ?CouponUsagePublisher $couponUsagePublisher = null
) {
$this->couponUsageProcessor = $couponUsageProcessor;
$this->updateInfoFactory = $updateInfoFactory;
+ $this->couponUsagePublisher = $couponUsagePublisher
+ ?? \Magento\Framework\App\ObjectManager::getInstance()->get(CouponUsagePublisher::class);
}
/**
@@ -66,7 +76,9 @@ public function execute(OrderInterface $subject, bool $increment): OrderInterfac
$updateInfo->setCouponAlreadyApplied(true);
}
- $this->couponUsageProcessor->process($updateInfo);
+ $this->couponUsagePublisher->publish($updateInfo);
+ $this->couponUsageProcessor->updateCustomerRulesUsages($updateInfo);
+ $this->couponUsageProcessor->updateCouponUsages($updateInfo);
return $subject;
}
diff --git a/app/code/Magento/SalesRule/Model/Coupon/Usage/Processor.php b/app/code/Magento/SalesRule/Model/Coupon/Usage/Processor.php
index e6dae81cf6eb..2a1de27876f7 100644
--- a/app/code/Magento/SalesRule/Model/Coupon/Usage/Processor.php
+++ b/app/code/Magento/SalesRule/Model/Coupon/Usage/Processor.php
@@ -89,7 +89,7 @@ public function updateRuleUsages(UpdateInfo $updateInfo): void
}
$rule->loadCouponCode();
- if ($isIncrement || $rule->getTimesUsed() > 0) {
+ if ((!$updateInfo->isCouponAlreadyApplied() && $isIncrement) || !$isIncrement) {
$rule->setTimesUsed($rule->getTimesUsed() + ($isIncrement ? 1 : -1));
$rule->save();
}
diff --git a/app/code/Magento/SalesRule/Model/CouponUsageConsumer.php b/app/code/Magento/SalesRule/Model/CouponUsageConsumer.php
index a3224f52ea53..266e9ddf97cc 100644
--- a/app/code/Magento/SalesRule/Model/CouponUsageConsumer.php
+++ b/app/code/Magento/SalesRule/Model/CouponUsageConsumer.php
@@ -80,7 +80,6 @@ public function process(OperationInterface $operation): void
$data = $this->serializer->unserialize($serializedData);
$updateInfo = $this->updateInfoFactory->create();
$updateInfo->setData($data);
- $this->processor->updateCouponUsages($updateInfo);
$this->processor->updateRuleUsages($updateInfo);
} catch (NotFoundException $e) {
$this->logger->critical($e->getMessage());
diff --git a/app/code/Magento/SalesRule/etc/db_schema.xml b/app/code/Magento/SalesRule/etc/db_schema.xml
index 3912ba3642ba..8c33870de493 100644
--- a/app/code/Magento/SalesRule/etc/db_schema.xml
+++ b/app/code/Magento/SalesRule/etc/db_schema.xml
@@ -36,7 +36,7 @@
default="0" comment="Discount Step"/>
-
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php
index cbf2aa7fe83f..b28bde114cda 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php
@@ -364,6 +364,54 @@ public function testFilterLn(): void
);
}
+ /**
+ * Verify that products returned in a correct order
+ *
+ * @magentoApiDataFixture Magento/Catalog/_files/products_for_search.php
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ */
+ public function testSortMultipleFields(): void
+ {
+ $query = <<graphQlQuery($query);
+ $this->assertEquals(5, $response['products']['total_count']);
+ $prod1 = $this->productRepository->get('search_product_5');
+ $prod2 = $this->productRepository->get('search_product_4');
+ $prod3 = $this->productRepository->get('search_product_3');
+ $prod4 = $this->productRepository->get('search_product_1');
+ $prod5 = $this->productRepository->get('search_product_2');
+
+ $filteredProducts = [$prod1, $prod2, $prod3, $prod4, $prod5];
+ $productItemsInResponse = array_map(null, $response['products']['items'], $filteredProducts);
+ foreach ($productItemsInResponse as $itemIndex => $itemArray) {
+ $this->assertNotEmpty($itemArray);
+ $this->assertResponseFields(
+ $productItemsInResponse[$itemIndex][0],
+ [
+ 'name' => $filteredProducts[$itemIndex]->getName(),
+ ]
+ );
+ }
+ }
+
/**
* Compare arrays by value in 'name' field.
*
@@ -2158,9 +2206,9 @@ public function testProductBasicFullTextSearchQuery(): void
}
}
QUERY;
- $prod1 = $this->productRepository->get('blue_briefs');
+ $prod1 = $this->productRepository->get('navy-striped-shoes');
$prod2 = $this->productRepository->get('grey_shorts');
- $prod3 = $this->productRepository->get('navy-striped-shoes');
+ $prod3 = $this->productRepository->get('blue_briefs');
$response = $this->graphQlQuery($query);
$this->assertEquals(3, $response['products']['total_count']);
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogGraphQl/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogGraphQl/ProductSearchTest.php
index 950b26e3b9f5..5c95e99cf4a2 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogGraphQl/ProductSearchTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogGraphQl/ProductSearchTest.php
@@ -371,4 +371,90 @@ private function getProductSearchQueryWithMatchType(
}
QUERY;
}
+
+ #[
+ DataFixture(CategoryFixture::class, as: 'category'),
+ DataFixture(
+ ProductFixture::class,
+ [
+ 'price' => 0,
+ 'category_ids' => ['$category.id$'],
+ ],
+ 'product1'
+ ),
+ DataFixture(
+ ProductFixture::class,
+ [
+ 'price' => 0.5,
+ 'category_ids' => ['$category.id$'],
+ ],
+ 'product2'
+ ),
+ DataFixture(
+ ProductFixture::class,
+ [
+ 'price' => 1,
+ 'category_ids' => ['$category.id$'],
+ ],
+ 'product3'
+ ),
+ DataFixture(
+ ProductFixture::class,
+ [
+ 'price' => 1.5,
+ 'category_ids' => ['$category.id$'],
+ ],
+ 'product4'
+ ),
+ ]
+ public function testProductSearchWithZeroPriceProducts()
+ {
+
+ $response = $this->graphQlQuery($this->getSearchQueryBasedOnPriceRange(0, null));
+ $this->assertEquals(4, $response['products']['totalResult']);
+
+ $response = $this->graphQlQuery($this->getSearchQueryBasedOnPriceRange(0, 0));
+ $this->assertEquals(1, $response['products']['totalResult']);
+
+ $response = $this->graphQlQuery($this->getSearchQueryBasedOnPriceRange(0.5, null));
+ $this->assertEquals(3, $response['products']['totalResult']);
+
+ $response = $this->graphQlQuery($this->getSearchQueryBasedOnPriceRange(0, 1));
+ $this->assertEquals(3, $response['products']['totalResult']);
+ }
+
+ /**
+ * Prepare search query for products with price range
+ *
+ * @param float $priceFrom
+ * @param float|null $priceTo
+ * @return string
+ */
+ private function getSearchQueryBasedOnPriceRange(float $priceFrom, null|float $priceTo): string
+ {
+ $priceToFilter = $priceTo !== null ? ',to:"' . $priceTo . '"' : '';
+ return <<quoteFactory = $objectManager->get(QuoteFactory::class);
$this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class);
$this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class);
+ $this->fixtures = DataFixtureStorageManager::getStorage();
+ $this->quoteIdMaskedFactory = $objectManager->get(\Magento\Quote\Model\QuoteIdMaskFactory::class);
+ $this->quoteIdMaskedResource = $objectManager->get(\Magento\Quote\Model\ResourceModel\Quote\QuoteIdMask::class);
}
protected function tearDown(): void
@@ -101,6 +131,75 @@ public function testMergeGuestWithCustomerCart()
self::assertEquals(1, $item2['quantity']);
}
+ #[
+ DataFixture(ProductFixture::class, ['sku' => 'simple1', 'price' => 10], as:'p1'),
+ DataFixture(ProductFixture::class, ['sku' => 'simple2', 'price' => 20], as:'p2'),
+ DataFixture(BundleSelectionFixture::class, ['sku' => '$p1.sku$', 'price' => 10, 'price_type' => 0], as:'link1'),
+ DataFixture(BundleSelectionFixture::class, ['sku' => '$p2.sku$', 'price' => 25, 'price_type' => 0], as:'link2'),
+ DataFixture(BundleOptionFixture::class, ['title' => 'Checkbox Options', 'type' => 'checkbox',
+ 'required' => 1,'product_links' => ['$link1$', '$link2$']], 'opt1'),
+ DataFixture(BundleOptionFixture::class, ['title' => 'Checkbox Options', 'type' => 'checkbox',
+ 'required' => 1,'product_links' => ['$link1$', '$link2$']], 'opt2'),
+ DataFixture(
+ BundleProductFixture::class,
+ ['sku' => 'bundle-product-multiselect-checkbox-options','price' => 50,'price_type' => 1,
+ '_options' => ['$opt1$', '$opt2$']],
+ as:'bp1'
+ ),
+ DataFixture(Customer::class, ['email' => 'me@example.com'], as: 'customer'),
+ DataFixture(CustomerCart::class, ['customer_id' => '$customer.id$'], as: 'customerCart'),
+ DataFixture(
+ AddBundleProductToCart::class,
+ [
+ 'cart_id' => '$customerCart.id$',
+ 'product_id' => '$bp1.id$',
+ 'selections' => [['$p1.id$'], ['$p2.id$']],
+ 'qty' => 1
+ ]
+ ),
+ DataFixture(GuestCartFixture::class, as: 'guestCart'),
+ DataFixture(
+ AddBundleProductToCart::class,
+ [
+ 'cart_id' => '$guestCart.id$',
+ 'product_id' => '$bp1.id$',
+ 'selections' => [['$p1.id$'], ['$p2.id$']],
+ 'qty' => 2
+ ]
+ )
+ ]
+ public function testMergeGuestWithCustomerCartBundleProduct()
+ {
+ $guestCart = $this->fixtures->get('guestCart');
+ $guestQuoteMaskedId = $this->quoteIdToMaskedId->execute((int)$guestCart->getId());
+
+ $customerCart = $this->fixtures->get('customerCart');
+ $customerCartId = (int)$customerCart->getId();
+ $customerQuoteMaskedId = $this->quoteIdToMaskedId->execute($customerCartId);
+ if (!$customerQuoteMaskedId) {
+ $quoteIdMask = $this->quoteIdMaskedFactory->create()->setQuoteId($customerCartId);
+ $this->quoteIdMaskedResource->save($quoteIdMask);
+ $customerQuoteMaskedId = $this->quoteIdToMaskedId->execute($customerCartId);
+ }
+
+ $queryHeader = $this->getHeaderMap('me@example.com', 'password');
+ $cartMergeQuery = $this->getCartMergeMutation($guestQuoteMaskedId, $customerQuoteMaskedId);
+ $mergeResponse = $this->graphQlMutation($cartMergeQuery, [], '', $queryHeader);
+ self::assertArrayHasKey('mergeCarts', $mergeResponse);
+
+ $cartResponse = $mergeResponse['mergeCarts'];
+ self::assertArrayHasKey('items', $cartResponse);
+ self::assertCount(1, $cartResponse['items']);
+ $cartResponse = $this->graphQlMutation($this->getCartQuery($customerQuoteMaskedId), [], '', $queryHeader);
+
+ self::assertArrayHasKey('cart', $cartResponse);
+ self::assertArrayHasKey('items', $cartResponse['cart']);
+ self::assertCount(1, $cartResponse['cart']['items']);
+ $item1 = $cartResponse['cart']['items'][0];
+ self::assertArrayHasKey('quantity', $item1);
+ self::assertEquals(3, $item1['quantity']);
+ }
+
/**
* @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_saved.php
* @magentoApiDataFixture Magento/Customer/_files/customer.php
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddProductWithOptionsToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddProductWithOptionsToCartTest.php
index 868232288ed5..ad13d7202000 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddProductWithOptionsToCartTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddProductWithOptionsToCartTest.php
@@ -105,6 +105,64 @@ public function testAddProductWithOptionsResponse()
);
}
+ #[
+ DataFixture(GuestCart::class, as: 'quote'),
+ DataFixture(QuoteIdMask::class, ['cart_id' => '$quote.id$'], 'quoteIdMask'),
+ DataFixture(
+ Product::class,
+ [
+ 'sku' => 'simple1',
+ 'options' => [
+ [
+ 'title' => 'option1',
+ 'type' => ProductCustomOptionInterface::OPTION_TYPE_FIELD,
+ ]
+ ]
+ ],
+ 'product1'
+ ),
+ DataFixture(Indexer::class, as: 'indexer')
+ ]
+ public function testAddSameProductWithDifferentOptionValues()
+ {
+ $uidEncoder = Bootstrap::getObjectManager()->create(Uid::class);
+
+ $cartId = DataFixtureStorageManager::getStorage()->get('quoteIdMask')->getMaskedId();
+ $product = DataFixtureStorageManager::getStorage()->get('product1');
+ /* @var \Magento\Catalog\Api\Data\ProductInterface $product */
+ $sku = $product->getSku();
+ $option = $product->getOptions();
+ $optionUid = $uidEncoder->encode(
+ 'custom-option' . '/' . $option[0]->getData()['option_id']
+ );
+
+ // Assert if product options for item added to the cart
+ // are present in mutation response after product with selected option was added
+ $mutation = $this->getAddProductWithDifferentOptionValuesMutation(
+ $cartId,
+ $sku,
+ $optionUid
+ );
+ $response = $this->graphQlMutation($mutation);
+
+ $this->assertArrayHasKey('items', $response['addProductsToCart']['cart']);
+ $this->assertCount(2, $response['addProductsToCart']['cart']['items']);
+ $this->assertArrayHasKey('customizable_options', $response['addProductsToCart']['cart']['items'][0]);
+
+ $this->assertEquals(
+ $response['addProductsToCart']['cart']['items'],
+ $this->getExpectedResponseForDifferentOptionValues($optionUid, $sku)
+ );
+
+ // Assert if product options for item in the cart are present in the response
+ $query = $this->getCartQueryForDifferentOptionValues($cartId);
+ $response = $this->graphQlQuery($query);
+ $this->assertEquals(
+ $this->getExpectedResponseForDifferentOptionValues($optionUid, $sku),
+ $response['cart']['items']
+ );
+ }
+
#[
DataFixture(GuestCart::class, as: 'quote'),
DataFixture(QuoteIdMask::class, ['cart_id' => '$quote.id$'], 'quoteIdMask'),
@@ -268,7 +326,67 @@ private function getAddProductWithOptionMutation(string $cartId, string $sku, st
}
}
}
-}
+}
+QRY;
+ }
+
+ /**
+ * Returns mutation for the test with different option values
+ *
+ * @param string $cartId
+ * @param string $sku
+ * @param string $optionUid
+ * @return string
+ */
+ private function getAddProductWithDifferentOptionValuesMutation(
+ string $cartId,
+ string $sku,
+ string $optionUid
+ ): string {
+ return << [
+ "quantity" => 1,
+ "product" => ["sku" => "{$sku}"],
+ "customizable_options" => [
+ 0 => [
+ "customizable_option_uid" => "{$optionUid}",
+ "label" => "option1",
+ "values" => [
+ 0 => [
+ "customizable_option_value_uid" => "{$optionUid}",
+ "value" => "value1"
+ ]
+ ]
+ ]
+ ]
+ ],
+ 1 => [
+ "quantity" => 1,
+ "product" => ["sku" => "{$sku}"],
+ "customizable_options" => [
+ 0 => [
+ "customizable_option_uid" => "{$optionUid}",
+ "label" => "option1",
+ "values" => [
+ 0 => [
+ "customizable_option_value_uid" => "{$optionUid}",
+ "value" => "value2"
+ ]
+ ]
+ ]
+ ]
+ ],
+ ];
+ }
}
diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Coupon/UpdateCouponUsagesTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Coupon/UpdateCouponUsagesTest.php
index 777959d2df8c..1f0c81f21412 100644
--- a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Coupon/UpdateCouponUsagesTest.php
+++ b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Coupon/UpdateCouponUsagesTest.php
@@ -124,4 +124,44 @@ public function testCancelOrderBeforeUsageConsumerExecution(): void
$this->cartManagement->placeOrder($cart->getId());
$consumer->process(1);
}
+
+ #[
+ DataFixture(ProductFixture::class, as: 'p1'),
+ DataFixture(
+ SalesRuleFixture::class,
+ ['coupon_code' => 'once', 'uses_per_coupon' => 1, 'discount_amount' => 10]
+ ),
+ DataFixture(Customer::class, as: 'customer'),
+
+ DataFixture(CustomerCart::class, ['customer_id' => '$customer.id$'], 'cart1'),
+ DataFixture(AddProductToCart::class, ['cart_id' => '$cart1.id$', 'product_id' => '$p1.id$']),
+ DataFixture(SetBillingAddress::class, ['cart_id' => '$cart1.id$']),
+ DataFixture(SetShippingAddress::class, ['cart_id' => '$cart1.id$']),
+ DataFixture(SetDeliveryMethod::class, ['cart_id' => '$cart1.id$']),
+ DataFixture(SetPaymentMethod::class, ['cart_id' => '$cart1.id$']),
+
+ DataFixture(GuestCart::class, as: 'cart2'),
+ DataFixture(AddProductToCart::class, ['cart_id' => '$cart2.id$', 'product_id' => '$p1.id$']),
+ DataFixture(SetBillingAddress::class, ['cart_id' => '$cart2.id$']),
+ DataFixture(SetShippingAddress::class, ['cart_id' => '$cart2.id$']),
+ DataFixture(SetDeliveryMethod::class, ['cart_id' => '$cart2.id$']),
+ DataFixture(SetPaymentMethod::class, ['cart_id' => '$cart2.id$']),
+ ]
+ public function testCancelOrderBeforeConsumerAndRuleTimesUsed(): void
+ {
+ $cart = $this->fixtures->get('cart1');
+ $this->couponManagement->set($cart->getId(), 'once');
+ $orderId = $this->cartManagement->placeOrder($cart->getId());
+ $this->orderManagement->cancel($orderId);
+ $consumer = $this->consumerFactory->get('sales.rule.update.coupon.usage');
+ $consumer->process(2);
+
+ $cart = $this->fixtures->get('cart2');
+ $customer = $this->fixtures->get('customer');
+ $this->cartManagement->assignCustomer($cart->getId(), $customer->getId(), 1);
+ $cart = $this->cartRepository->get($cart->getId());
+ $this->couponManagement->set($cart->getId(), 'once');
+ $this->cartManagement->placeOrder($cart->getId());
+ $consumer->process(1);
+ }
}