diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php index 4375092591d194962b108e2ba0351b1a03fa7e5c..40516e55e930c4021699dad26b43a4e83fe26a80 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php @@ -281,7 +281,7 @@ class FlatTableBuilder if (!empty($columnValueNames)) { $select->joinLeft( $temporaryValueTableName, - sprintf('e.%1$s = %2$s.%1$s', $linkField, $temporaryTableName), + sprintf('e.%1$s = %2$s.%1$s', $linkField, $temporaryValueTableName), $columnValueNames ); $allColumns = array_merge($allColumns, $columnValueNames); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/FlatTableBuilderTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/FlatTableBuilderTest.php index 7e5a8305467e8e979a9ccc79d39d7ffdb70bb339..d90261f068f5023d8987bab6d64585032385c8ee 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/FlatTableBuilderTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/FlatTableBuilderTest.php @@ -107,44 +107,42 @@ class FlatTableBuilderTest extends \PHPUnit_Framework_TestCase public function testBuild() { - list($storeId, $changedIds, $valueFieldSuffix, $tableDropSuffix, $fillTmpTables) = [1, [], '', '', true]; + $storeId = 1; + $changedIds = []; + $valueFieldSuffix = '_value'; + $tableDropSuffix = ''; + $fillTmpTables = true; $tableName = 'catalog_product_entity'; $attributeTable = 'catalog_product_entity_int'; $temporaryTableName = 'catalog_product_entity_int_tmp_indexer'; - $temporaryValueTableName = 'catalog_product_entity_int_tmp_indexer'; + $temporaryValueTableName = 'catalog_product_entity_int_tmp_indexer_value'; $linkField = 'entity_id'; $statusId = 22; + $eavCustomField = 'space_weight'; + $eavCustomValueField = $eavCustomField . $valueFieldSuffix; $this->flatIndexerMock->expects($this->once())->method('getAttributes')->willReturn([]); $this->flatIndexerMock->expects($this->exactly(3))->method('getFlatColumns') - ->willReturnOnConsecutiveCalls( - [], - [$linkField => []], - [$linkField => []] - ); + ->willReturnOnConsecutiveCalls([], [$eavCustomValueField => []], [$eavCustomValueField => []]); $this->flatIndexerMock->expects($this->once())->method('getFlatIndexes')->willReturn([]); $statusAttributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute::class) ->disableOriginalConstructor() ->getMock(); + $eavCustomAttributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute::class) + ->disableOriginalConstructor() + ->getMock(); $this->flatIndexerMock->expects($this->once())->method('getTablesStructure') ->willReturn( [ - 'catalog_product_entity' => [ - $linkField => $statusAttributeMock - ], + 'catalog_product_entity' => [$linkField => $statusAttributeMock], 'catalog_product_entity_int' => [ - $linkField => $statusAttributeMock + $linkField => $statusAttributeMock, + $eavCustomField => $eavCustomAttributeMock ] ] ); $this->flatIndexerMock->expects($this->atLeastOnce())->method('getTable') - ->withConsecutive( - [$tableName], - ['catalog_product_website'] - ) - ->willReturnOnConsecutiveCalls( - $tableName, - 'catalog_product_website' - ); + ->withConsecutive([$tableName], ['catalog_product_website']) + ->willReturnOnConsecutiveCalls($tableName, 'catalog_product_website'); $this->flatIndexerMock->expects($this->once())->method('getAttribute') ->with('status') ->willReturn($statusAttributeMock); @@ -155,6 +153,9 @@ class FlatTableBuilderTest extends \PHPUnit_Framework_TestCase $statusAttributeMock->expects($this->atLeastOnce())->method('getBackend')->willReturn( $backendMock ); + $eavCustomAttributeMock->expects($this->atLeastOnce())->method('getBackend')->willReturn( + $backendMock + ); $statusAttributeMock->expects($this->atLeastOnce())->method('getId')->willReturn($statusId); $tableMock = $this->getMockBuilder(\Magento\Framework\DB\Ddl\Table::class) ->disableOriginalConstructor() @@ -185,12 +186,12 @@ class FlatTableBuilderTest extends \PHPUnit_Framework_TestCase [ $temporaryTableName, "e.{$linkField} = {$temporaryTableName}.{$linkField}", - [$linkField] + [$linkField, $eavCustomField] ], [ $temporaryValueTableName, - "e.{$linkField} = " . $temporaryValueTableName . ".{$linkField}", - [$linkField] + "e.{$linkField} = {$temporaryValueTableName}.{$linkField}", + [$eavCustomValueField] ] )->willReturnSelf(); $this->metadataPoolMock->expects($this->atLeastOnce())->method('getMetadata')->with(ProductInterface::class) diff --git a/app/code/Magento/CatalogInventory/etc/events.xml b/app/code/Magento/CatalogInventory/etc/events.xml index a1476c2c3f8b161568a042725d6b04c37aeb1be9..d9db59b7a17663b48b2ba2df7677d4147cba11b5 100644 --- a/app/code/Magento/CatalogInventory/etc/events.xml +++ b/app/code/Magento/CatalogInventory/etc/events.xml @@ -33,9 +33,6 @@ <event name="sales_order_item_cancel"> <observer name="inventory" instance="Magento\CatalogInventory\Observer\CancelOrderItemObserver"/> </event> - <event name="sales_order_creditmemo_save_after"> - <observer name="inventory" instance="Magento\CatalogInventory\Observer\RefundOrderInventoryObserver"/> - </event> <event name="catalog_product_save_after"> <observer name="inventory" instance="Magento\CatalogInventory\Observer\SaveInventoryDataObserver"/> </event> diff --git a/app/code/Magento/Checkout/view/frontend/web/js/region-updater.js b/app/code/Magento/Checkout/view/frontend/web/js/region-updater.js index e06b8922b2252fd497db66a79db35af8c543db09..eba77927be79ea6189884f412be03be7ec7a76be 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/region-updater.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/region-updater.js @@ -7,9 +7,10 @@ define([ 'jquery', 'mage/template', + 'underscore', 'jquery/ui', 'mage/validation' -], function ($, mageTemplate) { +], function ($, mageTemplate, _) { 'use strict'; $.widget('mage.regionUpdater', { @@ -124,6 +125,8 @@ define([ * @private */ _clearError: function () { + var args = ['clearError', this.options.regionListId, this.options.regionInputId, this.options.postcodeId]; + if (this.options.clearError && typeof this.options.clearError === 'function') { this.options.clearError.call(this); } else { @@ -133,8 +136,8 @@ define([ this.options.form = $(this.options.form); - this.options.form && this.options.form.data('validator') && this.options.form.validation('clearError', - this.options.regionListId, this.options.regionInputId, this.options.postcodeId); + this.options.form && this.options.form.data('validator') && + this.options.form.validation.apply(this.options.form, _.compact(args)); // Clean up errors on region & zip fix $(this.options.regionInputId).removeClass('mage-error').parent().find('[generated]').remove(); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/CreditmemoLoader.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/CreditmemoLoader.php index 36f1310079ed534d928fa03be511a64265f71dc5..ac3ffdc3d622c956074b3fc174df6c24e4cabff3 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/CreditmemoLoader.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/CreditmemoLoader.php @@ -217,9 +217,9 @@ class CreditmemoLoader extends DataObject foreach ($creditmemo->getAllItems() as $creditmemoItem) { $orderItem = $creditmemoItem->getOrderItem(); $parentId = $orderItem->getParentItemId(); - if (isset($backToStock[$orderItem->getId()])) { + if ($parentId && isset($backToStock[$parentId]) && $backToStock[$parentId]) { $creditmemoItem->setBackToStock(true); - } elseif ($orderItem->getParentItem() && isset($backToStock[$parentId]) && $backToStock[$parentId]) { + } elseif (isset($backToStock[$orderItem->getId()])) { $creditmemoItem->setBackToStock(true); } elseif (empty($savedData)) { $creditmemoItem->setBackToStock( diff --git a/app/code/Magento/Sales/Model/Order/CreditmemoFactory.php b/app/code/Magento/Sales/Model/Order/CreditmemoFactory.php index 21104933a51d6d8fda5d225ff4b879a07def3d20..ff687074e4a66cc50c457c685fc1757d1529dcb6 100644 --- a/app/code/Magento/Sales/Model/Order/CreditmemoFactory.php +++ b/app/code/Magento/Sales/Model/Order/CreditmemoFactory.php @@ -3,7 +3,6 @@ * Copyright © 2016 Magento. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Sales\Model\Order; /** @@ -23,6 +22,11 @@ class CreditmemoFactory */ protected $taxConfig; + /** + * @var \Magento\Framework\Unserialize\Unserialize + */ + protected $unserialize; + /** * Factory constructor * @@ -57,7 +61,12 @@ class CreditmemoFactory $item = $this->convertor->itemToCreditmemoItem($orderItem); if ($orderItem->isDummy()) { - $qty = 1; + if (isset($data['qtys'][$orderItem->getParentItemId()])) { + $parentQty = $data['qtys'][$orderItem->getParentItemId()]; + } else { + $parentQty = $orderItem->getParentItem() ? $orderItem->getParentItem()->getQtyToRefund() : 1; + } + $qty = $this->calculateProductOptions($orderItem, $parentQty); $orderItem->setLockedDoShip(true); } else { if (isset($qtys[$orderItem->getId()])) { @@ -132,7 +141,12 @@ class CreditmemoFactory $item = $this->convertor->itemToCreditmemoItem($orderItem); if ($orderItem->isDummy()) { - $qty = 1; + if (isset($data['qtys'][$orderItem->getParentItemId()])) { + $parentQty = $data['qtys'][$orderItem->getParentItemId()]; + } else { + $parentQty = $orderItem->getParentItem() ? $orderItem->getParentItem()->getQtyToRefund() : 1; + } + $qty = $this->calculateProductOptions($orderItem, $parentQty); } else { if (isset($qtys[$orderItem->getId()])) { $qty = (double)$qtys[$orderItem->getId()]; @@ -245,4 +259,38 @@ class CreditmemoFactory $creditmemo->setAdjustmentNegative($data['adjustment_negative']); } } + + /** + * @param \Magento\Sales\Api\Data\OrderItemInterface $orderItem + * @param array $qtys + * @return int + */ + private function calculateProductOptions(\Magento\Sales\Api\Data\OrderItemInterface $orderItem, $parentQty) + { + $qty = $parentQty; + $productOptions = $orderItem->getProductOptions(); + if (isset($productOptions['bundle_selection_attributes'])) { + $bundleSelectionAttributes = $this->getUnserialize() + ->unserialize($productOptions['bundle_selection_attributes']); + if ($bundleSelectionAttributes) { + $qty = $bundleSelectionAttributes['qty'] * $parentQty; + } + } + return $qty; + } + + /** + * Get Unserialize + * + * @return \Magento\Framework\Unserialize\Unserialize + * @deprecated + */ + private function getUnserialize() + { + if (!$this->unserialize) { + $this->unserialize = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Unserialize\Unserialize::class); + } + return $this->unserialize; + } } diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/create/sidebar/items.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/create/sidebar/items.phtml index 59bb31a94fa0cadadf177f695271c1efeaa69aa6..a8ccf82c5fe06030055381d9c13228661308e04e 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/create/sidebar/items.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/create/sidebar/items.phtml @@ -66,7 +66,13 @@ <?php endif; ?> <?php if ($block->canDisplayPrice()): ?> - <td class="col-price"><?php /* @noEscape */ echo $block->getItemPrice($_item) ?></td> + <td class="col-price"> + <?php if ($block->getDataId() == 'cart'): ?> + <?php /* @noEscape */ echo $block->getItemPrice($_item->getProduct()) ?> + <?php else: ?> + <?php /* @noEscape */ echo $block->getItemPrice($_item) ?> + <?php endif; ?> + </td> <?php endif; ?> <?php if ($block->canRemoveItems()): ?> diff --git a/app/code/Magento/SalesInventory/Model/Order/ReturnProcessor.php b/app/code/Magento/SalesInventory/Model/Order/ReturnProcessor.php index 7752d7131031cf0daa63f4c8b8bf49609e3090ed..3680bbb3a1eaefa3ee822eefd99b7b21b320a3f8 100644 --- a/app/code/Magento/SalesInventory/Model/Order/ReturnProcessor.php +++ b/app/code/Magento/SalesInventory/Model/Order/ReturnProcessor.php @@ -6,7 +6,6 @@ namespace Magento\SalesInventory\Model\Order; use Magento\Sales\Api\Data\CreditmemoInterface; -use Magento\Sales\Api\Data\CreditmemoItemInterface; use Magento\Sales\Api\Data\OrderInterface; /** @@ -29,52 +28,35 @@ class ReturnProcessor */ private $priceIndexer; - /** - * @var \Magento\Sales\Api\CreditmemoRepositoryInterface - */ - private $creditmemoRepository; - /** * @var \Magento\Store\Model\StoreManagerInterface */ private $storeManager; - /** - * @var \Magento\Sales\Api\OrderRepositoryInterface - */ - private $orderRepository; - /** * @var \Magento\Sales\Api\OrderItemRepositoryInterface */ private $orderItemRepository; /** - * ReturnToStockPlugin constructor. - * @param \Magento\CatalogInventory\Api\StockConfigurationInterface $stockConfiguration + * ReturnProcessor constructor. * @param \Magento\CatalogInventory\Api\StockManagementInterface $stockManagement * @param \Magento\CatalogInventory\Model\Indexer\Stock\Processor $stockIndexer * @param \Magento\Catalog\Model\Indexer\Product\Price\Processor $priceIndexer - * @param \Magento\Sales\Api\CreditmemoRepositoryInterface $creditmemoRepository * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\Sales\Api\OrderRepositoryInterface $orderRepository * @param \Magento\Sales\Api\OrderItemRepositoryInterface $orderItemRepository */ public function __construct( \Magento\CatalogInventory\Api\StockManagementInterface $stockManagement, \Magento\CatalogInventory\Model\Indexer\Stock\Processor $stockIndexer, \Magento\Catalog\Model\Indexer\Product\Price\Processor $priceIndexer, - \Magento\Sales\Api\CreditmemoRepositoryInterface $creditmemoRepository, \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Sales\Api\OrderRepositoryInterface $orderRepository, \Magento\Sales\Api\OrderItemRepositoryInterface $orderItemRepository ) { $this->stockManagement = $stockManagement; $this->stockIndexerProcessor = $stockIndexer; $this->priceIndexer = $priceIndexer; - $this->creditmemoRepository = $creditmemoRepository; $this->storeManager = $storeManager; - $this->orderRepository = $orderRepository; $this->orderItemRepository = $orderItemRepository; } @@ -82,22 +64,22 @@ class ReturnProcessor * @param CreditmemoInterface $creditmemo * @param OrderInterface $order * @param array $returnToStockItems + * @param bool $isAutoReturn * @return void */ public function execute( CreditmemoInterface $creditmemo, OrderInterface $order, - array $returnToStockItems = [] + array $returnToStockItems = [], + $isAutoReturn = false ) { $itemsToUpdate = []; foreach ($creditmemo->getItems() as $item) { - $qty = $item->getQty(); $productId = $item->getProductId(); $orderItem = $this->orderItemRepository->get($item->getOrderItemId()); $parentItemId = $orderItem->getParentItemId(); - if ($this->canReturnItem($item, $qty, $parentItemId, $returnToStockItems)) { - $parentItem = $parentItemId ? $this->getItemByOrderId($creditmemo, $parentItemId) : false; - $qty = $parentItem ? $parentItem->getQty() * $qty : $qty; + $qty = $item->getQty(); + if ($isAutoReturn || $this->canReturnItem($item, $qty, $parentItemId, $returnToStockItems)) { if (isset($itemsToUpdate[$productId])) { $itemsToUpdate[$productId] += $qty; } else { @@ -122,21 +104,6 @@ class ReturnProcessor } } - /** - * @param \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo - * @param int $parentItemId - * @return bool|CreditmemoItemInterface - */ - private function getItemByOrderId(\Magento\Sales\Api\Data\CreditmemoInterface $creditmemo, $parentItemId) - { - foreach ($creditmemo->getItems() as $item) { - if ($item->getOrderItemId() == $parentItemId) { - return $item; - } - } - return false; - } - /** * @param \Magento\Sales\Api\Data\CreditmemoItemInterface $item * @param int $qty diff --git a/app/code/Magento/CatalogInventory/Observer/RefundOrderInventoryObserver.php b/app/code/Magento/SalesInventory/Observer/RefundOrderInventoryObserver.php similarity index 57% rename from app/code/Magento/CatalogInventory/Observer/RefundOrderInventoryObserver.php rename to app/code/Magento/SalesInventory/Observer/RefundOrderInventoryObserver.php index 9702bfc7cfe425b2c8987c1aec4e86430e1d62ac..acdebcf976a2eead77eb6074611968a9831cd524 100644 --- a/app/code/Magento/CatalogInventory/Observer/RefundOrderInventoryObserver.php +++ b/app/code/Magento/SalesInventory/Observer/RefundOrderInventoryObserver.php @@ -4,54 +4,74 @@ * See COPYING.txt for license details. */ -namespace Magento\CatalogInventory\Observer; +namespace Magento\SalesInventory\Observer; use Magento\CatalogInventory\Api\StockConfigurationInterface; use Magento\CatalogInventory\Api\StockManagementInterface; use Magento\Framework\Event\Observer as EventObserver; use Magento\Framework\Event\ObserverInterface; +use Magento\Sales\Model\OrderRepository; +use Magento\SalesInventory\Model\Order\ReturnProcessor; /** * Catalog inventory module observer + * @deprecated */ class RefundOrderInventoryObserver implements ObserverInterface { /** * @var StockConfigurationInterface */ - protected $stockConfiguration; + private $stockConfiguration; /** * @var StockManagementInterface */ - protected $stockManagement; + private $stockManagement; /** * @var \Magento\CatalogInventory\Model\Indexer\Stock\Processor */ - protected $stockIndexerProcessor; + private $stockIndexerProcessor; /** * @var \Magento\Catalog\Model\Indexer\Product\Price\Processor */ - protected $priceIndexer; + private $priceIndexer; /** + * @var \Magento\SalesInventory\Model\Order\ReturnProcessor + */ + private $returnProcessor; + + /** + * @var \Magento\Sales\Api\OrderRepositoryInterface + */ + private $orderRepository; + + /** + * RefundOrderInventoryObserver constructor. * @param StockConfigurationInterface $stockConfiguration * @param StockManagementInterface $stockManagement * @param \Magento\CatalogInventory\Model\Indexer\Stock\Processor $stockIndexerProcessor * @param \Magento\Catalog\Model\Indexer\Product\Price\Processor $priceIndexer + * @param ReturnProcessor $returnProcessor + * @param \Magento\Sales\Api\OrderRepositoryInterface $orderRepository */ public function __construct( StockConfigurationInterface $stockConfiguration, StockManagementInterface $stockManagement, \Magento\CatalogInventory\Model\Indexer\Stock\Processor $stockIndexerProcessor, - \Magento\Catalog\Model\Indexer\Product\Price\Processor $priceIndexer + \Magento\Catalog\Model\Indexer\Product\Price\Processor $priceIndexer, + \Magento\SalesInventory\Model\Order\ReturnProcessor $returnProcessor, + \Magento\Sales\Api\OrderRepositoryInterface $orderRepository ) { $this->stockConfiguration = $stockConfiguration; $this->stockManagement = $stockManagement; $this->stockIndexerProcessor = $stockIndexerProcessor; $this->priceIndexer = $priceIndexer; + $this->returnProcessor = $returnProcessor; + $this->orderRepository = $orderRepository; } /** @@ -64,31 +84,18 @@ class RefundOrderInventoryObserver implements ObserverInterface { /* @var $creditmemo \Magento\Sales\Model\Order\Creditmemo */ $creditmemo = $observer->getEvent()->getCreditmemo(); - $itemsToUpdate = []; - foreach ($creditmemo->getAllItems() as $item) { - $qty = $item->getQty(); - if (($item->getBackToStock() && $qty) || $this->stockConfiguration->isAutoReturnEnabled()) { - $productId = $item->getProductId(); - $parentItemId = $item->getOrderItem()->getParentItemId(); - /* @var $parentItem \Magento\Sales\Model\Order\Creditmemo\Item */ - $parentItem = $parentItemId ? $creditmemo->getItemByOrderId($parentItemId) : false; - $qty = $parentItem ? $parentItem->getQty() * $qty : $qty; - if (isset($itemsToUpdate[$productId])) { - $itemsToUpdate[$productId] += $qty; - } else { - $itemsToUpdate[$productId] = $qty; - } + $order = $this->orderRepository->get($creditmemo->getOrderId()); + $returnToStockItems = []; + foreach ($creditmemo->getItems() as $item) { + if ($item->getBackToStock()) { + $returnToStockItems[] = $item->getOrderItemId(); } } - if (!empty($itemsToUpdate)) { - $this->stockManagement->revertProductsSale( - $itemsToUpdate, - $creditmemo->getStore()->getWebsiteId() - ); - - $updatedItemIds = array_keys($itemsToUpdate); - $this->stockIndexerProcessor->reindexList($updatedItemIds); - $this->priceIndexer->reindexList($updatedItemIds); - } + $this->returnProcessor->execute( + $creditmemo, + $order, + $returnToStockItems, + $this->stockConfiguration->isAutoReturnEnabled() + ); } } diff --git a/app/code/Magento/SalesInventory/Test/Unit/Model/Order/ReturnProcessorTest.php b/app/code/Magento/SalesInventory/Test/Unit/Model/Order/ReturnProcessorTest.php index 523759d54645a1e2db471db948fa116cf59cd32e..efa3bff32c0fa2b40c3a9a57ec04cb1ec5c9f257 100644 --- a/app/code/Magento/SalesInventory/Test/Unit/Model/Order/ReturnProcessorTest.php +++ b/app/code/Magento/SalesInventory/Test/Unit/Model/Order/ReturnProcessorTest.php @@ -5,9 +5,7 @@ */ namespace Magento\SalesInventory\Test\Unit\Model\Order; -use Magento\CatalogInventory\Api\StockConfigurationInterface; use Magento\CatalogInventory\Api\StockManagementInterface; -use Magento\Sales\Api\CreditmemoRepositoryInterface; use Magento\Sales\Api\Data\CreditmemoInterface; use Magento\Sales\Api\Data\CreditmemoItemInterface; use Magento\Sales\Api\Data\OrderInterface; @@ -49,21 +47,11 @@ class ReturnProcessorTest extends \PHPUnit_Framework_TestCase */ private $priceIndexerMock; - /** - * @var \PHPUnit_Framework_MockObject_MockObject|CreditmemoRepositoryInterface - */ - private $creditmemoRepositoryMock; - /** * @var \PHPUnit_Framework_MockObject_MockObject|StoreManagerInterface */ private $storeManagerMock; - /** - * @var \PHPUnit_Framework_MockObject_MockObject|OrderRepositoryInterface - */ - private $orderRepositoryMock; - /** * @var \PHPUnit_Framework_MockObject_MockObject|OrderItemRepositoryInterface */ @@ -95,13 +83,10 @@ class ReturnProcessorTest extends \PHPUnit_Framework_TestCase $this->priceIndexerMock = $this->getMockBuilder(\Magento\Catalog\Model\Indexer\Product\Price\Processor::class) ->disableOriginalConstructor() ->getMock(); - $this->creditmemoRepositoryMock = $this->getMockBuilder(CreditmemoRepositoryInterface::class) - ->disableOriginalConstructor() - ->getMock(); $this->storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class) ->disableOriginalConstructor() ->getMock(); - $this->orderRepositoryMock = $this->getMockBuilder(OrderRepositoryInterface::class) + $this->orderItemRepositoryMock = $this->getMockBuilder(OrderRepositoryInterface::class) ->disableOriginalConstructor() ->getMock(); $this->orderItemRepositoryMock = $this->getMockBuilder(OrderItemRepositoryInterface::class) @@ -127,9 +112,7 @@ class ReturnProcessorTest extends \PHPUnit_Framework_TestCase $this->stockManagementMock, $this->stockIndexerProcessorMock, $this->priceIndexerMock, - $this->creditmemoRepositoryMock, $this->storeManagerMock, - $this->orderRepositoryMock, $this->orderItemRepositoryMock ); } @@ -139,6 +122,7 @@ class ReturnProcessorTest extends \PHPUnit_Framework_TestCase $orderItemId = 99; $productId = 50; $returnToStockItems = [$orderItemId]; + $parentItemId = 52; $qty = 1; $storeId = 0; $webSiteId = 10; @@ -147,10 +131,6 @@ class ReturnProcessorTest extends \PHPUnit_Framework_TestCase ->method('getItems') ->willReturn([$this->creditmemoItemMock]); - $this->creditmemoItemMock->expects($this->once()) - ->method('getQty') - ->willReturn($qty); - $this->creditmemoItemMock->expects($this->exactly(2)) ->method('getOrderItemId') ->willReturn($orderItemId); @@ -190,6 +170,14 @@ class ReturnProcessorTest extends \PHPUnit_Framework_TestCase ->method('reindexList') ->with([$productId]); + $this->orderItemMock->expects($this->once()) + ->method('getParentItemId') + ->willReturn($parentItemId); + + $this->creditmemoItemMock->expects($this->once()) + ->method('getQty') + ->willReturn($qty); + $this->returnProcessor->execute($this->creditmemoMock, $this->orderMock, $returnToStockItems); } } diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Observer/RefundOrderInventoryObserverTest.php b/app/code/Magento/SalesInventory/Test/Unit/Observer/RefundOrderInventoryObserverTest.php similarity index 65% rename from app/code/Magento/CatalogInventory/Test/Unit/Observer/RefundOrderInventoryObserverTest.php rename to app/code/Magento/SalesInventory/Test/Unit/Observer/RefundOrderInventoryObserverTest.php index e440ed3380498a0c8d0deb9fb4edbd68a5d2c295..4e553493d07f63ed9f7d0a2d0d0ec7129d7580e7 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Observer/RefundOrderInventoryObserverTest.php +++ b/app/code/Magento/SalesInventory/Test/Unit/Observer/RefundOrderInventoryObserverTest.php @@ -3,9 +3,12 @@ * Copyright © 2016 Magento. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\CatalogInventory\Test\Unit\Observer; +namespace Magento\SalesInventory\Test\Unit\Observer; -use Magento\CatalogInventory\Observer\RefundOrderInventoryObserver; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Model\OrderRepository; +use Magento\SalesInventory\Model\Order\ReturnProcessor; +use Magento\SalesInventory\Observer\RefundOrderInventoryObserver; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -47,6 +50,26 @@ class RefundOrderInventoryObserverTest extends \PHPUnit_Framework_TestCase */ protected $eventObserver; + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + protected $objectManagerHelper; + + /** + * @var OrderRepository|\PHPUnit_Framework_MockObject_MockObject + */ + protected $orderRepositoryMock; + + /** + * @var ReturnProcessor|\PHPUnit_Framework_MockObject_MockObject + */ + protected $returnProcessorMock; + + /** + * @var OrderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $orderMock; + protected function setUp() { $this->stockIndexerProcessor = $this->getMock( @@ -93,8 +116,22 @@ class RefundOrderInventoryObserverTest extends \PHPUnit_Framework_TestCase ->method('getEvent') ->will($this->returnValue($this->event)); - $this->observer = (new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this))->getObject( - \Magento\CatalogInventory\Observer\RefundOrderInventoryObserver::class, + $this->orderRepositoryMock = $this->getMockBuilder(OrderRepository::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->returnProcessorMock = $this->getMockBuilder(ReturnProcessor::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->orderMock = $this->getMockBuilder(OrderInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->observer = $this->objectManagerHelper->getObject( + \Magento\SalesInventory\Observer\RefundOrderInventoryObserver::class, [ 'stockConfiguration' => $this->stockConfiguration, 'stockManagement' => $this->stockManagement, @@ -102,83 +139,67 @@ class RefundOrderInventoryObserverTest extends \PHPUnit_Framework_TestCase 'priceIndexer' => $this->priceIndexer, ] ); + + $this->objectManagerHelper->setBackwardCompatibleProperty( + $this->observer, + 'orderRepository', + $this->orderRepositoryMock + ); + $this->objectManagerHelper->setBackwardCompatibleProperty( + $this->observer, + 'returnProcessor', + $this->returnProcessorMock + ); } public function testRefundOrderInventory() { - $websiteId = 0; $ids = ['1', '14']; $items = []; $isAutoReturnEnabled = true; - $store = $this->getMock( - \Magento\Store\Model\Store::class, - ['getWebsiteId'], - [], - '', - false - ); - $store->expects($this->once())->method('getWebsiteId')->will($this->returnValue($websiteId)); + $creditMemo = $this->getMock(\Magento\Sales\Model\Order\Creditmemo::class, [], [], '', false); - $itemsToUpdate = []; foreach ($ids as $id) { $item = $this->getCreditMemoItem($id); $items[] = $item; - $itemsToUpdate[$item->getProductId()] = $item->getQty(); } - $creditMemo = $this->getMock(\Magento\Sales\Model\Order\Creditmemo::class, [], [], '', false); + $creditMemo->expects($this->once()) - ->method('getAllItems') + ->method('getItems') ->will($this->returnValue($items)); - $creditMemo->expects($this->once())->method('getStore')->will($this->returnValue($store)); $this->stockConfiguration->expects($this->any()) ->method('isAutoReturnEnabled') ->will($this->returnValue($isAutoReturnEnabled)); - $this->stockManagement->expects($this->once()) - ->method('revertProductsSale') - ->with($itemsToUpdate, $websiteId); - - $this->stockIndexerProcessor->expects($this->once()) - ->method('reindexList') - ->with($ids); - - $this->priceIndexer->expects($this->once()) - ->method('reindexList') - ->with($ids); - $this->event->expects($this->once()) ->method('getCreditmemo') ->will($this->returnValue($creditMemo)); + $this->orderRepositoryMock->expects($this->once()) + ->method('get') + ->willReturn($this->orderMock); + + $this->returnProcessorMock->expects($this->once()) + ->method('execute') + ->with($creditMemo, $this->orderMock, $ids, $isAutoReturnEnabled); + $this->observer->execute($this->eventObserver); } private function getCreditMemoItem($productId) { - $parentItemId = false; $backToStock = true; - $qty = 1; $item = $this->getMock( \Magento\Sales\Model\Order\Creditmemo\Item::class, - ['getProductId', 'getOrderItem', 'getBackToStock', 'getQty', '__wakeup'], - [], - '', - false - ); - $orderItem = $this->getMock( - \Magento\Sales\Model\Order\Item::class, - ['getParentItemId', '__wakeup'], + ['getOrderItemId', 'getBackToStock', 'getQty', '__wakeup'], [], '', false ); - $orderItem->expects($this->any())->method('getParentItemId')->willReturn($parentItemId); - $item->expects($this->any())->method('getOrderItem')->willReturn($orderItem); - $item->expects($this->any())->method('getProductId')->will($this->returnValue($productId)); $item->expects($this->any())->method('getBackToStock')->willReturn($backToStock); - $item->expects($this->any())->method('getQty')->willReturn($qty); + $item->expects($this->any())->method('getOrderItemId')->willReturn($productId); return $item; } } diff --git a/app/code/Magento/SalesInventory/etc/events.xml b/app/code/Magento/SalesInventory/etc/events.xml new file mode 100644 index 0000000000000000000000000000000000000000..a71ed7f8a28a16a4ded03e507876757ec94f436c --- /dev/null +++ b/app/code/Magento/SalesInventory/etc/events.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd"> + <event name="sales_order_creditmemo_save_after"> + <observer name="inventory" instance="Magento\SalesInventory\Observer\RefundOrderInventoryObserver"/> + </event> +</config> diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertProductQtyDecreasedAfterCreditmemo.php b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertProductQtyDecreasedAfterCreditmemo.php new file mode 100644 index 0000000000000000000000000000000000000000..e5c3ab4dad9ee121c1332649dabd8715c3544449 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertProductQtyDecreasedAfterCreditmemo.php @@ -0,0 +1,118 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProduct\Test\Constraint; + +use Magento\Catalog\Test\Page\Adminhtml\CatalogProductEdit; +use Magento\Catalog\Test\Page\Adminhtml\CatalogProductIndex; +use Magento\Mtf\Constraint\AbstractConstraint; +use Magento\Mtf\Fixture\FixtureFactory; +use Magento\Mtf\Fixture\FixtureInterface; +use Magento\Mtf\ObjectManager; +use Magento\Mtf\System\Event\EventManagerInterface; +use Magento\Sales\Test\Fixture\OrderInjectable; + +/** + * Class AssertProductQtyDecreasedAfterCreditmemo + */ +class AssertProductQtyDecreasedAfterCreditmemo extends AbstractConstraint +{ + /** + * @var FixtureFactory + */ + protected $fixtureFactory; + + /** + * Skip fields for create product fixture. + * + * @var array + */ + protected $skipFields = [ + 'attribute_set_id', + 'website_ids', + 'checkout_data', + 'type_id', + 'price', + ]; + + /** + * AssertFirstProductForm constructor. + * @param ObjectManager $objectManager + */ + public function __construct( + ObjectManager $objectManager, + EventManagerInterface $eventManager, + FixtureFactory $fixtureFactory + ) { + $this->fixtureFactory = $fixtureFactory; + parent::__construct($objectManager, $eventManager); + } + + /** + * Assert form data equals fixture data + * + * @param OrderInjectable $order + * @param array $data + * @param CatalogProductIndex $productGrid + * @param CatalogProductEdit $productPage + * @return void + */ + public function processAssert( + OrderInjectable $order, + array $data, + CatalogProductIndex $productGrid, + CatalogProductEdit $productPage + ) { + $product = $this->getProduct($order, $data); + $this->objectManager->get(\Magento\Catalog\Test\Constraint\AssertProductForm::class)->processAssert( + $product, + $productGrid, + $productPage + ); + } + + /** + * Get product's fixture. + * + * @param OrderInjectable $order + * @param array $data + * @param int $index [optional] + * @return FixtureInterface + */ + protected function getProduct(OrderInjectable $order, array $data, $index = 0) + { + if (!isset($data['items_data'][$index]['back_to_stock']) + || $data['items_data'][$index]['back_to_stock'] != 'Yes' + ) { + return $order->getEntityId()['products'][$index]; + } + $product = $order->getEntityId()['products'][$index]; + $productData = $product->getData(); + $checkoutDataQty = $productData['checkout_data']['qty']; + + $productKey = ''; + foreach ($productData['checkout_data']['options']['configurable_options'] as $option) { + $productKey .= ' ' . $option['title'] . ':' . $option['value']; + } + $productKey = trim($productKey); + $optionProduct = $productData['configurable_attributes_data']['matrix'][$productKey]; + $optionProduct['qty'] -= ($checkoutDataQty - $data['items_data'][$index]['qty']); + $productData = $optionProduct; + + $productData = array_diff_key($productData, array_flip($this->skipFields)); + + return $this->fixtureFactory->create(get_class($product), ['data' => $productData]); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return 'Product qty was decreased after creditmemo creation.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct.xml index 4a69a7604bca3d74119b07bdcea3b2115a1f6648..32f1957d4173f87b10069b60c8c428b921e260fb 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct.xml +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct.xml @@ -71,7 +71,40 @@ </dataset> <dataset name="configurable_with_qty_1"> - <field name="name" xsi:type="string">Test configurable product %isolation%</field> + <field name="name" xsi:type="string">sku_test_configurable_product_%isolation%</field> + <field name="sku" xsi:type="string">sku_test_configurable_product_%isolation%</field> + <field name="price" xsi:type="array"> + <item name="dataset" xsi:type="string">price_40</item> + </field> + <field name="product_has_weight" xsi:type="string">This item has weight</field> + <field name="weight" xsi:type="string">30</field> + <field name="status" xsi:type="string">Yes</field> + <field name="visibility" xsi:type="string">Catalog, Search</field> + <field name="tax_class_id" xsi:type="array"> + <item name="dataset" xsi:type="string">taxable_goods</item> + </field> + <field name="url_key" xsi:type="string">configurable-product-%isolation%</field> + <field name="configurable_attributes_data" xsi:type="array"> + <item name="dataset" xsi:type="string">default</item> + </field> + <field name="quantity_and_stock_status" xsi:type="array"> + <item name="is_in_stock" xsi:type="string">In Stock</item> + </field> + <field name="website_ids" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="dataset" xsi:type="string">default</item> + </item> + </field> + <field name="attribute_set_id" xsi:type="array"> + <item name="dataset" xsi:type="string">default</item> + </field> + <field name="checkout_data" xsi:type="array"> + <item name="dataset" xsi:type="string">configurable_options_with_qty_1</item> + </field> + </dataset> + + <dataset name="configurable_with_qty_2"> + <field name="name" xsi:type="string">sku_test_configurable_product_%isolation%</field> <field name="sku" xsi:type="string">sku_test_configurable_product_%isolation%</field> <field name="price" xsi:type="array"> <item name="dataset" xsi:type="string">price_40</item> diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateCreditMemoEntityTest.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateCreditMemoEntityTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..dd92edc82b3310ad9610c70138578a220e4358cd --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateCreditMemoEntityTest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\Sales\Test\TestCase\CreateCreditMemoEntityTest" summary="Create Credit Memo for Offline Payment Methods" ticketId="MAGETWO-59074"> + <variation name="CreateCreditMemoEntityWithConfigurableTestVariation1" ticketId="MAGETWO-12447"> + <data name="description" xsi:type="string">Assert items return to stock (partial refund)</data> + <data name="data/items_data/0/back_to_stock" xsi:type="string">Yes</data> + <data name="data/items_data/0/qty" xsi:type="string">1</data> + <data name="order/dataset" xsi:type="string">default</data> + <data name="order/data/entity_id/products" xsi:type="string">configurableProduct::configurable_with_qty_1</data> + <data name="order/data/price/dataset" xsi:type="string">full_refund</data> + <constraint name="Magento\Sales\Test\Constraint\AssertRefundSuccessCreateMessage" /> + <constraint name="Magento\ConfigurableProduct\Test\Constraint\AssertProductQtyDecreasedAfterCreditmemo" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertProductQtyDecreasedAfterCreditmemo.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertProductQtyDecreasedAfterCreditmemo.php new file mode 100644 index 0000000000000000000000000000000000000000..f48e9e198210c02a66870674cbc35178b7d91df4 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertProductQtyDecreasedAfterCreditmemo.php @@ -0,0 +1,110 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Test\Constraint; + +use Magento\Catalog\Test\Page\Adminhtml\CatalogProductEdit; +use Magento\Catalog\Test\Page\Adminhtml\CatalogProductIndex; +use Magento\Mtf\Constraint\AbstractConstraint; +use Magento\Mtf\Fixture\FixtureFactory; +use Magento\Mtf\Fixture\FixtureInterface; +use Magento\Mtf\ObjectManager; +use Magento\Mtf\System\Event\EventManagerInterface; +use Magento\Sales\Test\Fixture\OrderInjectable; + +/** + * Class AssertProductQtyDecreasedAfterCreditmemo + */ +class AssertProductQtyDecreasedAfterCreditmemo extends AbstractConstraint +{ + /** + * @var FixtureFactory + */ + protected $fixtureFactory; + + /** + * Skip fields for create product fixture. + * + * @var array + */ + protected $skipFields = [ + 'attribute_set_id', + 'website_ids', + 'checkout_data', + 'type_id', + 'price', + ]; + + /** + * AssertFirstProductForm constructor. + * @param ObjectManager $objectManager + */ + public function __construct( + ObjectManager $objectManager, + EventManagerInterface $eventManager, + FixtureFactory $fixtureFactory + ) { + $this->fixtureFactory = $fixtureFactory; + parent::__construct($objectManager, $eventManager); + } + + /** + * Assert form data equals fixture data + * + * @param OrderInjectable $order + * @param array $data + * @param CatalogProductIndex $productGrid + * @param CatalogProductEdit $productPage + * @return void + */ + public function processAssert( + OrderInjectable $order, + array $data, + CatalogProductIndex $productGrid, + CatalogProductEdit $productPage + ) { + $product = $this->getProduct($order, $data); + $this->objectManager->get(\Magento\Catalog\Test\Constraint\AssertProductForm::class)->processAssert( + $product, + $productGrid, + $productPage + ); + } + + /** + * Get product's fixture. + * + * @param OrderInjectable $order + * @param array $data + * @param int $index [optional] + * @return FixtureInterface + */ + protected function getProduct(OrderInjectable $order, array $data, $index = 0) + { + if (!isset($data['items_data'][$index]['back_to_stock']) + || $data['items_data'][$index]['back_to_stock'] != 'Yes' + ) { + return $order->getEntityId()['products'][$index]; + } + $product = $order->getEntityId()['products'][$index]; + $productData = $product->getData(); + $checkoutDataQty = $productData['checkout_data']['qty']; + $productData['quantity_and_stock_status']['qty'] -= ($checkoutDataQty - $data['items_data'][$index]['qty']); + + $productData = array_diff_key($productData, array_flip($this->skipFields)); + + return $this->fixtureFactory->create(get_class($product), ['data' => $productData]); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return 'Product qty was decreased after creditmemo creation.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateCreditMemoEntityTest.php b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateCreditMemoEntityTest.php index 9d19d10f4d40c6c3e317a97e7e83cd3c79e6f8ae..92b341bef2675fe635f804da2d9a17c5eea54c49 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateCreditMemoEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateCreditMemoEntityTest.php @@ -90,32 +90,7 @@ class CreateCreditMemoEntityTest extends Injectable return [ 'ids' => ['creditMemoIds' => $result['creditMemoIds']], - 'product' => $this->getProduct($order, $data), 'customer' => $order->getDataFieldConfig('customer_id')['source']->getCustomer() ]; } - - /** - * Get product's fixture. - * - * @param OrderInjectable $order - * @param array $data - * @param int $index [optional] - * @return FixtureInterface - */ - protected function getProduct(OrderInjectable $order, array $data, $index = 0) - { - if (!isset($data['items_data'][$index]['back_to_stock']) - || $data['items_data'][$index]['back_to_stock'] != 'Yes' - ) { - return $order->getEntityId()['products'][$index]; - } - $product = $order->getEntityId()['products'][$index]; - $productData = $product->getData(); - $checkoutDataQty = $productData['checkout_data']['qty']; - $productData['quantity_and_stock_status']['qty'] -= ($checkoutDataQty - $data['items_data'][$index]['qty']); - $productData = array_diff_key($productData, array_flip($this->skipFields)); - - return $this->fixtureFactory->create(get_class($product), ['data' => $productData]); - } } diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateCreditMemoEntityTest.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateCreditMemoEntityTest.xml index 02b9640acbea4159c0560e8a4727a13cc36e1199..b2cf9843598f48af778edda5a4240409fd9d0365 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateCreditMemoEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateCreditMemoEntityTest.xml @@ -22,7 +22,7 @@ <constraint name="Magento\Sales\Test\Constraint\AssertRefundOrderStatusInCommentsHistory" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderCommentsHistoryNotifyStatus" /> <constraint name="Magento\Sales\Test\Constraint\AssertRefundedGrandTotalOnFrontend" /> - <constraint name="Magento\Catalog\Test\Constraint\AssertProductForm" /> + <constraint name="Magento\Sales\Test\Constraint\AssertProductQtyDecreasedAfterCreditmemo" /> <constraint name="Magento\Sales\Test\Constraint\AssertCreditMemoItems" /> </variation> <variation name="CreateCreditMemoEntityTestVariation2" summary="Assert 0 shipping refund">