From f4de663ae5bc43a2d3bb1f09ec85599a65722691 Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh Date: Tue, 14 Jan 2020 15:43:39 +0200 Subject: [PATCH 1/6] MC-30131: [Magento Cloud] When choosing all products, no products appear --- .../ResourceModel/Fulltext/Collection/SearchResultApplier.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php index ad52f81bf8eda..491d505b5bd85 100644 --- a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php +++ b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php @@ -7,8 +7,8 @@ namespace Magento\Elasticsearch\Model\ResourceModel\Fulltext\Collection; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface; -use Magento\Framework\Data\Collection; use Magento\Framework\Api\Search\SearchResultInterface; +use Magento\Framework\Data\Collection; /** * Resolve specific attributes for search criteria. @@ -71,7 +71,7 @@ public function apply() $this->collection->getSelect()->where('e.entity_id IN (?)', $ids); $orderList = join(',', $ids); $this->collection->getSelect()->reset(\Magento\Framework\DB\Select::ORDER); - $this->collection->getSelect()->order("FIELD(e.entity_id,$orderList)"); + $this->collection->getSelect()->order(new \Zend_Db_Expr("FIELD(e.entity_id,$orderList)")); } /** From d546aa237a50b30ebcd617d066bd136e3cf73c2b Mon Sep 17 00:00:00 2001 From: "rostyslav.hymon" Date: Wed, 15 Jan 2020 09:20:22 +0200 Subject: [PATCH 2/6] MC-29959: [Magento Cloud] Options for downloadable products not found in row(s) error while importing products --- .../DownloadableImportExport/Helper/Data.php | 6 +- .../Helper/Uploader.php | 13 +- .../Export/Product/Type/Downloadable.php | 15 ++ .../Model/Export/RowCustomizer.php | 170 ++++++++++++++++++ .../Import/Product/Type/Downloadable.php | 10 +- .../Import/Product/Type/DownloadableTest.php | 5 +- .../DownloadableImportExport/etc/di.xml | 16 ++ .../DownloadableImportExport/etc/export.xml | 10 ++ ...nloadable_with_link_url_and_sample_url.php | 87 +++++++++ ..._with_link_url_and_sample_url_rollback.php | 40 +++++ .../Model/DownloadableTest.php | 61 ++++--- 11 files changed, 399 insertions(+), 34 deletions(-) create mode 100644 app/code/Magento/DownloadableImportExport/Model/Export/Product/Type/Downloadable.php create mode 100644 app/code/Magento/DownloadableImportExport/Model/Export/RowCustomizer.php create mode 100644 app/code/Magento/DownloadableImportExport/etc/di.xml create mode 100644 app/code/Magento/DownloadableImportExport/etc/export.xml create mode 100644 dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url.php create mode 100644 dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url_rollback.php diff --git a/app/code/Magento/DownloadableImportExport/Helper/Data.php b/app/code/Magento/DownloadableImportExport/Helper/Data.php index fa4f7d656cdbe..91e290dbbcdf3 100644 --- a/app/code/Magento/DownloadableImportExport/Helper/Data.php +++ b/app/code/Magento/DownloadableImportExport/Helper/Data.php @@ -8,7 +8,7 @@ use Magento\DownloadableImportExport\Model\Import\Product\Type\Downloadable; /** - * Class Data + * Helper for import-export downloadable product */ class Data extends \Magento\Framework\App\Helper\AbstractHelper { @@ -47,6 +47,7 @@ public function isRowDownloadableNoValid(array $rowData) * @param array $option * @param array $existingOptions * @return array + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function fillExistOptions(array $base, array $option, array $existingOptions) { @@ -59,6 +60,9 @@ public function fillExistOptions(array $base, array $option, array $existingOpti && $option['sample_file'] == $existingOption['sample_file'] && $option['sample_type'] == $existingOption['sample_type'] && $option['product_id'] == $existingOption['product_id']) { + if (empty($existingOption['website_id'])) { + unset($existingOption['website_id']); + } $result = array_replace($base, $option, $existingOption); } } diff --git a/app/code/Magento/DownloadableImportExport/Helper/Uploader.php b/app/code/Magento/DownloadableImportExport/Helper/Uploader.php index 197250faaea91..e6ead5d5cc021 100644 --- a/app/code/Magento/DownloadableImportExport/Helper/Uploader.php +++ b/app/code/Magento/DownloadableImportExport/Helper/Uploader.php @@ -8,7 +8,7 @@ use Magento\Framework\App\Filesystem\DirectoryList; /** - * Class Uploader + * Uploader helper for downloadable products */ class Uploader extends \Magento\Framework\App\Helper\AbstractHelper { @@ -105,6 +105,17 @@ public function getUploader($type, $parameters) return $this->fileUploader; } + /** + * Check a file or directory exists + * + * @param string $fileName + * @return bool + */ + public function isFileExist(string $fileName): bool + { + return $this->mediaDirectory->isExist($this->fileUploader->getDestDir().$fileName); + } + /** * Get all allowed extensions * diff --git a/app/code/Magento/DownloadableImportExport/Model/Export/Product/Type/Downloadable.php b/app/code/Magento/DownloadableImportExport/Model/Export/Product/Type/Downloadable.php new file mode 100644 index 0000000000000..716e65e00d1aa --- /dev/null +++ b/app/code/Magento/DownloadableImportExport/Model/Export/Product/Type/Downloadable.php @@ -0,0 +1,15 @@ +storeManager = $storeManager; + $this->linkRepository = $linkRepository; + $this->sampleRepository = $sampleRepository; + } + + /** + * Prepare configurable data for export + * + * @param ProductCollection $collection + * @param int[] $productIds + * @return void + */ + public function prepareData($collection, $productIds): void + { + $productCollection = clone $collection; + $productCollection->addAttributeToFilter('entity_id', ['in' => $productIds]) + ->addAttributeToFilter('type_id', ['eq' => Type::TYPE_DOWNLOADABLE]) + ->addAttributeToSelect('links_title') + ->addAttributeToSelect('samples_title'); + // set global scope during export + $this->storeManager->setCurrentStore(Store::DEFAULT_STORE_ID); + foreach ($collection as $product) { + $productLinks = $this->linkRepository->getLinksByProduct($product); + $productSamples = $this->sampleRepository->getSamplesByProduct($product); + $this->downloadableData[$product->getId()] = []; + $linksData = []; + $samplesData = []; + foreach ($productLinks as $linkId => $link) { + $linkData = $link->getData(); + $linkData['group_title'] = $product->getData('links_title'); + $linksData[$linkId] = $this->optionRowToCellString($linkData); + } + foreach ($productSamples as $sampleId => $sample) { + $sampleData = $sample->getData(); + $sampleData['group_title'] = $product->getData('samples_title'); + $samplesData[$sampleId] = $this->optionRowToCellString($sampleData); + } + $this->downloadableData[$product->getId()] = [ + Downloadable::COL_DOWNLOADABLE_LINKS => implode( + ImportProduct::PSEUDO_MULTI_LINE_SEPARATOR, + $linksData + ), + Downloadable::COL_DOWNLOADABLE_SAMPLES => implode( + Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, + $samplesData + )]; + } + } + + /** + * Convert option row to cell string + * + * @param array $option + * @return string + */ + private function optionRowToCellString(array $option): string + { + $result = []; + foreach ($option as $attributeCode => $value) { + if ($value) { + $result[] = $attributeCode . ImportProduct::PAIR_NAME_VALUE_SEPARATOR . $value; + } + } + return implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $result); + } + + /** + * Set headers columns + * + * @param array $columns + * @return array + */ + public function addHeaderColumns($columns): array + { + return array_merge($columns, $this->downloadableColumns); + } + + /** + * Add downloadable data for export + * + * @param array $dataRow + * @param int $productId + * @return array + */ + public function addData($dataRow, $productId): array + { + if (!empty($this->downloadableData[$productId])) { + $dataRow = array_merge($dataRow, $this->downloadableData[$productId]); + } + return $dataRow; + } + + /** + * Calculate the largest links block + * + * @param array $additionalRowsCount + * @param int $productId + * @return array + */ + public function getAdditionalRowsCount($additionalRowsCount, $productId): array + { + if (!empty($this->downloadableData[$productId])) { + $additionalRowsCount = max($additionalRowsCount, count($this->downloadableData[$productId])); + } + return $additionalRowsCount; + } +} diff --git a/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php b/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php index c9cdf52f55dd1..f148550dd96bb 100644 --- a/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php +++ b/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php @@ -896,12 +896,16 @@ protected function parseSampleOption($values) protected function uploadDownloadableFiles($fileName, $type = 'links', $renameFileOff = false) { try { - $res = $this->uploaderHelper->getUploader($type, $this->parameters)->move($fileName, $renameFileOff); - return $res['file']; + $uploader = $this->uploaderHelper->getUploader($type, $this->parameters); + if (!$this->uploaderHelper->isFileExist($fileName)) { + $uploader->move($fileName, $renameFileOff); + $fileName = $uploader['file']; + } } catch (\Exception $e) { $this->_entityModel->addRowError(self::ERROR_MOVE_FILE, $this->rowNum); - return ''; + $fileName = ''; } + return $fileName; } /** diff --git a/app/code/Magento/DownloadableImportExport/Test/Unit/Model/Import/Product/Type/DownloadableTest.php b/app/code/Magento/DownloadableImportExport/Test/Unit/Model/Import/Product/Type/DownloadableTest.php index 9cb6b061b14ee..482bfa4f7c569 100644 --- a/app/code/Magento/DownloadableImportExport/Test/Unit/Model/Import/Product/Type/DownloadableTest.php +++ b/app/code/Magento/DownloadableImportExport/Test/Unit/Model/Import/Product/Type/DownloadableTest.php @@ -9,7 +9,7 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManager; /** - * Class DownloadableTest + * Class DownloadableTest for downloadable products import * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -164,7 +164,7 @@ protected function setUp() // 7. $fileHelper $this->uploaderHelper = $this->createPartialMock( \Magento\DownloadableImportExport\Helper\Uploader::class, - ['getUploader'] + ['getUploader', 'isFileExist'] ); $this->uploaderHelper->expects($this->any())->method('getUploader')->willReturn($this->uploaderMock); $this->downloadableHelper = $this->createPartialMock( @@ -660,6 +660,7 @@ public function testSetUploaderDirFalse($newSku, $bunch, $allowImport, $parsedOp $metadataPoolMock->expects($this->any())->method('getLinkField')->willReturn('entity_id'); $this->downloadableHelper->expects($this->atLeastOnce()) ->method('fillExistOptions')->willReturn($parsedOptions['link']); + $this->uploaderHelper->method('isFileExist')->willReturn(false); $this->downloadableModelMock = $this->objectManagerHelper->getObject( \Magento\DownloadableImportExport\Model\Import\Product\Type\Downloadable::class, diff --git a/app/code/Magento/DownloadableImportExport/etc/di.xml b/app/code/Magento/DownloadableImportExport/etc/di.xml new file mode 100644 index 0000000000000..06768d3e72a8b --- /dev/null +++ b/app/code/Magento/DownloadableImportExport/etc/di.xml @@ -0,0 +1,16 @@ + + + + + + + Magento\DownloadableImportExport\Model\Export\RowCustomizer + + + + diff --git a/app/code/Magento/DownloadableImportExport/etc/export.xml b/app/code/Magento/DownloadableImportExport/etc/export.xml new file mode 100644 index 0000000000000..b6e419cc2c389 --- /dev/null +++ b/app/code/Magento/DownloadableImportExport/etc/export.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url.php b/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url.php new file mode 100644 index 0000000000000..32fed4730adfc --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url.php @@ -0,0 +1,87 @@ +get(StoreManagerInterface::class); +$storeManager->setCurrentStore($storeManager->getStore('admin')->getId()); + +$domainManager = $objectManager->get(DomainManagerInterface::class); +$domainManager->addDomains( + [ + 'example.com', + 'www.example.com', + 'www.sample.example.com', + 'google.com' + ] +); + +$product = $objectManager->get(ProductInterface::class); +$product + ->setTypeId(Type::TYPE_DOWNLOADABLE) + ->setId(1) + ->setAttributeSetId(4) + ->setName('Downloadable Product') + ->setSku('downloadable-product') + ->setPrice(10) + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setLinksPurchasedSeparately(true) + ->setLinksTitle('Links') + ->setSamplesTitle('Samples') + ->setStockData( + [ + 'qty' => 100, + 'is_in_stock' => 1, + 'manage_stock' => 1, + ] + ); + +$linkFactory = $objectManager->get(LinkInterfaceFactory::class); +/** @var LinkInterface $link */ +$link = $linkFactory->create(); +$link->setTitle('Downloadable Product Link'); +$link->setIsShareable(Link::LINK_SHAREABLE_CONFIG); +$link->setLinkUrl('http://example.com/downloadable.txt'); +$link->setLinkType(Download::LINK_TYPE_URL); +$link->setStoreId($product->getStoreId()); +$link->setWebsiteId($product->getStore()->getWebsiteId()); +$link->setProductWebsiteIds($product->getWebsiteIds()); +$link->setSortOrder(1); +$link->setPrice(0); +$link->setNumberOfDownloads(0); + +$sampleFactory = $objectManager->get(SampleInterfaceFactory::class); +$sample = $sampleFactory->create(); +$sample->setTitle('Downloadable Product Sample') + ->setSampleType(Download::LINK_TYPE_URL) + ->setSampleUrl('http://example.com/downloadable.txt') + ->setStoreId($product->getStoreId()) + ->setWebsiteId($product->getStore()->getWebsiteId()) + ->setProductWebsiteIds($product->getWebsiteIds()) + ->setSortOrder(10); + +$extension = $product->getExtensionAttributes(); +$extension->setDownloadableProductLinks([$link]); +$extension->setDownloadableProductSamples([$sample]); +$product->setExtensionAttributes($extension); + +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url_rollback.php b/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url_rollback.php new file mode 100644 index 0000000000000..9a2e1c74fcd33 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url_rollback.php @@ -0,0 +1,40 @@ +getInstance()->reinitialize(); + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var DomainManagerInterface $domainManager */ +$domainManager = $objectManager->get(DomainManagerInterface::class); +$domainManager->removeDomains( + [ + 'example.com', + 'www.example.com', + 'www.sample.example.com', + 'google.com' + ] +); + +/** @var \Magento\Framework\Registry $registry */ +$registry = $objectManager->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager + ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); +try { + $product = $productRepository->get('downloadable-product', false, null, true); + $productRepository->delete($product); +} catch (NoSuchEntityException $e) { // @codingStandardsIgnoreLine +} +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/DownloadableImportExport/Model/DownloadableTest.php b/dev/tests/integration/testsuite/Magento/DownloadableImportExport/Model/DownloadableTest.php index d0e4471e2ea68..861be98c13e72 100644 --- a/dev/tests/integration/testsuite/Magento/DownloadableImportExport/Model/DownloadableTest.php +++ b/dev/tests/integration/testsuite/Magento/DownloadableImportExport/Model/DownloadableTest.php @@ -6,7 +6,11 @@ namespace Magento\DownloadableImportExport\Model; use Magento\CatalogImportExport\Model\AbstractProductExportImportTestCase; +use Magento\Catalog\Model\Product; +/** + * Test export and import downloadable products + */ class DownloadableTest extends AbstractProductExportImportTestCase { /** @@ -17,15 +21,7 @@ public function exportImportDataProvider(): array return [ 'downloadable-product' => [ [ - 'Magento/Downloadable/_files/product_downloadable.php' - ], - [ - 'downloadable-product', - ], - ], - 'downloadable-product-with-files' => [ - [ - 'Magento/Downloadable/_files/product_downloadable_with_files.php' + 'Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url.php' ], [ 'downloadable-product', @@ -46,43 +42,54 @@ public function exportImportDataProvider(): array * @param string[] $skippedAttributes * @return void * @dataProvider exportImportDataProvider - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function testImportExport(array $fixtures, array $skus, array $skippedAttributes = []): void { - $this->markTestSkipped('Uncomment after MAGETWO-38240 resolved'); + $skippedAttributes = array_merge(self::$skippedAttributes, ['downloadable_links']); + parent::testImportExport($fixtures, $skus, $skippedAttributes); } /** * @inheritdoc */ protected function assertEqualsSpecificAttributes( - \Magento\Catalog\Model\Product $expectedProduct, - \Magento\Catalog\Model\Product $actualProduct + Product $expectedProduct, + Product $actualProduct ): void { - $expectedProductLinks = $expectedProduct->getExtensionAttributes()->getDownloadableProductLinks(); + $expectedProductLinks = $expectedProduct->getExtensionAttributes()->getDownloadableProductLinks(); $expectedProductSamples = $expectedProduct->getExtensionAttributes()->getDownloadableProductSamples(); - $actualProductLinks = $actualProduct->getExtensionAttributes()->getDownloadableProductLinks(); + $actualProductLinks = $actualProduct->getExtensionAttributes()->getDownloadableProductLinks(); $actualProductSamples = $actualProduct->getExtensionAttributes()->getDownloadableProductSamples(); $this->assertEquals(count($expectedProductLinks), count($actualProductLinks)); $this->assertEquals(count($expectedProductSamples), count($actualProductSamples)); - - $expectedLinksArray = []; - foreach ($expectedProductLinks as $link) { - $expectedLinksArray[] = $link->getData(); + $actualLinks = $this->getDataWithSortingById($actualProductLinks); + $expectedLinks = $this->getDataWithSortingById($actualProductLinks); + foreach ($actualLinks as $key => $actualLink) { + $this->assertEquals($expectedLinks[$key], $actualLink); } - foreach ($actualProductLinks as $actualLink) { - $this->assertContains($expectedLinksArray, $actualLink->getData()); + $actualSamples = $this->getDataWithSortingById($actualProductSamples); + $expectedSamples = $this->getDataWithSortingById($expectedProductSamples); + foreach ($actualSamples as $key => $actualSample) { + $this->assertEquals($expectedSamples[$key], $actualSample); } + } - $expectedSamplesArray = []; - foreach ($expectedProductSamples as $sample) { - $expectedSamplesArray[] = $sample->getData(); - } - foreach ($actualProductSamples as $actualSample) { - $this->assertContains($expectedSamplesArray, $actualSample->getData()); + /** + * Get data with sorting by id + * + * @param array $objects + * + * @return array + */ + private function getDataWithSortingById(array $objects) + { + $result = []; + foreach ($objects as $object) { + $result[$object->getId()] = $object->getData(); } + + return $result; } } From b6880b20e8cd7049d69f67cb5c8fb60e092384db Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh Date: Wed, 15 Jan 2020 10:40:01 +0200 Subject: [PATCH 3/6] MC-23986: Cart price rule based on payment methods not aplied in checkout --- ...ingAddressAndProductWithTierPricesTest.xml | 1 + ...ippingMethodInReviewAndPaymentStepTest.xml | 12 +--- .../web/js/action/select-payment-method.js | 2 +- .../Model/Checkout/Plugin/GuestValidation.php | 31 +++------- .../Model/Checkout/Plugin/Validation.php | 37 ++++------- .../Checkout/Plugin/GuestValidationTest.php | 37 +---------- .../Model/Checkout/Plugin/ValidationTest.php | 40 ++++-------- .../Promo/Quote/Edit/Tab/Conditions.php | 28 ++++++--- .../SalesRule/Model/Quote/Discount.php | 6 ++ .../Model/Rule/Condition/Address.php | 1 + ...StorefrontApplyDiscountCodeActionGroup.xml | 2 +- .../view/frontend/requirejs-config.js | 14 +++++ .../js/action/select-payment-method-mixin.js | 50 +++++++++++++++ .../view/frontend/web/js/model/coupon.js | 49 +++++++++++++++ .../frontend/web/js/view/payment/discount.js | 11 ++-- .../Model/Rule/Condition/AddressTest.php | 50 +++++++++++++++ .../Model/Rule/Condition/ConditionHelper.php | 62 +++++++++++++++++++ .../Model/Rule/Condition/ProductTest.php | 55 ++-------------- .../SalesRule/_files/rules_payment_method.php | 47 ++++++++++++++ .../Php/_files/phpcpd/blacklist/common.txt | 3 +- 20 files changed, 346 insertions(+), 192 deletions(-) create mode 100644 app/code/Magento/SalesRule/view/frontend/requirejs-config.js create mode 100644 app/code/Magento/SalesRule/view/frontend/web/js/action/select-payment-method-mixin.js create mode 100644 app/code/Magento/SalesRule/view/frontend/web/js/model/coupon.js create mode 100644 dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/AddressTest.php create mode 100644 dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ConditionHelper.php create mode 100644 dev/tests/integration/testsuite/Magento/SalesRule/_files/rules_payment_method.php diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndProductWithTierPricesTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndProductWithTierPricesTest.xml index 07d29aa0aac4a..92eae461019a2 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndProductWithTierPricesTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndProductWithTierPricesTest.xml @@ -77,6 +77,7 @@ + diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontNotApplicableShippingMethodInReviewAndPaymentStepTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontNotApplicableShippingMethodInReviewAndPaymentStepTest.xml index 1a427bbe77166..f11d25a30f073 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontNotApplicableShippingMethodInReviewAndPaymentStepTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontNotApplicableShippingMethodInReviewAndPaymentStepTest.xml @@ -27,12 +27,6 @@ - - - - - - @@ -75,10 +69,6 @@ - - - - @@ -187,7 +177,7 @@ - + diff --git a/app/code/Magento/Checkout/view/frontend/web/js/action/select-payment-method.js b/app/code/Magento/Checkout/view/frontend/web/js/action/select-payment-method.js index 34f1700749794..5adbd9356a8d8 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/action/select-payment-method.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/action/select-payment-method.js @@ -7,7 +7,7 @@ * @api */ define([ - '../model/quote' + 'Magento_Checkout/js/model/quote' ], function (quote) { 'use strict'; diff --git a/app/code/Magento/CheckoutAgreements/Model/Checkout/Plugin/GuestValidation.php b/app/code/Magento/CheckoutAgreements/Model/Checkout/Plugin/GuestValidation.php index fbceca0906702..95330c9d01381 100644 --- a/app/code/Magento/CheckoutAgreements/Model/Checkout/Plugin/GuestValidation.php +++ b/app/code/Magento/CheckoutAgreements/Model/Checkout/Plugin/GuestValidation.php @@ -11,7 +11,7 @@ use Magento\CheckoutAgreements\Model\Api\SearchCriteria\ActiveStoreAgreementsFilter; /** - * Class GuestValidation + * Guest checkout agreements validation. * * Plugin that checks if checkout agreement enabled and validates all agreements. * Current plugin is duplicate from Magento\CheckoutAgreements\Model\Checkout\Plugin\Validation due to different @@ -58,6 +58,8 @@ public function __construct( } /** + * Validates agreements before save payment information and order placing. + * * @param \Magento\Checkout\Api\GuestPaymentInformationManagementInterface $subject * @param string $cartId * @param string $email @@ -80,28 +82,8 @@ public function beforeSavePaymentInformationAndPlaceOrder( } /** - * @param \Magento\Checkout\Api\GuestPaymentInformationManagementInterface $subject - * @param string $cartId - * @param string $email - * @param \Magento\Quote\Api\Data\PaymentInterface $paymentMethod - * @param \Magento\Quote\Api\Data\AddressInterface|null $billingAddress - * @throws \Magento\Framework\Exception\CouldNotSaveException - * @return void - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function beforeSavePaymentInformation( - \Magento\Checkout\Api\GuestPaymentInformationManagementInterface $subject, - $cartId, - $email, - \Magento\Quote\Api\Data\PaymentInterface $paymentMethod, - \Magento\Quote\Api\Data\AddressInterface $billingAddress = null - ) { - if ($this->isAgreementEnabled()) { - $this->validateAgreements($paymentMethod); - } - } - - /** + * Validates agreements. + * * @param \Magento\Quote\Api\Data\PaymentInterface $paymentMethod * @throws \Magento\Framework\Exception\CouldNotSaveException * @return void @@ -123,7 +105,8 @@ private function validateAgreements(\Magento\Quote\Api\Data\PaymentInterface $pa } /** - * Verify if agreement validation needed + * Verify if agreement validation needed. + * * @return bool */ private function isAgreementEnabled() diff --git a/app/code/Magento/CheckoutAgreements/Model/Checkout/Plugin/Validation.php b/app/code/Magento/CheckoutAgreements/Model/Checkout/Plugin/Validation.php index 67e2a6c9ec334..04f625238d249 100644 --- a/app/code/Magento/CheckoutAgreements/Model/Checkout/Plugin/Validation.php +++ b/app/code/Magento/CheckoutAgreements/Model/Checkout/Plugin/Validation.php @@ -11,19 +11,19 @@ use Magento\CheckoutAgreements\Model\Api\SearchCriteria\ActiveStoreAgreementsFilter; /** - * Class Validation + * Checkout agreements validation. */ class Validation { /** * @var \Magento\Framework\App\Config\ScopeConfigInterface */ - protected $scopeConfiguration; + private $scopeConfiguration; /** * @var \Magento\Checkout\Api\AgreementsValidatorInterface */ - protected $agreementsValidator; + private $agreementsValidator; /** * @var \Magento\CheckoutAgreements\Api\CheckoutAgreementsListInterface @@ -54,6 +54,8 @@ public function __construct( } /** + * Validates agreements before save payment information and order placing. + * * @param \Magento\Checkout\Api\PaymentInformationManagementInterface $subject * @param int $cartId * @param \Magento\Quote\Api\Data\PaymentInterface $paymentMethod @@ -74,31 +76,13 @@ public function beforeSavePaymentInformationAndPlaceOrder( } /** - * @param \Magento\Checkout\Api\PaymentInformationManagementInterface $subject - * @param int $cartId - * @param \Magento\Quote\Api\Data\PaymentInterface $paymentMethod - * @param \Magento\Quote\Api\Data\AddressInterface|null $billingAddress - * @throws \Magento\Framework\Exception\CouldNotSaveException - * @return void - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function beforeSavePaymentInformation( - \Magento\Checkout\Api\PaymentInformationManagementInterface $subject, - $cartId, - \Magento\Quote\Api\Data\PaymentInterface $paymentMethod, - \Magento\Quote\Api\Data\AddressInterface $billingAddress = null - ) { - if ($this->isAgreementEnabled()) { - $this->validateAgreements($paymentMethod); - } - } - - /** + * Validates agreements. + * * @param \Magento\Quote\Api\Data\PaymentInterface $paymentMethod * @throws \Magento\Framework\Exception\CouldNotSaveException * @return void */ - protected function validateAgreements(\Magento\Quote\Api\Data\PaymentInterface $paymentMethod) + private function validateAgreements(\Magento\Quote\Api\Data\PaymentInterface $paymentMethod) { $agreements = $paymentMethod->getExtensionAttributes() === null ? [] @@ -115,10 +99,11 @@ protected function validateAgreements(\Magento\Quote\Api\Data\PaymentInterface $ } /** - * Verify if agreement validation needed + * Verify if agreement validation needed. + * * @return bool */ - protected function isAgreementEnabled() + private function isAgreementEnabled() { $isAgreementsEnabled = $this->scopeConfiguration->isSetFlag( AgreementsProvider::PATH_ENABLED, diff --git a/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/GuestValidationTest.php b/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/GuestValidationTest.php index 3d7b910c7abc5..b685d3edff275 100644 --- a/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/GuestValidationTest.php +++ b/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/GuestValidationTest.php @@ -10,7 +10,6 @@ use Magento\Store\Model\ScopeInterface; /** - * Class GuestValidationTest * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class GuestValidationTest extends \PHPUnit\Framework\TestCase @@ -109,7 +108,7 @@ public function testBeforeSavePaymentInformationAndPlaceOrder() $this->paymentMock->expects(static::atLeastOnce()) ->method('getExtensionAttributes') ->willReturn($this->extensionAttributesMock); - $this->model->beforeSavePaymentInformation( + $this->model->beforeSavePaymentInformationAndPlaceOrder( $this->subjectMock, $cartId, $email, @@ -144,7 +143,7 @@ public function testBeforeSavePaymentInformationAndPlaceOrderIfAgreementsNotVali $this->paymentMock->expects(static::atLeastOnce()) ->method('getExtensionAttributes') ->willReturn($this->extensionAttributesMock); - $this->model->beforeSavePaymentInformation( + $this->model->beforeSavePaymentInformationAndPlaceOrder( $this->subjectMock, $cartId, $email, @@ -156,36 +155,4 @@ public function testBeforeSavePaymentInformationAndPlaceOrderIfAgreementsNotVali "The order wasn't placed. First, agree to the terms and conditions, then try placing your order again." ); } - - public function testBeforeSavePaymentInformation() - { - $cartId = 100; - $email = 'email@example.com'; - $agreements = [1, 2, 3]; - $this->scopeConfigMock - ->expects($this->once()) - ->method('isSetFlag') - ->with(AgreementsProvider::PATH_ENABLED, ScopeInterface::SCOPE_STORE) - ->willReturn(true); - $searchCriteriaMock = $this->createMock(\Magento\Framework\Api\SearchCriteria::class); - $this->agreementsFilterMock->expects($this->once()) - ->method('buildSearchCriteria') - ->willReturn($searchCriteriaMock); - $this->checkoutAgreementsListMock->expects($this->once()) - ->method('getList') - ->with($searchCriteriaMock) - ->willReturn([1]); - $this->extensionAttributesMock->expects($this->once())->method('getAgreementIds')->willReturn($agreements); - $this->agreementsValidatorMock->expects($this->once())->method('isValid')->with($agreements)->willReturn(true); - $this->paymentMock->expects(static::atLeastOnce()) - ->method('getExtensionAttributes') - ->willReturn($this->extensionAttributesMock); - $this->model->beforeSavePaymentInformation( - $this->subjectMock, - $cartId, - $email, - $this->paymentMock, - $this->addressMock - ); - } } diff --git a/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/ValidationTest.php b/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/ValidationTest.php index 7f11fad202401..d3422ae6a8893 100644 --- a/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/ValidationTest.php +++ b/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/ValidationTest.php @@ -10,7 +10,6 @@ use Magento\Store\Model\ScopeInterface; /** - * Class ValidationTest * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ValidationTest extends \PHPUnit\Framework\TestCase @@ -108,7 +107,12 @@ public function testBeforeSavePaymentInformationAndPlaceOrder() $this->paymentMock->expects(static::atLeastOnce()) ->method('getExtensionAttributes') ->willReturn($this->extensionAttributesMock); - $this->model->beforeSavePaymentInformation($this->subjectMock, $cartId, $this->paymentMock, $this->addressMock); + $this->model->beforeSavePaymentInformationAndPlaceOrder( + $this->subjectMock, + $cartId, + $this->paymentMock, + $this->addressMock + ); } /** @@ -136,35 +140,15 @@ public function testBeforeSavePaymentInformationAndPlaceOrderIfAgreementsNotVali $this->paymentMock->expects(static::atLeastOnce()) ->method('getExtensionAttributes') ->willReturn($this->extensionAttributesMock); - $this->model->beforeSavePaymentInformation($this->subjectMock, $cartId, $this->paymentMock, $this->addressMock); + $this->model->beforeSavePaymentInformationAndPlaceOrder( + $this->subjectMock, + $cartId, + $this->paymentMock, + $this->addressMock + ); $this->expectExceptionMessage( "The order wasn't placed. First, agree to the terms and conditions, then try placing your order again." ); } - - public function testBeforeSavePaymentInformation() - { - $cartId = 100; - $agreements = [1, 2, 3]; - $this->scopeConfigMock - ->expects($this->once()) - ->method('isSetFlag') - ->with(AgreementsProvider::PATH_ENABLED, ScopeInterface::SCOPE_STORE) - ->willReturn(true); - $searchCriteriaMock = $this->createMock(\Magento\Framework\Api\SearchCriteria::class); - $this->agreementsFilterMock->expects($this->once()) - ->method('buildSearchCriteria') - ->willReturn($searchCriteriaMock); - $this->checkoutAgreementsListMock->expects($this->once()) - ->method('getList') - ->with($searchCriteriaMock) - ->willReturn([1]); - $this->extensionAttributesMock->expects($this->once())->method('getAgreementIds')->willReturn($agreements); - $this->agreementsValidatorMock->expects($this->once())->method('isValid')->with($agreements)->willReturn(true); - $this->paymentMock->expects(static::atLeastOnce()) - ->method('getExtensionAttributes') - ->willReturn($this->extensionAttributesMock); - $this->model->beforeSavePaymentInformation($this->subjectMock, $cartId, $this->paymentMock, $this->addressMock); - } } diff --git a/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Conditions.php b/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Conditions.php index 1038f289eada2..ff905bf5cb9ff 100644 --- a/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Conditions.php +++ b/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Conditions.php @@ -3,10 +3,19 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\SalesRule\Block\Adminhtml\Promo\Quote\Edit\Tab; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Data\Form\Element\Fieldset; +use Magento\SalesRule\Model\Rule; +/** + * Block for rendering Conditions tab on Sales Rules creation page. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class Conditions extends \Magento\Backend\Block\Widget\Form\Generic implements \Magento\Ui\Component\Layout\Tabs\TabInterface { @@ -33,8 +42,6 @@ class Conditions extends \Magento\Backend\Block\Widget\Form\Generic implements private $ruleFactory; /** - * Constructor - * * @param \Magento\Backend\Block\Template\Context $context * @param \Magento\Framework\Registry $registry * @param \Magento\Framework\Data\FormFactory $formFactory @@ -60,7 +67,8 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc + * * @codeCoverageIgnore */ public function getTabClass() @@ -69,7 +77,7 @@ public function getTabClass() } /** - * {@inheritdoc} + * @inheritdoc */ public function getTabUrl() { @@ -77,7 +85,7 @@ public function getTabUrl() } /** - * {@inheritdoc} + * @inheritdoc */ public function isAjaxLoaded() { @@ -85,7 +93,7 @@ public function isAjaxLoaded() } /** - * {@inheritdoc} + * @inheritdoc */ public function getTabLabel() { @@ -93,7 +101,7 @@ public function getTabLabel() } /** - * {@inheritdoc} + * @inheritdoc */ public function getTabTitle() { @@ -101,7 +109,7 @@ public function getTabTitle() } /** - * {@inheritdoc} + * @inheritdoc */ public function canShowTab() { @@ -109,7 +117,7 @@ public function canShowTab() } /** - * {@inheritdoc} + * @inheritdoc */ public function isHidden() { @@ -133,7 +141,7 @@ protected function _prepareForm() /** * Handles addition of conditions tab to supplied form. * - * @param \Magento\SalesRule\Model\Rule $model + * @param Rule $model * @param string $fieldsetId * @param string $formName * @return \Magento\Framework\Data\Form diff --git a/app/code/Magento/SalesRule/Model/Quote/Discount.php b/app/code/Magento/SalesRule/Model/Quote/Discount.php index 69abac8309f90..a580a8f9d2eaa 100644 --- a/app/code/Magento/SalesRule/Model/Quote/Discount.php +++ b/app/code/Magento/SalesRule/Model/Quote/Discount.php @@ -85,6 +85,7 @@ public function __construct( * @param \Magento\Quote\Model\Quote\Address\Total $total * @return $this * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) */ public function collect( \Magento\Quote\Model\Quote $quote, @@ -95,6 +96,11 @@ public function collect( $store = $this->storeManager->getStore($quote->getStoreId()); $address = $shippingAssignment->getShipping()->getAddress(); + + if ($quote->currentPaymentWasSet()) { + $address->setPaymentMethod($quote->getPayment()->getMethod()); + } + $this->calculator->reset($address); $items = $shippingAssignment->getItems(); diff --git a/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php b/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php index 29cdf34c5a784..cf6301cb31a9c 100644 --- a/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php +++ b/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php @@ -65,6 +65,7 @@ public function loadAttributeOptions() 'base_subtotal' => __('Subtotal'), 'total_qty' => __('Total Items Quantity'), 'weight' => __('Total Weight'), + 'payment_method' => __('Payment Method'), 'shipping_method' => __('Shipping Method'), 'postcode' => __('Shipping Postcode'), 'region' => __('Shipping Region'), diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontApplyDiscountCodeActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontApplyDiscountCodeActionGroup.xml index 063409e9fc7ea..3cf96a8b3dc06 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontApplyDiscountCodeActionGroup.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontApplyDiscountCodeActionGroup.xml @@ -15,7 +15,7 @@ - + diff --git a/app/code/Magento/SalesRule/view/frontend/requirejs-config.js b/app/code/Magento/SalesRule/view/frontend/requirejs-config.js new file mode 100644 index 0000000000000..13b701c6fe65a --- /dev/null +++ b/app/code/Magento/SalesRule/view/frontend/requirejs-config.js @@ -0,0 +1,14 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +var config = { + config: { + mixins: { + 'Magento_Checkout/js/action/select-payment-method': { + 'Magento_SalesRule/js/action/select-payment-method-mixin': true + } + } + } +}; diff --git a/app/code/Magento/SalesRule/view/frontend/web/js/action/select-payment-method-mixin.js b/app/code/Magento/SalesRule/view/frontend/web/js/action/select-payment-method-mixin.js new file mode 100644 index 0000000000000..50d54d4e59789 --- /dev/null +++ b/app/code/Magento/SalesRule/view/frontend/web/js/action/select-payment-method-mixin.js @@ -0,0 +1,50 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'jquery', + 'mage/utils/wrapper', + 'Magento_Checkout/js/model/quote', + 'Magento_SalesRule/js/model/payment/discount-messages', + 'Magento_Checkout/js/action/set-payment-information', + 'Magento_Checkout/js/action/get-totals', + 'Magento_SalesRule/js/model/coupon' +], function ($, wrapper, quote, messageContainer, setPaymentInformationAction, getTotalsAction, coupon) { + 'use strict'; + + return function (selectPaymentMethodAction) { + + return wrapper.wrap(selectPaymentMethodAction, function (originalSelectPaymentMethodAction, paymentMethod) { + + originalSelectPaymentMethodAction(paymentMethod); + + $.when( + setPaymentInformationAction( + messageContainer, + { + method: paymentMethod.method + } + ) + ).done( + function () { + var deferred = $.Deferred(), + + /** + * Update coupon form. + */ + updateCouponCallback = function () { + if (quote.totals() && !quote.totals()['coupon_code']) { + coupon.setCouponCode(''); + coupon.setIsApplied(false); + } + }; + + getTotalsAction([], deferred); + $.when(deferred).done(updateCouponCallback); + } + ); + }); + }; + +}); diff --git a/app/code/Magento/SalesRule/view/frontend/web/js/model/coupon.js b/app/code/Magento/SalesRule/view/frontend/web/js/model/coupon.js new file mode 100644 index 0000000000000..1e3e057bbb401 --- /dev/null +++ b/app/code/Magento/SalesRule/view/frontend/web/js/model/coupon.js @@ -0,0 +1,49 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +/** + * Coupon model. + */ +define([ + 'ko', + 'domReady!' +], function (ko) { + 'use strict'; + + var couponCode = ko.observable(null), + isApplied = ko.observable(null); + + return { + couponCode: couponCode, + isApplied: isApplied, + + /** + * @return {*} + */ + getCouponCode: function () { + return couponCode; + }, + + /** + * @return {Boolean} + */ + getIsApplied: function () { + return isApplied; + }, + + /** + * @param {*} couponCodeValue + */ + setCouponCode: function (couponCodeValue) { + couponCode(couponCodeValue); + }, + + /** + * @param {Boolean} isAppliedValue + */ + setIsApplied: function (isAppliedValue) { + isApplied(isAppliedValue); + } + }; +}); diff --git a/app/code/Magento/SalesRule/view/frontend/web/js/view/payment/discount.js b/app/code/Magento/SalesRule/view/frontend/web/js/view/payment/discount.js index d2902d8863f3d..9c83cb7ba40ba 100644 --- a/app/code/Magento/SalesRule/view/frontend/web/js/view/payment/discount.js +++ b/app/code/Magento/SalesRule/view/frontend/web/js/view/payment/discount.js @@ -9,18 +9,19 @@ define([ 'uiComponent', 'Magento_Checkout/js/model/quote', 'Magento_SalesRule/js/action/set-coupon-code', - 'Magento_SalesRule/js/action/cancel-coupon' -], function ($, ko, Component, quote, setCouponCodeAction, cancelCouponAction) { + 'Magento_SalesRule/js/action/cancel-coupon', + 'Magento_SalesRule/js/model/coupon' +], function ($, ko, Component, quote, setCouponCodeAction, cancelCouponAction, coupon) { 'use strict'; var totals = quote.getTotals(), - couponCode = ko.observable(null), - isApplied; + couponCode = coupon.getCouponCode(), + isApplied = coupon.getIsApplied(); if (totals()) { couponCode(totals()['coupon_code']); } - isApplied = ko.observable(couponCode() != null); + isApplied(couponCode() != null); return Component.extend({ defaults: { diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/AddressTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/AddressTest.php new file mode 100644 index 0000000000000..17730262d2dfd --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/AddressTest.php @@ -0,0 +1,50 @@ +objectManager = Bootstrap::getObjectManager(); + } + + /** + * Tests cart price rule validation. + * + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + * @magentoConfigFixture default_store payment/checkmo/active 1 + * @magentoDataFixture Magento/SalesRule/_files/rules_payment_method.php + * @magentoDataFixture Magento/Checkout/_files/quote_with_payment_saved.php + */ + public function testValidateRule() + { + $quote = $this->getQuote('test_order_1_with_payment'); + $rule = $this->getSalesRule('50% Off on Checkmo Payment Method'); + + $this->assertTrue( + $rule->validate($quote->getBillingAddress()), + 'Cart price rule validation failed.' + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ConditionHelper.php b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ConditionHelper.php new file mode 100644 index 0000000000000..e857ab902fcc5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ConditionHelper.php @@ -0,0 +1,62 @@ +objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('reserved_order_id', $reservedOrderId) + ->create(); + + /** @var CartRepositoryInterface $quoteRepository */ + $quoteRepository = $this->objectManager->get(CartRepositoryInterface::class); + $items = $quoteRepository->getList($searchCriteria)->getItems(); + return array_pop($items); + } + + /** + * Gets rule by name. + * + * @param string $name + * @return \Magento\SalesRule\Model\Rule + * @throws \Magento\Framework\Exception\InputException + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + private function getSalesRule(string $name): \Magento\SalesRule\Model\Rule + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('name', $name) + ->create(); + + /** @var CartRepositoryInterface $quoteRepository */ + $ruleRepository = $this->objectManager->get(RuleRepositoryInterface::class); + $items = $ruleRepository->getList($searchCriteria)->getItems(); + + $rule = array_pop($items); + /** @var \Magento\SalesRule\Model\Converter\ToModel $converter */ + $converter = $this->objectManager->get(\Magento\SalesRule\Model\Converter\ToModel::class); + + return $converter->toModel($rule); + } +} diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ProductTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ProductTest.php index 70fa11fc78c87..917ff085f7429 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ProductTest.php @@ -6,21 +6,21 @@ namespace Magento\SalesRule\Model\Rule\Condition; -use Magento\Quote\Api\CartRepositoryInterface; -use Magento\Framework\Api\SearchCriteriaBuilder; -use Magento\Quote\Api\Data\CartInterface; -use Magento\SalesRule\Api\RuleRepositoryInterface; - /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ProductTest extends \PHPUnit\Framework\TestCase { + use ConditionHelper; + /** * @var \Magento\Framework\ObjectManagerInterface */ private $objectManager; + /** + * @inheritDoc + */ protected function setUp() { $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); @@ -127,49 +127,4 @@ public function testValidateQtySalesRuleWithConfigurable() $rule->validate($quote->getBillingAddress()) ); } - - /** - * Gets quote by reserved order id. - * - * @param string $reservedOrderId - * @return CartInterface - */ - private function getQuote($reservedOrderId) - { - /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ - $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); - $searchCriteria = $searchCriteriaBuilder->addFilter('reserved_order_id', $reservedOrderId) - ->create(); - - /** @var CartRepositoryInterface $quoteRepository */ - $quoteRepository = $this->objectManager->get(CartRepositoryInterface::class); - $items = $quoteRepository->getList($searchCriteria)->getItems(); - return array_pop($items); - } - - /** - * Gets rule by name. - * - * @param string $name - * @return \Magento\SalesRule\Model\Rule - * @throws \Magento\Framework\Exception\InputException - * @throws \Magento\Framework\Exception\NoSuchEntityException - */ - private function getSalesRule(string $name): \Magento\SalesRule\Model\Rule - { - /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ - $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); - $searchCriteria = $searchCriteriaBuilder->addFilter('name', $name) - ->create(); - - /** @var CartRepositoryInterface $quoteRepository */ - $ruleRepository = $this->objectManager->get(RuleRepositoryInterface::class); - $items = $ruleRepository->getList($searchCriteria)->getItems(); - - $rule = array_pop($items); - /** @var \Magento\SalesRule\Model\Converter\ToModel $converter */ - $converter = $this->objectManager->get(\Magento\SalesRule\Model\Converter\ToModel::class); - - return $converter->toModel($rule); - } } diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/rules_payment_method.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/rules_payment_method.php new file mode 100644 index 0000000000000..25f208d34d8e0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/rules_payment_method.php @@ -0,0 +1,47 @@ +create(\Magento\SalesRule\Model\Rule::class); +$salesRule->setData( + [ + 'name' => '50% Off on Checkmo Payment Method', + 'is_active' => 1, + 'customer_group_ids' => [\Magento\Customer\Model\GroupManagement::NOT_LOGGED_IN_ID], + 'coupon_type' => \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON, + 'simple_action' => 'by_percent', + 'discount_amount' => 50, + 'discount_step' => 0, + 'stop_rules_processing' => 1, + 'website_ids' => [ + Bootstrap::getObjectManager()->get( + \Magento\Store\Model\StoreManagerInterface::class + )->getWebsite()->getId() + ] + ] +); + +$salesRule->getConditions()->loadArray([ + 'type' => \Magento\SalesRule\Model\Rule\Condition\Combine::class, + 'attribute' => null, + 'operator' => null, + 'value' => '1', + 'is_value_processed' => null, + 'aggregator' => 'any', + 'conditions' => + [ + [ + 'type' => \Magento\SalesRule\Model\Rule\Condition\Address::class, + 'attribute' => 'payment_method', + 'operator' => '==', + 'value' => 'checkmo' + ], + ], +]); + +$salesRule->save(); diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt index 59b171a86e1cd..411f02e2c5930 100644 --- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt @@ -217,4 +217,5 @@ Magento/Config/App/Config/Type Magento/InventoryReservationCli/Test/Integration Magento/InventoryAdminUi/Controller/Adminhtml Magento/Newsletter/Model/Queue -Magento/Framework/Mail/Template \ No newline at end of file +Magento/Framework/Mail/Template +Magento/CheckoutAgreements/Model/Checkout/Plugin From 73ed5cc4868803ed6a84cb66283566697c579acd Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh Date: Thu, 16 Jan 2020 09:53:00 +0200 Subject: [PATCH 4/6] MC-30141: [On Pre] [2.2.7] Status Change Doesn't Update the Stock Index --- .../Model/Import/Product.php | 78 ++++++++- .../Model/Import/Product/StatusProcessor.php | 153 ++++++++++++++++++ .../Model/Import/Product/StockProcessor.php | 57 +++++++ .../Test/Unit/Model/Import/ProductTest.php | 15 +- .../Magento/CatalogImportExport/etc/di.xml | 7 + .../Model/Import/ProductTest.php | 39 ++++- .../Model/Import/_files/disable_product.csv | 2 + .../Model/Import/_files/enable_product.csv | 2 + 8 files changed, 344 insertions(+), 9 deletions(-) create mode 100644 app/code/Magento/CatalogImportExport/Model/Import/Product/StatusProcessor.php create mode 100644 app/code/Magento/CatalogImportExport/Model/Import/Product/StockProcessor.php create mode 100644 dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/disable_product.csv create mode 100644 dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/enable_product.csv diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index 7ebc397cbe650..f7b15c9330fc5 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -13,6 +13,8 @@ use Magento\CatalogImportExport\Model\Import\Product\ImageTypeProcessor; use Magento\CatalogImportExport\Model\Import\Product\MediaGalleryProcessor; use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface as ValidatorInterface; +use Magento\CatalogImportExport\Model\Import\Product\StatusProcessor; +use Magento\CatalogImportExport\Model\Import\Product\StockProcessor; use Magento\CatalogImportExport\Model\StockItemImporterInterface; use Magento\CatalogInventory\Api\Data\StockItemInterface; use Magento\Framework\App\Filesystem\DirectoryList; @@ -746,6 +748,15 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity */ private $productRepository; + /** + * @var StatusProcessor + */ + private $statusProcessor; + /** + * @var StockProcessor + */ + private $stockProcessor; + /** * @param \Magento\Framework\Json\Helper\Data $jsonHelper * @param \Magento\ImportExport\Helper\Data $importExportData @@ -791,6 +802,8 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity * @param StockItemImporterInterface|null $stockItemImporter * @param DateTimeFactory $dateTimeFactory * @param ProductRepositoryInterface|null $productRepository + * @param StatusProcessor|null $statusProcessor + * @param StockProcessor|null $stockProcessor * @throws LocalizedException * @throws \Magento\Framework\Exception\FileSystemException * @SuppressWarnings(PHPMD.ExcessiveParameterList) @@ -840,7 +853,9 @@ public function __construct( MediaGalleryProcessor $mediaProcessor = null, StockItemImporterInterface $stockItemImporter = null, DateTimeFactory $dateTimeFactory = null, - ProductRepositoryInterface $productRepository = null + ProductRepositoryInterface $productRepository = null, + StatusProcessor $statusProcessor = null, + StockProcessor $stockProcessor = null ) { $this->_eventManager = $eventManager; $this->stockRegistry = $stockRegistry; @@ -876,6 +891,10 @@ public function __construct( $this->mediaProcessor = $mediaProcessor ?: ObjectManager::getInstance()->get(MediaGalleryProcessor::class); $this->stockItemImporter = $stockItemImporter ?: ObjectManager::getInstance() ->get(StockItemImporterInterface::class); + $this->statusProcessor = $statusProcessor ?: ObjectManager::getInstance() + ->get(StatusProcessor::class); + $this->stockProcessor = $stockProcessor ?: ObjectManager::getInstance() + ->get(StockProcessor::class); parent::__construct( $jsonHelper, $importExportData, @@ -1290,12 +1309,18 @@ protected function _saveLinks() protected function _saveProductAttributes(array $attributesData) { $linkField = $this->getProductEntityLinkField(); + $statusAttributeId = (int) $this->retrieveAttributeByCode('status')->getId(); foreach ($attributesData as $tableName => $skuData) { + $linkIdBySkuForStatusChanged = []; $tableData = []; foreach ($skuData as $sku => $attributes) { $linkId = $this->_oldSku[strtolower($sku)][$linkField]; foreach ($attributes as $attributeId => $storeValues) { foreach ($storeValues as $storeId => $storeValue) { + if ($attributeId === $statusAttributeId) { + $this->statusProcessor->setStatus($sku, $storeId, $storeValue); + $linkIdBySkuForStatusChanged[strtolower($sku)] = $linkId; + } $tableData[] = [ $linkField => $linkId, 'attribute_id' => $attributeId, @@ -1305,6 +1330,9 @@ protected function _saveProductAttributes(array $attributesData) } } } + if ($linkIdBySkuForStatusChanged) { + $this->statusProcessor->loadOldStatus($linkIdBySkuForStatusChanged); + } $this->_connection->insertOnDuplicate($tableName, $tableData, ['value']); } @@ -2188,6 +2216,7 @@ protected function _saveStockItem() while ($bunch = $this->_dataSourceModel->getNextBunch()) { $stockData = []; $productIdsToReindex = []; + $stockChangedProductIds = []; // Format bunch to stock data rows foreach ($bunch as $rowNum => $rowData) { if (!$this->isRowAllowedToImport($rowData, $rowNum)) { @@ -2197,8 +2226,16 @@ protected function _saveStockItem() $row = []; $sku = $rowData[self::COL_SKU]; if ($this->skuProcessor->getNewSku($sku) !== null) { + $stockItem = $this->getRowExistingStockItem($rowData); + $existingStockItemData = $stockItem->getData(); $row = $this->formatStockDataForRow($rowData); $productIdsToReindex[] = $row['product_id']; + $storeId = $this->getRowStoreId($rowData); + if (!empty(array_diff_assoc($row, $existingStockItemData)) + || $this->statusProcessor->isStatusChanged($sku, $storeId) + ) { + $stockChangedProductIds[] = $row['product_id']; + } } if (!isset($stockData[$sku])) { @@ -2211,11 +2248,24 @@ protected function _saveStockItem() $this->stockItemImporter->import($stockData); } + $this->reindexStockStatus($stockChangedProductIds); $this->reindexProducts($productIdsToReindex); } return $this; } + /** + * Reindex stock status for provided product IDs + * + * @param array $productIds + */ + private function reindexStockStatus(array $productIds): void + { + if ($productIds) { + $this->stockProcessor->reindexList($productIds); + } + } + /** * Initiate product reindex by product ids * @@ -3259,4 +3309,30 @@ private function composeLinkKey(int $productId, int $linkedId, int $linkTypeId) { return "{$productId}-{$linkedId}-{$linkTypeId}"; } + + /** + * Get row store ID + * + * @param array $rowData + * @return int + */ + private function getRowStoreId(array $rowData): int + { + return !empty($rowData[self::COL_STORE]) + ? (int) $this->getStoreIdByCode($rowData[self::COL_STORE]) + : Store::DEFAULT_STORE_ID; + } + + /** + * Get row stock item model + * + * @param array $rowData + * @return StockItemInterface + */ + private function getRowExistingStockItem(array $rowData): StockItemInterface + { + $productId = $this->skuProcessor->getNewSku($rowData[self::COL_SKU])['entity_id']; + $websiteId = $this->stockConfiguration->getDefaultScopeId(); + return $this->stockRegistry->getStockItem($productId, $websiteId); + } } diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/StatusProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/StatusProcessor.php new file mode 100644 index 0000000000000..1c6d679848216 --- /dev/null +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/StatusProcessor.php @@ -0,0 +1,153 @@ +oldData = []; + $this->newData = []; + $this->resourceFactory = $resourceFactory; + $this->resourceConnection = $resourceConnection; + $this->metadataPool = $metadataPool; + } + + /** + * Check if status has changed for given (sku, storeId) + * + * @param string $sku + * @param int $storeId + * @return bool + */ + public function isStatusChanged(string $sku, int $storeId): bool + { + $sku = strtolower($sku); + if (!isset($this->newData[$sku][$storeId])) { + $changed = false; + } elseif (!isset($this->oldData[$sku][$storeId])) { + $changed = true; + } else { + $oldStatus = (int) $this->oldData[$sku][$storeId]; + $newStatus = (int) $this->newData[$sku][$storeId]; + $changed = $oldStatus !== $newStatus; + } + return $changed; + } + + /** + * Load old status data + * + * @param array $linkIdBySku + */ + public function loadOldStatus(array $linkIdBySku): void + { + $connection = $this->resourceConnection->getConnection(); + $linkId = $this->getProductEntityLinkField(); + $select = $connection->select() + ->from($this->getAttribute()->getBackend()->getTable()) + ->columns([$linkId, 'store_id', 'value']) + ->where(sprintf('%s IN (?)', $linkId), array_values($linkIdBySku)); + $skuByLinkId = array_flip($linkIdBySku); + + foreach ($connection->fetchAll($select) as $item) { + if (isset($skuByLinkId[$item[$linkId]])) { + $this->oldData[$skuByLinkId[$item[$linkId]]][$item['store_id']] = $item['value']; + } + } + } + + /** + * Set SKU status for given storeId + * + * @param string $sku + * @param string $storeId + * @param int $value + */ + public function setStatus(string $sku, string $storeId, int $value): void + { + $sku = strtolower($sku); + $this->newData[$sku][$storeId] = $value; + } + + /** + * Get product entity link field. + * + * @return string + */ + private function getProductEntityLinkField() + { + if (!$this->productEntityLinkField) { + $this->productEntityLinkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); + } + + return $this->productEntityLinkField; + } + + /** + * Get Attribute model + * + * @return AbstractAttribute + */ + private function getAttribute(): AbstractAttribute + { + if ($this->attribute === null) { + $this->attribute = $this->resourceFactory->create()->getAttribute(self::ATTRIBUTE_CODE); + } + return $this->attribute; + } +} diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/StockProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/StockProcessor.php new file mode 100644 index 0000000000000..76508d1ebec89 --- /dev/null +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/StockProcessor.php @@ -0,0 +1,57 @@ +indexerRegistry = $indexerRegistry; + $this->indexers = array_filter($indexers); + } + + /** + * Reindex products by ids + * + * @param array $ids + * @return void + */ + public function reindexList(array $ids = []): void + { + if ($ids) { + foreach ($this->indexers as $indexerName) { + $indexer = $this->indexerRegistry->get($indexerName); + if (!$indexer->isScheduled()) { + $indexer->reindexList($ids); + } + } + } + } +} diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php index 40041fe90db96..ff724ddc746aa 100644 --- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php +++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php @@ -13,8 +13,8 @@ use PHPUnit\Framework\MockObject\MockObject; /** - * Class ProductTest - * @package Magento\CatalogImportExport\Test\Unit\Model\Import + * Test import entity product model + * * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -547,6 +547,17 @@ public function testSaveProductAttributes() $this->_connection->expects($this->once()) ->method('insertOnDuplicate') ->with($testTable, $tableData, ['value']); + $attribute = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) + ->disableOriginalConstructor() + ->setMethods(['getId']) + ->getMockForAbstractClass(); + $attribute->expects($this->once())->method('getId')->willReturn(1); + $resource = $this->getMockBuilder(\Magento\CatalogImportExport\Model\Import\Proxy\Product\ResourceModel::class) + ->disableOriginalConstructor() + ->setMethods(['getAttribute']) + ->getMock(); + $resource->expects($this->once())->method('getAttribute')->willReturn($attribute); + $this->_resourceFactory->expects($this->once())->method('create')->willReturn($resource); $this->setPropertyValue($this->importProduct, '_oldSku', [$testSku => ['entity_id' => self::ENTITY_ID]]); $object = $this->invokeMethod($this->importProduct, '_saveProductAttributes', [$attributesData]); $this->assertEquals($this->importProduct, $object); diff --git a/app/code/Magento/CatalogImportExport/etc/di.xml b/app/code/Magento/CatalogImportExport/etc/di.xml index 4e2fe390e0b17..3d629dd106b9e 100644 --- a/app/code/Magento/CatalogImportExport/etc/di.xml +++ b/app/code/Magento/CatalogImportExport/etc/di.xml @@ -16,6 +16,13 @@ + + + + Magento\CatalogInventory\Model\Indexer\Stock\Processor::INDEXER_ID + + + diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php index 4e2b73de301a3..fdbda7e817d56 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php @@ -19,6 +19,9 @@ use Magento\Catalog\Model\Product; use Magento\Catalog\Model\ResourceModel\Product as ProductResource; use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface; +use Magento\CatalogInventory\Model\Stock; +use Magento\CatalogInventory\Model\StockRegistry; +use Magento\CatalogInventory\Model\StockRegistryStorage; use Magento\Framework\App\Bootstrap; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\ObjectManager; @@ -230,9 +233,9 @@ public function testSaveStockItemQty() $existingProductIds = [$id1, $id2, $id3]; $stockItems = []; foreach ($existingProductIds as $productId) { - /** @var $stockRegistry \Magento\CatalogInventory\Model\StockRegistry */ + /** @var $stockRegistry StockRegistry */ $stockRegistry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\CatalogInventory\Model\StockRegistry::class + StockRegistry::class ); $stockItem = $stockRegistry->getStockItem($productId, 1); @@ -261,9 +264,9 @@ public function testSaveStockItemQty() /** @var $stockItmBeforeImport \Magento\CatalogInventory\Model\Stock\Item */ foreach ($stockItems as $productId => $stockItmBeforeImport) { - /** @var $stockRegistry \Magento\CatalogInventory\Model\StockRegistry */ + /** @var $stockRegistry StockRegistry */ $stockRegistry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\CatalogInventory\Model\StockRegistry::class + StockRegistry::class ); $stockItemAfterImport = $stockRegistry->getStockItem($productId, 1); @@ -2031,9 +2034,9 @@ public function testProductWithUseConfigSettings() $this->_model->importData(); foreach ($products as $sku => $manageStockUseConfig) { - /** @var \Magento\CatalogInventory\Model\StockRegistry $stockRegistry */ + /** @var StockRegistry $stockRegistry */ $stockRegistry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\CatalogInventory\Model\StockRegistry::class + StockRegistry::class ); $stockItem = $stockRegistry->getStockItemBySku($sku); $this->assertEquals($manageStockUseConfig, $stockItem->getUseConfigManageStock()); @@ -2941,4 +2944,28 @@ public function testImportConfigurableProductImages() } $this->assertEquals($expected, $actual); } + + /** + * Test that product stock status is updated after import + * + * @magentoDataFixture mediaImportImageFixture + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testProductStockStatusShouldBeUpdated() + { + /** @var $stockRegistry StockRegistry */ + $stockRegistry = $this->objectManager->create(StockRegistry::class); + /** @var StockRegistryStorage $stockRegistryStorage */ + $stockRegistryStorage = $this->objectManager->get(StockRegistryStorage::class); + $status = $stockRegistry->getStockStatusBySku('simple'); + $this->assertEquals(Stock::STOCK_IN_STOCK, $status->getStockStatus()); + $this->importDataForMediaTest('disable_product.csv'); + $stockRegistryStorage->clean(); + $status = $stockRegistry->getStockStatusBySku('simple'); + $this->assertEquals(Stock::STOCK_OUT_OF_STOCK, $status->getStockStatus()); + $this->importDataForMediaTest('enable_product.csv'); + $stockRegistryStorage->clean(); + $status = $stockRegistry->getStockStatusBySku('simple'); + $this->assertEquals(Stock::STOCK_IN_STOCK, $status->getStockStatus()); + } } diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/disable_product.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/disable_product.csv new file mode 100644 index 0000000000000..b366fb63afd92 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/disable_product.csv @@ -0,0 +1,2 @@ +"sku", "product_online" +"simple", "0" diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/enable_product.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/enable_product.csv new file mode 100644 index 0000000000000..eb36621bc61e1 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/enable_product.csv @@ -0,0 +1,2 @@ +"sku", "product_online" +"simple", "1" From b1d69cb1f864e756538a7579ba286f1a12367e19 Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh Date: Fri, 17 Jan 2020 10:08:20 +0200 Subject: [PATCH 5/6] MC-30258: [On Pre] Multi Shipping Checkout Terms and Conditions Manual Check Fails Validation. --- .../Model/Checkout/Plugin/Validation.php | 30 ++++++++++--- .../Model/Checkout/Plugin/ValidationTest.php | 44 ++++++++++++++++++- 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/CheckoutAgreements/Model/Checkout/Plugin/Validation.php b/app/code/Magento/CheckoutAgreements/Model/Checkout/Plugin/Validation.php index 67e2a6c9ec334..5722099435118 100644 --- a/app/code/Magento/CheckoutAgreements/Model/Checkout/Plugin/Validation.php +++ b/app/code/Magento/CheckoutAgreements/Model/Checkout/Plugin/Validation.php @@ -7,11 +7,12 @@ namespace Magento\CheckoutAgreements\Model\Checkout\Plugin; use Magento\CheckoutAgreements\Model\AgreementsProvider; -use Magento\Store\Model\ScopeInterface; use Magento\CheckoutAgreements\Model\Api\SearchCriteria\ActiveStoreAgreementsFilter; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Store\Model\ScopeInterface; /** - * Class Validation + * Class Validation validates the agreement based on the payment method */ class Validation { @@ -35,25 +36,37 @@ class Validation */ private $activeStoreAgreementsFilter; + /** + * Quote repository. + * + * @var \Magento\Quote\Api\CartRepositoryInterface + */ + private $quoteRepository; + /** * @param \Magento\Checkout\Api\AgreementsValidatorInterface $agreementsValidator * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfiguration * @param \Magento\CheckoutAgreements\Api\CheckoutAgreementsListInterface $checkoutAgreementsList * @param ActiveStoreAgreementsFilter $activeStoreAgreementsFilter + * @param CartRepositoryInterface $quoteRepository */ public function __construct( \Magento\Checkout\Api\AgreementsValidatorInterface $agreementsValidator, \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfiguration, \Magento\CheckoutAgreements\Api\CheckoutAgreementsListInterface $checkoutAgreementsList, - \Magento\CheckoutAgreements\Model\Api\SearchCriteria\ActiveStoreAgreementsFilter $activeStoreAgreementsFilter + \Magento\CheckoutAgreements\Model\Api\SearchCriteria\ActiveStoreAgreementsFilter $activeStoreAgreementsFilter, + CartRepositoryInterface $quoteRepository ) { $this->agreementsValidator = $agreementsValidator; $this->scopeConfiguration = $scopeConfiguration; $this->checkoutAgreementsList = $checkoutAgreementsList; $this->activeStoreAgreementsFilter = $activeStoreAgreementsFilter; + $this->quoteRepository = $quoteRepository; } /** + * Check validation before saving the payment information and place order + * * @param \Magento\Checkout\Api\PaymentInformationManagementInterface $subject * @param int $cartId * @param \Magento\Quote\Api\Data\PaymentInterface $paymentMethod @@ -74,13 +87,16 @@ public function beforeSavePaymentInformationAndPlaceOrder( } /** + * Check validation before saving the payment information + * * @param \Magento\Checkout\Api\PaymentInformationManagementInterface $subject * @param int $cartId * @param \Magento\Quote\Api\Data\PaymentInterface $paymentMethod * @param \Magento\Quote\Api\Data\AddressInterface|null $billingAddress - * @throws \Magento\Framework\Exception\CouldNotSaveException * @return void * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @throws \Magento\Framework\Exception\NoSuchEntityException + * @throws \Magento\Framework\Exception\CouldNotSaveException */ public function beforeSavePaymentInformation( \Magento\Checkout\Api\PaymentInformationManagementInterface $subject, @@ -88,12 +104,15 @@ public function beforeSavePaymentInformation( \Magento\Quote\Api\Data\PaymentInterface $paymentMethod, \Magento\Quote\Api\Data\AddressInterface $billingAddress = null ) { - if ($this->isAgreementEnabled()) { + $quote = $this->quoteRepository->getActive($cartId); + if ($this->isAgreementEnabled() && !$quote->getIsMultiShipping()) { $this->validateAgreements($paymentMethod); } } /** + * Validate agreements base on the payment method + * * @param \Magento\Quote\Api\Data\PaymentInterface $paymentMethod * @throws \Magento\Framework\Exception\CouldNotSaveException * @return void @@ -116,6 +135,7 @@ protected function validateAgreements(\Magento\Quote\Api\Data\PaymentInterface $ /** * Verify if agreement validation needed + * * @return bool */ protected function isAgreementEnabled() diff --git a/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/ValidationTest.php b/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/ValidationTest.php index 7f11fad202401..db8469f34af62 100644 --- a/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/ValidationTest.php +++ b/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/ValidationTest.php @@ -10,7 +10,7 @@ use Magento\Store\Model\ScopeInterface; /** - * Class ValidationTest + * Class ValidationTest validates the agreement based on the payment method * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ValidationTest extends \PHPUnit\Framework\TestCase @@ -60,12 +60,24 @@ class ValidationTest extends \PHPUnit\Framework\TestCase */ private $agreementsFilterMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $quoteMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $quoteRepositoryMock; + protected function setUp() { $this->agreementsValidatorMock = $this->createMock(\Magento\Checkout\Api\AgreementsValidatorInterface::class); $this->subjectMock = $this->createMock(\Magento\Checkout\Api\PaymentInformationManagementInterface::class); $this->paymentMock = $this->createMock(\Magento\Quote\Api\Data\PaymentInterface::class); $this->addressMock = $this->createMock(\Magento\Quote\Api\Data\AddressInterface::class); + $this->quoteMock = $this->createPartialMock(\Magento\Quote\Model\Quote::class, ['getIsMultiShipping']); + $this->quoteRepositoryMock = $this->createMock(\Magento\Quote\Api\CartRepositoryInterface::class); $this->extensionAttributesMock = $this->createPartialMock( \Magento\Quote\Api\Data\PaymentExtension::class, ['getAgreementIds'] @@ -82,7 +94,8 @@ protected function setUp() $this->agreementsValidatorMock, $this->scopeConfigMock, $this->checkoutAgreementsListMock, - $this->agreementsFilterMock + $this->agreementsFilterMock, + $this->quoteRepositoryMock ); } @@ -96,6 +109,15 @@ public function testBeforeSavePaymentInformationAndPlaceOrder() ->with(AgreementsProvider::PATH_ENABLED, ScopeInterface::SCOPE_STORE) ->willReturn(true); $searchCriteriaMock = $this->createMock(\Magento\Framework\Api\SearchCriteria::class); + $this->quoteMock + ->expects($this->once()) + ->method('getIsMultiShipping') + ->willReturn(false); + $this->quoteRepositoryMock + ->expects($this->once()) + ->method('getActive') + ->with($cartId) + ->willReturn($this->quoteMock); $this->agreementsFilterMock->expects($this->once()) ->method('buildSearchCriteria') ->willReturn($searchCriteriaMock); @@ -124,6 +146,15 @@ public function testBeforeSavePaymentInformationAndPlaceOrderIfAgreementsNotVali ->with(AgreementsProvider::PATH_ENABLED, ScopeInterface::SCOPE_STORE) ->willReturn(true); $searchCriteriaMock = $this->createMock(\Magento\Framework\Api\SearchCriteria::class); + $this->quoteMock + ->expects($this->once()) + ->method('getIsMultiShipping') + ->willReturn(false); + $this->quoteRepositoryMock + ->expects($this->once()) + ->method('getActive') + ->with($cartId) + ->willReturn($this->quoteMock); $this->agreementsFilterMock->expects($this->once()) ->method('buildSearchCriteria') ->willReturn($searchCriteriaMock); @@ -152,6 +183,15 @@ public function testBeforeSavePaymentInformation() ->method('isSetFlag') ->with(AgreementsProvider::PATH_ENABLED, ScopeInterface::SCOPE_STORE) ->willReturn(true); + $this->quoteMock + ->expects($this->once()) + ->method('getIsMultiShipping') + ->willReturn(false); + $this->quoteRepositoryMock + ->expects($this->once()) + ->method('getActive') + ->with($cartId) + ->willReturn($this->quoteMock); $searchCriteriaMock = $this->createMock(\Magento\Framework\Api\SearchCriteria::class); $this->agreementsFilterMock->expects($this->once()) ->method('buildSearchCriteria') From 4c952ed9b65351358bba0884571316dfa135d240 Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh Date: Tue, 21 Jan 2020 11:04:43 +0200 Subject: [PATCH 6/6] MC-23986: Cart price rule based on payment methods not aplied in checkout --- .../Test/Unit/Model/Checkout/Plugin/ValidationTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/ValidationTest.php b/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/ValidationTest.php index 6a4953a3cfe70..64c91edb4e27d 100644 --- a/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/ValidationTest.php +++ b/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/ValidationTest.php @@ -130,7 +130,7 @@ public function testBeforeSavePaymentInformationAndPlaceOrder() $this->paymentMock->expects(static::atLeastOnce()) ->method('getExtensionAttributes') ->willReturn($this->extensionAttributesMock); - $this->model->beforeSavePaymentInformationAndPlaceOrder( + $this->model->beforeSavePaymentInformation( $this->subjectMock, $cartId, $this->paymentMock, @@ -172,7 +172,7 @@ public function testBeforeSavePaymentInformationAndPlaceOrderIfAgreementsNotVali $this->paymentMock->expects(static::atLeastOnce()) ->method('getExtensionAttributes') ->willReturn($this->extensionAttributesMock); - $this->model->beforeSavePaymentInformationAndPlaceOrder( + $this->model->beforeSavePaymentInformation( $this->subjectMock, $cartId, $this->paymentMock,