From ecab07afe2fba857cad473b7ab6c7389879a7b49 Mon Sep 17 00:00:00 2001 From: Dmytro Yushkin Date: Mon, 22 Jul 2019 16:22:10 -0500 Subject: [PATCH 01/22] MC-17939: RMA created via REST API does not contain all information --- dev/tests/integration/testsuite/Magento/Sales/_files/order.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order.php index 6b9cf3bc613ce..9ea85aae56cbb 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/order.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order.php @@ -45,7 +45,8 @@ ->setPrice($product->getPrice()) ->setRowTotal($product->getPrice()) ->setProductType('simple') - ->setName($product->getName()); + ->setName($product->getName()) + ->setSku($product->getSku()); /** @var Order $order */ $order = $objectManager->create(Order::class); From 54590fd275a7e7c127fd7329427ff1e8122d369b Mon Sep 17 00:00:00 2001 From: Buba Suma Date: Tue, 23 Jul 2019 18:41:53 -0500 Subject: [PATCH 02/22] MC-18196: Deleted products still showing in the wishlist - Fix deleted products are not removed from wishlist - Fix options are not pre-filled when previewing wishlist item --- app/code/Magento/Wishlist/Helper/Data.php | 12 ++- .../Magento/Wishlist/Model/Item/Option.php | 20 ++++- .../Wishlist/Model/WishlistCleaner.php | 67 ++++++++++++++ .../Wishlist/Plugin/Helper/Product/View.php | 51 +++++++++++ .../Plugin/Model/ResourceModel/Product.php | 52 +++++++++++ .../ConfiguredPrice/ConfigurableProduct.php | 38 +++++--- .../Test/Unit/Model/WishlistCleanerTest.php | 87 +++++++++++++++++++ .../Model/ResourceModel/ProductTest.php | 53 +++++++++++ .../Magento/Wishlist/etc/adminhtml/di.xml | 3 + app/code/Magento/Wishlist/etc/frontend/di.xml | 3 + 10 files changed, 368 insertions(+), 18 deletions(-) create mode 100644 app/code/Magento/Wishlist/Model/WishlistCleaner.php create mode 100644 app/code/Magento/Wishlist/Plugin/Helper/Product/View.php create mode 100644 app/code/Magento/Wishlist/Plugin/Model/ResourceModel/Product.php create mode 100644 app/code/Magento/Wishlist/Test/Unit/Model/WishlistCleanerTest.php create mode 100644 app/code/Magento/Wishlist/Test/Unit/Plugin/Model/ResourceModel/ProductTest.php diff --git a/app/code/Magento/Wishlist/Helper/Data.php b/app/code/Magento/Wishlist/Helper/Data.php index 6c1ebd87b4e8d..2984d68f8deeb 100644 --- a/app/code/Magento/Wishlist/Helper/Data.php +++ b/app/code/Magento/Wishlist/Helper/Data.php @@ -639,6 +639,7 @@ public function getProductUrl($item, $additional = []) $product = $item->getProduct(); } $buyRequest = $item->getBuyRequest(); + $fragment = []; if (is_object($buyRequest)) { $config = $buyRequest->getSuperProductConfig(); if ($config && !empty($config['product_id'])) { @@ -648,7 +649,16 @@ public function getProductUrl($item, $additional = []) $this->_storeManager->getStore()->getStoreId() ); } + $fragment = $buyRequest->getSuperAttribute() ?? []; + if ($buyRequest->getQty()) { + $additional['_query']['qty'] = $buyRequest->getQty(); + } + } + $url = $product->getUrlModel()->getUrl($product, $additional); + if ($fragment) { + $url .= '#' . http_build_query($fragment); } - return $product->getUrlModel()->getUrl($product, $additional); + + return $url; } } diff --git a/app/code/Magento/Wishlist/Model/Item/Option.php b/app/code/Magento/Wishlist/Model/Item/Option.php index 61acfcb666531..d3205b676b08c 100644 --- a/app/code/Magento/Wishlist/Model/Item/Option.php +++ b/app/code/Magento/Wishlist/Model/Item/Option.php @@ -6,13 +6,15 @@ namespace Magento\Wishlist\Model\Item; use Magento\Catalog\Model\Product; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\Wishlist\Model\Item; use Magento\Catalog\Api\ProductRepositoryInterface; /** * Item option model - * @method int getProductId() * + * @method int getProductId() * @api * @since 100.0.2 */ @@ -34,6 +36,11 @@ class Option extends \Magento\Framework\Model\AbstractModel implements */ protected $productRepository; + /** + * @var \Psr\Log\LoggerInterface + */ + private $logger; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -41,6 +48,7 @@ class Option extends \Magento\Framework\Model\AbstractModel implements * @param \Magento\Framework\Model\ResourceModel\AbstractResource|null $resource * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection * @param array $data + * @param \Psr\Log\LoggerInterface|null $logger */ public function __construct( \Magento\Framework\Model\Context $context, @@ -48,10 +56,12 @@ public function __construct( ProductRepositoryInterface $productRepository, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, - array $data = [] + array $data = [], + \Psr\Log\LoggerInterface $logger = null ) { parent::__construct($context, $registry, $resource, $resourceCollection, $data); $this->productRepository = $productRepository; + $this->logger = $logger ?? ObjectManager::getInstance()->get(\Psr\Log\LoggerInterface::class); } /** @@ -123,7 +133,11 @@ public function getProduct() { //In some cases product_id is present instead product instance if (null === $this->_product && $this->getProductId()) { - $this->_product = $this->productRepository->getById($this->getProductId()); + try { + $this->_product = $this->productRepository->getById($this->getProductId()); + } catch (NoSuchEntityException $exception) { + $this->logger->error($exception); + } } return $this->_product; } diff --git a/app/code/Magento/Wishlist/Model/WishlistCleaner.php b/app/code/Magento/Wishlist/Model/WishlistCleaner.php new file mode 100644 index 0000000000000..0189ad32d7655 --- /dev/null +++ b/app/code/Magento/Wishlist/Model/WishlistCleaner.php @@ -0,0 +1,67 @@ +itemOptionResourceModel = $itemOptionResourceModel; + $this->itemResourceModel = $itemResourceModel; + } + + /** + * Deletes all wishlist items related the specified product + * + * @param ProductInterface $product + * @throws LocalizedException + */ + public function execute(ProductInterface $product) + { + $connection = $this->itemResourceModel->getConnection(); + + $selectQuery = $connection + ->select() + ->from(['w_item' => $this->itemResourceModel->getMainTable()]) + ->join( + ['w_item_option' => $this->itemOptionResourceModel->getMainTable()], + 'w_item.wishlist_item_id = w_item_option.wishlist_item_id' + ) + ->where('w_item_option.product_id = ?', $product->getId()); + + $connection->query($selectQuery->deleteFromSelect('w_item')); + } +} diff --git a/app/code/Magento/Wishlist/Plugin/Helper/Product/View.php b/app/code/Magento/Wishlist/Plugin/Helper/Product/View.php new file mode 100644 index 0000000000000..58deb33a788ff --- /dev/null +++ b/app/code/Magento/Wishlist/Plugin/Helper/Product/View.php @@ -0,0 +1,51 @@ +getRequest()->getParam('qty'); + if ($qty) { + if (null === $params || !$params instanceof DataObject) { + $params = new DataObject((array) $params); + } + if (!$params->getBuyRequest()) { + $params->setBuyRequest(new DataObject([])); + } + $params->getBuyRequest()->setQty($qty); + } + + return [$resultPage, $productId, $controller, $params]; + } +} diff --git a/app/code/Magento/Wishlist/Plugin/Model/ResourceModel/Product.php b/app/code/Magento/Wishlist/Plugin/Model/ResourceModel/Product.php new file mode 100644 index 0000000000000..e5492cd686356 --- /dev/null +++ b/app/code/Magento/Wishlist/Plugin/Model/ResourceModel/Product.php @@ -0,0 +1,52 @@ +wishlistCleaner = $wishlistCleaner; + } + + /** + * Cleans up wishlist items referencing the product being deleted + * + * @param ProductResourceModel $productResourceModel + * @param mixed $product + * @return void + * @throws LocalizedException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeDelete( + ProductResourceModel $productResourceModel, + $product + ) { + if ($product instanceof ProductInterface) { + $this->wishlistCleaner->execute($product); + } + } +} diff --git a/app/code/Magento/Wishlist/Pricing/ConfiguredPrice/ConfigurableProduct.php b/app/code/Magento/Wishlist/Pricing/ConfiguredPrice/ConfigurableProduct.php index e1fafe35e43ff..7a16b83eaf51d 100644 --- a/app/code/Magento/Wishlist/Pricing/ConfiguredPrice/ConfigurableProduct.php +++ b/app/code/Magento/Wishlist/Pricing/ConfiguredPrice/ConfigurableProduct.php @@ -31,11 +31,11 @@ class ConfigurableProduct extends AbstractPrice */ public function getConfiguredAmount(): \Magento\Framework\Pricing\Amount\AmountInterface { - /** @var \Magento\Wishlist\Model\Item\Option $customOption */ - $customOption = $this->getProduct()->getCustomOption('simple_product'); - $product = $customOption ? $customOption->getProduct() : $this->getProduct(); - - return $product->getPriceInfo()->getPrice(ConfiguredPriceInterface::CONFIGURED_PRICE_CODE)->getAmount(); + return $this + ->getProduct() + ->getPriceInfo() + ->getPrice(ConfiguredPriceInterface::CONFIGURED_PRICE_CODE) + ->getAmount(); } /** @@ -45,11 +45,11 @@ public function getConfiguredAmount(): \Magento\Framework\Pricing\Amount\AmountI */ public function getConfiguredRegularAmount(): \Magento\Framework\Pricing\Amount\AmountInterface { - /** @var \Magento\Wishlist\Model\Item\Option $customOption */ - $customOption = $this->getProduct()->getCustomOption('simple_product'); - $product = $customOption ? $customOption->getProduct() : $this->getProduct(); - - return $product->getPriceInfo()->getPrice(ConfiguredPriceInterface::CONFIGURED_REGULAR_PRICE_CODE)->getAmount(); + return $this + ->getProduct() + ->getPriceInfo() + ->getPrice(ConfiguredPriceInterface::CONFIGURED_REGULAR_PRICE_CODE) + ->getAmount(); } /** @@ -57,10 +57,7 @@ public function getConfiguredRegularAmount(): \Magento\Framework\Pricing\Amount\ */ public function getValue() { - /** @var \Magento\Wishlist\Model\Item\Option $customOption */ - $customOption = $this->getProduct()->getCustomOption('simple_product'); - $product = $customOption ? $customOption->getProduct() : $this->getProduct(); - $price = $product->getPriceInfo()->getPrice(self::PRICE_CODE)->getValue(); + $price = $this->getProduct()->getPriceInfo()->getPrice(self::PRICE_CODE)->getValue(); return max(0, $price); } @@ -73,4 +70,17 @@ public function setItem(ItemInterface $item) $this->item = $item; return $this; } + + /** + * @inheritDoc + */ + public function getProduct() + { + /** @var \Magento\Catalog\Model\Product $product */ + $product = parent::getProduct(); + /** @var \Magento\Wishlist\Model\Item\Option $customOption */ + $customOption = $product->getCustomOption('simple_product'); + + return $customOption ? ($customOption->getProduct() ?? $product) : $product; + } } diff --git a/app/code/Magento/Wishlist/Test/Unit/Model/WishlistCleanerTest.php b/app/code/Magento/Wishlist/Test/Unit/Model/WishlistCleanerTest.php new file mode 100644 index 0000000000000..7eca21f9cee08 --- /dev/null +++ b/app/code/Magento/Wishlist/Test/Unit/Model/WishlistCleanerTest.php @@ -0,0 +1,87 @@ +itemOptionResourceModel = $this->createMock(ItemOptionResourceModel::class); + $this->itemResourceModel = $this->createMock(ItemResourceModel::class); + $this->model = new WishlistCleaner($this->itemOptionResourceModel, $this->itemResourceModel); + } + + /** + * Asserts that wishlist items related to a specific product are deleted + */ + public function testExecute() + { + $productId = 1; + $itemTable = 'table_item'; + $itemOptionTable = 'table_item_option'; + $product = $this->createMock(ProductInterface::class); + $product->expects($this->once())->method('getId')->willReturn($productId); + $connection = $this->createMock(AdapterInterface::class); + $this->itemResourceModel->expects($this->once())->method('getConnection')->willReturn($connection); + $this->itemResourceModel->expects($this->once())->method('getMainTable')->willReturn($itemTable); + $this->itemOptionResourceModel->expects($this->once())->method('getMainTable')->willReturn($itemOptionTable); + $select = $this->createMock(Select::class); + $connection->expects($this->once())->method('query')->with($select); + $connection->expects($this->once()) + ->method('select') + ->willReturn($select); + $select->expects($this->once()) + ->method('from') + ->with(['w_item' => $itemTable]) + ->willReturnSelf(); + $select->expects($this->once()) + ->method('join') + ->with(['w_item_option' => $itemOptionTable], 'w_item.wishlist_item_id = w_item_option.wishlist_item_id') + ->willReturnSelf(); + $select->expects($this->once()) + ->method('where') + ->with('w_item_option.product_id = ?', $productId) + ->willReturnSelf(); + $select->expects($this->once()) + ->method('deleteFromSelect') + ->with('w_item') + ->willReturnSelf(); + + $this->model->execute($product); + } +} diff --git a/app/code/Magento/Wishlist/Test/Unit/Plugin/Model/ResourceModel/ProductTest.php b/app/code/Magento/Wishlist/Test/Unit/Plugin/Model/ResourceModel/ProductTest.php new file mode 100644 index 0000000000000..ab999902f61bd --- /dev/null +++ b/app/code/Magento/Wishlist/Test/Unit/Plugin/Model/ResourceModel/ProductTest.php @@ -0,0 +1,53 @@ +wishlistCleaner = $this->createMock(WishlistCleaner::class); + $this->model = new Plugin($this->wishlistCleaner); + } + + /** + * Asserts that item option cleaner is executed when product is deleted + * + * @return void + */ + public function testExecute() + { + $product = $this->createMock(ProductInterface::class); + $productResourceModel = $this->createMock(ProductResourceModel::class); + $this->wishlistCleaner->expects($this->once())->method('execute')->with($product); + $this->model->beforeDelete($productResourceModel, $product); + } +} diff --git a/app/code/Magento/Wishlist/etc/adminhtml/di.xml b/app/code/Magento/Wishlist/etc/adminhtml/di.xml index 2e222f8193840..124b8c17c3f36 100644 --- a/app/code/Magento/Wishlist/etc/adminhtml/di.xml +++ b/app/code/Magento/Wishlist/etc/adminhtml/di.xml @@ -21,4 +21,7 @@ Magento\Wishlist\Model\Session\Storage + + + diff --git a/app/code/Magento/Wishlist/etc/frontend/di.xml b/app/code/Magento/Wishlist/etc/frontend/di.xml index f28e85fff0a15..c1c03802ba904 100644 --- a/app/code/Magento/Wishlist/etc/frontend/di.xml +++ b/app/code/Magento/Wishlist/etc/frontend/di.xml @@ -53,4 +53,7 @@ + + + From d0cf14c54c03b070150268853e14ce1494076623 Mon Sep 17 00:00:00 2001 From: Buba Suma Date: Tue, 30 Jul 2019 17:54:08 -0500 Subject: [PATCH 03/22] MC-18368: Category Schedule Update unchecks use default value box at store view scope - Fix url_key and url_path are regenerated when scheduling update for a catalog category within store view scope regardless default value is checked or not --- .../CategoryUrlPathAutogeneratorObserver.php | 11 +- ...tegoryUrlPathAutogeneratorObserverTest.php | 230 ++++++++++-------- 2 files changed, 138 insertions(+), 103 deletions(-) diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php index 713dd6ac0c736..7f987124040fd 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php @@ -9,7 +9,6 @@ use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator; use Magento\CatalogUrlRewrite\Service\V1\StoreViewService; use Magento\Catalog\Api\CategoryRepositoryInterface; -use Magento\Framework\Event\Observer; use Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider; use Magento\Framework\Event\ObserverInterface; use Magento\Store\Model\Store; @@ -68,13 +67,15 @@ public function execute(\Magento\Framework\Event\Observer $observer) { /** @var Category $category */ $category = $observer->getEvent()->getCategory(); - $useDefaultAttribute = !$category->isObjectNew() && !empty($category->getData('use_default')['url_key']); + $useDefaultAttribute = !empty($category->getData('use_default')['url_key']); if ($category->getUrlKey() !== false && !$useDefaultAttribute) { $resultUrlKey = $this->categoryUrlPathGenerator->getUrlKey($category); $this->updateUrlKey($category, $resultUrlKey); - } else if ($useDefaultAttribute) { - $resultUrlKey = $category->formatUrlKey($category->getOrigData('name')); - $this->updateUrlKey($category, $resultUrlKey); + } elseif ($useDefaultAttribute) { + if (!$category->isObjectNew()) { + $resultUrlKey = $category->formatUrlKey($category->getOrigData('name')); + $this->updateUrlKey($category, $resultUrlKey); + } $category->setUrlKey(null)->setUrlPath(null); } } diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryUrlPathAutogeneratorObserverTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryUrlPathAutogeneratorObserverTest.php index 1b4d1e08aa208..0a570adab309a 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryUrlPathAutogeneratorObserverTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryUrlPathAutogeneratorObserverTest.php @@ -3,37 +3,51 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\CatalogUrlRewrite\Test\Unit\Observer; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; class CategoryUrlPathAutogeneratorObserverTest extends \PHPUnit\Framework\TestCase { - /** @var \Magento\CatalogUrlRewrite\Observer\CategoryUrlPathAutogeneratorObserver */ - protected $categoryUrlPathAutogeneratorObserver; + /** + * @var \Magento\CatalogUrlRewrite\Observer\CategoryUrlPathAutogeneratorObserver + */ + private $categoryUrlPathAutogeneratorObserver; - /** @var \PHPUnit_Framework_MockObject_MockObject */ - protected $categoryUrlPathGenerator; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $categoryUrlPathGenerator; - /** @var \PHPUnit_Framework_MockObject_MockObject */ - protected $childrenCategoriesProvider; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $childrenCategoriesProvider; - /** @var \PHPUnit_Framework_MockObject_MockObject */ - protected $observer; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $observer; - /** @var \PHPUnit_Framework_MockObject_MockObject */ - protected $category; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $category; /** * @var \Magento\CatalogUrlRewrite\Service\V1\StoreViewService|\PHPUnit_Framework_MockObject_MockObject */ - protected $storeViewService; + private $storeViewService; /** * @var \Magento\Catalog\Model\ResourceModel\Category|\PHPUnit_Framework_MockObject_MockObject */ - protected $categoryResource; + private $categoryResource; + /** + * @inheritDoc + */ protected function setUp() { $this->observer = $this->createPartialMock( @@ -41,16 +55,15 @@ protected function setUp() ['getEvent', 'getCategory'] ); $this->categoryResource = $this->createMock(\Magento\Catalog\Model\ResourceModel\Category::class); - $this->category = $this->createPartialMock(\Magento\Catalog\Model\Category::class, [ - 'setUrlKey', - 'setUrlPath', + $this->category = $this->createPartialMock( + \Magento\Catalog\Model\Category::class, + [ 'dataHasChangedFor', - 'isObjectNew', 'getResource', - 'getUrlKey', 'getStoreId', - 'getData' - ]); + 'formatUrlKey' + ] + ); $this->category->expects($this->any())->method('getResource')->willReturn($this->categoryResource); $this->observer->expects($this->any())->method('getEvent')->willReturnSelf(); $this->observer->expects($this->any())->method('getCategory')->willReturn($this->category); @@ -73,106 +86,125 @@ protected function setUp() ); } - public function testSetCategoryUrlAndCategoryPath() + /** + * @param $isObjectNew + * @throws \Magento\Framework\Exception\LocalizedException + * @dataProvider shouldFormatUrlKeyAndGenerateUrlPathIfUrlKeyIsNotUsingDefaultValueDataProvider + */ + public function testShouldFormatUrlKeyAndGenerateUrlPathIfUrlKeyIsNotUsingDefaultValue($isObjectNew) { - $this->category->expects($this->once())->method('getUrlKey')->willReturn('category'); - $this->categoryUrlPathGenerator->expects($this->once())->method('getUrlKey')->willReturn('urk_key'); - $this->category->expects($this->once())->method('setUrlKey')->with('urk_key')->willReturnSelf(); - $this->categoryUrlPathGenerator->expects($this->once())->method('getUrlPath')->willReturn('url_path'); - $this->category->expects($this->once())->method('setUrlPath')->with('url_path')->willReturnSelf(); - $this->category->expects($this->exactly(2))->method('isObjectNew')->willReturn(true); - + $expectedUrlKey = 'formatted_url_key'; + $expectedUrlPath = 'generated_url_path'; + $categoryData = ['use_default' => ['url_key' => 0], 'url_key' => 'some_key', 'url_path' => '']; + $this->category->setData($categoryData); + $this->category->isObjectNew($isObjectNew); + $this->categoryUrlPathGenerator->expects($this->once())->method('getUrlKey')->willReturn($expectedUrlKey); + $this->categoryUrlPathGenerator->expects($this->once())->method('getUrlPath')->willReturn($expectedUrlPath); + $this->assertEquals($categoryData['url_key'], $this->category->getUrlKey()); + $this->assertEquals($categoryData['url_path'], $this->category->getUrlPath()); $this->categoryUrlPathAutogeneratorObserver->execute($this->observer); + $this->assertEquals($expectedUrlKey, $this->category->getUrlKey()); + $this->assertEquals($expectedUrlPath, $this->category->getUrlPath()); + $this->categoryResource->expects($this->never())->method('saveAttribute'); } - public function testExecuteWithoutUrlKeyAndUrlPathUpdating() + /** + * @return array + */ + public function shouldFormatUrlKeyAndGenerateUrlPathIfUrlKeyIsNotUsingDefaultValueDataProvider() { - $this->category->expects($this->once())->method('getUrlKey')->willReturn(false); - $this->category->expects($this->never())->method('setUrlKey'); - $this->category->expects($this->never())->method('setUrlPath'); - $this->categoryUrlPathAutogeneratorObserver->execute($this->observer); + return [ + [true], + [false], + ]; } /** - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Invalid URL key + * @param $isObjectNew + * @throws \Magento\Framework\Exception\LocalizedException + * @dataProvider shouldResetUrlPathAndUrlKeyIfUrlKeyIsUsingDefaultValueDataProvider */ - public function testExecuteWithException() + public function testShouldResetUrlPathAndUrlKeyIfUrlKeyIsUsingDefaultValue($isObjectNew) { - $categoryName = 'test'; - $categoryData = ['url_key' => 0]; - $this->category->expects($this->once())->method('getUrlKey')->willReturn($categoryName); - $this->category->expects($this->once()) - ->method('getData') - ->with('use_default') - ->willReturn($categoryData); - $this->categoryUrlPathGenerator->expects($this->once()) - ->method('getUrlKey') - ->with($this->category) - ->willReturn(null); + $categoryData = ['use_default' => ['url_key' => 1], 'url_key' => 'some_key', 'url_path' => 'some_path']; + $this->category->setData($categoryData); + $this->category->isObjectNew($isObjectNew); + $this->category->expects($this->any())->method('formatUrlKey')->willReturn('formatted_key'); + $this->assertEquals($categoryData['url_key'], $this->category->getUrlKey()); + $this->assertEquals($categoryData['url_path'], $this->category->getUrlPath()); $this->categoryUrlPathAutogeneratorObserver->execute($this->observer); + $this->assertNull($this->category->getUrlKey()); + $this->assertNull($this->category->getUrlPath()); } - public function testUrlKeyAndUrlPathUpdating() + /** + * @return array + */ + public function shouldResetUrlPathAndUrlKeyIfUrlKeyIsUsingDefaultValueDataProvider() { - $this->categoryUrlPathGenerator->expects($this->once())->method('getUrlKey')->with($this->category) - ->willReturn('url_key'); - $this->categoryUrlPathGenerator->expects($this->once())->method('getUrlPath')->with($this->category) - ->willReturn('url_path'); - - $this->category->expects($this->once())->method('getUrlKey')->willReturn('not_formatted_url_key'); - $this->category->expects($this->once())->method('setUrlKey')->with('url_key')->willReturnSelf(); - $this->category->expects($this->once())->method('setUrlPath')->with('url_path')->willReturnSelf(); - // break code execution - $this->category->expects($this->exactly(2))->method('isObjectNew')->willReturn(true); + return [ + [true], + [false], + ]; + } + /** + * @param $useDefaultUrlKey + * @param $isObjectNew + * @throws \Magento\Framework\Exception\LocalizedException + * @dataProvider shouldThrowExceptionIfUrlKeyIsEmptyDataProvider + */ + public function testShouldThrowExceptionIfUrlKeyIsEmpty($useDefaultUrlKey, $isObjectNew) + { + $this->expectExceptionMessage('Invalid URL key'); + $categoryData = ['use_default' => ['url_key' => $useDefaultUrlKey], 'url_key' => '', 'url_path' => '']; + $this->category->setData($categoryData); + $this->category->isObjectNew($isObjectNew); + $this->assertEquals($isObjectNew, $this->category->isObjectNew()); + $this->assertEquals($categoryData['url_key'], $this->category->getUrlKey()); + $this->assertEquals($categoryData['url_path'], $this->category->getUrlPath()); $this->categoryUrlPathAutogeneratorObserver->execute($this->observer); + $this->assertEquals($categoryData['url_key'], $this->category->getUrlKey()); + $this->assertEquals($categoryData['url_path'], $this->category->getUrlPath()); } - public function testUrlPathAttributeNoUpdatingIfCategoryIsNew() + /** + * @return array + */ + public function shouldThrowExceptionIfUrlKeyIsEmptyDataProvider() { - $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlKey')->willReturn('url_key'); - $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlPath')->willReturn('url_path'); - - $this->category->expects($this->any())->method('getUrlKey')->willReturn('not_formatted_url_key'); - $this->category->expects($this->any())->method('setUrlKey')->willReturnSelf(); - $this->category->expects($this->any())->method('setUrlPath')->willReturnSelf(); - - $this->category->expects($this->exactly(2))->method('isObjectNew')->willReturn(true); - $this->categoryResource->expects($this->never())->method('saveAttribute'); - - $this->categoryUrlPathAutogeneratorObserver->execute($this->observer); + return [ + [0, false], + [0, true], + [1, false], + ]; } public function testUrlPathAttributeUpdating() { - $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlKey')->willReturn('url_key'); - $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlPath')->willReturn('url_path'); - - $this->category->expects($this->any())->method('getUrlKey')->willReturn('not_formatted_url_key'); - $this->category->expects($this->any())->method('setUrlKey')->willReturnSelf(); - $this->category->expects($this->any())->method('setUrlPath')->willReturnSelf(); - $this->category->expects($this->exactly(2))->method('isObjectNew')->willReturn(false); - + $categoryData = ['url_key' => 'some_key', 'url_path' => '']; + $this->category->setData($categoryData); + $this->category->isObjectNew(false); + $expectedUrlKey = 'formatted_url_key'; + $expectedUrlPath = 'generated_url_path'; + $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlKey')->willReturn($expectedUrlKey); + $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlPath')->willReturn($expectedUrlPath); $this->categoryResource->expects($this->once())->method('saveAttribute')->with($this->category, 'url_path'); - - // break code execution $this->category->expects($this->once())->method('dataHasChangedFor')->with('url_path')->willReturn(false); - $this->categoryUrlPathAutogeneratorObserver->execute($this->observer); } public function testChildrenUrlPathAttributeNoUpdatingIfParentUrlPathIsNotChanged() { + $categoryData = ['url_key' => 'some_key', 'url_path' => '']; + $this->category->setData($categoryData); + $this->category->isObjectNew(false); + $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlKey')->willReturn('url_key'); $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlPath')->willReturn('url_path'); $this->categoryResource->expects($this->once())->method('saveAttribute')->with($this->category, 'url_path'); - $this->category->expects($this->any())->method('getUrlKey')->willReturn('not_formatted_url_key'); - $this->category->expects($this->any())->method('setUrlKey')->willReturnSelf(); - $this->category->expects($this->any())->method('setUrlPath')->willReturnSelf(); - $this->category->expects($this->exactly(2))->method('isObjectNew')->willReturn(false); // break code execution $this->category->expects($this->once())->method('dataHasChangedFor')->with('url_path')->willReturn(false); @@ -181,13 +213,12 @@ public function testChildrenUrlPathAttributeNoUpdatingIfParentUrlPathIsNotChange public function testChildrenUrlPathAttributeUpdatingForSpecificStore() { + $categoryData = ['url_key' => 'some_key', 'url_path' => '']; + $this->category->setData($categoryData); + $this->category->isObjectNew(false); + $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlKey')->willReturn('generated_url_key'); $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlPath')->willReturn('generated_url_path'); - - $this->category->expects($this->any())->method('getUrlKey')->willReturn('not_formatted_url_key'); - $this->category->expects($this->any())->method('setUrlKey')->willReturnSelf(); - $this->category->expects($this->any())->method('setUrlPath')->willReturnSelf(); - $this->category->expects($this->exactly(2))->method('isObjectNew')->willReturn(false); $this->category->expects($this->any())->method('dataHasChangedFor')->willReturn(true); // only for specific store $this->category->expects($this->atLeastOnce())->method('getStoreId')->willReturn(1); @@ -195,15 +226,18 @@ public function testChildrenUrlPathAttributeUpdatingForSpecificStore() $childCategoryResource = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Category::class) ->disableOriginalConstructor()->getMock(); $childCategory = $this->getMockBuilder(\Magento\Catalog\Model\Category::class) - ->setMethods([ - 'getUrlPath', - 'setUrlPath', - 'getResource', - 'getStore', - 'getStoreId', - 'setStoreId' - ]) - ->disableOriginalConstructor()->getMock(); + ->setMethods( + [ + 'getUrlPath', + 'setUrlPath', + 'getResource', + 'getStore', + 'getStoreId', + 'setStoreId' + ] + ) + ->disableOriginalConstructor() + ->getMock(); $childCategory->expects($this->any())->method('getResource')->willReturn($childCategoryResource); $childCategory->expects($this->once())->method('setStoreId')->with(1); From a285d530d349cba00d020872d5b767a574acf80f Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi Date: Tue, 6 Aug 2019 15:04:43 -0500 Subject: [PATCH 04/22] MAGETWO-99401: order-related save_after_commit callbacks are not called for guest checkouts --- app/etc/di.xml | 3 ++ .../Framework/Interception/Config/Config.php | 4 +- .../Model/ExecuteCommitCallbacks.php | 51 +++++++++++++++++++ .../Model/ResourceModel/AbstractResource.php | 14 +---- 4 files changed, 58 insertions(+), 14 deletions(-) create mode 100644 lib/internal/Magento/Framework/Model/ExecuteCommitCallbacks.php diff --git a/app/etc/di.xml b/app/etc/di.xml index 1a74fd9d7f840..50088d41f1b4b 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -1777,4 +1777,7 @@ type="Magento\Framework\Mail\MimeMessage" /> + + + diff --git a/lib/internal/Magento/Framework/Interception/Config/Config.php b/lib/internal/Magento/Framework/Interception/Config/Config.php index 3f16e9275bd08..b7b716506fe83 100644 --- a/lib/internal/Magento/Framework/Interception/Config/Config.php +++ b/lib/internal/Magento/Framework/Interception/Config/Config.php @@ -202,7 +202,9 @@ private function initializeUncompiled($classDefinitions = []) private function generateIntercepted($classDefinitions) { $config = []; - foreach ($this->_scopeList->getAllScopes() as $scope) { + $scopes = $this->_scopeList->getAllScopes(); + array_unshift($scopes, 'primary'); + foreach (array_unique($scopes) as $scope) { $config = array_replace_recursive($config, $this->_reader->read($scope)); } unset($config['preferences']); diff --git a/lib/internal/Magento/Framework/Model/ExecuteCommitCallbacks.php b/lib/internal/Magento/Framework/Model/ExecuteCommitCallbacks.php new file mode 100644 index 0000000000000..21ac4a43b488e --- /dev/null +++ b/lib/internal/Magento/Framework/Model/ExecuteCommitCallbacks.php @@ -0,0 +1,51 @@ +logger = $logger; + } + + /** + * Execute callbacks after commit. + * + * @param AdapterInterface $subject + * @param AdapterInterface $result + * @return AdapterInterface + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterCommit(AdapterInterface $subject, AdapterInterface $result): AdapterInterface + { + if ($result->getTransactionLevel() === 0) { + $callbacks = CallbackPool::get(spl_object_hash($result)); + foreach ($callbacks as $callback) { + try { + call_user_func($callback); + } catch (\Throwable $e) { + $this->logger->critical($e); + } + } + } + + return $result; + } +} diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/AbstractResource.php b/lib/internal/Magento/Framework/Model/ResourceModel/AbstractResource.php index 255e6d94d741f..c5b1ca1524949 100644 --- a/lib/internal/Magento/Framework/Model/ResourceModel/AbstractResource.php +++ b/lib/internal/Magento/Framework/Model/ResourceModel/AbstractResource.php @@ -87,19 +87,7 @@ public function addCommitCallback($callback) public function commit() { $this->getConnection()->commit(); - /** - * Process after commit callbacks - */ - if ($this->getConnection()->getTransactionLevel() === 0) { - $callbacks = CallbackPool::get(spl_object_hash($this->getConnection())); - try { - foreach ($callbacks as $callback) { - call_user_func($callback); - } - } catch (\Exception $e) { - $this->getLogger()->critical($e); - } - } + return $this; } From 419c4168d4396cac5d1d0a6d3d741eb71861a145 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi Date: Wed, 7 Aug 2019 14:20:12 -0500 Subject: [PATCH 05/22] MAGETWO-99401: order-related save_after_commit callbacks are not called for guest checkouts --- .../Magento/Framework/Config/Scope.php | 4 +++- .../Framework/Config/Test/Unit/ScopeTest.php | 18 +++++++++-------- .../Framework/Interception/Config/Config.php | 4 +--- .../Model/ExecuteCommitCallbacks.php | 20 +++++++++++++++++-- .../Model/ResourceModel/AbstractResource.php | 14 ++++++++++++- 5 files changed, 45 insertions(+), 15 deletions(-) diff --git a/lib/internal/Magento/Framework/Config/Scope.php b/lib/internal/Magento/Framework/Config/Scope.php index c43a4550cd79d..4c1ada9a40eb5 100644 --- a/lib/internal/Magento/Framework/Config/Scope.php +++ b/lib/internal/Magento/Framework/Config/Scope.php @@ -69,7 +69,9 @@ public function setCurrentScope($scope) public function getAllScopes() { $codes = $this->_areaList->getCodes(); - array_unshift($codes, $this->_defaultScope); + array_unshift($codes, 'global'); + array_unshift($codes, 'primary'); + return $codes; } } diff --git a/lib/internal/Magento/Framework/Config/Test/Unit/ScopeTest.php b/lib/internal/Magento/Framework/Config/Test/Unit/ScopeTest.php index 0ed59e73a25a2..fa2f2c79ef952 100644 --- a/lib/internal/Magento/Framework/Config/Test/Unit/ScopeTest.php +++ b/lib/internal/Magento/Framework/Config/Test/Unit/ScopeTest.php @@ -6,23 +6,25 @@ namespace Magento\Framework\Config\Test\Unit; -use \Magento\Framework\Config\Scope; +use Magento\Framework\App\AreaList; +use Magento\Framework\Config\Scope; +use PHPUnit\Framework\MockObject\MockObject; class ScopeTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\Framework\Config\Scope + * @var Scope */ - protected $model; + private $model; /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\App\AreaList + * @var MockObject|AreaList */ - protected $areaListMock; + private $areaListMock; protected function setUp() { - $this->areaListMock = $this->createPartialMock(\Magento\Framework\App\AreaList::class, ['getCodes']); + $this->areaListMock = $this->createPartialMock(AreaList::class, ['getCodes']); $this->model = new Scope($this->areaListMock); } @@ -35,10 +37,10 @@ public function testScopeSetGet() public function testGetAllScopes() { - $expectedBalances = ['primary', 'test_scope']; + $expectedBalances = ['primary', 'global', 'test_scope']; $this->areaListMock->expects($this->once()) ->method('getCodes') - ->will($this->returnValue(['test_scope'])); + ->willReturn(['test_scope']); $this->assertEquals($expectedBalances, $this->model->getAllScopes()); } } diff --git a/lib/internal/Magento/Framework/Interception/Config/Config.php b/lib/internal/Magento/Framework/Interception/Config/Config.php index b7b716506fe83..3f16e9275bd08 100644 --- a/lib/internal/Magento/Framework/Interception/Config/Config.php +++ b/lib/internal/Magento/Framework/Interception/Config/Config.php @@ -202,9 +202,7 @@ private function initializeUncompiled($classDefinitions = []) private function generateIntercepted($classDefinitions) { $config = []; - $scopes = $this->_scopeList->getAllScopes(); - array_unshift($scopes, 'primary'); - foreach (array_unique($scopes) as $scope) { + foreach ($this->_scopeList->getAllScopes() as $scope) { $config = array_replace_recursive($config, $this->_reader->read($scope)); } unset($config['preferences']); diff --git a/lib/internal/Magento/Framework/Model/ExecuteCommitCallbacks.php b/lib/internal/Magento/Framework/Model/ExecuteCommitCallbacks.php index 21ac4a43b488e..799f8ffda253c 100644 --- a/lib/internal/Magento/Framework/Model/ExecuteCommitCallbacks.php +++ b/lib/internal/Magento/Framework/Model/ExecuteCommitCallbacks.php @@ -20,6 +20,9 @@ class ExecuteCommitCallbacks */ private $logger; + /** + * @param LoggerInterface $logger + */ public function __construct(LoggerInterface $logger) { $this->logger = $logger; @@ -31,12 +34,11 @@ public function __construct(LoggerInterface $logger) * @param AdapterInterface $subject * @param AdapterInterface $result * @return AdapterInterface - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function afterCommit(AdapterInterface $subject, AdapterInterface $result): AdapterInterface { if ($result->getTransactionLevel() === 0) { - $callbacks = CallbackPool::get(spl_object_hash($result)); + $callbacks = CallbackPool::get(spl_object_hash($subject)); foreach ($callbacks as $callback) { try { call_user_func($callback); @@ -48,4 +50,18 @@ public function afterCommit(AdapterInterface $subject, AdapterInterface $result) return $result; } + + /** + * Drop callbacks after rollBack. + * + * @param AdapterInterface $subject + * @param AdapterInterface $result + * @return AdapterInterface + */ + public function afterRollBack(AdapterInterface $subject, AdapterInterface $result): AdapterInterface + { + CallbackPool::clear(spl_object_hash($subject)); + + return $result; + } } diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/AbstractResource.php b/lib/internal/Magento/Framework/Model/ResourceModel/AbstractResource.php index c5b1ca1524949..255e6d94d741f 100644 --- a/lib/internal/Magento/Framework/Model/ResourceModel/AbstractResource.php +++ b/lib/internal/Magento/Framework/Model/ResourceModel/AbstractResource.php @@ -87,7 +87,19 @@ public function addCommitCallback($callback) public function commit() { $this->getConnection()->commit(); - + /** + * Process after commit callbacks + */ + if ($this->getConnection()->getTransactionLevel() === 0) { + $callbacks = CallbackPool::get(spl_object_hash($this->getConnection())); + try { + foreach ($callbacks as $callback) { + call_user_func($callback); + } + } catch (\Exception $e) { + $this->getLogger()->critical($e); + } + } return $this; } From 4ff68938a49c079ab5643746a8ab278ba902b105 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi Date: Wed, 7 Aug 2019 16:06:20 -0500 Subject: [PATCH 06/22] MAGETWO-99401: order-related save_after_commit callbacks are not called for guest checkouts --- .../Magento/Framework/Config/Scope.php | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/internal/Magento/Framework/Config/Scope.php b/lib/internal/Magento/Framework/Config/Scope.php index 4c1ada9a40eb5..e4b25c7eb5dcc 100644 --- a/lib/internal/Magento/Framework/Config/Scope.php +++ b/lib/internal/Magento/Framework/Config/Scope.php @@ -5,15 +5,13 @@ */ namespace Magento\Framework\Config; -class Scope implements \Magento\Framework\Config\ScopeInterface, \Magento\Framework\Config\ScopeListInterface -{ - /** - * Default application scope - * - * @var string - */ - protected $_defaultScope; +use Magento\Framework\App\AreaList; +/** + * Scope config + */ +class Scope implements ScopeInterface, ScopeListInterface +{ /** * Current config scope * @@ -24,19 +22,19 @@ class Scope implements \Magento\Framework\Config\ScopeInterface, \Magento\Framew /** * List of all available areas * - * @var \Magento\Framework\App\AreaList + * @var AreaList */ protected $_areaList; /** * Constructor * - * @param \Magento\Framework\App\AreaList $areaList + * @param AreaList $areaList * @param string $defaultScope */ - public function __construct(\Magento\Framework\App\AreaList $areaList, $defaultScope = 'primary') + public function __construct(AreaList $areaList, $defaultScope = 'primary') { - $this->_defaultScope = $this->_currentScope = $defaultScope; + $this->_currentScope = $defaultScope; $this->_areaList = $areaList; } From 76b348d90f74b8f4857829b84d4f736acdbf186c Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi Date: Tue, 6 Aug 2019 14:57:10 -0500 Subject: [PATCH 07/22] MC-18847: Special Price & Regular Price Display Issues on Configurable Product --- .../LinkedProductSelectBuilderComposite.php | 45 ------------------- .../Pricing/Render/FinalPriceBox.php | 12 ++++- .../Magento/ConfigurableProduct/etc/di.xml | 9 +--- 3 files changed, 11 insertions(+), 55 deletions(-) delete mode 100644 app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/LinkedProductSelectBuilderComposite.php diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/LinkedProductSelectBuilderComposite.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/LinkedProductSelectBuilderComposite.php deleted file mode 100644 index 59a7b81e068a5..0000000000000 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/LinkedProductSelectBuilderComposite.php +++ /dev/null @@ -1,45 +0,0 @@ -linkedProductSelectBuilder = $linkedProductSelectBuilder; - } - - /** - * {@inheritdoc} - */ - public function build($productId) - { - $selects = []; - foreach ($this->linkedProductSelectBuilder as $productSelectBuilder) { - $selects = array_merge($selects, $productSelectBuilder->build($productId)); - } - - return $selects; - } -} diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php b/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php index 9689352ab888e..a920a2a8e43aa 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php @@ -17,6 +17,9 @@ use Magento\Framework\Pricing\SaleableInterface; use Magento\Framework\View\Element\Template\Context; +/** + * Class for final_price box rendering + */ class FinalPriceBox extends \Magento\Catalog\Pricing\Render\FinalPriceBox { /** @@ -24,6 +27,11 @@ class FinalPriceBox extends \Magento\Catalog\Pricing\Render\FinalPriceBox */ private $lowestPriceOptionsProvider; + /** + * @var ConfigurableOptionsProviderInterface + */ + private $configurableOptionsProvider; + /** * @param Context $context * @param SaleableInterface $saleableItem @@ -34,7 +42,6 @@ class FinalPriceBox extends \Magento\Catalog\Pricing\Render\FinalPriceBox * @param LowestPriceOptionsProviderInterface $lowestPriceOptionsProvider * @param SalableResolverInterface|null $salableResolver * @param MinimalPriceCalculatorInterface|null $minimalPriceCalculator - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( Context $context, @@ -56,6 +63,7 @@ public function __construct( $salableResolver, $minimalPriceCalculator ); + $this->configurableOptionsProvider = $configurableOptionsProvider; $this->lowestPriceOptionsProvider = $lowestPriceOptionsProvider ?: ObjectManager::getInstance()->get(LowestPriceOptionsProviderInterface::class); } @@ -68,7 +76,7 @@ public function __construct( public function hasSpecialPrice() { $product = $this->getSaleableItem(); - foreach ($this->lowestPriceOptionsProvider->getProducts($product) as $subProduct) { + foreach ($this->configurableOptionsProvider->getProducts($product) as $subProduct) { $regularPrice = $subProduct->getPriceInfo()->getPrice(RegularPrice::PRICE_CODE)->getValue(); $finalPrice = $subProduct->getPriceInfo()->getPrice(FinalPrice::PRICE_CODE)->getValue(); if ($finalPrice < $regularPrice) { diff --git a/app/code/Magento/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml index 498591fc31569..b8f7ed67a9868 100644 --- a/app/code/Magento/ConfigurableProduct/etc/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/di.xml @@ -205,13 +205,6 @@ Magento\Catalog\Model\Indexer\Product\Full - - - - Magento\Catalog\Model\ResourceModel\Product\Indexer\LinkedProductSelectBuilderByIndexPrice - - - Magento\ConfigurableProduct\Model\ResourceModel\Product\LinkedProductSelectBuilder @@ -220,7 +213,7 @@ Magento\ConfigurableProduct\Model\ResourceModel\Product\StockStatusBaseSelectProcessor - LinkedProductSelectBuilderByIndexMinPrice + Magento\Catalog\Model\ResourceModel\Product\Indexer\LinkedProductSelectBuilderByIndexPrice From 9f72fee09162ddf235cd693ff14c7b4b7f2bbd57 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi Date: Wed, 7 Aug 2019 22:34:02 -0500 Subject: [PATCH 08/22] MC-18847: Special Price & Regular Price Display Issues on Configurable Product --- .../Price/ConfigurableOptionsProvider.php | 22 ++--- .../Pricing/Render/FinalPriceBox.php | 22 ++--- .../Unit/Pricing/Render/FinalPriceBoxTest.php | 94 +++++++++---------- 3 files changed, 59 insertions(+), 79 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableOptionsProvider.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableOptionsProvider.php index 6cc5625df5b18..9a19f9338593c 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableOptionsProvider.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableOptionsProvider.php @@ -7,16 +7,15 @@ namespace Magento\ConfigurableProduct\Pricing\Price; use Magento\Catalog\Api\Data\ProductInterface; -use Magento\Catalog\Model\ResourceModel\Product\LinkedProductSelectBuilderInterface; -use Magento\Framework\App\ResourceConnection; use Magento\ConfigurableProduct\Model\Product\Type\Configurable; -use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; -use Magento\Framework\App\RequestSafetyInterface; +/** + * Provide configurable child products for price calculation + */ class ConfigurableOptionsProvider implements ConfigurableOptionsProviderInterface { /** - * @var \Magento\ConfigurableProduct\Model\Product\Type\Configurable + * @var Configurable */ private $configurable; @@ -27,24 +26,15 @@ class ConfigurableOptionsProvider implements ConfigurableOptionsProviderInterfac /** * @param Configurable $configurable - * @param ResourceConnection $resourceConnection - * @param LinkedProductSelectBuilderInterface $linkedProductSelectBuilder - * @param CollectionFactory $collectionFactory - * @param RequestSafetyInterface $requestSafety - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( - Configurable $configurable, - ResourceConnection $resourceConnection, - LinkedProductSelectBuilderInterface $linkedProductSelectBuilder, - CollectionFactory $collectionFactory, - RequestSafetyInterface $requestSafety + Configurable $configurable ) { $this->configurable = $configurable; } /** - * {@inheritdoc} + * @inheritdoc */ public function getProducts(ProductInterface $product) { diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php b/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php index a920a2a8e43aa..1ad92fc7a39e7 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php @@ -10,8 +10,6 @@ use Magento\Catalog\Pricing\Price\MinimalPriceCalculatorInterface; use Magento\Catalog\Pricing\Price\RegularPrice; use Magento\ConfigurableProduct\Pricing\Price\ConfigurableOptionsProviderInterface; -use Magento\ConfigurableProduct\Pricing\Price\LowestPriceOptionsProviderInterface; -use Magento\Framework\App\ObjectManager; use Magento\Framework\Pricing\Price\PriceInterface; use Magento\Framework\Pricing\Render\RendererPool; use Magento\Framework\Pricing\SaleableInterface; @@ -22,11 +20,6 @@ */ class FinalPriceBox extends \Magento\Catalog\Pricing\Render\FinalPriceBox { - /** - * @var LowestPriceOptionsProviderInterface - */ - private $lowestPriceOptionsProvider; - /** * @var ConfigurableOptionsProviderInterface */ @@ -39,20 +32,18 @@ class FinalPriceBox extends \Magento\Catalog\Pricing\Render\FinalPriceBox * @param RendererPool $rendererPool * @param ConfigurableOptionsProviderInterface $configurableOptionsProvider * @param array $data - * @param LowestPriceOptionsProviderInterface $lowestPriceOptionsProvider - * @param SalableResolverInterface|null $salableResolver - * @param MinimalPriceCalculatorInterface|null $minimalPriceCalculator + * @param SalableResolverInterface $salableResolver + * @param MinimalPriceCalculatorInterface $minimalPriceCalculator */ public function __construct( Context $context, SaleableInterface $saleableItem, PriceInterface $price, RendererPool $rendererPool, + SalableResolverInterface $salableResolver, + MinimalPriceCalculatorInterface $minimalPriceCalculator, ConfigurableOptionsProviderInterface $configurableOptionsProvider, - array $data = [], - LowestPriceOptionsProviderInterface $lowestPriceOptionsProvider = null, - SalableResolverInterface $salableResolver = null, - MinimalPriceCalculatorInterface $minimalPriceCalculator = null + array $data = [] ) { parent::__construct( $context, @@ -63,9 +54,8 @@ public function __construct( $salableResolver, $minimalPriceCalculator ); + $this->configurableOptionsProvider = $configurableOptionsProvider; - $this->lowestPriceOptionsProvider = $lowestPriceOptionsProvider ?: - ObjectManager::getInstance()->get(LowestPriceOptionsProviderInterface::class); } /** diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Render/FinalPriceBoxTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Render/FinalPriceBoxTest.php index 3c4b9b4392ad7..32a8add20a603 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Render/FinalPriceBoxTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Render/FinalPriceBoxTest.php @@ -5,63 +5,74 @@ */ namespace Magento\ConfigurableProduct\Test\Unit\Pricing\Render; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Pricing\Renderer\SalableResolverInterface; use Magento\Catalog\Pricing\Price\FinalPrice; +use Magento\Catalog\Pricing\Price\MinimalPriceCalculatorInterface; use Magento\Catalog\Pricing\Price\RegularPrice; -use Magento\ConfigurableProduct\Pricing\Price\LowestPriceOptionsProviderInterface; +use Magento\ConfigurableProduct\Pricing\Price\ConfigurableOptionsProviderInterface; use Magento\ConfigurableProduct\Pricing\Render\FinalPriceBox; +use Magento\Framework\Pricing\Price\PriceInterface; +use Magento\Framework\Pricing\PriceInfoInterface; +use Magento\Framework\Pricing\Render\RendererPool; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\View\Element\Template\Context; +use PHPUnit\Framework\MockObject\MockObject; class FinalPriceBoxTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\Framework\View\Element\Template\Context|\PHPUnit_Framework_MockObject_MockObject + * @var Context|MockObject */ private $context; /** - * @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject + * @var Product|MockObject */ private $saleableItem; /** - * @var \Magento\Framework\Pricing\Price\PriceInterface|\PHPUnit_Framework_MockObject_MockObject + * @var PriceInterface|MockObject */ private $price; /** - * @var \Magento\Framework\Pricing\Render\RendererPool|\PHPUnit_Framework_MockObject_MockObject + * @var RendererPool|MockObject */ private $rendererPool; /** - * @var LowestPriceOptionsProviderInterface|\PHPUnit_Framework_MockObject_MockObject + * @var SalableResolverInterface|MockObject */ - private $lowestPriceOptionsProvider; + private $salableResolver; + + /** + * @var MinimalPriceCalculatorInterface|MockObject + */ + private $minimalPriceCalculator; + + /** + * @var ConfigurableOptionsProviderInterface|MockObject + */ + private $configurableOptionsProvider; /** * @var FinalPriceBox */ private $model; + /** + * @inheritDoc + */ protected function setUp() { - $this->context = $this->getMockBuilder(\Magento\Framework\View\Element\Template\Context::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->saleableItem = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->price = $this->getMockBuilder(\Magento\Framework\Pricing\Price\PriceInterface::class) - ->getMockForAbstractClass(); - - $this->rendererPool = $this->getMockBuilder(\Magento\Framework\Pricing\Render\RendererPool::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->lowestPriceOptionsProvider = $this->getMockBuilder(LowestPriceOptionsProviderInterface::class) - ->getMockForAbstractClass(); + $this->context = $this->createMock(Context::class); + $this->saleableItem = $this->createMock(Product::class); + $this->price = $this->createMock(PriceInterface::class); + $this->rendererPool = $this->createMock(RendererPool::class); + $this->salableResolver = $this->createMock(SalableResolverInterface::class); + $this->minimalPriceCalculator = $this->createMock(MinimalPriceCalculatorInterface::class); + $this->configurableOptionsProvider = $this->createMock(ConfigurableOptionsProviderInterface::class); $this->model = (new ObjectManager($this))->getObject( FinalPriceBox::class, @@ -70,7 +81,9 @@ protected function setUp() 'saleableItem' => $this->saleableItem, 'price' => $this->price, 'rendererPool' => $this->rendererPool, - 'lowestPriceOptionsProvider' => $this->lowestPriceOptionsProvider, + 'salableResolver' => $this->salableResolver, + 'minimalPriceCalculator' => $this->minimalPriceCalculator, + 'configurableOptionsProvider' => $this->configurableOptionsProvider, ] ); } @@ -82,28 +95,19 @@ protected function setUp() * @dataProvider hasSpecialPriceDataProvider */ public function testHasSpecialPrice( - $regularPrice, - $finalPrice, - $expected + float $regularPrice, + float $finalPrice, + bool $expected ) { - $priceMockOne = $this->getMockBuilder(\Magento\Framework\Pricing\Price\PriceInterface::class) - ->getMockForAbstractClass(); - + $priceMockOne = $this->createMock(PriceInterface::class); $priceMockOne->expects($this->once()) ->method('getValue') ->willReturn($regularPrice); - - $priceMockTwo = $this->getMockBuilder(\Magento\Framework\Pricing\Price\PriceInterface::class) - ->getMockForAbstractClass(); - + $priceMockTwo = $this->createMock(PriceInterface::class); $priceMockTwo->expects($this->once()) ->method('getValue') ->willReturn($finalPrice); - - $priceInfoMock = $this->getMockBuilder(\Magento\Framework\Pricing\PriceInfo\Base::class) - ->disableOriginalConstructor() - ->getMock(); - + $priceInfoMock = $this->createMock(PriceInfoInterface::class); $priceInfoMock->expects($this->exactly(2)) ->method('getPrice') ->willReturnMap([ @@ -111,15 +115,11 @@ public function testHasSpecialPrice( [FinalPrice::PRICE_CODE, $priceMockTwo], ]); - $productMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class) - ->setMethods(['getPriceInfo']) - ->getMockForAbstractClass(); - + $productMock = $this->createMock(Product::class); $productMock->expects($this->exactly(2)) ->method('getPriceInfo') ->willReturn($priceInfoMock); - - $this->lowestPriceOptionsProvider->expects($this->once()) + $this->configurableOptionsProvider->expects($this->once()) ->method('getProducts') ->with($this->saleableItem) ->willReturn([$productMock]); @@ -130,7 +130,7 @@ public function testHasSpecialPrice( /** * @return array */ - public function hasSpecialPriceDataProvider() + public function hasSpecialPriceDataProvider(): array { return [ [10., 20., false], From 0b68747e024468d90b5a9463687c0515e11b473b Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi Date: Wed, 7 Aug 2019 23:11:30 -0500 Subject: [PATCH 09/22] MC-18847: Special Price & Regular Price Display Issues on Configurable Product --- .../Pricing/Render/FinalPriceBox.php | 4 ++-- .../Test/Unit/Pricing/Render/FinalPriceBoxTest.php | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php b/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php index 1ad92fc7a39e7..b48a987c23526 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php @@ -30,10 +30,10 @@ class FinalPriceBox extends \Magento\Catalog\Pricing\Render\FinalPriceBox * @param SaleableInterface $saleableItem * @param PriceInterface $price * @param RendererPool $rendererPool - * @param ConfigurableOptionsProviderInterface $configurableOptionsProvider - * @param array $data * @param SalableResolverInterface $salableResolver * @param MinimalPriceCalculatorInterface $minimalPriceCalculator + * @param ConfigurableOptionsProviderInterface $configurableOptionsProvider + * @param array $data */ public function __construct( Context $context, diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Render/FinalPriceBoxTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Render/FinalPriceBoxTest.php index 32a8add20a603..776986a761cf8 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Render/FinalPriceBoxTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Render/FinalPriceBoxTest.php @@ -110,10 +110,12 @@ public function testHasSpecialPrice( $priceInfoMock = $this->createMock(PriceInfoInterface::class); $priceInfoMock->expects($this->exactly(2)) ->method('getPrice') - ->willReturnMap([ - [RegularPrice::PRICE_CODE, $priceMockOne], - [FinalPrice::PRICE_CODE, $priceMockTwo], - ]); + ->willReturnMap( + [ + [RegularPrice::PRICE_CODE, $priceMockOne], + [FinalPrice::PRICE_CODE, $priceMockTwo], + ] + ); $productMock = $this->createMock(Product::class); $productMock->expects($this->exactly(2)) From 7ee9b1eac38ada29b58592db468ce29ce516c296 Mon Sep 17 00:00:00 2001 From: Dmytro Yushkin Date: Thu, 8 Aug 2019 14:25:20 -0500 Subject: [PATCH 10/22] MC-18701: API Attribute Option update creates same value multiple times --- .../Product/Attribute/OptionManagement.php | 11 -- .../Entity/Attribute/OptionManagement.php | 41 +++++- .../Entity/Attribute/OptionManagementTest.php | 133 +++++++++--------- app/code/Magento/Eav/i18n/en_US.csv | 1 + 4 files changed, 105 insertions(+), 81 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/OptionManagement.php b/app/code/Magento/Catalog/Model/Product/Attribute/OptionManagement.php index 8b638feafaafc..b797308c30fb0 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/OptionManagement.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/OptionManagement.php @@ -43,17 +43,6 @@ public function getItems($attributeCode) */ public function add($attributeCode, $option) { - /** @var \Magento\Eav\Api\Data\AttributeOptionInterface[] $currentOptions */ - $currentOptions = $this->getItems($attributeCode); - if (is_array($currentOptions)) { - array_walk($currentOptions, function (&$attributeOption) { - /** @var \Magento\Eav\Api\Data\AttributeOptionInterface $attributeOption */ - $attributeOption = $attributeOption->getLabel(); - }); - if (in_array($option->getLabel(), $currentOptions, true)) { - return false; - } - } return $this->eavOptionManagement->add( \Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE, $attributeCode, diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php b/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php index 3c3bc083fdf8f..070df3957dfdb 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php @@ -3,9 +3,11 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Eav\Model\Entity\Attribute; +use Magento\Eav\Api\Data\AttributeInterface as EavAttributeInterface; use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Exception\StateException; @@ -64,6 +66,15 @@ public function add($entityType, $attributeCode, $option) } } + if (!$this->isAdminStoreLabelUnique($attribute, (string) $options['value'][$optionId][0])) { + throw new InputException( + __( + 'Admin store attribute option label "%1" is already exists.', + $options['value'][$optionId][0] + ) + ); + } + if ($option->getIsDefault()) { $attribute->setDefault([$optionId]); } @@ -134,10 +145,10 @@ public function getItems($entityType, $attributeCode) /** * Validate option * - * @param \Magento\Eav\Api\Data\AttributeInterface $attribute + * @param EavAttributeInterface $attribute * @param int $optionId - * @throws NoSuchEntityException * @return void + *@throws NoSuchEntityException */ protected function validateOption($attribute, $optionId) { @@ -167,13 +178,13 @@ private function getOptionId(\Magento\Eav\Api\Data\AttributeOptionInterface $opt * Set option value * * @param \Magento\Eav\Api\Data\AttributeOptionInterface $option - * @param \Magento\Eav\Api\Data\AttributeInterface $attribute + * @param EavAttributeInterface $attribute * @param string $optionLabel * @return void */ private function setOptionValue( \Magento\Eav\Api\Data\AttributeOptionInterface $option, - \Magento\Eav\Api\Data\AttributeInterface $attribute, + EavAttributeInterface $attribute, string $optionLabel ) { $optionId = $attribute->getSource()->getOptionId($optionLabel); @@ -188,4 +199,26 @@ private function setOptionValue( } } } + + /** + * Checks if the incoming admin store attribute option label is unique. + * + * @param EavAttributeInterface $attribute + * @param string $adminStoreLabel + * @return bool + */ + private function isAdminStoreLabelUnique( + EavAttributeInterface $attribute, + string $adminStoreLabel + ) :bool { + $attribute->setStoreId(0); + + foreach ($attribute->getSource()->toOptionArray() as $existingAttributeOption) { + if ($existingAttributeOption['label'] === $adminStoreLabel) { + return false; + } + } + + return true; + } } diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php index b63a4dd2c9ae6..f23814e0de0c4 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php @@ -6,6 +6,12 @@ namespace Magento\Eav\Test\Unit\Model\Entity\Attribute; +use Magento\Eav\Api\Data\AttributeOptionInterface as EavAttributeOptionInterface; +use Magento\Eav\Api\Data\AttributeOptionLabelInterface as EavAttributeOptionLabelInterface; +use Magento\Eav\Model\Entity\Attribute\AbstractAttribute as EavAbstractAttribute; +use Magento\Eav\Model\Entity\Attribute\Source\Table as EavAttributeSource; +use PHPUnit\Framework\MockObject\MockObject as MockObject; + class OptionManagementTest extends \PHPUnit\Framework\TestCase { /** @@ -38,25 +44,9 @@ public function testAdd() { $entityType = 42; $attributeCode = 'atrCde'; - $optionMock = $this->getMockForAbstractClass( - \Magento\Eav\Api\Data\AttributeOptionInterface::class, - [], - '', - false, - false, - true, - ['getSourceLabels'] - ); - $attributeMock = $this->getMockForAbstractClass( - \Magento\Framework\Model\AbstractModel::class, - [], - '', - false, - false, - true, - ['usesSource', 'setDefault', 'setOption'] - ); - $labelMock = $this->createMock(\Magento\Eav\Api\Data\AttributeOptionLabelInterface::class); + $attributeMock = $this->getAttribute(); + $optionMock = $this->getAttributeOption(); + $labelMock = $this->getAttributeOptionLabel(); $option = ['value' => [ 'id_new_option' => [ @@ -92,15 +82,7 @@ public function testAddWithEmptyAttributeCode() { $entityType = 42; $attributeCode = ''; - $optionMock = $this->getMockForAbstractClass( - \Magento\Eav\Api\Data\AttributeOptionInterface::class, - [], - '', - false, - false, - true, - ['getSourceLabels'] - ); + $optionMock = $this->getAttributeOption(); $this->resourceModelMock->expects($this->never())->method('save'); $this->model->add($entityType, $attributeCode, $optionMock); } @@ -113,24 +95,8 @@ public function testAddWithWrongOptions() { $entityType = 42; $attributeCode = 'testAttribute'; - $optionMock = $this->getMockForAbstractClass( - \Magento\Eav\Api\Data\AttributeOptionInterface::class, - [], - '', - false, - false, - true, - ['getSourceLabels'] - ); - $attributeMock = $this->getMockForAbstractClass( - \Magento\Framework\Model\AbstractModel::class, - [], - '', - false, - false, - true, - ['usesSource', 'setDefault', 'setOption'] - ); + $attributeMock = $this->getAttribute(); + $optionMock = $this->getAttributeOption(); $this->attributeRepositoryMock->expects($this->once())->method('get')->with($entityType, $attributeCode) ->willReturn($attributeMock); $attributeMock->expects($this->once())->method('usesSource')->willReturn(false); @@ -146,25 +112,9 @@ public function testAddWithCannotSaveException() { $entityType = 42; $attributeCode = 'atrCde'; - $optionMock = $this->getMockForAbstractClass( - \Magento\Eav\Api\Data\AttributeOptionInterface::class, - [], - '', - false, - false, - true, - ['getSourceLabels'] - ); - $attributeMock = $this->getMockForAbstractClass( - \Magento\Framework\Model\AbstractModel::class, - [], - '', - false, - false, - true, - ['usesSource', 'setDefault', 'setOption'] - ); - $labelMock = $this->createMock(\Magento\Eav\Api\Data\AttributeOptionLabelInterface::class); + $optionMock = $this->getAttributeOption(); + $attributeMock = $this->getAttribute(); + $labelMock = $this->getAttributeOptionLabel(); $option = ['value' => [ 'id_new_option' => [ @@ -340,7 +290,7 @@ public function testGetItems() true, ['getOptions'] ); - $optionsMock = [$this->createMock(\Magento\Eav\Api\Data\AttributeOptionInterface::class)]; + $optionsMock = [$this->createMock(EavAttributeOptionInterface::class)]; $this->attributeRepositoryMock->expects($this->once())->method('get')->with($entityType, $attributeCode) ->willReturn($attributeMock); $attributeMock->expects($this->once())->method('getOptions')->willReturn($optionsMock); @@ -380,4 +330,55 @@ public function testGetItemsWithEmptyAttributeCode() $attributeCode = ''; $this->model->getItems($entityType, $attributeCode); } + + /** + * Returns attribute entity mock. + * + * @param array $attributeOptions attribute options for return + * @return MockObject|EavAbstractAttribute + */ + private function getAttribute(array $attributeOptions = []) + { + $attribute = $this->getMockBuilder(EavAbstractAttribute::class) + ->disableOriginalConstructor() + ->setMethods( + [ + 'usesSource', + 'setDefault', + 'setOption', + 'setStoreId', + 'getSource', + ] + ) + ->getMock(); + $source = $this->getMockBuilder(EavAttributeSource::class) + ->disableOriginalConstructor() + ->getMock(); + + $attribute->method('getSource')->willReturn($source); + $source->method('toOptionArray')->willReturn($attributeOptions); + + return $attribute; + } + + /** + * Return attribute option entity mock. + * + * @return MockObject|EavAttributeOptionInterface + */ + private function getAttributeOption() + { + return $this->getMockBuilder(EavAttributeOptionInterface::class) + ->setMethods(['getSourceLabels']) + ->getMockForAbstractClass(); + } + + /** + * @return MockObject|EavAttributeOptionLabelInterface + */ + private function getAttributeOptionLabel() + { + return $this->getMockBuilder(EavAttributeOptionLabelInterface::class) + ->getMockForAbstractClass(); + } } diff --git a/app/code/Magento/Eav/i18n/en_US.csv b/app/code/Magento/Eav/i18n/en_US.csv index 73f8b359d1c1b..fa4b026501d1b 100644 --- a/app/code/Magento/Eav/i18n/en_US.csv +++ b/app/code/Magento/Eav/i18n/en_US.csv @@ -143,3 +143,4 @@ hello,hello "The value of attribute not valid","The value of attribute not valid" "EAV types and attributes","EAV types and attributes" "Entity types declaration cache","Entity types declaration cache" +"Admin store attribute option label ""%1"" is already exists.","Admin store attribute option label ""%1"" is already exists." From fede04866f4a84e11b9e414941a3d029af866183 Mon Sep 17 00:00:00 2001 From: Dmytro Yushkin Date: Fri, 9 Aug 2019 10:02:59 -0500 Subject: [PATCH 11/22] MC-18701: API Attribute Option update creates same value multiple times --- .../Eav/Model/Entity/Attribute/OptionManagement.php | 13 +++++++++++-- .../Swatches/Model/SwatchAttributeOptionAddTest.php | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php b/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php index 070df3957dfdb..b93d2e1c16871 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php @@ -41,7 +41,16 @@ public function __construct( } /** - * @inheritdoc + * Add option to attribute. + * + * @param int $entityType + * @param string $attributeCode + * @param \Magento\Eav\Api\Data\AttributeOptionInterface $option + * @return string + * @throws InputException + * @throws NoSuchEntityException + * @throws StateException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function add($entityType, $attributeCode, $option) { @@ -148,7 +157,7 @@ public function getItems($entityType, $attributeCode) * @param EavAttributeInterface $attribute * @param int $optionId * @return void - *@throws NoSuchEntityException + * @throws NoSuchEntityException */ protected function validateOption($attribute, $optionId) { diff --git a/dev/tests/integration/testsuite/Magento/Swatches/Model/SwatchAttributeOptionAddTest.php b/dev/tests/integration/testsuite/Magento/Swatches/Model/SwatchAttributeOptionAddTest.php index 84ba587f5e784..b68c9b421851e 100644 --- a/dev/tests/integration/testsuite/Magento/Swatches/Model/SwatchAttributeOptionAddTest.php +++ b/dev/tests/integration/testsuite/Magento/Swatches/Model/SwatchAttributeOptionAddTest.php @@ -36,7 +36,7 @@ public function testSwatchOptionAdd() $attribute = $this->objectManager ->create(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class) ->load('color_swatch', 'attribute_code'); - $optionsPerAttribute = 3; + $optionsPerAttribute = 4; $data['options']['option'] = array_reduce( range(10, $optionsPerAttribute), From d3abf99436e0f8fbfd1077c2ce2469cb581a3a72 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko Date: Fri, 9 Aug 2019 12:06:37 -0500 Subject: [PATCH 12/22] MC-19060: [Magento Cloud] Incorrect SKU wishlist count --- .../Magento/Backend/view/adminhtml/templates/widget/grid.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml index 63cdae13490ac..b577904a15d4e 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml @@ -45,7 +45,7 @@ $numColumns = $block->getColumns() !== null ? count($block->getColumns()) : 0; getMainButtonsHtml() ? '
' . $block->getMainButtonsHtml() . '
' : '' ?> - getCollection()->getSize(); ?> + getCollection()->count(); ?>
getUiId('total-count') ?>> From 8ee02eee9a52d424e86413ff5ed0c16c2f98059c Mon Sep 17 00:00:00 2001 From: Dmytro Yushkin Date: Fri, 9 Aug 2019 13:49:14 -0500 Subject: [PATCH 13/22] MC-18701: API Attribute Option update creates same value multiple times --- .../Eav/Model/Entity/Attribute/OptionManagement.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php b/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php index b93d2e1c16871..0ea4c324fe5c9 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php @@ -75,7 +75,7 @@ public function add($entityType, $attributeCode, $option) } } - if (!$this->isAdminStoreLabelUnique($attribute, (string) $options['value'][$optionId][0])) { + if (!$this->isAttributeOptionLabelExists($attribute, (string) $options['value'][$optionId][0])) { throw new InputException( __( 'Admin store attribute option label "%1" is already exists.', @@ -210,17 +210,19 @@ private function setOptionValue( } /** - * Checks if the incoming admin store attribute option label is unique. + * Checks if the incoming attribute option label for admin store is already exists. * * @param EavAttributeInterface $attribute * @param string $adminStoreLabel + * @param int $storeId * @return bool */ - private function isAdminStoreLabelUnique( + private function isAttributeOptionLabelExists( EavAttributeInterface $attribute, - string $adminStoreLabel + string $adminStoreLabel, + int $storeId = 0 ) :bool { - $attribute->setStoreId(0); + $attribute->setStoreId($storeId); foreach ($attribute->getSource()->toOptionArray() as $existingAttributeOption) { if ($existingAttributeOption['label'] === $adminStoreLabel) { From b26be830634c1650fd9f3ab6b20380ed3fb902ff Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko Date: Fri, 9 Aug 2019 15:12:26 -0500 Subject: [PATCH 14/22] MC-19060: [Magento Cloud] Incorrect SKU wishlist count --- .../Magento/Backend/view/adminhtml/templates/widget/grid.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml index b577904a15d4e..d3b1caa2ac077 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml @@ -23,7 +23,7 @@ $numColumns = $block->getColumns() !== null ? count($block->getColumns()) : 0; canDisplayContainer()) : ?>
- getLayout()->getMessagesBlock()->getGroupedHtml() ?> + getLayout()->getMessagesBlock()->getGroupedHtml() ?>
From 39648951d0dfbf30912cb205698be25ac435ca97 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko Date: Mon, 12 Aug 2019 13:26:52 -0500 Subject: [PATCH 15/22] MC-19060: [Magento Cloud] Incorrect SKU wishlist count --- .../Backend/view/adminhtml/templates/widget/grid.phtml | 2 +- .../Wishlist/Model/ResourceModel/Item/Collection/Grid.php | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml index d3b1caa2ac077..b712bc6c95315 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml @@ -45,7 +45,7 @@ $numColumns = $block->getColumns() !== null ? count($block->getColumns()) : 0; getMainButtonsHtml() ? '
' . $block->getMainButtonsHtml() . '
' : '' ?> - getCollection()->count(); ?> + getCollection()->getSize(); ?>
getUiId('total-count') ?>> diff --git a/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php b/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php index 36e70e9bdb1af..8b932458a2db9 100644 --- a/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php +++ b/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php @@ -149,4 +149,12 @@ public function addFieldToFilter($field, $condition = null) } return parent::addFieldToFilter($field, $condition); } + + public function getSize() + { + $this->setVisibilityFilter(true); + $this->setInStockFilter(true); + + return parent::getSize(); + } } From c631a818d7c3598be9cbcc4bb394ad5aa4ff71cc Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko Date: Mon, 12 Aug 2019 15:06:24 -0500 Subject: [PATCH 16/22] MC-19060: [Magento Cloud] Incorrect SKU wishlist count --- .../Model/ResourceModel/Item/Collection/Grid.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php b/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php index 8b932458a2db9..a6102727d34a1 100644 --- a/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php +++ b/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php @@ -4,14 +4,13 @@ * See COPYING.txt for license details. */ -/** - * Wishlist item collection grouped by customer id - */ namespace Magento\Wishlist\Model\ResourceModel\Item\Collection; use Magento\Customer\Controller\RegistryConstants as RegistryConstants; /** + * Wishlist item collection grouped by customer id + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Grid extends \Magento\Wishlist\Model\ResourceModel\Item\Collection @@ -150,10 +149,13 @@ public function addFieldToFilter($field, $condition = null) return parent::addFieldToFilter($field, $condition); } + /** + * @inheritdoc + */ public function getSize() { - $this->setVisibilityFilter(true); - $this->setInStockFilter(true); + $this->setVisibilityFilter(); + $this->setInStockFilter(); return parent::getSize(); } From d7c25272fd044993f98521db23bd4c49bfd57f1d Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko Date: Mon, 12 Aug 2019 15:33:36 -0500 Subject: [PATCH 17/22] MC-19060: [Magento Cloud] Incorrect SKU wishlist count --- .../Model/ResourceModel/Item/Collection/Grid.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php b/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php index a6102727d34a1..a25b117165ad0 100644 --- a/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php +++ b/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php @@ -152,11 +152,11 @@ public function addFieldToFilter($field, $condition = null) /** * @inheritdoc */ - public function getSize() - { - $this->setVisibilityFilter(); - $this->setInStockFilter(); - - return parent::getSize(); - } +// public function getSize() +// { +// $this->setVisibilityFilter(); +// $this->setInStockFilter(); +// +// return parent::getSize(); +// } } From 412dc0e1e3911a17f26fb975ab96b4e8a2770c41 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko Date: Mon, 12 Aug 2019 16:34:00 -0500 Subject: [PATCH 18/22] MC-19060: [Magento Cloud] Incorrect SKU wishlist count --- .../Model/ResourceModel/Item/Collection/Grid.php | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php b/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php index a25b117165ad0..65c3c23c76713 100644 --- a/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php +++ b/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php @@ -95,6 +95,8 @@ public function __construct( protected function _initSelect() { parent::_initSelect(); + $this->setVisibilityFilter(); + $this->setInStockFilter(); $this->addCustomerIdFilter( $this->_registryManager->registry(RegistryConstants::CURRENT_CUSTOMER_ID) )->resetSortOrder()->addDaysInWishlist()->addStoreData(); @@ -148,15 +150,4 @@ public function addFieldToFilter($field, $condition = null) } return parent::addFieldToFilter($field, $condition); } - - /** - * @inheritdoc - */ -// public function getSize() -// { -// $this->setVisibilityFilter(); -// $this->setInStockFilter(); -// -// return parent::getSize(); -// } } From 250180b7ee67d75c888dedc4ca99012148f66945 Mon Sep 17 00:00:00 2001 From: Arnob Saha Date: Tue, 30 Jul 2019 15:46:01 -0500 Subject: [PATCH 19/22] MC-18519: Shipping price shows 0 when you return from multiple checkout to cart - Define type of the function arguments and short description of the class --- .../Multishipping/Block/Checkout/Overview.php | 128 ++++++++++++++---- .../Model/Cart/CartTotalRepositoryPlugin.php | 71 ++++++++++ .../CheckingWithMinicartActionGroup.xml | 24 ++++ ...eckingWithMultipleAddressesActionGroup.xml | 27 ++++ .../ActionGroup/PlaceOrderActionGroup.xml | 20 +++ .../ActionGroup/ReviewOrderActionGroup.xml | 39 ++++++ .../SelectBillingInfoActionGroup.xml | 16 +++ .../SelectShippingInfoActionGroup.xml | 33 +++++ .../Mftf/Page/MultishippingCheckoutPage.xml | 18 +++ .../Test/Mftf/Section/MinicartSection.xml | 18 +++ .../Mftf/Section/MultishippingSection.xml | 10 ++ .../Mftf/Section/PaymentMethodSection.xml | 14 ++ .../Test/Mftf/Section/ReviewOrderSection.xml | 27 ++++ .../Mftf/Section/ShippingMethodSection.xml | 18 +++ ...toreFrontCheckingWithMultishipmentTest.xml | 63 +++++++++ ...oreFrontCheckingWithSingleShipmentTest.xml | 63 +++++++++ ...toreFrontMinicartWithMultishipmentTest.xml | 65 +++++++++ .../Cart/CartTotalRepositoryPluginTest.php | 81 +++++++++++ app/code/Magento/Multishipping/etc/di.xml | 12 ++ 19 files changed, 723 insertions(+), 24 deletions(-) create mode 100644 app/code/Magento/Multishipping/Model/Cart/CartTotalRepositoryPlugin.php create mode 100644 app/code/Magento/Multishipping/Test/Mftf/ActionGroup/CheckingWithMinicartActionGroup.xml create mode 100644 app/code/Magento/Multishipping/Test/Mftf/ActionGroup/CheckingWithMultipleAddressesActionGroup.xml create mode 100644 app/code/Magento/Multishipping/Test/Mftf/ActionGroup/PlaceOrderActionGroup.xml create mode 100644 app/code/Magento/Multishipping/Test/Mftf/ActionGroup/ReviewOrderActionGroup.xml create mode 100644 app/code/Magento/Multishipping/Test/Mftf/ActionGroup/SelectBillingInfoActionGroup.xml create mode 100644 app/code/Magento/Multishipping/Test/Mftf/ActionGroup/SelectShippingInfoActionGroup.xml create mode 100644 app/code/Magento/Multishipping/Test/Mftf/Page/MultishippingCheckoutPage.xml create mode 100644 app/code/Magento/Multishipping/Test/Mftf/Section/MinicartSection.xml create mode 100644 app/code/Magento/Multishipping/Test/Mftf/Section/PaymentMethodSection.xml create mode 100644 app/code/Magento/Multishipping/Test/Mftf/Section/ReviewOrderSection.xml create mode 100644 app/code/Magento/Multishipping/Test/Mftf/Section/ShippingMethodSection.xml create mode 100644 app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithMultishipmentTest.xml create mode 100644 app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithSingleShipmentTest.xml create mode 100644 app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontMinicartWithMultishipmentTest.xml create mode 100644 app/code/Magento/Multishipping/Test/Unit/Model/Cart/CartTotalRepositoryPluginTest.php create mode 100644 app/code/Magento/Multishipping/etc/di.xml diff --git a/app/code/Magento/Multishipping/Block/Checkout/Overview.php b/app/code/Magento/Multishipping/Block/Checkout/Overview.php index 5963e62e948f9..d17da90c58bef 100644 --- a/app/code/Magento/Multishipping/Block/Checkout/Overview.php +++ b/app/code/Magento/Multishipping/Block/Checkout/Overview.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Multishipping\Block\Checkout; use Magento\Framework\Pricing\PriceCurrencyInterface; @@ -12,8 +13,8 @@ * Multishipping checkout overview information * * @api - * @author Magento Core Team - * @since 100.0.2 + * @author Magento Core Team + * @since 100.0.2 */ class Overview extends \Magento\Sales\Block\Items\AbstractItems { @@ -48,13 +49,13 @@ class Overview extends \Magento\Sales\Block\Items\AbstractItems protected $totalsReader; /** - * @param \Magento\Framework\View\Element\Template\Context $context + * @param \Magento\Framework\View\Element\Template\Context $context * @param \Magento\Multishipping\Model\Checkout\Type\Multishipping $multishipping - * @param \Magento\Tax\Helper\Data $taxHelper - * @param PriceCurrencyInterface $priceCurrency - * @param \Magento\Quote\Model\Quote\TotalsCollector $totalsCollector - * @param \Magento\Quote\Model\Quote\TotalsReader $totalsReader - * @param array $data + * @param \Magento\Tax\Helper\Data $taxHelper + * @param PriceCurrencyInterface $priceCurrency + * @param \Magento\Quote\Model\Quote\TotalsCollector $totalsCollector + * @param \Magento\Quote\Model\Quote\TotalsReader $totalsReader + * @param array $data */ public function __construct( \Magento\Framework\View\Element\Template\Context $context, @@ -74,6 +75,37 @@ public function __construct( $this->totalsReader = $totalsReader; } + /** + * Overwrite the total value of shipping amount for viewing purpose + * + * @param array $totals + * @return mixed + * @throws \Exception + */ + private function getMultishippingTotals($totals) + { + if (isset($totals['shipping']) && !empty($totals['shipping'])) { + $total = $totals['shipping']; + $shippingMethod = $total->getAddress()->getShippingMethod(); + if (isset($shippingMethod) && !empty($shippingMethod)) { + $shippingRate = $total->getAddress()->getShippingRateByCode($shippingMethod); + $shippingPrice = $shippingRate->getPrice(); + } else { + $shippingPrice = $total->getAddress()->getShippingAmount(); + } + /** + * @var \Magento\Store\Api\Data\StoreInterface + */ + $store = $this->getQuote()->getStore(); + $amountPrice = $store->getBaseCurrency() + ->convert($shippingPrice, $store->getCurrentCurrencyCode()); + $total->setBaseShippingAmount($shippingPrice); + $total->setShippingAmount($amountPrice); + $total->setValue($amountPrice); + } + return $totals; + } + /** * Initialize default item renderer * @@ -98,6 +130,8 @@ public function getCheckout() } /** + * Get billing address + * * @return Address */ public function getBillingAddress() @@ -106,6 +140,8 @@ public function getBillingAddress() } /** + * Get payment info + * * @return string */ public function getPaymentHtml() @@ -124,6 +160,8 @@ public function getPayment() } /** + * Get shipping addresses + * * @return array */ public function getShippingAddresses() @@ -132,6 +170,8 @@ public function getShippingAddresses() } /** + * Get number of shipping addresses + * * @return int|mixed */ public function getShippingAddressCount() @@ -145,8 +185,10 @@ public function getShippingAddressCount() } /** - * @param Address $address - * @return bool + * Get shipping address rate + * + * @param Address $address + * @return bool * @SuppressWarnings(PHPMD.BooleanGetMethodName) */ public function getShippingAddressRate($address) @@ -159,27 +201,36 @@ public function getShippingAddressRate($address) } /** - * @param Address $address + * Get shipping price including tax + * + * @param Address $address * @return mixed */ public function getShippingPriceInclTax($address) { - $exclTax = $address->getShippingAmount(); + $rate = $address->getShippingRateByCode($address->getShippingMethod()); + $exclTax = $rate->getPrice(); $taxAmount = $address->getShippingTaxAmount(); return $this->formatPrice($exclTax + $taxAmount); } /** - * @param Address $address + * Get shipping price excluding tax + * + * @param Address $address * @return mixed */ public function getShippingPriceExclTax($address) { - return $this->formatPrice($address->getShippingAmount()); + $rate = $address->getShippingRateByCode($address->getShippingMethod()); + $shippingAmount = $rate->getPrice(); + return $this->formatPrice($shippingAmount); } /** - * @param float $price + * Format price + * + * @param float $price * @return mixed * * @codeCoverageIgnore @@ -195,7 +246,9 @@ public function formatPrice($price) } /** - * @param Address $address + * Get shipping address items + * + * @param Address $address * @return array */ public function getShippingAddressItems($address): array @@ -204,7 +257,9 @@ public function getShippingAddressItems($address): array } /** - * @param Address $address + * Get shipping address totals + * + * @param Address $address * @return mixed */ public function getShippingAddressTotals($address) @@ -223,6 +278,8 @@ public function getShippingAddressTotals($address) } /** + * Get total price + * * @return float */ public function getTotal() @@ -231,6 +288,8 @@ public function getTotal() } /** + * Get the Edit addresses URL + * * @return string */ public function getAddressesEditUrl() @@ -239,7 +298,9 @@ public function getAddressesEditUrl() } /** - * @param Address $address + * Get the Edit shipping address URL + * + * @param Address $address * @return string */ public function getEditShippingAddressUrl($address) @@ -248,7 +309,9 @@ public function getEditShippingAddressUrl($address) } /** - * @param Address $address + * Get the Edit billing address URL + * + * @param Address $address * @return string */ public function getEditBillingAddressUrl($address) @@ -257,6 +320,8 @@ public function getEditBillingAddressUrl($address) } /** + * Get the Edit shipping URL + * * @return string */ public function getEditShippingUrl() @@ -265,6 +330,8 @@ public function getEditShippingUrl() } /** + * Get Post ACtion URL + * * @return string */ public function getPostActionUrl() @@ -273,6 +340,8 @@ public function getPostActionUrl() } /** + * Get the Edit billing URL + * * @return string */ public function getEditBillingUrl() @@ -281,6 +350,8 @@ public function getEditBillingUrl() } /** + * Get back button URL + * * @return string */ public function getBackUrl() @@ -319,9 +390,11 @@ public function getQuote() } /** + * Get billin address totals + * + * @return mixed * @deprecated * typo in method name, see getBillingAddressTotals() - * @return mixed */ public function getBillinAddressTotals() { @@ -329,6 +402,8 @@ public function getBillinAddressTotals() } /** + * Get billing address totals + * * @return mixed */ public function getBillingAddressTotals() @@ -338,12 +413,17 @@ public function getBillingAddressTotals() } /** - * @param mixed $totals - * @param null $colspan + * Render total block + * + * @param mixed $totals + * @param null $colspan * @return string */ public function renderTotals($totals, $colspan = null) { + //check if the shipment is multi shipment + $totals = $this->getMultishippingTotals($totals); + if ($colspan === null) { $colspan = 3; } @@ -368,7 +448,7 @@ public function renderTotals($totals, $colspan = null) /** * Return row-level item html * - * @param \Magento\Framework\DataObject $item + * @param \Magento\Framework\DataObject $item * @return string */ public function getRowItemHtml(\Magento\Framework\DataObject $item) @@ -382,7 +462,7 @@ public function getRowItemHtml(\Magento\Framework\DataObject $item) /** * Retrieve renderer block for row-level item output * - * @param string $type + * @param string $type * @return \Magento\Framework\View\Element\AbstractBlock */ protected function _getRowItemRenderer($type) diff --git a/app/code/Magento/Multishipping/Model/Cart/CartTotalRepositoryPlugin.php b/app/code/Magento/Multishipping/Model/Cart/CartTotalRepositoryPlugin.php new file mode 100644 index 0000000000000..732bdee314f7c --- /dev/null +++ b/app/code/Magento/Multishipping/Model/Cart/CartTotalRepositoryPlugin.php @@ -0,0 +1,71 @@ +quoteRepository = $quoteRepository; + } + + /** + * Overwrite the CartTotalRepository quoteTotal and update the shipping price + * + * @param CartTotalRepository $subject + * @param Totals $quoteTotals + * @param String $cartId + * @return Totals + * @throws \Magento\Framework\Exception\NoSuchEntityException + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterGet( + CartTotalRepository $subject, + Totals $quoteTotals, + String $cartId + ) { + $quote = $this->quoteRepository->getActive($cartId); + if ($quote->getIsMultiShipping()) { + $shippingMethod = $quote->getShippingAddress()->getShippingMethod(); + if (isset($shippingMethod) && !empty($shippingMethod)) { + $shippingRate = $quote->getShippingAddress()->getShippingRateByCode($shippingMethod); + $shippingPrice = $shippingRate->getPrice(); + } else { + $shippingPrice = $quote->getShippingAddress()->getShippingAmount(); + } + /** + * @var \Magento\Store\Api\Data\StoreInterface + */ + $store = $quote->getStore(); + $amountPrice = $store->getBaseCurrency() + ->convert($shippingPrice, $store->getCurrentCurrencyCode()); + $quoteTotals->setBaseShippingAmount($shippingPrice); + $quoteTotals->setShippingAmount($amountPrice); + } + return $quoteTotals; + } +} diff --git a/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/CheckingWithMinicartActionGroup.xml b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/CheckingWithMinicartActionGroup.xml new file mode 100644 index 0000000000000..f648c1026b539 --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/CheckingWithMinicartActionGroup.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + $shippingMethodSubtotalPrice + $shippingMethodRadioText + + + + diff --git a/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/CheckingWithMultipleAddressesActionGroup.xml b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/CheckingWithMultipleAddressesActionGroup.xml new file mode 100644 index 0000000000000..333c2aec6c28e --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/CheckingWithMultipleAddressesActionGroup.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/PlaceOrderActionGroup.xml b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/PlaceOrderActionGroup.xml new file mode 100644 index 0000000000000..efb860e314780 --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/PlaceOrderActionGroup.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + diff --git a/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/ReviewOrderActionGroup.xml b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/ReviewOrderActionGroup.xml new file mode 100644 index 0000000000000..af7d897910ca3 --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/ReviewOrderActionGroup.xml @@ -0,0 +1,39 @@ + + + + + + + + + + $shippingMethodSubtotalPrice + $shippingMethodBasePrice + + + + + + + + + $firstShippingMethodSubtotalPrice + $firstShippingMethodBasePrice + + + + + + $secondShippingMethodSubtotalPrice + $secondShippingMethodBasePrice + + + + + diff --git a/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/SelectBillingInfoActionGroup.xml b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/SelectBillingInfoActionGroup.xml new file mode 100644 index 0000000000000..3f7578953df70 --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/SelectBillingInfoActionGroup.xml @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/SelectShippingInfoActionGroup.xml b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/SelectShippingInfoActionGroup.xml new file mode 100644 index 0000000000000..af0b2467862ba --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/SelectShippingInfoActionGroup.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Multishipping/Test/Mftf/Page/MultishippingCheckoutPage.xml b/app/code/Magento/Multishipping/Test/Mftf/Page/MultishippingCheckoutPage.xml new file mode 100644 index 0000000000000..001002e98271c --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/Page/MultishippingCheckoutPage.xml @@ -0,0 +1,18 @@ + + + + + +
+
+
+
+
+ + diff --git a/app/code/Magento/Multishipping/Test/Mftf/Section/MinicartSection.xml b/app/code/Magento/Multishipping/Test/Mftf/Section/MinicartSection.xml new file mode 100644 index 0000000000000..1a31911bd185e --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/Section/MinicartSection.xml @@ -0,0 +1,18 @@ + + + + +
+ + + + +
+
+ diff --git a/app/code/Magento/Multishipping/Test/Mftf/Section/MultishippingSection.xml b/app/code/Magento/Multishipping/Test/Mftf/Section/MultishippingSection.xml index e7d57af1172c6..45fafc3105c38 100644 --- a/app/code/Magento/Multishipping/Test/Mftf/Section/MultishippingSection.xml +++ b/app/code/Magento/Multishipping/Test/Mftf/Section/MultishippingSection.xml @@ -8,7 +8,17 @@ +
+ + + + +
+ + + +
diff --git a/app/code/Magento/Multishipping/Test/Mftf/Section/PaymentMethodSection.xml b/app/code/Magento/Multishipping/Test/Mftf/Section/PaymentMethodSection.xml new file mode 100644 index 0000000000000..4e7f4a497ad4d --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/Section/PaymentMethodSection.xml @@ -0,0 +1,14 @@ + + + + +
+ +
+
diff --git a/app/code/Magento/Multishipping/Test/Mftf/Section/ReviewOrderSection.xml b/app/code/Magento/Multishipping/Test/Mftf/Section/ReviewOrderSection.xml new file mode 100644 index 0000000000000..e13f28929dcc8 --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/Section/ReviewOrderSection.xml @@ -0,0 +1,27 @@ + + + + +
+ + + + + + + + + + + + + +
+
+ diff --git a/app/code/Magento/Multishipping/Test/Mftf/Section/ShippingMethodSection.xml b/app/code/Magento/Multishipping/Test/Mftf/Section/ShippingMethodSection.xml new file mode 100644 index 0000000000000..6a2290bcf1a43 --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/Section/ShippingMethodSection.xml @@ -0,0 +1,18 @@ + + + + +
+ + + + +
+
+ diff --git a/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithMultishipmentTest.xml b/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithMultishipmentTest.xml new file mode 100644 index 0000000000000..271f6e707cd69 --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithMultishipmentTest.xml @@ -0,0 +1,63 @@ + + + + + + + + + + <description value="Shipping price shows 0 when you return from multiple checkout to cart"/> + <severity value="MAJOR"/> + <testCaseId value="MC-18519"/> + <group value="Multishipment"/> + </annotations> + + <before> + <createData stepKey="category" entity="SimpleSubCategory"/> + <createData stepKey="product1" entity="SimpleProduct"> + <requiredEntity createDataKey="category"/> + </createData> + <createData stepKey="product2" entity="SimpleProduct"> + <requiredEntity createDataKey="category"/> + </createData> + <createData entity="Simple_US_Customer_Two_Addresses" stepKey="customer"/> + <createData entity="FreeShippinMethodConfig" stepKey="enableFreeShipping"/> + <createData entity="FlatRateShippingMethodConfig" stepKey="enableFlatRateShipping"/> + <magentoCLI command="config:set {{EnableCheckMoneyOrderPaymentMethod.path}} {{EnableCheckMoneyOrderPaymentMethod.value}}" stepKey="enableCheckMoneyOrderPaymentMethod"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginToStorefrontAccount"> + <argument name="Customer" value="$$customer$$"/> + </actionGroup> + </before> + + <amOnPage url="$$product1.name$$.html" stepKey="goToProduct1"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProduct1"> + <argument name="productName" value="$$product1.name$$"/> + </actionGroup> + <amOnPage url="$$product2.name$$.html" stepKey="goToProduct2"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProduct2"> + <argument name="productName" value="$$product2.name$$"/> + </actionGroup> + <actionGroup ref="StorefrontOpenCartFromMinicartActionGroup" stepKey="openCart"/> + <actionGroup ref="CheckingWithMultipleAddressesActionGroup" stepKey="checkoutWithMultipleAddresses"/> + <actionGroup ref="SelectMultiShippingInfoActionGroup" stepKey="checkoutWithMultipleShipping"/> + <actionGroup ref="SelectBillingInfoActionGroup" stepKey="checkoutWithPaymentMethod"/> + <actionGroup ref="ReviewOrderForMultiShipmentActionGroup" stepKey="reviewOrderForMultiShipment"/> + <actionGroup ref="PlaceOrderActionGroup" stepKey="placeOrder"/> + + <after> + <deleteData stepKey="deleteCategory" createDataKey="category"/> + <deleteData stepKey="deleteProduct1" createDataKey="product1"/> + <deleteData stepKey="deleteProduct2" createDataKey="product2"/> + <deleteData stepKey="deleteCustomer" createDataKey="customer"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + </test> +</tests> diff --git a/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithSingleShipmentTest.xml b/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithSingleShipmentTest.xml new file mode 100644 index 0000000000000..f425a44130ecb --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithSingleShipmentTest.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StoreFrontCheckingWithSingleShipmentTest"> + <annotations> + <features value="Multishipment"/> + <stories value="Checking multi shipment with single shipment adresses on front end order page"/> + <title value="Checking multi shipment with single shipment adresses on front end order page"/> + <description value="Shipping price shows 0 when you return from multiple checkout to cart"/> + <severity value="MAJOR"/> + <testCaseId value="MC-18519"/> + <group value="Multishipment"/> + </annotations> + + <before> + <createData stepKey="category" entity="SimpleSubCategory"/> + <createData stepKey="product1" entity="SimpleProduct"> + <requiredEntity createDataKey="category"/> + </createData> + <createData stepKey="product2" entity="SimpleProduct"> + <requiredEntity createDataKey="category"/> + </createData> + <createData entity="Simple_US_Customer_Two_Addresses" stepKey="customer"/> + <createData entity="FreeShippinMethodConfig" stepKey="enableFreeShipping"/> + <createData entity="FlatRateShippingMethodConfig" stepKey="enableFlatRateShipping"/> + <magentoCLI command="config:set {{EnableCheckMoneyOrderPaymentMethod.path}} {{EnableCheckMoneyOrderPaymentMethod.value}}" stepKey="enableCheckMoneyOrderPaymentMethod"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginToStorefrontAccount"> + <argument name="Customer" value="$$customer$$"/> + </actionGroup> + </before> + + <amOnPage url="$$product1.name$$.html" stepKey="goToProduct1"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProduct1"> + <argument name="productName" value="$$product1.name$$"/> + </actionGroup> + <amOnPage url="$$product2.name$$.html" stepKey="goToProduct2"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProduct2"> + <argument name="productName" value="$$product2.name$$"/> + </actionGroup> + <actionGroup ref="StorefrontOpenCartFromMinicartActionGroup" stepKey="openCart"/> + <actionGroup ref="CheckingWithSingleAddressActionGroup" stepKey="checkoutWithSingleAddresses"/> + <actionGroup ref="SelectSingleShippingInfoActionGroup" stepKey="checkoutWithSingleShipping"/> + <actionGroup ref="SelectBillingInfoActionGroup" stepKey="checkoutWithPaymentMethod"/> + <actionGroup ref="ReviewOrderForSingleShipmentActionGroup" stepKey="reviewOrderForSingleShipment"/> + <actionGroup ref="PlaceOrderActionGroup" stepKey="placeOrder"/> + + <after> + <deleteData stepKey="deleteCategory" createDataKey="category"/> + <deleteData stepKey="deleteProduct1" createDataKey="product1"/> + <deleteData stepKey="deleteProduct2" createDataKey="product2"/> + <deleteData stepKey="deleteCustomer" createDataKey="customer"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + </test> +</tests> diff --git a/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontMinicartWithMultishipmentTest.xml b/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontMinicartWithMultishipmentTest.xml new file mode 100644 index 0000000000000..8d5a58acc7e18 --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontMinicartWithMultishipmentTest.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StoreFrontMinicartWithMultishipmentTest"> + <annotations> + <features value="Multishipment"/> + <stories value="Checking multi shipment with multiple shipment at minicart from checkout"/> + <title value="Checking multi shipment with multiple shipment at minicart from checkout"/> + <description value="Shipping price shows 0 when you return from multiple checkout to cart"/> + <severity value="MAJOR"/> + <testCaseId value="MC-18519"/> + <group value="Multishipment"/> + </annotations> + + <before> + <createData stepKey="category" entity="SimpleSubCategory"/> + <createData stepKey="product1" entity="SimpleProduct"> + <requiredEntity createDataKey="category"/> + </createData> + <createData stepKey="product2" entity="SimpleProduct"> + <requiredEntity createDataKey="category"/> + </createData> + <createData entity="Simple_US_Customer_Two_Addresses" stepKey="customer"/> + <createData entity="FreeShippinMethodConfig" stepKey="enableFreeShipping"/> + <createData entity="FlatRateShippingMethodConfig" stepKey="enableFlatRateShipping"/> + <magentoCLI command="config:set {{EnableCheckMoneyOrderPaymentMethod.path}} {{EnableCheckMoneyOrderPaymentMethod.value}}" stepKey="enableCheckMoneyOrderPaymentMethod"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginToStorefrontAccount"> + <argument name="Customer" value="$$customer$$"/> + </actionGroup> + </before> + + <amOnPage url="$$product1.name$$.html" stepKey="goToProduct1"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProduct1"> + <argument name="productName" value="$$product1.name$$"/> + </actionGroup> + <amOnPage url="$$product2.name$$.html" stepKey="goToProduct2"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProduct2"> + <argument name="productName" value="$$product2.name$$"/> + </actionGroup> + <actionGroup ref="StorefrontOpenCartFromMinicartActionGroup" stepKey="openCart"/> + <actionGroup ref="CheckingWithMultipleAddressesActionGroup" stepKey="checkoutWithMultipleAddresses"/> + <actionGroup ref="SelectMultiShippingInfoActionGroup" stepKey="checkoutWithMultipleShipping"/> + <actionGroup ref="SelectBillingInfoActionGroup" stepKey="checkoutWithPaymentMethod"/> + <actionGroup ref="ReviewOrderForMultiShipmentActionGroup" stepKey="reviewOrderForMultiShipment"/> + <amOnPage url="/checkout/cart/index/" stepKey="amOnCheckoutCartIndexPage"/> + <actionGroup ref="StorefrontOpenCartFromMinicartActionGroup" stepKey="openCartAgain"/> + <actionGroup ref="CheckingWithMinicartActionGroup" stepKey="checkoutWithMinicart"/> + + <after> + <deleteData stepKey="deleteCategory" createDataKey="category"/> + <deleteData stepKey="deleteProduct1" createDataKey="product1"/> + <deleteData stepKey="deleteProduct2" createDataKey="product2"/> + <deleteData stepKey="deleteCustomer" createDataKey="customer"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + </test> +</tests> diff --git a/app/code/Magento/Multishipping/Test/Unit/Model/Cart/CartTotalRepositoryPluginTest.php b/app/code/Magento/Multishipping/Test/Unit/Model/Cart/CartTotalRepositoryPluginTest.php new file mode 100644 index 0000000000000..73b0b9ef3ca7a --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Unit/Model/Cart/CartTotalRepositoryPluginTest.php @@ -0,0 +1,81 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Multishipping\Test\Unit\Model\Cart; + +class CartTotalRepositoryPluginTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Multishipping\Model\Cart\CartTotalRepositoryPlugin + */ + private $modelRepository; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $quoteRepositoryMock; + + protected function setUp() + { + $this->quoteRepositoryMock = $this->createMock(\Magento\Quote\Api\CartRepositoryInterface::class); + $this->modelRepository = new \Magento\Multishipping\Model\Cart\CartTotalRepositoryPlugin( + $this->quoteRepositoryMock + ); + } + + /** + * Test quotTotal from cartRepository after get($cartId) function is called + */ + public function testAfterGet() + { + $cartId = "10"; + $shippingMethod = 'flatrate_flatrate'; + $shippingPrice = '10.00'; + $quoteMock = $this->createPartialMock( + \Magento\Quote\Model\Cart\Totals::class, + [ + 'getStore', + 'getShippingAddress', + 'getIsMultiShipping' + ] + ); + $this->quoteRepositoryMock->expects($this->once())->method('getActive')->with($cartId)->willReturn($quoteMock); + $quoteMock->expects($this->once())->method('getIsMultiShipping')->willReturn(true); + $shippingAddressMock = $this->createPartialMock( + \Magento\Quote\Model\Quote\Address::class, + [ + 'getShippingMethod', + 'getShippingRateByCode', + 'getShippingAmount' + ] + ); + $quoteMock->expects($this->any())->method('getShippingAddress')->willReturn($shippingAddressMock); + + $shippingAddressMock->expects($this->once())->method('getShippingMethod')->willReturn($shippingMethod); + $shippingAddressMock->expects($this->any())->method('getShippingAmount')->willReturn($shippingPrice); + $shippingRateMock = $this->createPartialMock( + \Magento\Quote\Model\Quote\Address\Rate::class, + [ + 'getPrice' + ] + ); + $shippingAddressMock->expects($this->once())->method('getShippingRateByCode')->willReturn($shippingRateMock); + + $shippingRateMock->expects($this->once())->method('getPrice')->willReturn($shippingPrice); + + $storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class) + ->disableOriginalConstructor() + ->getMock(); + $quoteMock->expects($this->any())->method('getStore')->willReturn($storeMock); + $storeMock->expects($this->any())->method('getBaseCurrency')->willReturnSelf(); + + $this->modelRepository->afterGet( + $this->createMock(\Magento\Quote\Model\Cart\CartTotalRepository::class), + $quoteMock, + $cartId + ); + } +} diff --git a/app/code/Magento/Multishipping/etc/di.xml b/app/code/Magento/Multishipping/etc/di.xml new file mode 100644 index 0000000000000..3bccf0b74bcd8 --- /dev/null +++ b/app/code/Magento/Multishipping/etc/di.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\Quote\Model\Cart\CartTotalRepository"> + <plugin name="multishipping_shipping_addresses" type="Magento\Multishipping\Model\Cart\CartTotalRepositoryPlugin" /> + </type> +</config> From ac14a2ddf3abc8caf3500b6c9a3660a58108499f Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Tue, 13 Aug 2019 11:18:07 -0500 Subject: [PATCH 20/22] MC-19060: [Magento Cloud] Incorrect SKU wishlist count --- .../Model/ResourceModel/Item/Collection/Grid.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php b/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php index 65c3c23c76713..1e65375e26f8b 100644 --- a/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php +++ b/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php @@ -95,11 +95,14 @@ public function __construct( protected function _initSelect() { parent::_initSelect(); - $this->setVisibilityFilter(); - $this->setInStockFilter(); $this->addCustomerIdFilter( $this->_registryManager->registry(RegistryConstants::CURRENT_CUSTOMER_ID) - )->resetSortOrder()->addDaysInWishlist()->addStoreData(); + ) + ->resetSortOrder() + ->addDaysInWishlist() + ->addStoreData() + ->setVisibilityFilter() + ->setInStockFilter(); return $this; } From 6e4326b6dfce4e66ff6cb84c66646595da01baaa Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Tue, 13 Aug 2019 16:36:19 -0500 Subject: [PATCH 21/22] MC-19060: [Magento Cloud] Incorrect SKU wishlist count --- .../ResourceModel/Item/Collection/Grid.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php b/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php index 1e65375e26f8b..45104ec843470 100644 --- a/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php +++ b/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php @@ -126,6 +126,18 @@ public function setOrder($field, $direction = self::SORT_ORDER_DESC) } } + /** + * Add quantity to filter + * + * @param $field + * @param $condition + * @return \Magento\Wishlist\Model\ResourceModel\Item\Collection + */ + private function addQtyFilter($field, $condition) + { + return parent::addFieldToFilter('main_table.' . $field, $condition); + } + /** * Add field filter to collection * @@ -150,6 +162,11 @@ public function addFieldToFilter($field, $condition = null) if (!isset($condition['datetime'])) { return $this->addDaysFilter($condition); } + break; + case 'qty': + if (isset($condition['from']) || isset($condition['to'])) { + return $this->addQtyFilter($field, $condition); + } } return parent::addFieldToFilter($field, $condition); } From a3d4ad6757d4409b07853d1451a2ab95b29258d2 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Tue, 13 Aug 2019 19:32:11 -0500 Subject: [PATCH 22/22] MC-19060: [Magento Cloud] Incorrect SKU wishlist count --- .../Wishlist/Model/ResourceModel/Item/Collection/Grid.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php b/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php index 45104ec843470..fb6b647811abb 100644 --- a/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php +++ b/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php @@ -129,11 +129,11 @@ public function setOrder($field, $direction = self::SORT_ORDER_DESC) /** * Add quantity to filter * - * @param $field - * @param $condition + * @param string $field + * @param array $condition * @return \Magento\Wishlist\Model\ResourceModel\Item\Collection */ - private function addQtyFilter($field, $condition) + private function addQtyFilter(string $field, array $condition) { return parent::addFieldToFilter('main_table.' . $field, $condition); }