diff --git a/app/code/Magento/Braintree/etc/adminhtml/system.xml b/app/code/Magento/Braintree/etc/adminhtml/system.xml index 5f62a9d7cf1920ebdfc8020befa0f78f00c396bc..e4f4e11983892f98cfc8945ee2a541c428b441dd 100644 --- a/app/code/Magento/Braintree/etc/adminhtml/system.xml +++ b/app/code/Magento/Braintree/etc/adminhtml/system.xml @@ -33,7 +33,7 @@ </requires> </field> <field id="braintree_cc_vault_active" translate="label" type="select" sortOrder="12" showInDefault="1" showInWebsite="1" showInStore="0"> - <label>Vault enabled</label> + <label>Vault Enabled</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree_cc_vault/active</config_path> <requires> @@ -155,7 +155,7 @@ <comment>It is recommended to set this value to "PayPal" per store views.</comment> </field> <field id="braintree_paypal_vault_active" translate="label" type="select" sortOrder="21" showInDefault="1" showInWebsite="1" showInStore="0"> - <label>Vault enabled</label> + <label>Vault Enabled</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree_paypal_vault/active</config_path> <requires> diff --git a/app/code/Magento/Braintree/view/adminhtml/web/styles.css b/app/code/Magento/Braintree/view/adminhtml/web/styles.css index 31f48cd0b28df1d8235b9ccc9d6b46b13b410bd4..81378f636eb61362ad2840bc859161c91ee8add1 100644 --- a/app/code/Magento/Braintree/view/adminhtml/web/styles.css +++ b/app/code/Magento/Braintree/view/adminhtml/web/styles.css @@ -3,6 +3,6 @@ * See COPYING.txt for license details. */ -.braintree-section .heading {display: inline-block; background: url("images/braintree_logo.png") no-repeat 0 50% / 18rem auto; padding-left: 20rem;} -.braintree-section .button-container {display: inline-block; float: right;} +.braintree-section .heading {background: url("images/braintree_logo.png") no-repeat 0 50% / 18rem auto; padding-left: 20rem;} +.braintree-section .button-container {float: right;} .braintree-section .config-alt {background: url("images/braintree_allinone.png") no-repeat scroll 0 0 / 100% auto; height: 28px; margin: 0.5rem 0 0; width: 230px;} \ No newline at end of file diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index 21c12bf0a8b1a64b34db70eaeedd4a78a9682976..75ec58ff908762ff82f3c93e6fa218abde6ba42a 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -1317,7 +1317,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity $select = $this->_connection->select()->from( $entityTable, - $this->getNewSkuFieldsForSelect() + array_merge($this->getNewSkuFieldsForSelect(), $this->getOldSkuFieldsForSelect()) )->where( 'sku IN (?)', array_keys($entityRowsIn) @@ -1330,10 +1330,45 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity $this->skuProcessor->setNewSkuData($sku, $key, $value); } } + + $this->updateOldSku($newProducts); } + return $this; } + /** + * Return additional data, needed to select. + * @return array + */ + private function getOldSkuFieldsForSelect() + { + return ['type_id', 'attribute_set_id']; + } + + /** + * Adds newly created products to _oldSku + * @param array $newProducts + * @return void + */ + private function updateOldSku(array $newProducts) + { + $oldSkus = []; + foreach ($newProducts as $info) { + $typeId = $info['type_id']; + $sku = $info['sku']; + $oldSkus[$sku] = [ + 'type_id' => $typeId, + 'attr_set_id' => $info['attribute_set_id'], + $this->getProductIdentifierField() => $info[$this->getProductIdentifierField()], + 'supported_type' => isset($this->_productTypeModels[$typeId]), + $this->getProductEntityLinkField() => $info[$this->getProductEntityLinkField()], + ]; + } + + $this->_oldSku = array_replace($this->_oldSku, $oldSkus); + } + /** * Get new SKU fields for select * @@ -1718,6 +1753,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity ['adapter' => $this, 'bunch' => $bunch] ); } + return $this; } @@ -2452,6 +2488,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity { $source = $this->_getSource(); $source->rewind(); + while ($source->valid()) { try { $rowData = $source->current(); @@ -2465,6 +2502,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity $rowData = $this->_customFieldsMapping($rowData); $this->validateRow($rowData, $source->key()); + $source->next(); } $this->checkUrlKeyDuplicates(); diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Type/AbstractType.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Type/AbstractType.php index a28f65aef1ece1feda826ae9a1a37635af59aae9..8461e617431d64fe3529162d54841d7da4740cdf 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Type/AbstractType.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Type/AbstractType.php @@ -567,4 +567,13 @@ abstract class AbstractType } return $this->productEntityLinkField; } + + /** + * Clean cached values + */ + public function __destruct() + { + self::$attributeCodeToId = []; + self::$commonAttributesCache = []; + } } diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php b/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php new file mode 100644 index 0000000000000000000000000000000000000000..16a296a3554548d31f1692f397cd0124e272a1af --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProduct\Pricing\Render; + +use Magento\Catalog\Pricing\Price\FinalPrice; +use Magento\Catalog\Pricing\Price\RegularPrice; +use Magento\ConfigurableProduct\Pricing\Price\ConfigurableOptionsProviderInterface; +use Magento\Framework\Pricing\Price\PriceInterface; +use Magento\Framework\Pricing\Render\RendererPool; +use Magento\Framework\Pricing\SaleableInterface; +use Magento\Framework\View\Element\Template\Context; + +class FinalPriceBox extends \Magento\Catalog\Pricing\Render\FinalPriceBox +{ + /** + * @var ConfigurableOptionsProviderInterface + */ + private $configurableOptionsProvider; + + /** + * @param Context $context + * @param SaleableInterface $saleableItem + * @param PriceInterface $price + * @param RendererPool $rendererPool + * @param ConfigurableOptionsProviderInterface $configurableOptionsProvider + * @param array $data + */ + public function __construct( + Context $context, + SaleableInterface $saleableItem, + PriceInterface $price, + RendererPool $rendererPool, + ConfigurableOptionsProviderInterface $configurableOptionsProvider, + array $data = [] + ) { + $this->configurableOptionsProvider = $configurableOptionsProvider; + parent::__construct($context, $saleableItem, $price, $rendererPool, $data); + } + + /** + * Define if the special price should be shown + * + * @return bool + */ + public function hasSpecialPrice() + { + $product = $this->getSaleableItem(); + 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) { + return true; + } + } + return false; + } +} diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Render/FinalPriceBoxTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Render/FinalPriceBoxTest.php new file mode 100644 index 0000000000000000000000000000000000000000..4dbcfed5315252c62494b6ede30d4d14f9a598b2 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Render/FinalPriceBoxTest.php @@ -0,0 +1,137 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProduct\Test\Unit\Pricing\Render; + +use Magento\Catalog\Pricing\Price\FinalPrice; +use Magento\Catalog\Pricing\Price\RegularPrice; +use Magento\ConfigurableProduct\Pricing\Price\ConfigurableOptionsProviderInterface; +use Magento\ConfigurableProduct\Pricing\Render\FinalPriceBox; + +class FinalPriceBoxTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Framework\View\Element\Template\Context|\PHPUnit_Framework_MockObject_MockObject + */ + private $context; + + /** + * @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject + */ + private $saleableItem; + + /** + * @var \Magento\Framework\Pricing\Price\PriceInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $price; + + /** + * @var \Magento\Framework\Pricing\Render\RendererPool|\PHPUnit_Framework_MockObject_MockObject + */ + private $rendererPool; + + /** + * @var ConfigurableOptionsProviderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configurableOptionsProvider; + + /** + * @var FinalPriceBox + */ + private $model; + + 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->configurableOptionsProvider = $this->getMockBuilder(ConfigurableOptionsProviderInterface::class) + ->getMockForAbstractClass(); + + $this->model = new FinalPriceBox( + $this->context, + $this->saleableItem, + $this->price, + $this->rendererPool, + $this->configurableOptionsProvider + ); + } + + /** + * @param float $regularPrice + * @param float $finalPrice + * @param bool $expected + * @dataProvider hasSpecialPriceDataProvider + */ + public function testHasSpecialPrice( + $regularPrice, + $finalPrice, + $expected + ) { + $priceMockOne = $this->getMockBuilder(\Magento\Framework\Pricing\Price\PriceInterface::class) + ->getMockForAbstractClass(); + + $priceMockOne->expects($this->once()) + ->method('getValue') + ->willReturn($regularPrice); + + $priceMockTwo = $this->getMockBuilder(\Magento\Framework\Pricing\Price\PriceInterface::class) + ->getMockForAbstractClass(); + + $priceMockTwo->expects($this->once()) + ->method('getValue') + ->willReturn($finalPrice); + + $priceInfoMock = $this->getMockBuilder(\Magento\Framework\Pricing\PriceInfo\Base::class) + ->disableOriginalConstructor() + ->getMock(); + + $priceInfoMock->expects($this->exactly(2)) + ->method('getPrice') + ->willReturnMap([ + [RegularPrice::PRICE_CODE, $priceMockOne], + [FinalPrice::PRICE_CODE, $priceMockTwo], + ]); + + $productMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class) + ->setMethods(['getPriceInfo']) + ->getMockForAbstractClass(); + + $productMock->expects($this->exactly(2)) + ->method('getPriceInfo') + ->willReturn($priceInfoMock); + + $this->configurableOptionsProvider->expects($this->once()) + ->method('getProducts') + ->with($this->saleableItem) + ->willReturn([$productMock]); + + $this->assertEquals($expected, $this->model->hasSpecialPrice()); + } + + /** + * @return array + */ + public function hasSpecialPriceDataProvider() + { + return [ + [10., 20., false], + [10., 10., false], + [20., 10., true], + ]; + } +} diff --git a/app/code/Magento/ConfigurableProduct/view/base/layout/catalog_product_prices.xml b/app/code/Magento/ConfigurableProduct/view/base/layout/catalog_product_prices.xml new file mode 100644 index 0000000000000000000000000000000000000000..47fe31681b5bf0d96ba4cf4935283d415de23ce7 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/view/base/layout/catalog_product_prices.xml @@ -0,0 +1,21 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd"> + <referenceBlock name="render.product.prices"> + <arguments> + <argument name="configurable" xsi:type="array"> + <item name="prices" xsi:type="array"> + <item name="final_price" xsi:type="array"> + <item name="render_class" xsi:type="string">Magento\ConfigurableProduct\Pricing\Render\FinalPriceBox</item> + <item name="render_template" xsi:type="string">Magento_ConfigurableProduct::product/price/final_price.phtml</item> + </item> + </item> + </argument> + </arguments> + </referenceBlock> +</layout> diff --git a/app/code/Magento/ConfigurableProduct/view/base/templates/product/price/final_price.phtml b/app/code/Magento/ConfigurableProduct/view/base/templates/product/price/final_price.phtml new file mode 100644 index 0000000000000000000000000000000000000000..5943b1ea2af5b5c4a9095a81b3a22dc1019f1da9 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/view/base/templates/product/price/final_price.phtml @@ -0,0 +1,60 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +// @codingStandardsIgnoreFile + +?> + +<?php +/** @var \Magento\ConfigurableProduct\Pricing\Render\FinalPriceBox$block */ + +/** @var \Magento\Framework\Pricing\Price\PriceInterface $priceModel */ +$priceModel = $block->getPriceType('regular_price'); + +/** @var \Magento\Framework\Pricing\Price\PriceInterface $finalPriceModel */ +$finalPriceModel = $block->getPriceType('final_price'); +$idSuffix = $block->getIdSuffix() ? $block->getIdSuffix() : ''; +$schema = ($block->getZone() == 'item_view') ? true : false; +?> +<?php if ($block->hasSpecialPrice()): ?> + <span class="special-price"> + <?php /* @escapeNotVerified */ echo $block->renderAmount($finalPriceModel->getAmount(), [ + 'display_label' => __('Special Price'), + 'price_id' => $block->getPriceId('product-price-' . $idSuffix), + 'price_type' => 'finalPrice', + 'include_container' => true, + 'schema' => $schema + ]); ?> + </span> + <span class="old-price sly-old-price no-display"> + <?php /* @escapeNotVerified */ echo $block->renderAmount($priceModel->getAmount(), [ + 'display_label' => __('Regular Price'), + 'price_id' => $block->getPriceId('old-price-' . $idSuffix), + 'price_type' => 'oldPrice', + 'include_container' => true, + 'skip_adjustments' => true + ]); ?> + </span> +<?php else: ?> + <?php /* @escapeNotVerified */ echo $block->renderAmount($finalPriceModel->getAmount(), [ + 'price_id' => $block->getPriceId('product-price-' . $idSuffix), + 'price_type' => 'finalPrice', + 'include_container' => true, + 'schema' => $schema + ]); ?> +<?php endif; ?> + +<?php if ($block->showMinimalPrice()): ?> + <?php if ($block->getUseLinkForAsLowAs()):?> + <a href="<?php /* @escapeNotVerified */ echo $block->getSaleableItem()->getProductUrl(); ?>" class="minimal-price-link"> + <?php /* @escapeNotVerified */ echo $block->renderAmountMinimal(); ?> + </a> + <?php else:?> + <span class="minimal-price-link"> + <?php /* @escapeNotVerified */ echo $block->renderAmountMinimal(); ?> + </span> + <?php endif?> +<?php endif; ?> diff --git a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js index bd0314aacf71106c11fa3759a48a9fde13967826..7bea20e78620134fff06d4cd259fee029f4fe637 100644 --- a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js +++ b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js @@ -28,6 +28,7 @@ define([ '<% } %>', mediaGallerySelector: '[data-gallery-role=gallery-placeholder]', mediaGalleryInitial: null, + slyOldPriceSelector: '.sly-old-price', onlyMainImg: false }, @@ -248,6 +249,7 @@ define([ this._resetChildren(element); } this._reloadPrice(); + this._displayRegularPriceBlock(this.simpleProduct); this._changeProductImage(); }, @@ -444,7 +446,7 @@ define([ }, /** - * Returns pracies for configured products + * Returns prices for configured products * * @param {*} config - Products configuration * @returns {*} @@ -487,6 +489,23 @@ define([ undefined : _.first(config.allowedProducts); + }, + + /** + * Show or hide regular price block + * + * @param {*} optionId + * @private + */ + _displayRegularPriceBlock: function (optionId) { + if (typeof optionId != 'undefined' + && this.options.spConfig.optionPrices[optionId].oldPrice.amount + != this.options.spConfig.optionPrices[optionId].finalPrice.amount + ) { + $(this.options.slyOldPriceSelector).show(); + } else { + $(this.options.slyOldPriceSelector).hide(); + } } }); diff --git a/app/code/Magento/Customer/view/base/ui_component/customer_form.xml b/app/code/Magento/Customer/view/base/ui_component/customer_form.xml index 41ffb74184badc80bfa823e294b2ebde1899952b..fdb9c3f317a4cb04fa1b9908ccde4ca98874ceb3 100644 --- a/app/code/Magento/Customer/view/base/ui_component/customer_form.xml +++ b/app/code/Magento/Customer/view/base/ui_component/customer_form.xml @@ -256,6 +256,10 @@ <item name="label" xsi:type="string" translate="true">Send Welcome Email From</item> <item name="dataType" xsi:type="string">number</item> <item name="formElement" xsi:type="string">select</item> + <item name="source" xsi:type="string">customer</item> + <item name="imports" xsi:type="array"> + <item name="value" xsi:type="string">${ $.provider }:data.customer.store_id</item> + </item> </item> </argument> </field> diff --git a/app/code/Magento/Email/Model/Template/Css/Processor.php b/app/code/Magento/Email/Model/Template/Css/Processor.php new file mode 100644 index 0000000000000000000000000000000000000000..ae7d083750863d2d7dbd5869341bbee772544421 --- /dev/null +++ b/app/code/Magento/Email/Model/Template/Css/Processor.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Email\Model\Template\Css; + +use Magento\Framework\View\Asset\NotationResolver\Variable; +use Magento\Framework\View\Asset\Repository; + +class Processor +{ + /** + * @var Repository + */ + private $assetRepository; + + /** + * @param Repository $assetRepository + */ + public function __construct(Repository $assetRepository) + { + $this->assetRepository = $assetRepository; + } + + /** + * Process css placeholders + * + * @param string $css + * @return string + */ + public function process($css) + { + $matches = []; + if (preg_match_all(Variable::VAR_REGEX, $css, $matches, PREG_SET_ORDER)) { + $replacements = []; + foreach ($matches as $match) { + if (!isset($replacements[$match[0]])) { + $replacements[$match[0]] = $this->getPlaceholderValue($match[1]); + } + } + $css = str_replace(array_keys($replacements), $replacements, $css); + } + return $css; + } + + /** + * Retrieve placeholder value + * + * @param string $placeholder + * @return string + */ + private function getPlaceholderValue($placeholder) + { + /** @var \Magento\Framework\View\Asset\File\FallbackContext $context */ + $context = $this->assetRepository->getStaticViewFileContext(); + + switch ($placeholder) { + case 'base_url_path': + return $context->getBaseUrl(); + case 'locale': + return $context->getLocale(); + default: + return ''; + } + } +} diff --git a/app/code/Magento/Email/Model/Template/Filter.php b/app/code/Magento/Email/Model/Template/Filter.php index 2bb9fc742f258090d1cda46b538053e00fb896e8..d9409d62f159cfd7c2ab1e6c32aae6d07a33b8fc 100644 --- a/app/code/Magento/Email/Model/Template/Filter.php +++ b/app/code/Magento/Email/Model/Template/Filter.php @@ -5,6 +5,9 @@ */ namespace Magento\Email\Model\Template; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\ReadInterface; use Magento\Framework\View\Asset\ContentProcessorException; use Magento\Framework\View\Asset\ContentProcessorInterface; @@ -153,6 +156,16 @@ class Filter extends \Magento\Framework\Filter\Template */ protected $configVariables; + /** + * @var \Magento\Email\Model\Template\Css\Processor + */ + private $cssProcessor; + + /** + * @var ReadInterface + */ + private $pubDirectory; + /** * @param \Magento\Framework\Stdlib\StringUtils $string * @param \Psr\Log\LoggerInterface $logger @@ -203,6 +216,31 @@ class Filter extends \Magento\Framework\Filter\Template parent::__construct($string, $variables); } + /** + * @deprecated + * @return Css\Processor + */ + private function getCssProcessor() + { + if (!$this->cssProcessor) { + $this->cssProcessor = ObjectManager::getInstance()->get(Css\Processor::class); + } + return $this->cssProcessor; + } + + /** + * @deprecated + * @param string $dirType + * @return ReadInterface + */ + private function getPubDirectory($dirType) + { + if (!$this->pubDirectory) { + $this->pubDirectory = ObjectManager::getInstance()->get(Filesystem::class)->getDirectoryRead($dirType); + } + return $this->pubDirectory; + } + /** * Set use absolute links flag * @@ -788,7 +826,9 @@ class Filter extends \Magento\Framework\Filter\Template return '/* ' . __('"file" parameter must be specified') . ' */'; } - $css = $this->getCssFilesContent([$params['file']]); + $css = $this->getCssProcessor()->process( + $this->getCssFilesContent([$params['file']]) + ); if (strpos($css, ContentProcessorInterface::ERROR_MESSAGE_PREFIX) !== false) { // Return compilation error wrapped in CSS comment @@ -889,7 +929,12 @@ class Filter extends \Magento\Framework\Filter\Template try { foreach ($files as $file) { $asset = $this->_assetRepo->createAsset($file, $designParams); - $css .= $asset->getContent(); + $pubDirectory = $this->getPubDirectory($asset->getContext()->getBaseDirType()); + if ($pubDirectory->isExist($asset->getPath())) { + $css .= $pubDirectory->readFile($asset->getPath()); + } else { + $css .= $asset->getContent(); + } } } catch (ContentProcessorException $exception) { $css = $exception->getMessage(); @@ -914,6 +959,8 @@ class Filter extends \Magento\Framework\Filter\Template $cssToInline = $this->getCssFilesContent( $this->getInlineCssFiles() ); + $cssToInline = $this->getCssProcessor()->process($cssToInline); + // Only run Emogrify if HTML and CSS contain content if ($html && $cssToInline) { try { diff --git a/app/code/Magento/Email/Test/Unit/Model/Template/Css/ProcessorTest.php b/app/code/Magento/Email/Test/Unit/Model/Template/Css/ProcessorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..fac9ba0f1b5aa902a22f08141e5f0c824a1a1382 --- /dev/null +++ b/app/code/Magento/Email/Test/Unit/Model/Template/Css/ProcessorTest.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Email\Test\Unit\Model\Template\Css; + +use Magento\Email\Model\Template\Css\Processor; +use Magento\Framework\View\Asset\File\FallbackContext; +use Magento\Framework\View\Asset\Repository; + +class ProcessorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Processor + */ + protected $processor; + + /** + * @var Repository|\PHPUnit_Framework_MockObject_MockObject + */ + protected $assetRepository; + + /** + * @var FallbackContext|\PHPUnit_Framework_MockObject_MockObject + */ + protected $fallbackContext; + + public function setUp() + { + $this->assetRepository = $this->getMockBuilder(Repository::class) + ->disableOriginalConstructor() + ->getMock(); + $this->fallbackContext = $this->getMockBuilder(FallbackContext::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->processor = new Processor($this->assetRepository); + } + + public function testProcess() + { + $url = 'http://magento.local/pub/static/'; + $locale = 'en_US'; + $css = '@import url("{{base_url_path}}frontend/_view/{{locale}}/css/email.css");'; + $expectedCss = '@import url("' . $url . 'frontend/_view/' . $locale . '/css/email.css");'; + + $this->assetRepository->expects($this->exactly(2)) + ->method('getStaticViewFileContext') + ->willReturn($this->fallbackContext); + $this->fallbackContext->expects($this->once()) + ->method('getBaseUrl') + ->willReturn($url); + $this->fallbackContext->expects($this->once()) + ->method('getLocale') + ->willReturn($locale); + $this->assertEquals($expectedCss, $this->processor->process($css)); + } +} diff --git a/app/code/Magento/Email/Test/Unit/Model/Template/FilterTest.php b/app/code/Magento/Email/Test/Unit/Model/Template/FilterTest.php index 84e87e9e8154156b7323e1cbfd5d6d11ebdaab39..bcfd95d897d298faf58543b08ad3385e71f66476 100644 --- a/app/code/Magento/Email/Test/Unit/Model/Template/FilterTest.php +++ b/app/code/Magento/Email/Test/Unit/Model/Template/FilterTest.php @@ -5,6 +5,13 @@ */ namespace Magento\Email\Test\Unit\Model\Template; +use Magento\Email\Model\Template\Css\Processor; +use Magento\Email\Model\Template\Filter; +use Magento\Framework\App\Area; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Filesystem\Directory\ReadInterface; +use Magento\Framework\View\Asset\File\FallbackContext; + /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -94,7 +101,6 @@ class FilterTest extends \PHPUnit_Framework_TestCase $this->escaper = $this->getMockBuilder(\Magento\Framework\Escaper::class) ->disableOriginalConstructor() - ->enableProxyingToOriginalMethods() ->getMock(); $this->assetRepo = $this->getMockBuilder(\Magento\Framework\View\Asset\Repository::class) @@ -138,7 +144,7 @@ class FilterTest extends \PHPUnit_Framework_TestCase /** * @param array|null $mockedMethods Methods to mock - * @return \Magento\Email\Model\Template\Filter|\PHPUnit_Framework_MockObject_MockObject + * @return Filter|\PHPUnit_Framework_MockObject_MockObject */ protected function getModel($mockedMethods = null) { @@ -252,13 +258,23 @@ class FilterTest extends \PHPUnit_Framework_TestCase public function testApplyInlineCss($html, $css, $expectedResults) { $filter = $this->getModel(['getCssFilesContent']); + $cssProcessor = $this->getMockBuilder(Processor::class) + ->disableOriginalConstructor() + ->getMock(); + $reflectionClass = new \ReflectionClass(Filter::class); + $reflectionProperty = $reflectionClass->getProperty('cssProcessor'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($filter, $cssProcessor); + $cssProcessor->expects($this->any()) + ->method('process') + ->willReturnArgument(0); $filter->expects($this->exactly(count($expectedResults))) ->method('getCssFilesContent') ->will($this->returnValue($css)); $designParams = [ - 'area' => \Magento\Framework\App\Area::AREA_FRONTEND, + 'area' => Area::AREA_FRONTEND, 'theme' => 'themeId', 'locale' => 'localeId', ]; @@ -269,6 +285,60 @@ class FilterTest extends \PHPUnit_Framework_TestCase } } + public function testGetCssFilesContent() + { + $file = 'css/email.css'; + $path = Area::AREA_FRONTEND . '/themeId/localeId'; + $css = 'p{color:black}'; + $designParams = [ + 'area' => Area::AREA_FRONTEND, + 'theme' => 'themeId', + 'locale' => 'localeId', + ]; + $filter = $this->getModel(); + + $asset = $this->getMockBuilder(\Magento\Framework\View\Asset\File::class) + ->disableOriginalConstructor() + ->getMock(); + + $fallbackContext = $this->getMockBuilder(FallbackContext::class) + ->disableOriginalConstructor() + ->getMock(); + $fallbackContext->expects($this->once()) + ->method('getBaseDirType') + ->willReturn(DirectoryList::STATIC_VIEW); + $asset->expects($this->atLeastOnce()) + ->method('getContext') + ->willReturn($fallbackContext); + + $asset->expects($this->atLeastOnce()) + ->method('getPath') + ->willReturn($path . DIRECTORY_SEPARATOR . $file); + $this->assetRepo->expects($this->once()) + ->method('createAsset') + ->with($file, $designParams) + ->willReturn($asset); + + $pubDirectory = $this->getMockBuilder(ReadInterface::class) + ->getMockForAbstractClass(); + $reflectionClass = new \ReflectionClass(Filter::class); + $reflectionProperty = $reflectionClass->getProperty('pubDirectory'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($filter, $pubDirectory); + $pubDirectory->expects($this->once()) + ->method('isExist') + ->with($path . DIRECTORY_SEPARATOR . $file) + ->willReturn(true); + $pubDirectory->expects($this->once()) + ->method('readFile') + ->with($path . DIRECTORY_SEPARATOR . $file) + ->willReturn($css); + + $filter->setDesignParams($designParams); + + $this->assertEquals($css, $filter->getCssFilesContent([$file])); + } + /** * @return array */ @@ -301,7 +371,19 @@ class FilterTest extends \PHPUnit_Framework_TestCase */ public function testApplyInlineCssThrowsExceptionWhenDesignParamsNotSet() { - $this->getModel()->applyInlineCss('test'); + $filter = $this->getModel(); + $cssProcessor = $this->getMockBuilder(Processor::class) + ->disableOriginalConstructor() + ->getMock(); + $reflectionClass = new \ReflectionClass(Filter::class); + $reflectionProperty = $reflectionClass->getProperty('cssProcessor'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($filter, $cssProcessor); + $cssProcessor->expects($this->any()) + ->method('process') + ->willReturnArgument(0); + + $filter->applyInlineCss('test'); } /** @@ -348,7 +430,10 @@ class FilterTest extends \PHPUnit_Framework_TestCase $construction = ["{{config path={$path}}}", 'config', " path={$path}"]; $scopeConfigValue = 'value'; - $storeMock = $this->getMock(\Magento\Store\Api\Data\StoreInterface::class, [], [], '', false); + $storeMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->storeManager->expects($this->once())->method('getStore')->willReturn($storeMock); $storeMock->expects($this->once())->method('getId')->willReturn(1); @@ -369,7 +454,9 @@ class FilterTest extends \PHPUnit_Framework_TestCase $construction = ["{{config path={$path}}}", 'config', " path={$path}"]; $scopeConfigValue = ''; - $storeMock = $this->getMock(\Magento\Store\Api\Data\StoreInterface::class, [], [], '', false); + $storeMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) + ->disableOriginalConstructor() + ->getMock(); $this->storeManager->expects($this->once())->method('getStore')->willReturn($storeMock); $storeMock->expects($this->once())->method('getId')->willReturn(1); diff --git a/app/code/Magento/Multishipping/Block/Checkout/Billing.php b/app/code/Magento/Multishipping/Block/Checkout/Billing.php index f34ed5fb2671a649f7f8abc2d201b9ffc19dc0b0..34570e12a29efc0071cad327af15f50d64686f35 100644 --- a/app/code/Magento/Multishipping/Block/Checkout/Billing.php +++ b/app/code/Magento/Multishipping/Block/Checkout/Billing.php @@ -35,6 +35,7 @@ class Billing extends \Magento\Payment\Block\Form\Container * @param \Magento\Checkout\Model\Session $checkoutSession * @param \Magento\Payment\Model\Method\SpecificationInterface $paymentSpecification * @param array $data + * @param array $additionalChecks */ public function __construct( \Magento\Framework\View\Element\Template\Context $context, @@ -43,12 +44,13 @@ class Billing extends \Magento\Payment\Block\Form\Container \Magento\Multishipping\Model\Checkout\Type\Multishipping $multishipping, \Magento\Checkout\Model\Session $checkoutSession, \Magento\Payment\Model\Method\SpecificationInterface $paymentSpecification, - array $data = [] + array $data = [], + array $additionalChecks = [] ) { $this->_multishipping = $multishipping; $this->_checkoutSession = $checkoutSession; $this->paymentSpecification = $paymentSpecification; - parent::__construct($context, $paymentHelper, $methodSpecificationFactory, $data); + parent::__construct($context, $paymentHelper, $methodSpecificationFactory, $data, $additionalChecks); $this->_isScopePrivate = true; } diff --git a/app/code/Magento/Payment/Api/Data/PaymentMethodInterface.php b/app/code/Magento/Payment/Api/Data/PaymentMethodInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..881595f60e5415fdeacf3968ccfd8f01b8539ec1 --- /dev/null +++ b/app/code/Magento/Payment/Api/Data/PaymentMethodInterface.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Payment\Api\Data; + +/** + * Payment method interface. + * + * @api + */ +interface PaymentMethodInterface +{ + /** + * Get code. + * + * @return string + */ + public function getCode(); + + /** + * Get title. + * + * @return string + */ + public function getTitle(); + + /** + * Get store id. + * + * @return int + */ + public function getStoreId(); + + /** + * Get is active. + * + * @return bool + * @SuppressWarnings(PHPMD.BooleanGetMethodName) + */ + public function getIsActive(); +} diff --git a/app/code/Magento/Payment/Api/PaymentMethodListInterface.php b/app/code/Magento/Payment/Api/PaymentMethodListInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..faf231562d9ed202073161a5dd7ac927e353a0ae --- /dev/null +++ b/app/code/Magento/Payment/Api/PaymentMethodListInterface.php @@ -0,0 +1,30 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Payment\Api; + +/** + * Payment method list interface. + * + * @api + */ +interface PaymentMethodListInterface +{ + /** + * Get list of payment methods. + * + * @param int $storeId + * @return \Magento\Payment\Api\Data\PaymentMethodInterface[] + */ + public function getList($storeId); + + /** + * Get list of active payment methods. + * + * @param int $storeId + * @return \Magento\Payment\Api\Data\PaymentMethodInterface[] + */ + public function getActiveList($storeId); +} diff --git a/app/code/Magento/Payment/Block/Form/Container.php b/app/code/Magento/Payment/Block/Form/Container.php index cdcd4137159a1994face7ed1ca5cd89e3c0ad3c2..d91c0e3dc39a454d39d6882da41374f4da71383f 100644 --- a/app/code/Magento/Payment/Block/Form/Container.php +++ b/app/code/Magento/Payment/Block/Form/Container.php @@ -5,6 +5,7 @@ */ namespace Magento\Payment\Block\Form; +use Magento\Framework\App\ObjectManager; use Magento\Payment\Model\Method\AbstractMethod; /** @@ -24,20 +25,38 @@ class Container extends \Magento\Framework\View\Element\Template /** @var \Magento\Payment\Model\Checks\SpecificationFactory */ protected $methodSpecificationFactory; + /** + * @var \Magento\Payment\Api\PaymentMethodListInterface + */ + private $paymentMethodList; + + /** + * @var \Magento\Payment\Model\Method\InstanceFactory + */ + private $paymentMethodInstanceFactory; + + /** + * @var array + */ + protected $additionalChecks; + /** * @param \Magento\Framework\View\Element\Template\Context $context * @param \Magento\Payment\Helper\Data $paymentHelper * @param \Magento\Payment\Model\Checks\SpecificationFactory $methodSpecificationFactory * @param array $data + * @param array $additionalChecks */ public function __construct( \Magento\Framework\View\Element\Template\Context $context, \Magento\Payment\Helper\Data $paymentHelper, \Magento\Payment\Model\Checks\SpecificationFactory $methodSpecificationFactory, - array $data = [] + array $data = [], + array $additionalChecks = [] ) { $this->_paymentHelper = $paymentHelper; $this->methodSpecificationFactory = $methodSpecificationFactory; + $this->additionalChecks = $additionalChecks; parent::__construct($context, $data); } @@ -69,13 +88,17 @@ class Container extends \Magento\Framework\View\Element\Template */ protected function _canUseMethod($method) { - return $this->methodSpecificationFactory->create( + $checks = array_merge( [ AbstractMethod::CHECK_USE_FOR_COUNTRY, AbstractMethod::CHECK_USE_FOR_CURRENCY, AbstractMethod::CHECK_ORDER_TOTAL_MIN_MAX, - ] - )->isApplicable( + AbstractMethod::CHECK_ZERO_TOTAL + ], + $this->additionalChecks + ); + + return $this->methodSpecificationFactory->create($checks)->isApplicable( $method, $this->getQuote() ); @@ -124,11 +147,11 @@ class Container extends \Magento\Framework\View\Element\Template $quote = $this->getQuote(); $store = $quote ? $quote->getStoreId() : null; $methods = []; - $specification = $this->methodSpecificationFactory->create([AbstractMethod::CHECK_ZERO_TOTAL]); - foreach ($this->_paymentHelper->getStoreMethods($store, $quote) as $method) { - if ($this->_canUseMethod($method) && $specification->isApplicable($method, $this->getQuote())) { - $this->_assignMethod($method); - $methods[] = $method; + foreach ($this->getPaymentMethodList()->getActiveList($store) as $method) { + $methodInstance = $this->getPaymentMethodInstanceFactory()->create($method); + if ($methodInstance->isAvailable($quote) && $this->_canUseMethod($methodInstance)) { + $this->_assignMethod($methodInstance); + $methods[] = $methodInstance; } } $this->setData('methods', $methods); @@ -150,4 +173,36 @@ class Container extends \Magento\Framework\View\Element\Template } return false; } + + /** + * Get payment method list. + * + * @return \Magento\Payment\Api\PaymentMethodListInterface + * @deprecated + */ + private function getPaymentMethodList() + { + if ($this->paymentMethodList === null) { + $this->paymentMethodList = ObjectManager::getInstance()->get( + \Magento\Payment\Api\PaymentMethodListInterface::class + ); + } + return $this->paymentMethodList; + } + + /** + * Get payment method instance factory. + * + * @return \Magento\Payment\Model\Method\InstanceFactory + * @deprecated + */ + private function getPaymentMethodInstanceFactory() + { + if ($this->paymentMethodInstanceFactory === null) { + $this->paymentMethodInstanceFactory = ObjectManager::getInstance()->get( + \Magento\Payment\Model\Method\InstanceFactory::class + ); + } + return $this->paymentMethodInstanceFactory; + } } diff --git a/app/code/Magento/Payment/Helper/Data.php b/app/code/Magento/Payment/Helper/Data.php index 78cde93ba6717a85a04feb6a2bec21090d551695..0c5518a5c4c9f98e0e9f615b8df78bc85163abc9 100644 --- a/app/code/Magento/Payment/Helper/Data.php +++ b/app/code/Magento/Payment/Helper/Data.php @@ -118,6 +118,7 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper * @param null|string|bool|int $store * @param Quote|null $quote * @return AbstractMethod[] + * @deprecated */ public function getStoreMethods($store = null, $quote = null) { diff --git a/app/code/Magento/Payment/Model/Method/InstanceFactory.php b/app/code/Magento/Payment/Model/Method/InstanceFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..c273c71e78330df854eabfb4b564675af028cb23 --- /dev/null +++ b/app/code/Magento/Payment/Model/Method/InstanceFactory.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Payment\Model\Method; + +use Magento\Payment\Api\Data\PaymentMethodInterface; + +/** + * Payment method instance factory. + */ +class InstanceFactory +{ + /** + * @var \Magento\Payment\Helper\Data + */ + private $helper; + + /** + * @param \Magento\Payment\Helper\Data $helper + */ + public function __construct( + \Magento\Payment\Helper\Data $helper + ) { + $this->helper = $helper; + } + + /** + * Create payment method instance. + * + * @param PaymentMethodInterface $paymentMethod + * @return \Magento\Payment\Model\MethodInterface + */ + public function create(PaymentMethodInterface $paymentMethod) + { + $methodInstance = $this->helper->getMethodInstance($paymentMethod->getCode()); + $methodInstance->setStore($paymentMethod->getStoreId()); + + return $methodInstance; + } +} diff --git a/app/code/Magento/Payment/Model/MethodList.php b/app/code/Magento/Payment/Model/MethodList.php index d547fe42f332ca4c49f0ff51ac16c0b59e538556..ff1f786c77f6175ab8d78feac5ee7d270a64fc45 100644 --- a/app/code/Magento/Payment/Model/MethodList.php +++ b/app/code/Magento/Payment/Model/MethodList.php @@ -6,12 +6,14 @@ namespace Magento\Payment\Model; +use Magento\Framework\App\ObjectManager; use Magento\Payment\Model\Method\AbstractMethod; class MethodList { /** * @var \Magento\Payment\Helper\Data + * @deprecated */ protected $paymentHelper; @@ -20,6 +22,16 @@ class MethodList */ protected $methodSpecificationFactory; + /** + * @var \Magento\Payment\Api\PaymentMethodListInterface + */ + private $paymentMethodList; + + /** + * @var \Magento\Payment\Model\Method\InstanceFactory + */ + private $paymentMethodInstanceFactory; + /** * @param \Magento\Payment\Helper\Data $paymentHelper * @param Checks\SpecificationFactory $specificationFactory @@ -40,14 +52,16 @@ class MethodList public function getAvailableMethods(\Magento\Quote\Api\Data\CartInterface $quote = null) { $store = $quote ? $quote->getStoreId() : null; - $methods = []; - foreach ($this->paymentHelper->getStoreMethods($store, $quote) as $method) { - if ($this->_canUseMethod($method, $quote)) { - $method->setInfoInstance($quote->getPayment()); - $methods[] = $method; + $availableMethods = []; + + foreach ($this->getPaymentMethodList()->getActiveList($store) as $method) { + $methodInstance = $this->getPaymentMethodInstanceFactory()->create($method); + if ($methodInstance->isAvailable($quote) && $this->_canUseMethod($methodInstance, $quote)) { + $methodInstance->setInfoInstance($quote->getPayment()); + $availableMethods[] = $methodInstance; } } - return $methods; + return $availableMethods; } /** @@ -71,4 +85,36 @@ class MethodList $quote ); } + + /** + * Get payment method list. + * + * @return \Magento\Payment\Api\PaymentMethodListInterface + * @deprecated + */ + private function getPaymentMethodList() + { + if ($this->paymentMethodList === null) { + $this->paymentMethodList = ObjectManager::getInstance()->get( + \Magento\Payment\Api\PaymentMethodListInterface::class + ); + } + return $this->paymentMethodList; + } + + /** + * Get payment method instance factory. + * + * @return \Magento\Payment\Model\Method\InstanceFactory + * @deprecated + */ + private function getPaymentMethodInstanceFactory() + { + if ($this->paymentMethodInstanceFactory === null) { + $this->paymentMethodInstanceFactory = ObjectManager::getInstance()->get( + \Magento\Payment\Model\Method\InstanceFactory::class + ); + } + return $this->paymentMethodInstanceFactory; + } } diff --git a/app/code/Magento/Payment/Model/PaymentMethod.php b/app/code/Magento/Payment/Model/PaymentMethod.php new file mode 100644 index 0000000000000000000000000000000000000000..581c4703f777e1a853dfd15f9afb8c8c7bcc67a6 --- /dev/null +++ b/app/code/Magento/Payment/Model/PaymentMethod.php @@ -0,0 +1,78 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Payment\Model; + +/** + * Payment method class. + */ +class PaymentMethod implements \Magento\Payment\Api\Data\PaymentMethodInterface +{ + /** + * @var string + */ + private $code; + + /** + * @var string + */ + private $title; + + /** + * @var int + */ + private $storeId; + + /** + * @var bool + */ + private $isActive; + + /** + * @param string $code + * @param string $title + * @param int $storeId + * @param bool $isActive + */ + public function __construct($code, $title, $storeId, $isActive) + { + $this->code = $code; + $this->title = $title; + $this->storeId = $storeId; + $this->isActive = $isActive; + } + + /** + * {@inheritdoc} + */ + public function getCode() + { + return $this->code; + } + + /** + * {@inheritdoc} + */ + public function getTitle() + { + return $this->title; + } + + /** + * {@inheritdoc} + */ + public function getStoreId() + { + return $this->storeId; + } + + /** + * {@inheritdoc} + */ + public function getIsActive() + { + return $this->isActive; + } +} diff --git a/app/code/Magento/Payment/Model/PaymentMethodList.php b/app/code/Magento/Payment/Model/PaymentMethodList.php new file mode 100644 index 0000000000000000000000000000000000000000..5968d201ca6869ab9d38327dc422b7f0b935956d --- /dev/null +++ b/app/code/Magento/Payment/Model/PaymentMethodList.php @@ -0,0 +1,92 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Payment\Model; + +use Magento\Payment\Api\Data\PaymentMethodInterface; + +/** + * Payment method list class. + */ +class PaymentMethodList implements \Magento\Payment\Api\PaymentMethodListInterface +{ + /** + * @var \Magento\Payment\Api\Data\PaymentMethodInterfaceFactory + */ + private $methodFactory; + + /** + * @var \Magento\Payment\Helper\Data + */ + private $helper; + + /** + * @param \Magento\Payment\Api\Data\PaymentMethodInterfaceFactory $methodFactory + * @param \Magento\Payment\Helper\Data $helper + */ + public function __construct( + \Magento\Payment\Api\Data\PaymentMethodInterfaceFactory $methodFactory, + \Magento\Payment\Helper\Data $helper + ) { + $this->methodFactory = $methodFactory; + $this->helper = $helper; + } + + /** + * {@inheritdoc} + */ + public function getList($storeId) + { + $methodsCodes = array_keys($this->helper->getPaymentMethods()); + + $methodsInstances = array_map( + function ($code) { + return $this->helper->getMethodInstance($code); + }, + $methodsCodes + ); + + $methodsInstances = array_filter($methodsInstances, function (MethodInterface $method) { + return !($method instanceof \Magento\Payment\Model\Method\Substitution); + }); + + @uasort( + $methodsInstances, + function (MethodInterface $a, MethodInterface $b) use ($storeId) { + return (int)$a->getConfigData('sort_order', $storeId) - (int)$b->getConfigData('sort_order', $storeId); + } + ); + + $methodList = array_map( + function (MethodInterface $methodInstance) use ($storeId) { + + return $this->methodFactory->create([ + 'code' => (string)$methodInstance->getCode(), + 'title' => (string)$methodInstance->getTitle(), + 'storeId' => (int)$storeId, + 'isActive' => (bool)$methodInstance->isActive($storeId) + ]); + }, + $methodsInstances + ); + + return array_values($methodList); + } + + /** + * {@inheritdoc} + */ + public function getActiveList($storeId) + { + $methodList = array_filter( + $this->getList($storeId), + function (PaymentMethodInterface $method) { + return $method->getIsActive(); + } + ); + + return array_values($methodList); + } +} diff --git a/app/code/Magento/Payment/Test/Unit/Gateway/Http/Client/SoapTest.php b/app/code/Magento/Payment/Test/Unit/Gateway/Http/Client/SoapTest.php index b8e1b0dcdf973dd9311c67dceccccbe3ad2f203b..2a3ae9c3063730aa8743b33c44aa831b6664f19c 100644 --- a/app/code/Magento/Payment/Test/Unit/Gateway/Http/Client/SoapTest.php +++ b/app/code/Magento/Payment/Test/Unit/Gateway/Http/Client/SoapTest.php @@ -48,6 +48,7 @@ class SoapTest extends \PHPUnit_Framework_TestCase \Magento\Payment\Gateway\Http\ConverterInterface::class )->getMockForAbstractClass(); $this->client = $this->getMockBuilder(\SoapClient::class) + ->setMethods(['__setSoapHeaders', '__soapCall', '__getLastRequest']) ->disableOriginalConstructor() ->getMock(); diff --git a/app/code/Magento/Payment/Test/Unit/Model/MethodListTest.php b/app/code/Magento/Payment/Test/Unit/Model/MethodListTest.php index 915ccddc05c89ac7b3bbbf836aaa80dc7f0f1c4e..872782407c12994e43aa4a026f4a340450b26b88 100644 --- a/app/code/Magento/Payment/Test/Unit/Model/MethodListTest.php +++ b/app/code/Magento/Payment/Test/Unit/Model/MethodListTest.php @@ -24,9 +24,14 @@ class MethodListTest extends \PHPUnit_Framework_TestCase protected $objectManager; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Payment\Api\PaymentMethodListInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $paymentMethodList; + + /** + * @var \Magento\Payment\Model\Method\InstanceFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $paymentHelperMock; + private $paymentMethodInstanceFactory; /** * @var \PHPUnit_Framework_MockObject_MockObject @@ -36,17 +41,35 @@ class MethodListTest extends \PHPUnit_Framework_TestCase protected function setUp() { $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->paymentHelperMock = $this->getMock(\Magento\Payment\Helper\Data::class, [], [], '', false); - $this->specificationFactoryMock = $this->getMock( + + $this->paymentMethodList = $this->getMockBuilder(\Magento\Payment\Api\PaymentMethodListInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->paymentMethodInstanceFactory = $this->getMockBuilder( + \Magento\Payment\Model\Method\InstanceFactory::class + )->disableOriginalConstructor()->getMock(); + + $this->specificationFactoryMock = $this->getMock( \Magento\Payment\Model\Checks\SpecificationFactory::class, [], [], '', false ); - $this->methodList = $this->objectManager->getObject( + $this->methodList = $this->objectManager->getObject( \Magento\Payment\Model\MethodList::class, [ - 'paymentHelper' => $this->paymentHelperMock, 'specificationFactory' => $this->specificationFactoryMock ] ); + + $this->objectManager->setBackwardCompatibleProperty( + $this->methodList, + 'paymentMethodList', + $this->paymentMethodList + ); + $this->objectManager->setBackwardCompatibleProperty( + $this->methodList, + 'paymentMethodInstanceFactory', + $this->paymentMethodInstanceFactory + ); } public function testGetAvailableMethods() @@ -58,12 +81,15 @@ class MethodListTest extends \PHPUnit_Framework_TestCase ->method('getPayment') ->will($this->returnValue($this->getMock(\Magento\Quote\Model\Quote\Payment::class, [], [], '', false))); - $methodMock = $this->getMock(\Magento\Payment\Model\Method\AbstractMethod::class, [], [], '', false); + $methodInstanceMock = $this->getMock(\Magento\Payment\Model\Method\AbstractMethod::class, [], [], '', false); + $methodInstanceMock->expects($this->once()) + ->method('isAvailable') + ->willReturn(true); $compositeMock = $this->getMock(\Magento\Payment\Model\Checks\Composite::class, [], [], '', false); $compositeMock->expects($this->atLeastOnce()) ->method('isApplicable') - ->with($methodMock, $quoteMock) + ->with($methodInstanceMock, $quoteMock) ->will($this->returnValue(true)); $this->specificationFactoryMock->expects($this->atLeastOnce()) @@ -76,18 +102,19 @@ class MethodListTest extends \PHPUnit_Framework_TestCase ]) ->will($this->returnValue($compositeMock)); - $storeMethods = [$methodMock]; - - $this->paymentHelperMock->expects($this->once()) - ->method('getStoreMethods') - ->with($storeId, $quoteMock) - ->will($this->returnValue($storeMethods)); + $methodMock = $this->getMockForAbstractClass(\Magento\Payment\Api\Data\PaymentMethodInterface::class); + $this->paymentMethodList->expects($this->once()) + ->method('getActiveList') + ->willReturn([$methodMock]); + $this->paymentMethodInstanceFactory->expects($this->once()) + ->method('create') + ->willReturn($methodInstanceMock); - $methodMock->expects($this->atLeastOnce()) + $methodInstanceMock->expects($this->atLeastOnce()) ->method('setInfoInstance') ->with($this->getMock(\Magento\Quote\Model\Quote\Payment::class, [], [], '', false)) ->will($this->returnSelf()); - $this->assertEquals([$methodMock], $this->methodList->getAvailableMethods($quoteMock)); + $this->assertEquals([$methodInstanceMock], $this->methodList->getAvailableMethods($quoteMock)); } } diff --git a/app/code/Magento/Payment/Test/Unit/Model/PaymentMethodListTest.php b/app/code/Magento/Payment/Test/Unit/Model/PaymentMethodListTest.php new file mode 100644 index 0000000000000000000000000000000000000000..c41e5ac2a1aac326970c6a7a2d39eab30b8a8bcc --- /dev/null +++ b/app/code/Magento/Payment/Test/Unit/Model/PaymentMethodListTest.php @@ -0,0 +1,215 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Payment\Test\Unit\Model; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Class PaymentMethodListTest. + */ +class PaymentMethodListTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var \Magento\Payment\Model\PaymentMethodList|\PHPUnit_Framework_MockObject_MockObject + */ + private $paymentMethodList; + + /** + * @var \Magento\Payment\Api\Data\PaymentMethodInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $methodFactoryMock; + + /** + * @var \Magento\Payment\Helper\Data|\PHPUnit_Framework_MockObject_MockObject + */ + private $helperMock; + + /** + * Setup. + * + * @return void + */ + public function setUp() + { + $this->methodFactoryMock = $this->getMockBuilder(\Magento\Payment\Api\Data\PaymentMethodInterfaceFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + $this->helperMock = $this->getMockBuilder(\Magento\Payment\Helper\Data::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->paymentMethodList = $this->objectManagerHelper->getObject( + \Magento\Payment\Model\PaymentMethodList::class, + [ + 'methodFactory' => $this->methodFactoryMock, + 'helper' => $this->helperMock + ] + ); + } + + /** + * Setup getList method. + * + * @param array $paymentMethodConfig + * @param array $methodInstancesMap + * @return void + */ + private function setUpGetList($paymentMethodConfig, $methodInstancesMap) + { + $this->helperMock->expects($this->once()) + ->method('getPaymentMethods') + ->willReturn($paymentMethodConfig); + $this->helperMock->expects($this->any()) + ->method('getMethodInstance') + ->willReturnMap($methodInstancesMap); + + $this->methodFactoryMock->expects($this->any()) + ->method('create') + ->willReturnCallback(function ($data) { + $paymentMethod = $this->getMockBuilder(\Magento\Payment\Api\Data\PaymentMethodInterface::class) + ->getMockForAbstractClass(); + $paymentMethod->expects($this->any()) + ->method('getCode') + ->willReturn($data['code']); + $paymentMethod->expects($this->any()) + ->method('getIsActive') + ->willReturn($data['isActive']); + + return $paymentMethod; + }); + } + + /** + * Test getList. + * + * @param int $storeId + * @param array $paymentMethodConfig + * @param array $methodInstancesMap + * @param array $expected + * @return void + * + * @dataProvider getListDataProvider + */ + public function testGetList($storeId, $paymentMethodConfig, $methodInstancesMap, $expected) + { + $this->setUpGetList($paymentMethodConfig, $methodInstancesMap); + + $codes = array_map( + function ($method) { + return $method->getCode(); + }, + $this->paymentMethodList->getList($storeId) + ); + + $this->assertEquals($expected, $codes); + } + + /** + * Data provider for getList. + * + * @return array + */ + public function getListDataProvider() + { + return [ + [ + 1, + ['method_code_1' => [], 'method_code_2' => []], + [ + ['method_code_1', $this->mockPaymentMethodInstance(1, 10, 'method_code_1', 'title', true)], + ['method_code_2', $this->mockPaymentMethodInstance(1, 5, 'method_code_2', 'title', true)] + ], + ['method_code_2', 'method_code_1'] + ] + ]; + } + + /** + * Test getActiveList. + * + * @param int $storeId + * @param array $paymentMethodConfig + * @param array $methodInstancesMap + * @param array $expected + * @return void + * + * @dataProvider getActiveListDataProvider + */ + public function testGetActiveList($storeId, $paymentMethodConfig, $methodInstancesMap, $expected) + { + $this->setUpGetList($paymentMethodConfig, $methodInstancesMap); + + $codes = array_map( + function ($method) { + return $method->getCode(); + }, + $this->paymentMethodList->getActiveList($storeId) + ); + + $this->assertEquals($expected, $codes); + } + + /** + * Data provider for getActiveList. + * + * @return array + */ + public function getActiveListDataProvider() + { + return [ + [ + 1, + ['method_code_1' => [], 'method_code_2' => []], + [ + ['method_code_1', $this->mockPaymentMethodInstance(1, 10, 'method_code_1', 'title', false)], + ['method_code_2', $this->mockPaymentMethodInstance(1, 5, 'method_code_2', 'title', true)] + ], + ['method_code_2'] + ] + ]; + } + + /** + * Mock payment method instance. + * + * @param int $storeId + * @param int $sortOrder + * @param string $code + * @param string $title + * @param bool $isActive + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function mockPaymentMethodInstance($storeId, $sortOrder, $code, $title, $isActive) + { + $paymentMethodInstance = $this->getMockBuilder(\Magento\Payment\Model\Method\AbstractMethod::class) + ->setMethods(['getCode', 'getTitle', 'isActive', 'getConfigData']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $paymentMethodInstance->expects($this->any()) + ->method('getConfigData') + ->willReturnMap([ + ['sort_order', $storeId, $sortOrder] + ]); + $paymentMethodInstance->expects($this->any()) + ->method('getCode') + ->willReturn($code); + $paymentMethodInstance->expects($this->any()) + ->method('getTitle') + ->willReturn($title); + $paymentMethodInstance->expects($this->any()) + ->method('isActive') + ->willReturn($isActive); + + return $paymentMethodInstance; + } +} diff --git a/app/code/Magento/Payment/etc/di.xml b/app/code/Magento/Payment/etc/di.xml index 73f8e2a76334be8c0bde6f1a2c69a063818ca928..c12c2d2cfd51c2be7688aacef7b023e618f2f77a 100644 --- a/app/code/Magento/Payment/etc/di.xml +++ b/app/code/Magento/Payment/etc/di.xml @@ -6,6 +6,8 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <preference for="Magento\Payment\Api\Data\PaymentMethodInterface" type="Magento\Payment\Model\PaymentMethod"/> + <preference for="Magento\Payment\Api\PaymentMethodListInterface" type="Magento\Payment\Model\PaymentMethodList"/> <preference for="Magento\Payment\Gateway\Validator\ResultInterface" type="Magento\Payment\Gateway\Validator\Result"/> <preference for="Magento\Payment\Gateway\ConfigFactoryInterface" type="Magento\Payment\Gateway\Config\ConfigFactory" /> <preference for="Magento\Payment\Gateway\Command\CommandManagerPoolInterface" type="Magento\Payment\Gateway\Command\CommandManagerPool" /> diff --git a/app/code/Magento/Payment/view/frontend/web/js/view/payment/iframe.js b/app/code/Magento/Payment/view/frontend/web/js/view/payment/iframe.js index c8a6fef58d31ea24dcb930b5e63c6912f8738eff..7d01c195791e482618ae2073a82564cb14aaf339 100644 --- a/app/code/Magento/Payment/view/frontend/web/js/view/payment/iframe.js +++ b/app/code/Magento/Payment/view/frontend/web/js/view/payment/iframe.js @@ -154,6 +154,7 @@ define( */ clearTimeout: function () { clearTimeout(this.timeoutId); + this.fail(); return this; }, diff --git a/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Fieldset/Payment.php b/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Fieldset/Payment.php index 4abe12cbc3715d5192a6b1a4f0a2ae44685a368d..725ebb2282418e9e6747c3fc28c72ff600f31e07 100644 --- a/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Fieldset/Payment.php +++ b/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Fieldset/Payment.php @@ -78,18 +78,10 @@ class Payment extends \Magento\Config\Block\System\Config\Form\Fieldset */ protected function _getHeaderTitleHtml($element) { - $html = '<div class="config-heading" ><div class="heading"><strong>' . $element->getLegend(); + $html = '<div class="config-heading" >'; $groupConfig = $element->getGroup(); - $html .= '</strong>'; - - if ($element->getComment()) { - $html .= '<span class="heading-intro">' . $element->getComment() . '</span>'; - } - $html .= '<div class="config-alt"></div>'; - $html .= '</div>'; - $disabledAttributeString = $this->_isPaymentEnabled($element) ? '' : ' disabled="disabled"'; $disabledClassString = $this->_isPaymentEnabled($element) ? '' : ' disabled'; $htmlId = $element->getHtmlId(); @@ -122,6 +114,13 @@ class Payment extends \Magento\Config\Block\System\Config\Form\Fieldset ) . '</a>'; } + $html .= '</div>'; + $html .= '<div class="heading"><strong>' . $element->getLegend() . '</strong>'; + + if ($element->getComment()) { + $html .= '<span class="heading-intro">' . $element->getComment() . '</span>'; + } + $html .= '<div class="config-alt"></div>'; $html .= '</div></div>'; return $html; diff --git a/app/code/Magento/Paypal/Helper/Data.php b/app/code/Magento/Paypal/Helper/Data.php index 0ecde3bdc5697488e33e3c027a6d0c9a2439d03d..ea69d2ffcf0794f09497d66444d35600a3d8e077 100644 --- a/app/code/Magento/Paypal/Helper/Data.php +++ b/app/code/Magento/Paypal/Helper/Data.php @@ -5,6 +5,8 @@ */ namespace Magento\Paypal\Helper; +use Magento\Framework\App\ObjectManager; +use Magento\Payment\Model\Method\AbstractMethod; use Magento\Paypal\Model\Billing\Agreement\MethodInterface; /** @@ -42,6 +44,16 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper */ private $configFactory; + /** + * @var \Magento\Payment\Api\PaymentMethodListInterface + */ + private $paymentMethodList; + + /** + * @var \Magento\Payment\Model\Method\InstanceFactory + */ + private $paymentMethodInstanceFactory; + /** * @param \Magento\Framework\App\Helper\Context $context * @param \Magento\Payment\Helper\Data $paymentData @@ -92,12 +104,20 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper */ public function getBillingAgreementMethods($store = null, $quote = null) { - $result = []; - foreach ($this->_paymentData->getStoreMethods($store, $quote) as $method) { - if ($method instanceof MethodInterface) { - $result[] = $method; + $activeMethods = array_map( + function (\Magento\Payment\Api\Data\PaymentMethodInterface $method) { + return $this->getPaymentMethodInstanceFactory()->create($method); + }, + $this->getPaymentMethodList()->getActiveList($store) + ); + + $result = array_filter( + $activeMethods, + function (AbstractMethod $method) use ($quote) { + return $method->isAvailable($quote) && $method instanceof MethodInterface; } - } + ); + return $result; } @@ -118,4 +138,36 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper } return $txnId; } + + /** + * Get payment method list. + * + * @return \Magento\Payment\Api\PaymentMethodListInterface + * @deprecated + */ + private function getPaymentMethodList() + { + if ($this->paymentMethodList === null) { + $this->paymentMethodList = ObjectManager::getInstance()->get( + \Magento\Payment\Api\PaymentMethodListInterface::class + ); + } + return $this->paymentMethodList; + } + + /** + * Get payment method instance factory. + * + * @return \Magento\Payment\Model\Method\InstanceFactory + * @deprecated + */ + private function getPaymentMethodInstanceFactory() + { + if ($this->paymentMethodInstanceFactory === null) { + $this->paymentMethodInstanceFactory = ObjectManager::getInstance()->get( + \Magento\Payment\Model\Method\InstanceFactory::class + ); + } + return $this->paymentMethodInstanceFactory; + } } diff --git a/app/code/Magento/Paypal/Model/Config.php b/app/code/Magento/Paypal/Model/Config.php index e02615d24ad3d2fbd4e90a768ce9594531acc602..0af8b18355ec32b045c4e609137cb0eed7944373 100644 --- a/app/code/Magento/Paypal/Model/Config.php +++ b/app/code/Magento/Paypal/Model/Config.php @@ -866,7 +866,9 @@ class Config extends AbstractConfig */ public function getExpressCheckoutStartUrl($token) { - return $this->getPaypalUrl(['cmd' => '_express-checkout', 'token' => $token]); + return sprintf('https://www.%spaypal.com/checkoutnow/2%s', + $this->getValue('sandboxFlag') ? 'sandbox.' : '', + '?token=' . urlencode($token)); } /** diff --git a/app/code/Magento/Paypal/Model/Express.php b/app/code/Magento/Paypal/Model/Express.php index d377e8ed626e15a65a3772041bb837328a4942f3..db1ecdf6b33799673cd22926f5d9dd1b7999f965 100644 --- a/app/code/Magento/Paypal/Model/Express.php +++ b/app/code/Magento/Paypal/Model/Express.php @@ -672,19 +672,13 @@ class Express extends \Magento\Payment\Model\Method\AbstractMethod $additionalData = $data->getData(PaymentInterface::KEY_ADDITIONAL_DATA); - if ( - !is_array($additionalData) - || !isset($additionalData[ExpressCheckout::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT]) - ) { + if (!is_array($additionalData)) { return $this; } - $this->getInfoInstance() - ->setAdditionalInformation( - ExpressCheckout::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT, - $additionalData[ExpressCheckout::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT] - ); - + foreach ($additionalData as $key => $value) { + $this->getInfoInstance()->setAdditionalInformation($key, $value); + } return $this; } diff --git a/app/code/Magento/Paypal/Model/Payflow/Transparent.php b/app/code/Magento/Paypal/Model/Payflow/Transparent.php index f0383dffc9b2443f96bf903b1b423306ff09b4ea..b5803c5ace3925b75e81df43992f714a74ed9905 100644 --- a/app/code/Magento/Paypal/Model/Payflow/Transparent.php +++ b/app/code/Magento/Paypal/Model/Payflow/Transparent.php @@ -166,6 +166,9 @@ class Transparent extends Payflowpro implements TransparentInterface $request->setData('trxtype', self::TRXTYPE_AUTH_ONLY); $request->setData('origid', $token); $request->setData('amt', $this->formatPrice($amount)); + $request->setData('currency', $order->getBaseCurrencyCode()); + $request->setData('taxamt', $this->formatPrice($order->getBaseTaxAmount())); + $request->setData('freightamt', $this->formatPrice($order->getBaseShippingAmount())); $response = $this->postRequest($request, $this->getConfig()); $this->processErrors($response); diff --git a/app/code/Magento/Paypal/Test/Unit/Helper/DataTest.php b/app/code/Magento/Paypal/Test/Unit/Helper/DataTest.php index fa0620a36a681eae6a48413044c6784876aaa889..239c5d6cda746d62fdc438791bb29bfc43f61c19 100644 --- a/app/code/Magento/Paypal/Test/Unit/Helper/DataTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Helper/DataTest.php @@ -8,9 +8,14 @@ namespace Magento\Paypal\Test\Unit\Helper; class DataTest extends \PHPUnit_Framework_TestCase { /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Payment\Api\PaymentMethodListInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $_paymentDataMock; + private $paymentMethodList; + + /** + * @var \Magento\Payment\Model\Method\InstanceFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $paymentMethodInstanceFactory; /** * @var \Magento\Paypal\Model\Config | \PHPUnit_Framework_MockObject_MockObject @@ -24,11 +29,13 @@ class DataTest extends \PHPUnit_Framework_TestCase protected function setUp() { - $this->_paymentDataMock = $this->getMockBuilder( - \Magento\Payment\Helper\Data::class - )->disableOriginalConstructor()->setMethods( - ['getStoreMethods', 'getPaymentMethods'] - )->getMock(); + $this->paymentMethodList = $this->getMockBuilder(\Magento\Payment\Api\PaymentMethodListInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->paymentMethodInstanceFactory = $this->getMockBuilder( + \Magento\Payment\Model\Method\InstanceFactory::class + )->disableOriginalConstructor()->getMock(); $this->configMock = $this->getMock( \Magento\Paypal\Model\Config::class, @@ -48,32 +55,41 @@ class DataTest extends \PHPUnit_Framework_TestCase $this->_helper = $objectManager->getObject( \Magento\Paypal\Helper\Data::class, [ - 'paymentData' => $this->_paymentDataMock, 'methodCodes' => ['expressCheckout' => 'paypal_express', 'hostedPro' => 'hosted_pro'], 'configFactory' => $configMockFactory ] ); + + $objectManager->setBackwardCompatibleProperty( + $this->_helper, + 'paymentMethodList', + $this->paymentMethodList + ); + $objectManager->setBackwardCompatibleProperty( + $this->_helper, + 'paymentMethodInstanceFactory', + $this->paymentMethodInstanceFactory + ); } /** * @dataProvider getBillingAgreementMethodsDataProvider * @param $store * @param $quote - * @param $paymentMethods + * @param $paymentMethodsMap * @param $expectedResult */ - public function testGetBillingAgreementMethods($store, $quote, $paymentMethods, $expectedResult) + public function testGetBillingAgreementMethods($store, $quote, $paymentMethodsMap, $expectedResult) { - $this->_paymentDataMock->expects( - $this->any() - )->method( - 'getStoreMethods' - )->with( - $store, - $quote - )->will( - $this->returnValue($paymentMethods) - ); + $this->paymentMethodList->expects(static::once()) + ->method('getActiveList') + ->with($store) + ->willReturn(array_column($paymentMethodsMap, 0)); + + $this->paymentMethodInstanceFactory->expects(static::any()) + ->method('create') + ->willReturnMap($paymentMethodsMap); + $this->assertEquals($expectedResult, $this->_helper->getBillingAgreementMethods($store, $quote)); } @@ -84,16 +100,40 @@ class DataTest extends \PHPUnit_Framework_TestCase { $quoteMock = $this->getMockBuilder( \Magento\Quote\Model\Quote::class - )->disableOriginalConstructor()->setMethods( - null - ); - $methodInterfaceMock = $this->getMockBuilder( - \Magento\Paypal\Model\Billing\Agreement\MethodInterface::class + )->disableOriginalConstructor()->getMock(); + + $methodMock = $this->getMockBuilder( + \Magento\Payment\Api\Data\PaymentMethodInterface::class )->getMock(); + $agreementMethodInstanceMock = $this->getMockBuilder( + \Magento\Paypal\Model\Method\Agreement::class + )->disableOriginalConstructor()->getMock(); + $agreementMethodInstanceMock->expects($this->any()) + ->method('isAvailable') + ->willReturn(true); + + $methodInstanceMock = $this->getMockBuilder( + \Magento\Payment\Model\Method\Cc::class + )->disableOriginalConstructor()->getMock(); + return [ - ['1', $quoteMock, [$methodInterfaceMock], [$methodInterfaceMock]], - ['1', $quoteMock, [new \StdClass()], []] + [ + '1', + $quoteMock, + [ + [$methodMock, $agreementMethodInstanceMock] + ], + [$agreementMethodInstanceMock] + ], + [ + '1', + $quoteMock, + [ + [$methodMock, $methodInstanceMock] + ], + [] + ] ]; } diff --git a/app/code/Magento/Paypal/Test/Unit/Model/ExpressTest.php b/app/code/Magento/Paypal/Test/Unit/Model/ExpressTest.php index 33065cc22713bcb654f8304505717288a9f21e0b..9816c37fa01267fa1e1952af91c76bab1fd4cf5c 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/ExpressTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/ExpressTest.php @@ -182,7 +182,9 @@ class ExpressTest extends \PHPUnit_Framework_TestCase $data = new DataObject( [ PaymentInterface::KEY_ADDITIONAL_DATA => [ - Express\Checkout::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT => $transportValue + Express\Checkout::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT => $transportValue, + Express\Checkout::PAYMENT_INFO_TRANSPORT_PAYER_ID => $transportValue, + Express\Checkout::PAYMENT_INFO_TRANSPORT_TOKEN => $transportValue ] ] ); @@ -202,11 +204,12 @@ class ExpressTest extends \PHPUnit_Framework_TestCase $this->parentAssignDataExpectation($data); - $paymentInfo->expects(static::once()) + $paymentInfo->expects(static::exactly(3)) ->method('setAdditionalInformation') - ->with( - Express\Checkout::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT, - $transportValue + ->withConsecutive( + [Express\Checkout::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT, $transportValue], + [Express\Checkout::PAYMENT_INFO_TRANSPORT_PAYER_ID, $transportValue], + [Express\Checkout::PAYMENT_INFO_TRANSPORT_TOKEN, $transportValue] ); $this->_model->assignData($data); diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php index a107918d16513b2fdce133e43aef00c5109798c8..2d757147cec6a89dc1b6b6b28f283fd29b98f640 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php @@ -122,7 +122,7 @@ class TransparentTest extends \PHPUnit_Framework_TestCase $this->orderMock = $this->getMockBuilder(\Magento\Sales\Model\Order::class) ->setMethods([ 'getCustomerId', 'getBillingAddress', 'getShippingAddress', 'getCustomerEmail', - 'getId', 'getIncrementId' + 'getId', 'getIncrementId', 'getBaseCurrencyCode' ]) ->disableOriginalConstructor() ->getMock(); @@ -164,6 +164,9 @@ class TransparentTest extends \PHPUnit_Framework_TestCase $this->paymentMock->expects($this->once()) ->method('getOrder') ->willReturn($this->orderMock); + $this->orderMock->expects($this->once()) + ->method('getBaseCurrencyCode') + ->willReturn('USD'); $this->orderMock->expects($this->once()) ->method('getBillingAddress') ->willReturn($this->addressBillingMock); diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml b/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml index b567ac87c4fe253b7b0e6423fb7e25f575521462..98f340f0a271135b241bcdc9a90f2e195f3d76b5 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml @@ -84,7 +84,7 @@ </requires> </field> <field id="payflowpro_cc_vault_active" translate="label" type="select" sortOrder="22" showInDefault="1" showInWebsite="1" showInStore="0"> - <label>Vault enabled</label> + <label>Vault Enabled</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/payflowpro_cc_vault/active</config_path> <attribute type="shared">1</attribute> diff --git a/app/code/Magento/Paypal/view/adminhtml/web/styles.css b/app/code/Magento/Paypal/view/adminhtml/web/styles.css index 9e7cfb2afc18e570c18fdc3fe96920606cbe255a..9119a2e317fb75062e74a9e6543732645fab76d5 100644 --- a/app/code/Magento/Paypal/view/adminhtml/web/styles.css +++ b/app/code/Magento/Paypal/view/adminhtml/web/styles.css @@ -14,12 +14,11 @@ .payflow-settings-notice { border:1px solid #d1d0ce;padding:0 10px;margin: 0;} .payflow-settings-notice .important-label {color:#f00;} .payflow-settings-notice ul.options-list {list-style:disc;padding:0 2em;} -.paypal-express-section .heading {display: inline-block; background: url("images/pp-logo-200px.png") no-repeat 0 50% / 18rem auto; padding-left: 20rem;} -.paypal-express-section .button-container {display: inline-block; float: right;} -.paypal-express-section .config-alt {background: url("images/pp-alt.png") no-repeat; height: 26px; margin: .5rem 0 0; width: 158px;} +.paypal-express-section .heading {background: url("images/pp-logo-200px.png") no-repeat 0 50% / 18rem auto; padding-left: 20rem;} +.paypal-express-section .button-container {float: right;} +.paypal-express-section .config-alt {background: url("images/pp-alt.png") no-repeat; height: 26px; margin: 0.5rem 0 0; width: 158px;} .paypal-express-section .link-more {margin-left: 5px;} -.paypal-other-section .heading {display: inline-block;} -.paypal-other-section .button-container {display: inline-block; float: right; margin: 1rem 0 0 !important;} +.paypal-other-section .button-container {float: right; margin: 1rem 0 0 !important;} .paypal-other-section > .entry-edit-head > a::before {left: auto !important; right: 1.3rem !important;} .paypal-all-in-one-section > .entry-edit-head {background: url("images/AM_mc_vs_dc_ae.jpg") no-repeat scroll 0 50% / 18rem auto; padding-left: 18rem;} .paypal-gateways-section > .entry-edit-head {background: url("images/pp-payflow-mark.png") no-repeat scroll 0 50% / 18rem auto; padding-left: 18rem;} diff --git a/app/code/Magento/Sales/Api/Data/CommentInterface.php b/app/code/Magento/Sales/Api/Data/CommentInterface.php index fcab786319340535c1c6309c884a678a91341d90..6e447e72ab1c21330ecd56ed81c1fd7bd5114865 100644 --- a/app/code/Magento/Sales/Api/Data/CommentInterface.php +++ b/app/code/Magento/Sales/Api/Data/CommentInterface.php @@ -23,14 +23,14 @@ interface CommentInterface const COMMENT = 'comment'; /** - * Gets the comment for the invoice. + * Gets the comment text. * * @return string Comment. */ public function getComment(); /** - * Sets the comment for the invoice. + * Sets the comment text. * * @param string $comment * @return $this @@ -38,14 +38,14 @@ interface CommentInterface public function setComment($comment); /** - * Gets the is-visible-on-storefront flag value for the invoice. + * Gets the is-visible-on-storefront flag value for the comment. * * @return int Is-visible-on-storefront flag value. */ public function getIsVisibleOnFront(); /** - * Sets the is-visible-on-storefront flag value for the invoice. + * Sets the is-visible-on-storefront flag value for the comment. * * @param int $isVisibleOnFront * @return $this diff --git a/app/code/Magento/Sales/Api/Data/CreditmemoCommentCreationInterface.php b/app/code/Magento/Sales/Api/Data/CreditmemoCommentCreationInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..283e8600738e731ee498a444309adc36c8123d21 --- /dev/null +++ b/app/code/Magento/Sales/Api/Data/CreditmemoCommentCreationInterface.php @@ -0,0 +1,33 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Api\Data; + +use Magento\Framework\Api\ExtensibleDataInterface; + +/** + * Interface CreditmemoCommentCreationInterface + * + * @api + */ +interface CreditmemoCommentCreationInterface extends ExtensibleDataInterface, CommentInterface +{ + /** + * Retrieve existing extension attributes object or create a new one. + * + * @return \Magento\Sales\Api\Data\CreditmemoCommentCreationExtensionInterface|null + */ + public function getExtensionAttributes(); + + /** + * Set an extension attributes object. + * + * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationExtensionInterface $extensionAttributes + * @return $this + */ + public function setExtensionAttributes( + \Magento\Sales\Api\Data\CreditmemoCommentCreationExtensionInterface $extensionAttributes + ); +} diff --git a/app/code/Magento/Sales/Api/Data/CreditmemoCreationArgumentsInterface.php b/app/code/Magento/Sales/Api/Data/CreditmemoCreationArgumentsInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..2735a94a777f095ed5f4b2c97e114fe75085ddc8 --- /dev/null +++ b/app/code/Magento/Sales/Api/Data/CreditmemoCreationArgumentsInterface.php @@ -0,0 +1,77 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Api\Data; + +/** + * Interface CreditmemoCreationArgumentsInterface + * + * @api + */ +interface CreditmemoCreationArgumentsInterface +{ + /** + * Gets the credit memo shipping amount. + * + * @return float|null Credit memo shipping amount. + */ + public function getShippingAmount(); + + /** + * Gets the credit memo positive adjustment. + * + * @return float|null Credit memo positive adjustment. + */ + public function getAdjustmentPositive(); + + /** + * Gets the credit memo negative adjustment. + * + * @return float|null Credit memo negative adjustment. + */ + public function getAdjustmentNegative(); + + /** + * Sets the credit memo shipping amount. + * + * @param float $amount + * @return $this + */ + public function setShippingAmount($amount); + + /** + * Sets the credit memo positive adjustment. + * + * @param float $amount + * @return $this + */ + public function setAdjustmentPositive($amount); + + /** + * Sets the credit memo negative adjustment. + * + * @param float $amount + * @return $this + */ + public function setAdjustmentNegative($amount); + + /** + * Gets existing extension attributes. + * + * @return \Magento\Sales\Api\Data\CreditmemoCreationArgumentsExtensionInterface|null + */ + public function getExtensionAttributes(); + + /** + * Sets extension attributes. + * + * @param \Magento\Sales\Api\Data\CreditmemoCreationArgumentsExtensionInterface $extensionAttributes + * + * @return $this + */ + public function setExtensionAttributes( + \Magento\Sales\Api\Data\CreditmemoCreationArgumentsExtensionInterface $extensionAttributes + ); +} diff --git a/app/code/Magento/Sales/Api/Data/CreditmemoItemCreationInterface.php b/app/code/Magento/Sales/Api/Data/CreditmemoItemCreationInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..1cd5f3c43be33aa555780af662013a7dda9838d4 --- /dev/null +++ b/app/code/Magento/Sales/Api/Data/CreditmemoItemCreationInterface.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Api\Data; + +use Magento\Framework\Api\ExtensibleDataInterface; + +/** + * Interface CreditmemoItemCreationInterface + * @api + */ +interface CreditmemoItemCreationInterface extends LineItemInterface, ExtensibleDataInterface +{ + /** + * Retrieve existing extension attributes object or create a new one. + * + * @return \Magento\Sales\Api\Data\CreditmemoItemCreationExtensionInterface|null + */ + public function getExtensionAttributes(); + + /** + * Set an extension attributes object. + * + * @param \Magento\Sales\Api\Data\CreditmemoItemCreationExtensionInterface $extensionAttributes + * @return $this + */ + public function setExtensionAttributes( + \Magento\Sales\Api\Data\CreditmemoItemCreationExtensionInterface $extensionAttributes + ); +} diff --git a/app/code/Magento/Sales/Api/Exception/CouldNotRefundExceptionInterface.php b/app/code/Magento/Sales/Api/Exception/CouldNotRefundExceptionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..dee24735af620f08fdedd21f2c95d2d778235959 --- /dev/null +++ b/app/code/Magento/Sales/Api/Exception/CouldNotRefundExceptionInterface.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Api\Exception; + +/** + * @api + */ +interface CouldNotRefundExceptionInterface +{ +} diff --git a/app/code/Magento/Sales/Api/RefundInvoiceInterface.php b/app/code/Magento/Sales/Api/RefundInvoiceInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..dc86ccbb58006b9d8417776ecf731151ea5225da --- /dev/null +++ b/app/code/Magento/Sales/Api/RefundInvoiceInterface.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Api; + +/** + * Interface RefundInvoiceInterface + * + * @api + */ +interface RefundInvoiceInterface +{ + /** + * Create refund for invoice + * + * @param int $invoiceId + * @param \Magento\Sales\Api\Data\CreditmemoItemCreationInterface[] $items + * @param bool|null $isOnline + * @param bool|null $notify + * @param bool|null $appendComment + * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface|null $comment + * @param \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface|null $arguments + * @return int + */ + public function execute( + $invoiceId, + array $items = [], + $isOnline = false, + $notify = false, + $appendComment = false, + \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null, + \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface $arguments = null + ); +} diff --git a/app/code/Magento/Sales/Api/RefundOrderInterface.php b/app/code/Magento/Sales/Api/RefundOrderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..20a782de421989abfa2c1466c7cd9675a6b20008 --- /dev/null +++ b/app/code/Magento/Sales/Api/RefundOrderInterface.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Api; + +/** + * Interface RefundOrderInterface + * + * @api + */ +interface RefundOrderInterface +{ + /** + * Create offline refund for order + * + * @param int $orderId + * @param \Magento\Sales\Api\Data\CreditmemoItemCreationInterface[] $items + * @param bool|null $notify + * @param bool|null $appendComment + * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface|null $comment + * @param \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface|null $arguments + * @return int + */ + public function execute( + $orderId, + array $items = [], + $notify = false, + $appendComment = false, + \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null, + \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface $arguments = null + ); +} diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Billing/Method/Form.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Billing/Method/Form.php index 9a910e48ec0f2e8344c35795dd544cfe9b1dc506..892faf66140180eb791d4bd8ff25c4b4c48ed930 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Billing/Method/Form.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Billing/Method/Form.php @@ -25,16 +25,18 @@ class Form extends \Magento\Payment\Block\Form\Container * @param \Magento\Payment\Model\Checks\SpecificationFactory $methodSpecificationFactory * @param \Magento\Backend\Model\Session\Quote $sessionQuote * @param array $data + * @param array $additionalChecks */ public function __construct( \Magento\Framework\View\Element\Template\Context $context, \Magento\Payment\Helper\Data $paymentHelper, \Magento\Payment\Model\Checks\SpecificationFactory $methodSpecificationFactory, \Magento\Backend\Model\Session\Quote $sessionQuote, - array $data = [] + array $data = [], + array $additionalChecks = [] ) { $this->_sessionQuote = $sessionQuote; - parent::__construct($context, $paymentHelper, $methodSpecificationFactory, $data); + parent::__construct($context, $paymentHelper, $methodSpecificationFactory, $data, $additionalChecks); } /** diff --git a/app/code/Magento/Sales/Exception/CouldNotRefundException.php b/app/code/Magento/Sales/Exception/CouldNotRefundException.php new file mode 100644 index 0000000000000000000000000000000000000000..59ef4d18b442eda80a5c72c66282c855ca878bd1 --- /dev/null +++ b/app/code/Magento/Sales/Exception/CouldNotRefundException.php @@ -0,0 +1,16 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Exception; + +use Magento\Framework\Exception\LocalizedException; +use Magento\Sales\Api\Exception\CouldNotRefundExceptionInterface; + +/** + * Class CouldNotRefundException + */ +class CouldNotRefundException extends LocalizedException implements CouldNotRefundExceptionInterface +{ +} diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo.php b/app/code/Magento/Sales/Model/Order/Creditmemo.php index 02b2826f14f6e4d320e7d5b7ad3a2c9b41ec6710..fa7dd86dbef19568afb2ff84e650518bc629d503 100644 --- a/app/code/Magento/Sales/Model/Order/Creditmemo.php +++ b/app/code/Magento/Sales/Model/Order/Creditmemo.php @@ -532,11 +532,24 @@ class Creditmemo extends AbstractModel implements EntityInterface, CreditmemoInt */ public function isLast() { - foreach ($this->getAllItems() as $item) { + $items = $this->getAllItems(); + foreach ($items as $item) { if (!$item->isLast()) { return false; } } + + if (empty($items)) { + $order = $this->getOrder(); + if ($order) { + foreach ($order->getItems() as $orderItem) { + if ($orderItem->canRefund()) { + return false; + } + } + } + } + return true; } diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/CommentCreation.php b/app/code/Magento/Sales/Model/Order/Creditmemo/CommentCreation.php new file mode 100644 index 0000000000000000000000000000000000000000..b110b2dbd3986bae566914eef3e164b2410939f3 --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/CommentCreation.php @@ -0,0 +1,86 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Creditmemo; + +use Magento\Sales\Api\Data\CreditmemoCommentCreationInterface; + +/** + * Class CommentCreation + */ +class CommentCreation implements CreditmemoCommentCreationInterface +{ + /** + * @var \Magento\Sales\Api\Data\CreditmemoCommentCreationExtensionInterface + */ + private $extensionAttributes; + + /** + * @var string + */ + private $comment; + + /** + * @var int + */ + private $isVisibleOnFront; + + /** + * Retrieve existing extension attributes object or create a new one. + * + * @return \Magento\Sales\Api\Data\CreditmemoCommentCreationExtensionInterface|null + */ + public function getExtensionAttributes() + { + return $this->extensionAttributes; + } + + /** + * Set an extension attributes object. + * + * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationExtensionInterface $extensionAttributes + * @return $this + */ + public function setExtensionAttributes( + \Magento\Sales\Api\Data\CreditmemoCommentCreationExtensionInterface $extensionAttributes + ) { + $this->extensionAttributes = $extensionAttributes; + return $this; + } + + /** + * @inheritdoc + */ + public function getComment() + { + return $this->comment; + } + + /** + * @inheritdoc + */ + public function setComment($comment) + { + $this->comment = $comment; + return $this; + } + + /** + * @inheritdoc + */ + public function getIsVisibleOnFront() + { + return $this->isVisibleOnFront; + } + + /** + * @inheritdoc + */ + public function setIsVisibleOnFront($isVisibleOnFront) + { + $this->isVisibleOnFront = $isVisibleOnFront; + return $this; + } +} diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/CreationArguments.php b/app/code/Magento/Sales/Model/Order/Creditmemo/CreationArguments.php new file mode 100644 index 0000000000000000000000000000000000000000..fd082bb1dd474b03704b51aec67eba5ea20b23be --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/CreationArguments.php @@ -0,0 +1,104 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Creditmemo; + +use Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface; + +/** + * Class CreationArguments + */ +class CreationArguments implements CreditmemoCreationArgumentsInterface +{ + /** + * @var float|null + */ + private $shippingAmount; + + /** + * @var float|null + */ + private $adjustmentPositive; + + /** + * @var float|null + */ + private $adjustmentNegative; + + /** + * @var \Magento\Sales\Api\Data\CreditmemoCreationArgumentsExtensionInterface + */ + private $extensionAttributes; + + /** + * @inheritdoc + */ + public function getShippingAmount() + { + return $this->shippingAmount; + } + + /** + * @inheritdoc + */ + public function getAdjustmentPositive() + { + return $this->adjustmentPositive; + } + + /** + * @inheritdoc + */ + public function getAdjustmentNegative() + { + return $this->adjustmentNegative; + } + + /** + * @inheritdoc + */ + public function setShippingAmount($amount) + { + $this->shippingAmount = $amount; + return $this; + } + + /** + * @inheritdoc + */ + public function setAdjustmentPositive($amount) + { + $this->adjustmentPositive = $amount; + return $this; + } + + /** + * @inheritdoc + */ + public function setAdjustmentNegative($amount) + { + $this->adjustmentNegative = $amount; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getExtensionAttributes() + { + return $this->extensionAttributes; + } + + /** + * {@inheritdoc} + */ + public function setExtensionAttributes( + \Magento\Sales\Api\Data\CreditmemoCreationArgumentsExtensionInterface $extensionAttributes + ) { + $this->extensionAttributes = $extensionAttributes; + + return $this; + } +} diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/CreditmemoValidator.php b/app/code/Magento/Sales/Model/Order/Creditmemo/CreditmemoValidator.php new file mode 100644 index 0000000000000000000000000000000000000000..e49a08e32d83945f1524fb7edaf2f0b6ca5539df --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/CreditmemoValidator.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Creditmemo; + +use Magento\Sales\Api\Data\CreditmemoInterface; + +/** + * Class CreditmemoValidator + */ +class CreditmemoValidator implements CreditmemoValidatorInterface +{ + /** + * @var \Magento\Sales\Model\Validator + */ + private $validator; + + /** + * InvoiceValidatorRunner constructor. + * @param \Magento\Sales\Model\Validator $validator + */ + public function __construct(\Magento\Sales\Model\Validator $validator) + { + $this->validator = $validator; + } + + /** + * @inheritdoc + */ + public function validate(CreditmemoInterface $entity, array $validators) + { + return $this->validator->validate($entity, $validators); + } +} diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/CreditmemoValidatorInterface.php b/app/code/Magento/Sales/Model/Order/Creditmemo/CreditmemoValidatorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..3889f3b985ff02603009084fa956fab65e505556 --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/CreditmemoValidatorInterface.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Creditmemo; + +use Magento\Sales\Api\Data\CreditmemoInterface; +use Magento\Sales\Exception\DocumentValidationException; +use Magento\Sales\Model\ValidatorInterface; + +/** + * Interface CreditmemoValidatorInterface + */ +interface CreditmemoValidatorInterface +{ + /** + * @param CreditmemoInterface $entity + * @param ValidatorInterface[] $validators + * @return string[] + * @throws DocumentValidationException + */ + public function validate(CreditmemoInterface $entity, array $validators); +} diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/Item/Validation/CreationQuantityValidator.php b/app/code/Magento/Sales/Model/Order/Creditmemo/Item/Validation/CreationQuantityValidator.php new file mode 100644 index 0000000000000000000000000000000000000000..edad1a2520632b31a89e355bb99913d0287df748 --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Item/Validation/CreationQuantityValidator.php @@ -0,0 +1,81 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Creditmemo\Item\Validation; + +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\Data\OrderInterfaceFactory; +use Magento\Sales\Api\Data\OrderItemInterface; +use Magento\Sales\Api\OrderItemRepositoryInterface; +use Magento\Sales\Model\Order\Item; +use Magento\Sales\Model\ValidatorInterface; + +/** + * Class CreationQuantityValidator + */ +class CreationQuantityValidator implements ValidatorInterface +{ + /** + * @var OrderItemRepositoryInterface + */ + private $orderItemRepository; + + /** + * @var OrderInterfaceFactory + */ + private $context; + + /** + * ItemCreationQuantityValidator constructor. + * @param OrderItemRepositoryInterface $orderItemRepository + * @param object $context + */ + public function __construct(OrderItemRepositoryInterface $orderItemRepository, $context = null) + { + $this->orderItemRepository = $orderItemRepository; + $this->context = $context; + } + + /** + * @inheritdoc + */ + public function validate($entity) + { + try { + $orderItem = $this->orderItemRepository->get($entity->getOrderItemId()); + if (!$this->isItemPartOfContextOrder($orderItem)) { + return [__('The creditmemo contains product item that is not part of the original order.')]; + } + } catch (NoSuchEntityException $e) { + return [__('The creditmemo contains product item that is not part of the original order.')]; + } + + if (!$this->isQtyAvailable($orderItem, $entity->getQty())) { + return [__('The quantity to refund must not be greater than the unrefunded quantity.')]; + } + + return []; + } + + /** + * @param Item $orderItem + * @param int $qty + * @return bool + */ + private function isQtyAvailable(Item $orderItem, $qty) + { + return $qty <= $orderItem->getQtyToRefund() || $orderItem->isDummy(); + } + + /** + * @param OrderItemInterface $orderItem + * @return bool + */ + private function isItemPartOfContextOrder(OrderItemInterface $orderItem) + { + return $this->context instanceof OrderInterface && $this->context->getEntityId() === $orderItem->getOrderId(); + } +} diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreation.php b/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreation.php new file mode 100644 index 0000000000000000000000000000000000000000..10e70402be6e4d4e6848f7f1065d44b22058a3d7 --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreation.php @@ -0,0 +1,86 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Creditmemo; + +use Magento\Sales\Api\Data\CreditmemoItemCreationInterface; + +/** + * Class LineItem + */ +class ItemCreation implements CreditmemoItemCreationInterface +{ + /** + * @var int + */ + private $orderItemId; + + /** + * @var float + */ + private $qty; + + /** + * @var \Magento\Sales\Api\Data\CreditmemoItemCreationExtensionInterface + */ + private $extensionAttributes; + + /** + * {@inheritdoc} + */ + public function getOrderItemId() + { + return $this->orderItemId; + } + + /** + * {@inheritdoc} + */ + public function setOrderItemId($orderItemId) + { + $this->orderItemId = $orderItemId; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getQty() + { + return $this->qty; + } + + /** + * {@inheritdoc} + */ + public function setQty($qty) + { + $this->qty = $qty; + return $this; + } + + /** + * Retrieve existing extension attributes object or create a new one. + * + * @return \Magento\Sales\Api\Data\CreditmemoItemCreationExtensionInterface|null + */ + public function getExtensionAttributes() + { + return $this->extensionAttributes; + } + + /** + * Set an extension attributes object. + * + * @param \Magento\Sales\Api\Data\CreditmemoItemCreationExtensionInterface $extensionAttributes + * @return $this + */ + public function setExtensionAttributes( + \Magento\Sales\Api\Data\CreditmemoItemCreationExtensionInterface $extensionAttributes + ) { + $this->extensionAttributes = $extensionAttributes; + return $this; + } +} diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreationValidator.php b/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreationValidator.php new file mode 100644 index 0000000000000000000000000000000000000000..f1b8e6d9cab9ba4324d8ca01ae1b9267f6e4c496 --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreationValidator.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Creditmemo; + +use Magento\Sales\Api\Data\CreditmemoItemCreationInterface; +use Magento\Sales\Api\Data\OrderInterface; + +/** + * Class ItemCreationValidator + */ +class ItemCreationValidator implements ItemCreationValidatorInterface +{ + /** + * @var \Magento\Sales\Model\Validator + */ + private $validator; + + /** + * InvoiceValidatorRunner constructor. + * @param \Magento\Sales\Model\Validator $validator + */ + public function __construct(\Magento\Sales\Model\Validator $validator) + { + $this->validator = $validator; + } + + /** + * @inheritdoc + */ + public function validate( + CreditmemoItemCreationInterface $entity, + array $validators, + OrderInterface $context = null + ) { + return $this->validator->validate($entity, $validators, $context); + } +} diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreationValidatorInterface.php b/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreationValidatorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..9f8bb84ccd16a175ee23de755169281026dc053b --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreationValidatorInterface.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Creditmemo; + +use Magento\Sales\Api\Data\CreditmemoItemCreationInterface; +use Magento\Sales\Api\Data\OrderInterface; + +/** + * Interface ItemCreationValidatorInterface + */ +interface ItemCreationValidatorInterface +{ + /** + * @param CreditmemoItemCreationInterface $item + * @param array $validators + * @param OrderInterface|null $context + * @return mixed + */ + public function validate(CreditmemoItemCreationInterface $item, array $validators, OrderInterface $context = null); +} diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/Notifier.php b/app/code/Magento/Sales/Model/Order/Creditmemo/Notifier.php new file mode 100644 index 0000000000000000000000000000000000000000..47dbecca6b59bdc727f1b58b18e3c88a73c5993f --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Notifier.php @@ -0,0 +1,41 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Creditmemo; + +/** + * CreditMemo notifier. + * + * @api + */ +class Notifier implements \Magento\Sales\Model\Order\Creditmemo\NotifierInterface +{ + /** + * @var \Magento\Sales\Model\Order\CreditMemo\SenderInterface[] + */ + private $senders; + + /** + * @param \Magento\Sales\Model\Order\CreditMemo\SenderInterface[] $senders + */ + public function __construct(array $senders = []) + { + $this->senders = $senders; + } + + /** + * {@inheritdoc} + */ + public function notify( + \Magento\Sales\Api\Data\OrderInterface $order, + \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo, + \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null, + $forceSyncMode = false + ) { + foreach ($this->senders as $sender) { + $sender->send($order, $creditmemo, $comment, $forceSyncMode); + } + } +} diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/NotifierInterface.php b/app/code/Magento/Sales/Model/Order/Creditmemo/NotifierInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..ef42bd18633cfc21f0b92df28dde52438ed22c52 --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/NotifierInterface.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Creditmemo; + +/** + * Interface for CreditMemo notifier. + * + * @api + */ +interface NotifierInterface +{ + /** + * Notifies customer. + * + * @param \Magento\Sales\Api\Data\OrderInterface $order + * @param \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo + * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface|null $comment + * @param bool $forceSyncMode + * + * @return void + */ + public function notify( + \Magento\Sales\Api\Data\OrderInterface $order, + \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo, + \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null, + $forceSyncMode = false + ); +} diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/RefundOperation.php b/app/code/Magento/Sales/Model/Order/Creditmemo/RefundOperation.php index ef60ad2ce51bea1c2610f8ca6289d3cf4686c6c9..f01dd0dc3732e12a287306edd7ed173e604e9dc9 100644 --- a/app/code/Magento/Sales/Model/Order/Creditmemo/RefundOperation.php +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/RefundOperation.php @@ -114,9 +114,8 @@ class RefundOperation $order->getBaseTotalInvoicedCost() - $creditmemo->getBaseCost() ); - if ($online) { - $order->getPayment()->refund($creditmemo); - } + $creditmemo->setDoTransaction($online); + $order->getPayment()->refund($creditmemo); $this->eventManager->dispatch('sales_order_creditmemo_refund', ['creditmemo' => $creditmemo]); } diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/Sender/EmailSender.php b/app/code/Magento/Sales/Model/Order/Creditmemo/Sender/EmailSender.php new file mode 100644 index 0000000000000000000000000000000000000000..76210505fd4672778cfae0742de64bc990ccac86 --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Sender/EmailSender.php @@ -0,0 +1,149 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Creditmemo\Sender; + +use Magento\Sales\Model\Order\Email\Sender; +use Magento\Sales\Model\Order\Creditmemo\SenderInterface; + +/** + * Email notification sender for Creditmemo. + */ +class EmailSender extends Sender implements SenderInterface +{ + /** + * @var \Magento\Payment\Helper\Data + */ + private $paymentHelper; + + /** + * @var \Magento\Sales\Model\ResourceModel\Order\Creditmemo + */ + private $creditmemoResource; + + /** + * @var \Magento\Framework\App\Config\ScopeConfigInterface + */ + private $globalConfig; + + /** + * @var \Magento\Framework\Event\ManagerInterface + */ + private $eventManager; + + /** + * @param \Magento\Sales\Model\Order\Email\Container\Template $templateContainer + * @param \Magento\Sales\Model\Order\Email\Container\CreditmemoIdentity $identityContainer + * @param \Magento\Sales\Model\Order\Email\SenderBuilderFactory $senderBuilderFactory + * @param \Psr\Log\LoggerInterface $logger + * @param \Magento\Sales\Model\Order\Address\Renderer $addressRenderer + * @param \Magento\Payment\Helper\Data $paymentHelper + * @param \Magento\Sales\Model\ResourceModel\Order\Creditmemo $creditmemoResource + * @param \Magento\Framework\App\Config\ScopeConfigInterface $globalConfig + * @param \Magento\Framework\Event\ManagerInterface $eventManager + */ + public function __construct( + \Magento\Sales\Model\Order\Email\Container\Template $templateContainer, + \Magento\Sales\Model\Order\Email\Container\CreditmemoIdentity $identityContainer, + \Magento\Sales\Model\Order\Email\SenderBuilderFactory $senderBuilderFactory, + \Psr\Log\LoggerInterface $logger, + \Magento\Sales\Model\Order\Address\Renderer $addressRenderer, + \Magento\Payment\Helper\Data $paymentHelper, + \Magento\Sales\Model\ResourceModel\Order\Creditmemo $creditmemoResource, + \Magento\Framework\App\Config\ScopeConfigInterface $globalConfig, + \Magento\Framework\Event\ManagerInterface $eventManager + ) { + parent::__construct( + $templateContainer, + $identityContainer, + $senderBuilderFactory, + $logger, + $addressRenderer + ); + + $this->paymentHelper = $paymentHelper; + $this->creditmemoResource = $creditmemoResource; + $this->globalConfig = $globalConfig; + $this->eventManager = $eventManager; + } + + /** + * Sends order creditmemo email to the customer. + * + * Email will be sent immediately in two cases: + * + * - if asynchronous email sending is disabled in global settings + * - if $forceSyncMode parameter is set to TRUE + * + * Otherwise, email will be sent later during running of + * corresponding cron job. + * + * @param \Magento\Sales\Api\Data\OrderInterface $order + * @param \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo + * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface|null $comment + * @param bool $forceSyncMode + * + * @return bool + */ + public function send( + \Magento\Sales\Api\Data\OrderInterface $order, + \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo, + \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null, + $forceSyncMode = false + ) { + $creditmemo->setSendEmail(true); + + if (!$this->globalConfig->getValue('sales_email/general/async_sending') || $forceSyncMode) { + $transport = [ + 'order' => $order, + 'creditmemo' => $creditmemo, + 'comment' => $comment ? $comment->getComment() : '', + 'billing' => $order->getBillingAddress(), + 'payment_html' => $this->getPaymentHtml($order), + 'store' => $order->getStore(), + 'formattedShippingAddress' => $this->getFormattedShippingAddress($order), + 'formattedBillingAddress' => $this->getFormattedBillingAddress($order), + ]; + + $this->eventManager->dispatch( + 'email_creditmemo_set_template_vars_before', + ['sender' => $this, 'transport' => $transport] + ); + + $this->templateContainer->setTemplateVars($transport); + + if ($this->checkAndSend($order)) { + $creditmemo->setEmailSent(true); + + $this->creditmemoResource->saveAttribute($creditmemo, ['send_email', 'email_sent']); + + return true; + } + } else { + $creditmemo->setEmailSent(null); + + $this->creditmemoResource->saveAttribute($creditmemo, 'email_sent'); + } + + $this->creditmemoResource->saveAttribute($creditmemo, 'send_email'); + + return false; + } + + /** + * Returns payment info block as HTML. + * + * @param \Magento\Sales\Api\Data\OrderInterface $order + * + * @return string + */ + private function getPaymentHtml(\Magento\Sales\Api\Data\OrderInterface $order) + { + return $this->paymentHelper->getInfoBlockHtml( + $order->getPayment(), + $this->identityContainer->getStore()->getStoreId() + ); + } +} diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/SenderInterface.php b/app/code/Magento/Sales/Model/Order/Creditmemo/SenderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..00d316a8ec98aa79f3ef991b187e9070f2c15901 --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/SenderInterface.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Creditmemo; + +/** + * Interface for notification sender for CreditMemo. + */ +interface SenderInterface +{ + /** + * Sends notification to a customer. + * + * @param \Magento\Sales\Api\Data\OrderInterface $order + * @param \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo + * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface|null $comment + * @param bool $forceSyncMode + * + * @return bool + */ + public function send( + \Magento\Sales\Api\Data\OrderInterface $order, + \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo, + \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null, + $forceSyncMode = false + ); +} diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/Validation/QuantityValidator.php b/app/code/Magento/Sales/Model/Order/Creditmemo/Validation/QuantityValidator.php new file mode 100644 index 0000000000000000000000000000000000000000..82fd0479166e5257730b9c0ec83079eb5d13b37e --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Validation/QuantityValidator.php @@ -0,0 +1,253 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Creditmemo\Validation; + +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\Data\OrderItemInterface; +use Magento\Sales\Api\Data\CreditmemoInterface; +use Magento\Sales\Api\InvoiceRepositoryInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Creditmemo; +use Magento\Sales\Model\Order\Item; +use Magento\Sales\Model\ValidatorInterface; + +/** + * Class QuantityValidator + */ +class QuantityValidator implements ValidatorInterface +{ + /** + * @var OrderRepositoryInterface + */ + private $orderRepository; + + /** + * @var InvoiceRepositoryInterface + */ + private $invoiceRepository; + + /** + * @var \Magento\Framework\Pricing\PriceCurrencyInterface + */ + private $priceCurrency; + + /** + * InvoiceValidator constructor. + * + * @param OrderRepositoryInterface $orderRepository + * @param InvoiceRepositoryInterface $invoiceRepository + * @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency + */ + public function __construct( + OrderRepositoryInterface $orderRepository, + InvoiceRepositoryInterface $invoiceRepository, + \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency + ) { + $this->orderRepository = $orderRepository; + $this->invoiceRepository = $invoiceRepository; + $this->priceCurrency = $priceCurrency; + } + + /** + * @inheritdoc + */ + public function validate($entity) + { + /** + * @var $entity CreditmemoInterface + */ + if ($entity->getOrderId() === null) { + return [__('Order Id is required for creditmemo document')]; + } + + $messages = []; + + $order = $this->orderRepository->get($entity->getOrderId()); + $orderItemsById = $this->getOrderItems($order); + $invoiceQtysRefundLimits = $this->getInvoiceQtysRefundLimits($entity, $order); + + $totalQuantity = 0; + foreach ($entity->getItems() as $item) { + if (!isset($orderItemsById[$item->getOrderItemId()])) { + $messages[] = __( + 'The creditmemo contains product SKU "%1" that is not part of the original order.', + $item->getSku() + ); + continue; + } + $orderItem = $orderItemsById[$item->getOrderItemId()]; + + if ( + !$this->canRefundItem($orderItem, $item->getQty(), $invoiceQtysRefundLimits) || + !$this->isQtyAvailable($orderItem, $item->getQty()) + ) { + $messages[] =__( + 'The quantity to creditmemo must not be greater than the unrefunded quantity' + . ' for product SKU "%1".', + $orderItem->getSku() + ); + } else { + $totalQuantity += $item->getQty(); + } + } + + if ($entity->getGrandTotal() <= 0) { + $messages[] = __('The credit memo\'s total must be positive.'); + } elseif ($totalQuantity <= 0 && !$this->canRefundShipping($order)) { + $messages[] = __('You can\'t create a creditmemo without products.'); + } + + return $messages; + } + + /** + * We can have problem with float in php (on some server $a=762.73;$b=762.73; $a-$b!=0) + * for this we have additional diapason for 0 + * TotalPaid - contains amount, that were not rounded. + * + * @param OrderInterface $order + * @return bool + */ + private function canRefundShipping(OrderInterface $order) + { + return !abs($this->priceCurrency->round($order->getShippingAmount()) - $order->getShippingRefunded()) < .0001; + } + + /** + * @param CreditmemoInterface $creditmemo + * @param OrderInterface $order + * @return array + */ + private function getInvoiceQtysRefundLimits(CreditmemoInterface $creditmemo, OrderInterface $order) + { + $invoiceQtysRefundLimits = []; + if ($creditmemo->getInvoiceId() !== null) { + $invoiceQtysRefunded = []; + $invoice = $this->invoiceRepository->get($creditmemo->getInvoiceId()); + foreach ($order->getCreditmemosCollection() as $createdCreditmemo) { + if ( + $createdCreditmemo->getState() != Creditmemo::STATE_CANCELED && + $createdCreditmemo->getInvoiceId() == $invoice->getId() + ) { + foreach ($createdCreditmemo->getAllItems() as $createdCreditmemoItem) { + $orderItemId = $createdCreditmemoItem->getOrderItem()->getId(); + if (isset($invoiceQtysRefunded[$orderItemId])) { + $invoiceQtysRefunded[$orderItemId] += $createdCreditmemoItem->getQty(); + } else { + $invoiceQtysRefunded[$orderItemId] = $createdCreditmemoItem->getQty(); + } + } + } + } + + foreach ($invoice->getItems() as $invoiceItem) { + $invoiceQtyCanBeRefunded = $invoiceItem->getQty(); + $orderItemId = $invoiceItem->getOrderItem()->getId(); + if (isset($invoiceQtysRefunded[$orderItemId])) { + $invoiceQtyCanBeRefunded = $invoiceQtyCanBeRefunded - $invoiceQtysRefunded[$orderItemId]; + } + $invoiceQtysRefundLimits[$orderItemId] = $invoiceQtyCanBeRefunded; + } + } + + return $invoiceQtysRefundLimits; + } + + /** + * @param OrderInterface $order + * @return OrderItemInterface[] + */ + private function getOrderItems(OrderInterface $order) + { + $orderItemsById = []; + foreach ($order->getItems() as $item) { + $orderItemsById[$item->getItemId()] = $item; + } + + return $orderItemsById; + } + + /** + * @param Item $orderItem + * @param int $qty + * @return bool + */ + private function isQtyAvailable(Item $orderItem, $qty) + { + return $qty <= $orderItem->getQtyToRefund() || $orderItem->isDummy(); + } + + /** + * Check if order item can be refunded + * + * @param \Magento\Sales\Model\Order\Item $item + * @param double $qty + * @param array $invoiceQtysRefundLimits + * @return bool + */ + private function canRefundItem(\Magento\Sales\Model\Order\Item $item, $qty, array $invoiceQtysRefundLimits) + { + if ($item->isDummy()) { + return $this->canRefundDummyItem($item, $qty, $invoiceQtysRefundLimits); + } + + return $this->canRefundNoDummyItem($item, $invoiceQtysRefundLimits); + } + + /** + * Check if no dummy order item can be refunded + * + * @param \Magento\Sales\Model\Order\Item $item + * @param array $invoiceQtysRefundLimits + * @return bool + */ + private function canRefundNoDummyItem(\Magento\Sales\Model\Order\Item $item, array $invoiceQtysRefundLimits = []) + { + if ($item->getQtyToRefund() < 0) { + return false; + } + if (isset($invoiceQtysRefundLimits[$item->getId()])) { + return $invoiceQtysRefundLimits[$item->getId()] > 0; + } + return true; + } + + /** + * @param Item $item + * @param int $qty + * @param array $invoiceQtysRefundLimits + * @return bool + */ + private function canRefundDummyItem(\Magento\Sales\Model\Order\Item $item, $qty, array $invoiceQtysRefundLimits) + { + if ($item->getHasChildren()) { + foreach ($item->getChildrenItems() as $child) { + if ($this->canRefundRequestedQty($child, $qty, $invoiceQtysRefundLimits)) { + return true; + } + } + } elseif ($item->getParentItem()) { + return $this->canRefundRequestedQty($item->getParentItem(), $qty, $invoiceQtysRefundLimits); + } + + return false; + } + + /** + * @param Item $item + * @param int $qty + * @param array $invoiceQtysRefundLimits + * @return bool + */ + private function canRefundRequestedQty( + \Magento\Sales\Model\Order\Item $item, + $qty, + array $invoiceQtysRefundLimits + ) { + return $qty === null ? $this->canRefundNoDummyItem($item, $invoiceQtysRefundLimits) : $qty > 0; + } +} diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/Validation/TotalsValidator.php b/app/code/Magento/Sales/Model/Order/Creditmemo/Validation/TotalsValidator.php new file mode 100644 index 0000000000000000000000000000000000000000..2cefc377b0674392b69c57d47d96ee418d57d788 --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Validation/TotalsValidator.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Creditmemo\Validation; + +use Magento\Framework\Pricing\PriceCurrencyInterface; +use Magento\Sales\Model\ValidatorInterface; + +/** + * Class TotalsValidator + */ +class TotalsValidator implements ValidatorInterface +{ + /** + * @var PriceCurrencyInterface + */ + private $priceCurrency; + + /** + * TotalsValidator constructor. + * + * @param PriceCurrencyInterface $priceCurrency + */ + public function __construct(PriceCurrencyInterface $priceCurrency) + { + $this->priceCurrency = $priceCurrency; + } + + /** + * @inheritDoc + */ + public function validate($entity) + { + $messages = []; + $baseOrderRefund = $this->priceCurrency->round( + $entity->getOrder()->getBaseTotalRefunded() + $entity->getBaseGrandTotal() + ); + if ($baseOrderRefund > $this->priceCurrency->round($entity->getOrder()->getBaseTotalPaid())) { + $baseAvailableRefund = $entity->getOrder()->getBaseTotalPaid() + - $entity->getOrder()->getBaseTotalRefunded(); + + $messages[] = __( + 'The most money available to refund is %1.', + $baseAvailableRefund + ); + } + + return $messages; + } +} diff --git a/app/code/Magento/Sales/Model/Order/CreditmemoDocumentFactory.php b/app/code/Magento/Sales/Model/Order/CreditmemoDocumentFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..469b226053cdd4e2b0658dcd596d7d740572f0c7 --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/CreditmemoDocumentFactory.php @@ -0,0 +1,151 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order; + +/** + * Class CreditmemoDocumentFactory + */ +class CreditmemoDocumentFactory +{ + /** + * @var \Magento\Sales\Model\Order\CreditmemoFactory + */ + private $creditmemoFactory; + + /** + * @var \Magento\Sales\Api\Data\CreditmemoCommentInterfaceFactory + */ + private $commentFactory; + + /** + * @var \Magento\Framework\EntityManager\HydratorPool + */ + private $hydratorPool; + + /** + * @var \Magento\Sales\Api\OrderRepositoryInterface + */ + private $orderRepository; + + /** + * CreditmemoDocumentFactory constructor. + * + * @param \Magento\Sales\Model\Order\CreditmemoFactory $creditmemoFactory + * @param \Magento\Sales\Api\Data\CreditmemoCommentInterfaceFactory $commentFactory + * @param \Magento\Framework\EntityManager\HydratorPool $hydratorPool + * @param \Magento\Sales\Api\OrderRepositoryInterface $orderRepository + */ + public function __construct( + \Magento\Sales\Model\Order\CreditmemoFactory $creditmemoFactory, + \Magento\Sales\Api\Data\CreditmemoCommentInterfaceFactory $commentFactory, + \Magento\Framework\EntityManager\HydratorPool $hydratorPool, + \Magento\Sales\Api\OrderRepositoryInterface $orderRepository + ) { + $this->creditmemoFactory = $creditmemoFactory; + $this->commentFactory = $commentFactory; + $this->hydratorPool = $hydratorPool; + $this->orderRepository = $orderRepository; + } + + /** + * Get array with original data for new Creditmemo document + * + * @param \Magento\Sales\Api\Data\CreditmemoItemCreationInterface[] $items + * @param \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface|null $arguments + * @return array + */ + private function getCreditmemoCreationData( + array $items = [], + \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface $arguments = null + ) { + $data = ['qtys' => []]; + foreach ($items as $item) { + $data['qtys'][$item->getOrderItemId()] = $item->getQty(); + } + if ($arguments) { + $hydrator = $this->hydratorPool->getHydrator( + \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface::class + ); + $data = array_merge($hydrator->extract($arguments), $data); + } + return $data; + } + + /** + * Attach comment to the Creditmemo document. + * + * @param \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo + * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment + * @param bool $appendComment + * @return \Magento\Sales\Api\Data\CreditmemoInterface + */ + private function attachComment( + \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo, + \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment, + $appendComment = false + ) { + $commentData = $this->hydratorPool->getHydrator( + \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface::class + )->extract($comment); + $comment = $this->commentFactory->create(['data' => $commentData]); + $comment->setParentId($creditmemo->getEntityId()) + ->setStoreId($creditmemo->getStoreId()) + ->setCreditmemo($creditmemo) + ->setIsCustomerNotified($appendComment); + $creditmemo->setComments([$comment]); + return $creditmemo; + + } + + /** + * Create new Creditmemo + * @param \Magento\Sales\Api\Data\OrderInterface $order + * @param \Magento\Sales\Api\Data\CreditmemoItemCreationInterface[] $items + * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface|null $comment + * @param bool|null $appendComment + * @param \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface|null $arguments + * @return \Magento\Sales\Api\Data\CreditmemoInterface + */ + public function createFromOrder( + \Magento\Sales\Api\Data\OrderInterface $order, + array $items = [], + \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null, + $appendComment = false, + \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface $arguments = null + ) { + $data = $this->getCreditmemoCreationData($items, $arguments); + $creditmemo = $this->creditmemoFactory->createByOrder($order, $data); + if ($comment) { + $creditmemo = $this->attachComment($creditmemo, $comment, $appendComment); + } + return $creditmemo; + } + + /** + * @param \Magento\Sales\Api\Data\InvoiceInterface $invoice + * @param \Magento\Sales\Api\Data\CreditmemoItemCreationInterface[] $items + * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface|null $comment + * @param bool|null $appendComment + * @param \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface|null $arguments + * @return \Magento\Sales\Api\Data\CreditmemoInterface + */ + public function createFromInvoice( + \Magento\Sales\Api\Data\InvoiceInterface $invoice, + array $items = [], + \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null, + $appendComment = false, + \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface $arguments = null + ) { + $data = $this->getCreditmemoCreationData($items, $arguments); + /** @var $invoice \Magento\Sales\Model\Order\Invoice */ + $invoice->setOrder($this->orderRepository->get($invoice->getOrderId())); + $creditmemo = $this->creditmemoFactory->createByInvoice($invoice, $data); + if ($comment) { + $creditmemo = $this->attachComment($creditmemo, $comment, $appendComment); + } + return $creditmemo; + } +} diff --git a/app/code/Magento/Sales/Model/Order/Invoice/Validation/CanRefund.php b/app/code/Magento/Sales/Model/Order/Invoice/Validation/CanRefund.php new file mode 100644 index 0000000000000000000000000000000000000000..8e68cade3caa7e1a83ad1b3522eab2d43d2672cc --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Invoice/Validation/CanRefund.php @@ -0,0 +1,106 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Invoice\Validation; + +use Magento\Payment\Model\InfoInterface; +use Magento\Payment\Model\MethodInterface; +use Magento\Sales\Api\Data\InvoiceInterface; +use Magento\Sales\Api\OrderPaymentRepositoryInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order\Invoice; +use Magento\Sales\Model\ValidatorInterface; + +/** + * Class CanRefund + */ +class CanRefund implements ValidatorInterface +{ + /** + * @var OrderPaymentRepositoryInterface + */ + private $paymentRepository; + + /** + * @var OrderRepositoryInterface + */ + private $orderRepository; + + /** + * CanRefund constructor. + * + * @param OrderPaymentRepositoryInterface $paymentRepository + * @param OrderRepositoryInterface $orderRepository + */ + public function __construct( + OrderPaymentRepositoryInterface $paymentRepository, + OrderRepositoryInterface $orderRepository + ) { + $this->paymentRepository = $paymentRepository; + $this->orderRepository = $orderRepository; + } + + /** + * @inheritdoc + */ + public function validate($entity) + { + if ( + $entity->getState() == Invoice::STATE_PAID && + $this->isGrandTotalEnoughToRefund($entity) && + $this->isPaymentAllowRefund($entity) + ) { + return []; + } + + return [__('We can\'t create creditmemo for the invoice.')]; + } + + /** + * @param InvoiceInterface $invoice + * @return bool + */ + private function isPaymentAllowRefund(InvoiceInterface $invoice) + { + $order = $this->orderRepository->get($invoice->getOrderId()); + $payment = $order->getPayment(); + if (!$payment instanceof InfoInterface) { + return false; + } + $method = $payment->getMethodInstance(); + return $this->canPartialRefund($method, $payment) || $this->canFullRefund($invoice, $method); + } + + /** + * @param InvoiceInterface $entity + * @return bool + */ + private function isGrandTotalEnoughToRefund(InvoiceInterface $entity) + { + return abs($entity->getBaseGrandTotal() - $entity->getBaseTotalRefunded()) >= .0001; + } + + /** + * @param MethodInterface $method + * @param InfoInterface $payment + * @return bool + */ + private function canPartialRefund(MethodInterface $method, InfoInterface $payment) + { + return $method->canRefund() && + $method->canRefundPartialPerInvoice() && + $payment->getAmountPaid() > $payment->getAmountRefunded(); + } + + /** + * @param InvoiceInterface $invoice + * @param MethodInterface $method + * @return bool + */ + private function canFullRefund(InvoiceInterface $invoice, MethodInterface $method) + { + return $method->canRefund() && !$invoice->getIsUsedForRefund(); + } +} diff --git a/app/code/Magento/Sales/Model/Order/Validation/CanRefund.php b/app/code/Magento/Sales/Model/Order/Validation/CanRefund.php new file mode 100644 index 0000000000000000000000000000000000000000..c6fc1a0d705e8d49f49918f7919ec5cfaa3ee199 --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Validation/CanRefund.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Validation; + +use Magento\Framework\Pricing\PriceCurrencyInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\ValidatorInterface; + +/** + * Class CanRefund + */ +class CanRefund implements ValidatorInterface +{ + /** + * @var PriceCurrencyInterface + */ + private $priceCurrency; + + /** + * CanRefund constructor. + * + * @param PriceCurrencyInterface $priceCurrency + */ + public function __construct(PriceCurrencyInterface $priceCurrency) + { + $this->priceCurrency = $priceCurrency; + } + + /** + * @inheritdoc + */ + public function validate($entity) + { + $messages = []; + if ($entity->getState() === Order::STATE_PAYMENT_REVIEW || + $entity->getState() === Order::STATE_HOLDED || + $entity->getState() === Order::STATE_CANCELED || + $entity->getState() === Order::STATE_CLOSED + ) { + $messages[] = __( + 'A creditmemo can not be created when an order has a status of %1', + $entity->getStatus() + ); + } elseif (!$this->isTotalPaidEnoughForRefund($entity)) { + $messages[] = __('The order does not allow a creditmemo to be created.'); + } + + return $messages; + } + + /** + * We can have problem with float in php (on some server $a=762.73;$b=762.73; $a-$b!=0) + * for this we have additional diapason for 0 + * TotalPaid - contains amount, that were not rounded. + * + * @param OrderInterface $order + * @return bool + */ + private function isTotalPaidEnoughForRefund(OrderInterface $order) + { + return !abs($this->priceCurrency->round($order->getTotalPaid()) - $order->getTotalRefunded()) < .0001; + } +} diff --git a/app/code/Magento/Sales/Model/RefundInvoice.php b/app/code/Magento/Sales/Model/RefundInvoice.php new file mode 100644 index 0000000000000000000000000000000000000000..37bf915aa505c6a757208b40a2c888515c6c8bc0 --- /dev/null +++ b/app/code/Magento/Sales/Model/RefundInvoice.php @@ -0,0 +1,252 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model; + +use Magento\Framework\App\ResourceConnection; +use Magento\Sales\Api\CreditmemoRepositoryInterface; +use Magento\Sales\Api\InvoiceRepositoryInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Api\RefundInvoiceInterface; +use Magento\Sales\Model\Order\Config as OrderConfig; +use Magento\Sales\Model\Order\Creditmemo\CreditmemoValidatorInterface; +use Magento\Sales\Model\Order\Creditmemo\ItemCreationValidatorInterface; +use Magento\Sales\Model\Order\Creditmemo\NotifierInterface; +use Magento\Sales\Model\Order\Creditmemo\Item\Validation\CreationQuantityValidator; +use Magento\Sales\Model\Order\Creditmemo\Validation\QuantityValidator; +use Magento\Sales\Model\Order\Creditmemo\Validation\TotalsValidator; +use Magento\Sales\Model\Order\CreditmemoDocumentFactory; +use Magento\Sales\Model\Order\Invoice\InvoiceValidatorInterface; +use Magento\Sales\Model\Order\OrderStateResolverInterface; +use Magento\Sales\Model\Order\OrderValidatorInterface; +use Magento\Sales\Model\Order\PaymentAdapterInterface; +use Magento\Sales\Model\Order\Validation\CanRefund; +use Psr\Log\LoggerInterface; + +/** + * Class RefundInvoice + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class RefundInvoice implements RefundInvoiceInterface +{ + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var OrderStateResolverInterface + */ + private $orderStateResolver; + + /** + * @var OrderRepositoryInterface + */ + private $orderRepository; + + /** + * @var InvoiceRepositoryInterface + */ + private $invoiceRepository; + + /** + * @var OrderValidatorInterface + */ + private $orderValidator; + + /** + * @var InvoiceValidatorInterface + */ + private $invoiceValidator; + + /** + * @var CreditmemoValidatorInterface + */ + private $creditmemoValidator; + + /** + * @var ItemCreationValidatorInterface + */ + private $itemCreationValidator; + + /** + * @var CreditmemoRepositoryInterface + */ + private $creditmemoRepository; + + /** + * @var Order\PaymentAdapterInterface + */ + private $paymentAdapter; + + /** + * @var CreditmemoDocumentFactory + */ + private $creditmemoDocumentFactory; + + /** + * @var Order\Creditmemo\NotifierInterface + */ + private $notifier; + + /** + * @var OrderConfig + */ + private $config; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * RefundInvoice constructor. + * + * @param ResourceConnection $resourceConnection + * @param OrderStateResolverInterface $orderStateResolver + * @param OrderRepositoryInterface $orderRepository + * @param InvoiceRepositoryInterface $invoiceRepository + * @param OrderValidatorInterface $orderValidator + * @param InvoiceValidatorInterface $invoiceValidator + * @param CreditmemoValidatorInterface $creditmemoValidator + * @param Order\Creditmemo\ItemCreationValidatorInterface $itemCreationValidator + * @param CreditmemoRepositoryInterface $creditmemoRepository + * @param PaymentAdapterInterface $paymentAdapter + * @param CreditmemoDocumentFactory $creditmemoDocumentFactory + * @param NotifierInterface $notifier + * @param OrderConfig $config + * @param LoggerInterface $logger + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function __construct( + ResourceConnection $resourceConnection, + OrderStateResolverInterface $orderStateResolver, + OrderRepositoryInterface $orderRepository, + InvoiceRepositoryInterface $invoiceRepository, + OrderValidatorInterface $orderValidator, + InvoiceValidatorInterface $invoiceValidator, + CreditmemoValidatorInterface $creditmemoValidator, + ItemCreationValidatorInterface $itemCreationValidator, + CreditmemoRepositoryInterface $creditmemoRepository, + PaymentAdapterInterface $paymentAdapter, + CreditmemoDocumentFactory $creditmemoDocumentFactory, + NotifierInterface $notifier, + OrderConfig $config, + LoggerInterface $logger + ) { + $this->resourceConnection = $resourceConnection; + $this->orderStateResolver = $orderStateResolver; + $this->orderRepository = $orderRepository; + $this->invoiceRepository = $invoiceRepository; + $this->orderValidator = $orderValidator; + $this->creditmemoValidator = $creditmemoValidator; + $this->itemCreationValidator = $itemCreationValidator; + $this->creditmemoRepository = $creditmemoRepository; + $this->paymentAdapter = $paymentAdapter; + $this->creditmemoDocumentFactory = $creditmemoDocumentFactory; + $this->notifier = $notifier; + $this->config = $config; + $this->logger = $logger; + $this->invoiceValidator = $invoiceValidator; + } + + /** + * @inheritdoc + */ + public function execute( + $invoiceId, + array $items = [], + $isOnline = false, + $notify = false, + $appendComment = false, + \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null, + \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface $arguments = null + ) { + $connection = $this->resourceConnection->getConnection('sales'); + $invoice = $this->invoiceRepository->get($invoiceId); + $order = $this->orderRepository->get($invoice->getOrderId()); + $creditmemo = $this->creditmemoDocumentFactory->createFromInvoice( + $invoice, + $items, + $comment, + ($appendComment && $notify), + $arguments + ); + $orderValidationResult = $this->orderValidator->validate( + $order, + [ + CanRefund::class + ] + ); + $invoiceValidationResult = $this->invoiceValidator->validate( + $invoice, + [ + \Magento\Sales\Model\Order\Invoice\Validation\CanRefund::class + ] + ); + $creditmemoValidationResult = $this->creditmemoValidator->validate( + $creditmemo, + [ + QuantityValidator::class, + TotalsValidator::class + ] + ); + $itemsValidation = []; + foreach ($items as $item) { + $itemsValidation = array_merge( + $itemsValidation, + $this->itemCreationValidator->validate( + $item, + [CreationQuantityValidator::class], + $order + ) + ); + } + $validationMessages = array_merge( + $orderValidationResult, + $invoiceValidationResult, + $creditmemoValidationResult, + $itemsValidation + ); + if (!empty($validationMessages )) { + throw new \Magento\Sales\Exception\DocumentValidationException( + __("Creditmemo Document Validation Error(s):\n" . implode("\n", $validationMessages)) + ); + } + $connection->beginTransaction(); + try { + $creditmemo->setState(\Magento\Sales\Model\Order\Creditmemo::STATE_REFUNDED); + $order = $this->paymentAdapter->refund($creditmemo, $order, $isOnline); + $order->setState( + $this->orderStateResolver->getStateForOrder($order, []) + ); + $order->setStatus($this->config->getStateDefaultStatus($order->getState())); + if (!$isOnline) { + $invoice->setIsUsedForRefund(true); + $invoice->setBaseTotalRefunded( + $invoice->getBaseTotalRefunded() + $creditmemo->getBaseGrandTotal() + ); + } + $this->invoiceRepository->save($invoice); + $order = $this->orderRepository->save($order); + $creditmemo = $this->creditmemoRepository->save($creditmemo); + $connection->commit(); + } catch (\Exception $e) { + $this->logger->critical($e); + $connection->rollBack(); + throw new \Magento\Sales\Exception\CouldNotRefundException( + __('Could not save a Creditmemo, see error log for details') + ); + } + if ($notify) { + if (!$appendComment) { + $comment = null; + } + $this->notifier->notify($order, $creditmemo, $comment); + } + + return $creditmemo->getEntityId(); + } +} diff --git a/app/code/Magento/Sales/Model/RefundOrder.php b/app/code/Magento/Sales/Model/RefundOrder.php new file mode 100644 index 0000000000000000000000000000000000000000..65d81df0d1de9fc4d2b2a7803369a0eda440ba12 --- /dev/null +++ b/app/code/Magento/Sales/Model/RefundOrder.php @@ -0,0 +1,214 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model; + +use Magento\Framework\App\ResourceConnection; +use Magento\Sales\Api\CreditmemoRepositoryInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Api\RefundOrderInterface; +use Magento\Sales\Model\Order\Config as OrderConfig; +use Magento\Sales\Model\Order\Creditmemo\CreditmemoValidatorInterface; +use Magento\Sales\Model\Order\Creditmemo\ItemCreationValidatorInterface; +use Magento\Sales\Model\Order\Creditmemo\NotifierInterface; +use Magento\Sales\Model\Order\Creditmemo\Item\Validation\CreationQuantityValidator; +use Magento\Sales\Model\Order\Creditmemo\Validation\QuantityValidator; +use Magento\Sales\Model\Order\Creditmemo\Validation\TotalsValidator; +use Magento\Sales\Model\Order\CreditmemoDocumentFactory; +use Magento\Sales\Model\Order\OrderStateResolverInterface; +use Magento\Sales\Model\Order\OrderValidatorInterface; +use Magento\Sales\Model\Order\PaymentAdapterInterface; +use Magento\Sales\Model\Order\Validation\CanRefund; +use Psr\Log\LoggerInterface; + +/** + * Class RefundOrder + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class RefundOrder implements RefundOrderInterface +{ + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var OrderStateResolverInterface + */ + private $orderStateResolver; + + /** + * @var OrderRepositoryInterface + */ + private $orderRepository; + + /** + * @var OrderValidatorInterface + */ + private $orderValidator; + + /** + * @var CreditmemoValidatorInterface + */ + private $creditmemoValidator; + + /** + * @var Order\Creditmemo\ItemCreationValidatorInterface + */ + private $itemCreationValidator; + + /** + * @var CreditmemoRepositoryInterface + */ + private $creditmemoRepository; + + /** + * @var Order\PaymentAdapterInterface + */ + private $paymentAdapter; + + /** + * @var CreditmemoDocumentFactory + */ + private $creditmemoDocumentFactory; + + /** + * @var Order\Creditmemo\NotifierInterface + */ + private $notifier; + + /** + * @var OrderConfig + */ + private $config; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * RefundOrder constructor. + * @param ResourceConnection $resourceConnection + * @param OrderStateResolverInterface $orderStateResolver + * @param OrderRepositoryInterface $orderRepository + * @param OrderValidatorInterface $orderValidator + * @param CreditmemoValidatorInterface $creditmemoValidator + * @param ItemCreationValidatorInterface $itemCreationValidator + * @param CreditmemoRepositoryInterface $creditmemoRepository + * @param PaymentAdapterInterface $paymentAdapter + * @param CreditmemoDocumentFactory $creditmemoDocumentFactory + * @param NotifierInterface $notifier + * @param OrderConfig $config + * @param LoggerInterface $logger + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function __construct( + ResourceConnection $resourceConnection, + OrderStateResolverInterface $orderStateResolver, + OrderRepositoryInterface $orderRepository, + OrderValidatorInterface $orderValidator, + CreditmemoValidatorInterface $creditmemoValidator, + ItemCreationValidatorInterface $itemCreationValidator, + CreditmemoRepositoryInterface $creditmemoRepository, + PaymentAdapterInterface $paymentAdapter, + CreditmemoDocumentFactory $creditmemoDocumentFactory, + NotifierInterface $notifier, + OrderConfig $config, + LoggerInterface $logger + ) { + $this->resourceConnection = $resourceConnection; + $this->orderStateResolver = $orderStateResolver; + $this->orderRepository = $orderRepository; + $this->orderValidator = $orderValidator; + $this->creditmemoValidator = $creditmemoValidator; + $this->itemCreationValidator = $itemCreationValidator; + $this->creditmemoRepository = $creditmemoRepository; + $this->paymentAdapter = $paymentAdapter; + $this->creditmemoDocumentFactory = $creditmemoDocumentFactory; + $this->notifier = $notifier; + $this->config = $config; + $this->logger = $logger; + } + + /** + * @inheritdoc + */ + public function execute( + $orderId, + array $items = [], + $notify = false, + $appendComment = false, + \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null, + \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface $arguments = null + ) { + $connection = $this->resourceConnection->getConnection('sales'); + $order = $this->orderRepository->get($orderId); + $creditmemo = $this->creditmemoDocumentFactory->createFromOrder( + $order, + $items, + $comment, + ($appendComment && $notify), + $arguments + ); + $orderValidationResult = $this->orderValidator->validate( + $order, + [ + CanRefund::class + ] + ); + $creditmemoValidationResult = $this->creditmemoValidator->validate( + $creditmemo, + [ + QuantityValidator::class, + TotalsValidator::class + ] + ); + $itemsValidation = []; + foreach ($items as $item) { + $itemsValidation = array_merge( + $itemsValidation, + $this->itemCreationValidator->validate( + $item, + [CreationQuantityValidator::class], + $order + ) + ); + } + $validationMessages = array_merge($orderValidationResult, $creditmemoValidationResult, $itemsValidation); + if (!empty($validationMessages)) { + throw new \Magento\Sales\Exception\DocumentValidationException( + __("Creditmemo Document Validation Error(s):\n" . implode("\n", $validationMessages)) + ); + } + $connection->beginTransaction(); + try { + $creditmemo->setState(\Magento\Sales\Model\Order\Creditmemo::STATE_REFUNDED); + $order = $this->paymentAdapter->refund($creditmemo, $order); + $order->setState( + $this->orderStateResolver->getStateForOrder($order, []) + ); + $order->setStatus($this->config->getStateDefaultStatus($order->getState())); + + $order = $this->orderRepository->save($order); + $creditmemo = $this->creditmemoRepository->save($creditmemo); + $connection->commit(); + } catch (\Exception $e) { + $this->logger->critical($e); + $connection->rollBack(); + throw new \Magento\Sales\Exception\CouldNotRefundException( + __('Could not save a Creditmemo, see error log for details') + ); + } + if ($notify) { + if (!$appendComment) { + $comment = null; + } + $this->notifier->notify($order, $creditmemo, $comment); + } + + return $creditmemo->getEntityId(); + } +} diff --git a/app/code/Magento/Sales/Model/Validator.php b/app/code/Magento/Sales/Model/Validator.php index b8d57ded2970209d5d2184945fbb22f13de47dbd..10aa0735b952e99e0d889b27ff378411d0d1d6d1 100644 --- a/app/code/Magento/Sales/Model/Validator.php +++ b/app/code/Magento/Sales/Model/Validator.php @@ -33,14 +33,20 @@ class Validator /** * @param object $entity * @param ValidatorInterface[] $validators - * @return string[] + * @param object|null $context + * @return \string[] * @throws ConfigurationMismatchException */ - public function validate($entity, array $validators) + public function validate($entity, array $validators, $context = null) { $messages = []; + $validatorArguments = []; + if ($context !== null) { + $validatorArguments['context'] = $context; + } + foreach ($validators as $validatorName) { - $validator = $this->objectManager->get($validatorName); + $validator = $this->objectManager->create($validatorName, $validatorArguments); if (!$validator instanceof ValidatorInterface) { throw new ConfigurationMismatchException( __( diff --git a/app/code/Magento/Sales/Model/ValidatorInterface.php b/app/code/Magento/Sales/Model/ValidatorInterface.php index 1882782e314f7b47b507c042697f223eadbf36b7..4489af44f40361f036e92671f776af479e45a4d7 100644 --- a/app/code/Magento/Sales/Model/ValidatorInterface.php +++ b/app/code/Magento/Sales/Model/ValidatorInterface.php @@ -15,7 +15,7 @@ interface ValidatorInterface { /** * @param object $entity - * @return array + * @return \Magento\Framework\Phrase[] * @throws DocumentValidationException * @throws NoSuchEntityException */ diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Item/Validation/CreateQuantityValidatorTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Item/Validation/CreateQuantityValidatorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1466a0f4fc9fe8d44841bcf4d31d4a34ab8abe44 --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Item/Validation/CreateQuantityValidatorTest.php @@ -0,0 +1,118 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Test\Unit\Model\Order\Creditmemo\Item\Validation; + +use Magento\Sales\Api\OrderItemRepositoryInterface; +use Magento\Sales\Model\Order\Creditmemo\Item\Validation\CreationQuantityValidator; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Model\Order\Item; + +/** + * Class CreateQuantityValidatorTest + */ +class CreateQuantityValidatorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var OrderItemRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $orderItemRepositoryMock; + + /** + * @var Item|\PHPUnit_Framework_MockObject_MockObject + */ + private $orderItemMock; + + /** + * @var CreationQuantityValidator|\PHPUnit_Framework_MockObject_MockObject + */ + private $createQuantityValidator; + + /** + * @var OrderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $contexMock; + + /** + * @var \stdClass|\PHPUnit_Framework_MockObject_MockObject + */ + private $entity; + + protected function setUp() + { + $this->orderItemRepositoryMock = $this->getMockBuilder(OrderItemRepositoryInterface::class) + ->disableOriginalConstructor() + ->setMethods(['get']) + ->getMockForAbstractClass(); + + $this->orderItemMock = $this->getMockBuilder(Item::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->entity = $this->getMockBuilder(\stdClass::class) + ->disableOriginalConstructor() + ->setMethods(['getOrderItemId', 'getQty']) + ->getMock(); + } + + /** + * @dataProvider dataProvider + */ + public function testValidateCreditMemoProductItems($orderItemId, $expectedResult, $withContext = false) + { + if ($orderItemId) { + $this->entity->expects($this->once()) + ->method('getOrderItemId') + ->willReturn($orderItemId); + + $this->orderItemRepositoryMock->expects($this->once()) + ->method('get') + ->with($orderItemId) + ->willReturn($this->orderItemMock); + } else { + $this->entity->expects($this->once()) + ->method('getOrderItemId') + ->willThrowException(new NoSuchEntityException()); + } + + $this->contexMock = null; + if ($withContext) { + $this->contexMock = $this->getMockBuilder(OrderInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->entity->expects($this->once()) + ->method('getQty') + ->willReturn(11); + } + + $this->createQuantityValidator = new CreationQuantityValidator( + $this->orderItemRepositoryMock, + $this->contexMock + ); + + $this->assertEquals($expectedResult, $this->createQuantityValidator->validate($this->entity)); + } + + public function dataProvider() + { + return [ + 'testValidateCreditMemoProductItems' => [ + 1, + [__('The creditmemo contains product item that is not part of the original order.')], + ], + 'testValidateWithException' => [ + null, + [__('The creditmemo contains product item that is not part of the original order.')] + ], + 'testValidateWithContext' => [ + 1, + [__('The quantity to refund must not be greater than the unrefunded quantity.')], + true + ], + ]; + } +} diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/RefundOperationTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/RefundOperationTest.php index 1b8d40d0427165f5c8587deb422b1ac9e3f4dbd2..2c5173507d997797c8d47cef8d7c70a6ceb56370 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/RefundOperationTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/RefundOperationTest.php @@ -50,7 +50,7 @@ class RefundOperationTest extends \PHPUnit_Framework_TestCase $this->creditmemoMock = $this->getMockBuilder(\Magento\Sales\Api\Data\CreditmemoInterface::class) ->disableOriginalConstructor() - ->setMethods(['getBaseCost']) + ->setMethods(['getBaseCost', 'setDoTransaction']) ->getMockForAbstractClass(); $this->paymentMock = $this->getMockBuilder(\Magento\Framework\Pricing\PriceCurrencyInterface::class) @@ -142,6 +142,7 @@ class RefundOperationTest extends \PHPUnit_Framework_TestCase public function testExecuteOffline($amounts) { $orderId = 1; + $online = false; $this->creditmemoMock->expects($this->once()) ->method('getState') ->willReturn(Creditmemo::STATE_REFUNDED); @@ -174,8 +175,17 @@ class RefundOperationTest extends \PHPUnit_Framework_TestCase $this->orderMock->expects($this->never()) ->method('setTotalOnlineRefunded'); - $this->orderMock->expects($this->never()) - ->method('getPayment'); + $this->orderMock->expects($this->once()) + ->method('getPayment') + ->willReturn($this->paymentMock); + + $this->paymentMock->expects($this->once()) + ->method('refund') + ->with($this->creditmemoMock); + + $this->creditmemoMock->expects($this->once()) + ->method('setDoTransaction') + ->with($online); $this->eventManagerMock->expects($this->once()) ->method('dispatch') @@ -186,7 +196,7 @@ class RefundOperationTest extends \PHPUnit_Framework_TestCase $this->assertEquals( $this->orderMock, - $this->subject->execute($this->creditmemoMock, $this->orderMock, false) + $this->subject->execute($this->creditmemoMock, $this->orderMock, $online) ); } @@ -197,6 +207,7 @@ class RefundOperationTest extends \PHPUnit_Framework_TestCase public function testExecuteOnline($amounts) { $orderId = 1; + $online = true; $this->creditmemoMock->expects($this->once()) ->method('getState') ->willReturn(Creditmemo::STATE_REFUNDED); @@ -229,6 +240,10 @@ class RefundOperationTest extends \PHPUnit_Framework_TestCase $this->orderMock->expects($this->never()) ->method('setTotalOfflineRefunded'); + $this->creditmemoMock->expects($this->once()) + ->method('setDoTransaction') + ->with($online); + $this->orderMock->expects($this->once()) ->method('getPayment') ->willReturn($this->paymentMock); @@ -238,7 +253,7 @@ class RefundOperationTest extends \PHPUnit_Framework_TestCase $this->assertEquals( $this->orderMock, - $this->subject->execute($this->creditmemoMock, $this->orderMock, true) + $this->subject->execute($this->creditmemoMock, $this->orderMock, $online) ); } diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d1fe28b21b59b47558c0e44d0b55eacc0f6b847c --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php @@ -0,0 +1,361 @@ +<?php + +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Test\Unit\Model\Order\Creditmemo\Sender; + +/** + * Unit test for email notification sender for Creditmemo. + * @SuppressWarnings(PHPMD.TooManyFields) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class EmailSenderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Sales\Model\Order\Creditmemo\Sender\EmailSender + */ + private $subject; + + /** + * @var \Magento\Sales\Model\Order|\PHPUnit_Framework_MockObject_MockObject + */ + private $orderMock; + + /** + * @var \Magento\Store\Model\Store|\PHPUnit_Framework_MockObject_MockObject + */ + private $storeMock; + + /** + * @var \Magento\Sales\Model\Order\Email\Sender|\PHPUnit_Framework_MockObject_MockObject + */ + private $senderMock; + + /** + * @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $loggerMock; + + /** + * @var \Magento\Sales\Api\Data\CreditmemoInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoMock; + + /** + * @var \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $commentMock; + + /** + * @var \Magento\Sales\Model\Order\Address|\PHPUnit_Framework_MockObject_MockObject + */ + private $addressMock; + + /** + * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $globalConfigMock; + + /** + * @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $eventManagerMock; + + /** + * @var \Magento\Payment\Model\Info|\PHPUnit_Framework_MockObject_MockObject + */ + private $paymentInfoMock; + + /** + * @var \Magento\Payment\Helper\Data|\PHPUnit_Framework_MockObject_MockObject + */ + private $paymentHelperMock; + + /** + * @var \Magento\Sales\Model\ResourceModel\Order\Creditmemo|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoResourceMock; + + /** + * @var \Magento\Sales\Model\Order\Address\Renderer|\PHPUnit_Framework_MockObject_MockObject + */ + private $addressRendererMock; + + /** + * @var \Magento\Sales\Model\Order\Email\Container\Template|\PHPUnit_Framework_MockObject_MockObject + */ + private $templateContainerMock; + + /** + * @var \Magento\Sales\Model\Order\Email\Container\CreditmemoIdentity|\PHPUnit_Framework_MockObject_MockObject + */ + private $identityContainerMock; + + /** + * @var \Magento\Sales\Model\Order\Email\SenderBuilderFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $senderBuilderFactoryMock; + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + protected function setUp() + { + $this->orderMock = $this->getMockBuilder(\Magento\Sales\Model\Order::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class) + ->setMethods(['getStoreId']) + ->disableOriginalConstructor() + ->getMock(); + + $this->storeMock->expects($this->any()) + ->method('getStoreId') + ->willReturn(1); + $this->orderMock->expects($this->any()) + ->method('getStore') + ->willReturn($this->storeMock); + + $this->senderMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Email\Sender::class) + ->disableOriginalConstructor() + ->setMethods(['send', 'sendCopyTo']) + ->getMock(); + + $this->loggerMock = $this->getMockBuilder(\Psr\Log\LoggerInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->creditmemoMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Creditmemo::class) + ->disableOriginalConstructor() + ->setMethods(['setSendEmail', 'setEmailSent']) + ->getMock(); + + $this->commentMock = $this->getMockBuilder(\Magento\Sales\Api\Data\CreditmemoCommentCreationInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->commentMock->expects($this->any()) + ->method('getComment') + ->willReturn('Comment text'); + + $this->addressMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Address::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->orderMock->expects($this->any()) + ->method('getBillingAddress') + ->willReturn($this->addressMock); + $this->orderMock->expects($this->any()) + ->method('getShippingAddress') + ->willReturn($this->addressMock); + + $this->globalConfigMock = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->eventManagerMock = $this->getMockBuilder(\Magento\Framework\Event\ManagerInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->paymentInfoMock = $this->getMockBuilder(\Magento\Payment\Model\Info::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->orderMock->expects($this->any()) + ->method('getPayment') + ->willReturn($this->paymentInfoMock); + + $this->paymentHelperMock = $this->getMockBuilder(\Magento\Payment\Helper\Data::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->paymentHelperMock->expects($this->any()) + ->method('getInfoBlockHtml') + ->with($this->paymentInfoMock, 1) + ->willReturn('Payment Info Block'); + + $this->creditmemoResourceMock = $this->getMockBuilder( + \Magento\Sales\Model\ResourceModel\Order\Creditmemo::class + )->disableOriginalConstructor() + ->getMock(); + + $this->addressRendererMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Address\Renderer::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->addressRendererMock->expects($this->any()) + ->method('format') + ->with($this->addressMock, 'html') + ->willReturn('Formatted address'); + + $this->templateContainerMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Email\Container\Template::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->identityContainerMock = $this->getMockBuilder( + \Magento\Sales\Model\Order\Email\Container\CreditmemoIdentity::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->identityContainerMock->expects($this->any()) + ->method('getStore') + ->willReturn($this->storeMock); + + $this->senderBuilderFactoryMock = $this->getMockBuilder( + \Magento\Sales\Model\Order\Email\SenderBuilderFactory::class + ) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $this->subject = new \Magento\Sales\Model\Order\Creditmemo\Sender\EmailSender( + $this->templateContainerMock, + $this->identityContainerMock, + $this->senderBuilderFactoryMock, + $this->loggerMock, + $this->addressRendererMock, + $this->paymentHelperMock, + $this->creditmemoResourceMock, + $this->globalConfigMock, + $this->eventManagerMock + ); + } + + /** + * @param int $configValue + * @param bool $forceSyncMode + * @param bool $isComment + * @param bool $emailSendingResult + * + * @dataProvider sendDataProvider + * + * @return void + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testSend($configValue, $forceSyncMode, $isComment, $emailSendingResult) + { + $this->globalConfigMock->expects($this->once()) + ->method('getValue') + ->with('sales_email/general/async_sending') + ->willReturn($configValue); + + if (!$isComment) { + $this->commentMock = null; + } + + $this->creditmemoMock->expects($this->once()) + ->method('setSendEmail') + ->with(true); + + if (!$configValue || $forceSyncMode) { + $transport = [ + 'order' => $this->orderMock, + 'creditmemo' => $this->creditmemoMock, + 'comment' => $isComment ? 'Comment text' : '', + 'billing' => $this->addressMock, + 'payment_html' => 'Payment Info Block', + 'store' => $this->storeMock, + 'formattedShippingAddress' => 'Formatted address', + 'formattedBillingAddress' => 'Formatted address', + ]; + + $this->eventManagerMock->expects($this->once()) + ->method('dispatch') + ->with( + 'email_creditmemo_set_template_vars_before', + [ + 'sender' => $this->subject, + 'transport' => $transport, + ] + ); + + $this->templateContainerMock->expects($this->once()) + ->method('setTemplateVars') + ->with($transport); + + $this->identityContainerMock->expects($this->once()) + ->method('isEnabled') + ->willReturn($emailSendingResult); + + if ($emailSendingResult) { + $this->senderBuilderFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->senderMock); + + $this->senderMock->expects($this->once()) + ->method('send'); + + $this->senderMock->expects($this->once()) + ->method('sendCopyTo'); + + $this->creditmemoMock->expects($this->once()) + ->method('setEmailSent') + ->with(true); + + $this->creditmemoResourceMock->expects($this->once()) + ->method('saveAttribute') + ->with($this->creditmemoMock, ['send_email', 'email_sent']); + + $this->assertTrue( + $this->subject->send( + $this->orderMock, + $this->creditmemoMock, + $this->commentMock, + $forceSyncMode + ) + ); + } else { + $this->creditmemoResourceMock->expects($this->once()) + ->method('saveAttribute') + ->with($this->creditmemoMock, 'send_email'); + + $this->assertFalse( + $this->subject->send( + $this->orderMock, + $this->creditmemoMock, + $this->commentMock, + $forceSyncMode + ) + ); + } + } else { + $this->creditmemoMock->expects($this->once()) + ->method('setEmailSent') + ->with(null); + + $this->creditmemoResourceMock->expects($this->at(0)) + ->method('saveAttribute') + ->with($this->creditmemoMock, 'email_sent'); + $this->creditmemoResourceMock->expects($this->at(1)) + ->method('saveAttribute') + ->with($this->creditmemoMock, 'send_email'); + + $this->assertFalse( + $this->subject->send( + $this->orderMock, + $this->creditmemoMock, + $this->commentMock, + $forceSyncMode + ) + ); + } + } + + /** + * @return array + */ + public function sendDataProvider() + { + return [ + 'Successful sync sending with comment' => [0, false, true, true], + 'Successful sync sending without comment' => [0, false, false, true], + 'Failed sync sending with comment' => [0, false, true, false], + 'Successful forced sync sending with comment' => [1, true, true, true], + 'Async sending' => [1, false, false, false], + ]; + } +} diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Validation/QuantityValidatorTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Validation/QuantityValidatorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..838c062956c24ac900835e53e746998f1de1271d --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Validation/QuantityValidatorTest.php @@ -0,0 +1,247 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Test\Unit\Model\Order\Creditmemo\Validation; + +use Magento\Framework\Pricing\PriceCurrencyInterface; +use Magento\Sales\Api\Data\CreditmemoInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\InvoiceRepositoryInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order\Creditmemo\Validation\QuantityValidator; + +/** + * Class QuantityValidatorTest + */ +class QuantityValidatorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var OrderRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $orderRepositoryMock; + + /** + * @var InvoiceRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $invoiceRepositoryMock; + + /** + * @var QuantityValidator + */ + private $validator; + + /** + * @var PriceCurrencyInterface + */ + private $priceCurrencyMock; + + /** + * @inheritDoc + */ + protected function setUp() + { + $this->orderRepositoryMock = $this->getMockBuilder(OrderRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->invoiceRepositoryMock = $this->getMockBuilder(InvoiceRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->priceCurrencyMock = $this->getMockBuilder(PriceCurrencyInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->validator = new QuantityValidator( + $this->orderRepositoryMock, + $this->invoiceRepositoryMock, + $this->priceCurrencyMock + ); + } + + public function testValidateWithoutItems() + { + $creditmemoMock = $this->getMockBuilder(CreditmemoInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $creditmemoMock->expects($this->exactly(2))->method('getOrderId') + ->willReturn(1); + $creditmemoMock->expects($this->once())->method('getItems') + ->willReturn([]); + $orderMock = $this->getMockBuilder(OrderInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $orderMock->expects($this->once())->method('getItems') + ->willReturn([]); + + $this->orderRepositoryMock->expects($this->once()) + ->method('get') + ->with(1) + ->willReturn($orderMock); + $creditmemoMock->expects($this->once())->method('getGrandTotal') + ->willReturn(0); + $this->assertEquals( + [ + __('The credit memo\'s total must be positive.') + ], + $this->validator->validate($creditmemoMock) + ); + } + + public function testValidateWithoutOrder() + { + $creditmemoMock = $this->getMockBuilder(CreditmemoInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $creditmemoMock->expects($this->once())->method('getOrderId') + ->willReturn(null); + $creditmemoMock->expects($this->never())->method('getItems'); + $this->assertEquals( + [__('Order Id is required for creditmemo document')], + $this->validator->validate($creditmemoMock) + ); + } + + public function testValidateWithWrongItemId() + { + $orderId = 1; + $orderItemId = 1; + $creditmemoMock = $this->getMockBuilder(CreditmemoInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $creditmemoMock->expects($this->exactly(2))->method('getOrderId') + ->willReturn($orderId); + $creditmemoItemMock = $this->getMockBuilder( + \Magento\Sales\Api\Data\CreditmemoItemInterface::class + )->disableOriginalConstructor() + ->getMockForAbstractClass(); + $creditmemoItemMock->expects($this->once())->method('getOrderItemId') + ->willReturn($orderItemId); + $creditmemoItemSku = 'sku'; + $creditmemoItemMock->expects($this->once())->method('getSku') + ->willReturn($creditmemoItemSku); + $creditmemoMock->expects($this->exactly(1))->method('getItems') + ->willReturn([$creditmemoItemMock]); + + $orderMock = $this->getMockBuilder(OrderInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $orderMock->expects($this->once())->method('getItems') + ->willReturn([]); + + $this->orderRepositoryMock->expects($this->once()) + ->method('get') + ->with($orderId) + ->willReturn($orderMock); + $creditmemoMock->expects($this->once())->method('getGrandTotal') + ->willReturn(12); + + $this->assertEquals( + [ + __( + 'The creditmemo contains product SKU "%1" that is not part of the original order.', + $creditmemoItemSku + ), + __('You can\'t create a creditmemo without products.') + ], + $this->validator->validate($creditmemoMock) + ); + } + + /** + * @param int $orderId + * @param int $orderItemId + * @param int $qtyToRequest + * @param int $qtyToRefund + * @param string $sku + * @param array $expected + * @dataProvider dataProviderForValidateQty + */ + public function testValidate($orderId, $orderItemId, $qtyToRequest, $qtyToRefund, $sku, $total, array $expected) + { + $creditmemoMock = $this->getMockBuilder(CreditmemoInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $creditmemoMock->expects($this->exactly(2))->method('getOrderId') + ->willReturn($orderId); + $creditmemoMock->expects($this->once())->method('getGrandTotal') + ->willReturn($total); + $creditmemoItemMock = $this->getMockBuilder( + \Magento\Sales\Api\Data\CreditmemoItemInterface::class + )->disableOriginalConstructor() + ->getMockForAbstractClass(); + $creditmemoItemMock->expects($this->exactly(2))->method('getOrderItemId') + ->willReturn($orderItemId); + $creditmemoItemMock->expects($this->never())->method('getSku') + ->willReturn($sku); + $creditmemoItemMock->expects($this->atLeastOnce())->method('getQty') + ->willReturn($qtyToRequest); + $creditmemoMock->expects($this->exactly(1))->method('getItems') + ->willReturn([$creditmemoItemMock]); + + $orderMock = $this->getMockBuilder(OrderInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $orderItemMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Item::class) + ->disableOriginalConstructor() + ->getMock(); + $orderItemMock->expects($this->exactly(2))->method('getQtyToRefund') + ->willReturn($qtyToRefund); + $creditmemoItemMock->expects($this->any())->method('getQty') + ->willReturn($qtyToRequest); + $orderMock->expects($this->once())->method('getItems') + ->willReturn([$orderItemMock]); + $orderItemMock->expects($this->once())->method('getItemId') + ->willReturn($orderItemId); + $orderItemMock->expects($this->any())->method('getSku') + ->willReturn($sku); + + $this->orderRepositoryMock->expects($this->once()) + ->method('get') + ->with($orderId) + ->willReturn($orderMock); + + $this->assertEquals( + $expected, + $this->validator->validate($creditmemoMock) + ); + } + + /** + * @return array + */ + public function dataProviderForValidateQty() + { + $sku = 'sku'; + + return [ + [ + 'orderId' => 1, + 'orderItemId' => 1, + 'qtyToRequest' => 1, + 'qtyToRefund' => 1, + 'sku', + 'total' => 15, + 'expected' => [] + ], + [ + 'orderId' => 1, + 'orderItemId' => 1, + 'qtyToRequest' => 2, + 'qtyToRefund' => 1, + 'sku', + 'total' => 0, + 'expected' => [ + __( + 'The quantity to creditmemo must not be greater than the unrefunded quantity' + . ' for product SKU "%1".', + $sku + ), + __('The credit memo\'s total must be positive.') + ] + ], + ]; + } +} diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/CreditmemoDocumentFactoryTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/CreditmemoDocumentFactoryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..521167f10aebd0993adebfef05e88f4b28ef1f54 --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/CreditmemoDocumentFactoryTest.php @@ -0,0 +1,265 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Test\Unit\Model\Order; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order\CreditmemoDocumentFactory; +use Magento\Sales\Api\Data\CreditmemoCommentInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Invoice; +use Magento\Sales\Api\Data\CreditmemoInterface; +use Magento\Sales\Api\Data\CreditmemoItemCreationInterface; +use Magento\Sales\Api\Data\CreditmemoCommentCreationInterface; +use Magento\Framework\EntityManager\HydratorPool; +use Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface; +use Magento\Sales\Model\Order\CreditmemoFactory; +use Magento\Framework\EntityManager\HydratorInterface; + +/** + * Class CreditmemoDocumentFactoryTest + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class CreditmemoDocumentFactoryTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var CreditmemoDocumentFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $factory; + + /** + * @var CreditmemoFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoFactoryMock; + + /** + * @var \Magento\Sales\Api\Data\CreditmemoCommentInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $commentFactoryMock; + + /** + * @var HydratorPool|\PHPUnit_Framework_MockObject_MockObject + */ + private $hydratorPoolMock; + + /** + * @var HydratorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $hydratorMock; + + /** + * @var \Magento\Sales\Model\Order|\PHPUnit_Framework_MockObject_MockObject + */ + private $orderMock; + + /** + * @var \Magento\Sales\Model\Order\Invoice|\PHPUnit_Framework_MockObject_MockObject + */ + private $invoiceMock; + + /** + * @var CreditmemoItemCreationInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoItemCreationMock; + + /** + * @var CreditmemoCommentCreationInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $commentCreationMock; + + /** + * @var CreditmemoCreationArgumentsInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $commentCreationArgumentsMock; + + /** + * @var CreditmemoInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoMock; + + /** + * @var CreditmemoCommentInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $commentMock; + + /** + * @var OrderRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $orderRepositoryMock; + + public function setUp() + { + $this->objectManager = new ObjectManager($this); + $this->creditmemoFactoryMock = $this->getMockBuilder(CreditmemoFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->commentFactoryMock = + $this->getMockBuilder('Magento\Sales\Api\Data\CreditmemoCommentInterfaceFactory') + ->setMethods(['create']) + ->getMock(); + $this->hydratorPoolMock = $this->getMockBuilder(HydratorPool::class) + ->disableOriginalConstructor() + ->getMock(); + $this->orderRepositoryMock = $this->getMockBuilder(OrderRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->orderMock = $this->getMockBuilder(Order::class) + ->disableOriginalConstructor() + ->getMock(); + $this->invoiceMock = $this->getMockBuilder(Invoice::class) + ->disableOriginalConstructor() + ->getMock(); + $this->creditmemoItemCreationMock = $this->getMockBuilder(CreditmemoItemCreationInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->creditmemoMock = $this->getMockBuilder(CreditmemoInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->hydratorMock = $this->getMockBuilder(HydratorInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->commentCreationArgumentsMock = $this->getMockBuilder(CreditmemoCreationArgumentsInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->commentCreationMock = $this->getMockBuilder(CreditmemoCommentCreationInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->creditmemoMock->expects($this->once()) + ->method('getEntityId') + ->willReturn(11); + + $this->commentMock = $this->getMockBuilder(CreditmemoCommentInterface::class) + ->disableOriginalConstructor() + ->setMethods( + array_merge( + get_class_methods(CreditmemoCommentInterface::class), + ['setStoreId', 'setCreditmemo'] + ) + ) + ->getMock(); + $this->factory = $this->objectManager->getObject( + CreditmemoDocumentFactory::class, + [ + 'creditmemoFactory' => $this->creditmemoFactoryMock, + 'commentFactory' => $this->commentFactoryMock, + 'hydratorPool' => $this->hydratorPoolMock, + 'orderRepository' => $this->orderRepositoryMock + ] + ); + } + + private function commonFactoryFlow() + { + $this->creditmemoItemCreationMock->expects($this->once()) + ->method('getOrderItemId') + ->willReturn(7); + $this->creditmemoItemCreationMock->expects($this->once()) + ->method('getQty') + ->willReturn(3); + $this->hydratorPoolMock->expects($this->exactly(2)) + ->method('getHydrator') + ->willReturnMap( + [ + [CreditmemoCreationArgumentsInterface::class, $this->hydratorMock], + [CreditmemoCommentCreationInterface::class, $this->hydratorMock], + ] + ); + $this->hydratorMock->expects($this->exactly(2)) + ->method('extract') + ->willReturnMap([ + [$this->commentCreationArgumentsMock, ['shipping_amount' => '20.00']], + [$this->commentCreationMock, ['comment' => 'text']] + ]); + $this->commentFactoryMock->expects($this->once()) + ->method('create') + ->with( + [ + 'data' => [ + 'comment' => 'text' + ] + ] + ) + ->willReturn($this->commentMock); + $this->creditmemoMock->expects($this->once()) + ->method('getEntityId') + ->willReturn(11); + $this->creditmemoMock->expects($this->once()) + ->method('getStoreId') + ->willReturn(1); + $this->commentMock->expects($this->once()) + ->method('setParentId') + ->with(11) + ->willReturnSelf(); + $this->commentMock->expects($this->once()) + ->method('setStoreId') + ->with(1) + ->willReturnSelf(); + $this->commentMock->expects($this->once()) + ->method('setIsCustomerNotified') + ->with(true) + ->willReturnSelf(); + $this->commentMock->expects($this->once()) + ->method('setCreditmemo') + ->with($this->creditmemoMock) + ->willReturnSelf(); + } + + public function testCreateFromOrder() + { + $this->commonFactoryFlow(); + $this->creditmemoFactoryMock->expects($this->once()) + ->method('createByOrder') + ->with( + $this->orderMock, + [ + 'shipping_amount' => '20.00', + 'qtys' => [7 => 3] + ] + ) + ->willReturn($this->creditmemoMock); + $this->factory->createFromOrder( + $this->orderMock, + [$this->creditmemoItemCreationMock], + $this->commentCreationMock, + true, + $this->commentCreationArgumentsMock + ); + } + + public function testCreateFromInvoice() + { + $this->commonFactoryFlow(); + $this->creditmemoFactoryMock->expects($this->once()) + ->method('createByInvoice') + ->with( + $this->invoiceMock, + [ + 'shipping_amount' => '20.00', + 'qtys' => [7 => 3] + ] + ) + ->willReturn($this->creditmemoMock); + $this->orderRepositoryMock->expects($this->once()) + ->method('get') + ->willReturn($this->orderMock); + $this->invoiceMock->expects($this->once()) + ->method('setOrder') + ->with($this->orderMock) + ->willReturnSelf(); + $this->factory->createFromInvoice( + $this->invoiceMock, + [$this->creditmemoItemCreationMock], + $this->commentCreationMock, + true, + $this->commentCreationArgumentsMock + ); + } +} diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Validation/CanRefundTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Validation/CanRefundTest.php new file mode 100644 index 0000000000000000000000000000000000000000..773f3b75c91f5c4f6aad03576b0cfe9dd4746ab1 --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Validation/CanRefundTest.php @@ -0,0 +1,131 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Test\Unit\Model\Order\Invoice\Validation; + +use Magento\Sales\Api\Data\OrderInterface; + +/** + * Class CanRefundTest + */ +class CanRefundTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Sales\Model\Order\Invoice|\PHPUnit_Framework_MockObject_MockObject + */ + private $invoiceMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $orderPaymentRepositoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $orderRepositoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $paymentMock; + + /** + * @var \Magento\Sales\Model\Order\Invoice\Validation\CanRefund + */ + private $validator; + + protected function setUp() + { + $this->invoiceMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Invoice::class) + ->disableOriginalConstructor() + ->getMock(); + $this->orderPaymentRepositoryMock = $this->getMockBuilder( + \Magento\Sales\Api\OrderPaymentRepositoryInterface::class + ) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->orderRepositoryMock = $this->getMockBuilder(\Magento\Sales\Api\OrderRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->paymentMock = $this->getMockBuilder(\Magento\Payment\Model\InfoInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->validator = new \Magento\Sales\Model\Order\Invoice\Validation\CanRefund( + $this->orderPaymentRepositoryMock, + $this->orderRepositoryMock + ); + } + + public function testValidateWrongInvoiceState() + { + $this->invoiceMock->expects($this->exactly(2)) + ->method('getState') + ->willReturnOnConsecutiveCalls( + \Magento\Sales\Model\Order\Invoice::STATE_OPEN, + \Magento\Sales\Model\Order\Invoice::STATE_CANCELED + ); + $this->assertEquals( + [__('We can\'t create creditmemo for the invoice.')], + $this->validator->validate($this->invoiceMock) + ); + $this->assertEquals( + [__('We can\'t create creditmemo for the invoice.')], + $this->validator->validate($this->invoiceMock) + ); + } + + public function testValidateInvoiceSumWasRefunded() + { + $this->invoiceMock->expects($this->once()) + ->method('getState') + ->willReturn(\Magento\Sales\Model\Order\Invoice::STATE_PAID); + $this->invoiceMock->expects($this->once()) + ->method('getBaseGrandTotal') + ->willReturn(1); + $this->invoiceMock->expects($this->once()) + ->method('getBaseTotalRefunded') + ->willReturn(1); + $this->assertEquals( + [__('We can\'t create creditmemo for the invoice.')], + $this->validator->validate($this->invoiceMock) + ); + } + + public function testValidate() + { + $this->invoiceMock->expects($this->once()) + ->method('getState') + ->willReturn(\Magento\Sales\Model\Order\Invoice::STATE_PAID); + $orderMock = $this->getMockBuilder(OrderInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->orderRepositoryMock->expects($this->once()) + ->method('get') + ->willReturn($orderMock); + $orderMock->expects($this->once()) + ->method('getPayment') + ->willReturn($this->paymentMock); + $methodInstanceMock = $this->getMockBuilder(\Magento\Payment\Model\MethodInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->paymentMock->expects($this->once()) + ->method('getMethodInstance') + ->willReturn($methodInstanceMock); + $methodInstanceMock->expects($this->atLeastOnce()) + ->method('canRefund') + ->willReturn(true); + $this->invoiceMock->expects($this->once()) + ->method('getBaseGrandTotal') + ->willReturn(1); + $this->invoiceMock->expects($this->once()) + ->method('getBaseTotalRefunded') + ->willReturn(0); + $this->assertEquals( + [], + $this->validator->validate($this->invoiceMock) + ); + } +} diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Validation/CanRefundTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Validation/CanRefundTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0b4246d46944444041d92089ddc19a4d270df86f --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Validation/CanRefundTest.php @@ -0,0 +1,113 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Test\Unit\Model\Order\Validation; + +use Magento\Sales\Model\Order; + +/** + * Class CanRefundTest + */ +class CanRefundTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Sales\Model\Order\Validation\CanRefund|\PHPUnit_Framework_MockObject_MockObject + */ + private $model; + + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + private $objectManager; + + /** + * @var \Magento\Sales\Api\Data\OrderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $orderMock; + + /** + * @var \Magento\Framework\Pricing\PriceCurrencyInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $priceCurrencyMock; + + protected function setUp() + { + $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->orderMock = $this->getMockBuilder(\Magento\Sales\Api\Data\OrderInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getStatus', 'getItems']) + ->getMockForAbstractClass(); + + $this->priceCurrencyMock = $this->getMockBuilder(\Magento\Framework\Pricing\PriceCurrencyInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->priceCurrencyMock->expects($this->any()) + ->method('round') + ->willReturnArgument(0); + $this->model = new \Magento\Sales\Model\Order\Validation\CanRefund( + $this->priceCurrencyMock + ); + } + + /** + * @param string $state + * + * @dataProvider canCreditmemoWrongStateDataProvider + */ + public function testCanCreditmemoWrongState($state) + { + $this->orderMock->expects($this->any()) + ->method('getState') + ->willReturn($state); + $this->orderMock->expects($this->once()) + ->method('getStatus') + ->willReturn('status'); + $this->orderMock->expects($this->never()) + ->method('getTotalPaid') + ->willReturn(15); + $this->orderMock->expects($this->never()) + ->method('getTotalRefunded') + ->willReturn(14); + $this->assertEquals( + [__('A creditmemo can not be created when an order has a status of %1', 'status')], + $this->model->validate($this->orderMock) + ); + } + + /** + * Data provider for testCanCreditmemoWrongState + * @return array + */ + public function canCreditmemoWrongStateDataProvider() + { + return [ + [Order::STATE_PAYMENT_REVIEW], + [Order::STATE_HOLDED], + [Order::STATE_CANCELED], + [Order::STATE_CLOSED], + ]; + } + + public function testCanCreditmemoNoMoney() + { + $this->orderMock->expects($this->any()) + ->method('getState') + ->willReturn(Order::STATE_PROCESSING); + $this->orderMock->expects($this->once()) + ->method('getTotalPaid') + ->willReturn(15); + $this->orderMock->expects($this->once()) + ->method('getTotalRefunded') + ->willReturn(15); + $this->assertEquals( + [ + __('The order does not allow a creditmemo to be created.') + ], + $this->model->validate($this->orderMock) + ); + } +} diff --git a/app/code/Magento/Sales/Test/Unit/Model/RefundInvoiceTest.php b/app/code/Magento/Sales/Test/Unit/Model/RefundInvoiceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1c4aab6dd2feb1f2ac4d260bc44245633bc17467 --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Model/RefundInvoiceTest.php @@ -0,0 +1,491 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Test\Unit\Model; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Sales\Api\CreditmemoRepositoryInterface; +use Magento\Sales\Api\Data\CreditmemoCommentCreationInterface; +use Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface; +use Magento\Sales\Api\Data\CreditmemoInterface; +use Magento\Sales\Api\Data\InvoiceInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\InvoiceRepositoryInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Config as OrderConfig; +use Magento\Sales\Model\Order\Creditmemo\CreditmemoValidatorInterface; +use Magento\Sales\Model\Order\Creditmemo\ItemCreationValidatorInterface; +use Magento\Sales\Api\Data\CreditmemoItemCreationInterface; +use Magento\Sales\Model\Order\CreditmemoDocumentFactory; +use Magento\Sales\Model\Order\Invoice\InvoiceValidatorInterface; +use Magento\Sales\Model\Order\OrderStateResolverInterface; +use Magento\Sales\Model\Order\OrderValidatorInterface; +use Magento\Sales\Model\Order\PaymentAdapterInterface; +use Magento\Sales\Model\Order\Creditmemo\NotifierInterface; +use Magento\Sales\Model\RefundInvoice; +use Psr\Log\LoggerInterface; + +/** + * Class RefundInvoiceTest + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.TooManyFields) + */ +class RefundInvoiceTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject + */ + private $resourceConnectionMock; + + /** + * @var OrderRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $orderRepositoryMock; + + /** + * @var InvoiceRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $invoiceRepositoryMock; + + /** + * @var CreditmemoDocumentFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoDocumentFactoryMock; + + /** + * @var CreditmemoValidatorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoValidatorMock; + + /** + * @var OrderValidatorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $orderValidatorMock; + + /** + * @var InvoiceValidatorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $invoiceValidatorMock; + + /** + * @var PaymentAdapterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $paymentAdapterMock; + + /** + * @var OrderStateResolverInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $orderStateResolverMock; + + /** + * @var OrderConfig|\PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + + /** + * @var Order\CreditmemoRepository|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoRepositoryMock; + + /** + * @var NotifierInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $notifierMock; + + /** + * @var RefundInvoice|\PHPUnit_Framework_MockObject_MockObject + */ + private $refundInvoice; + + /** + * @var CreditmemoCreationArgumentsInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoCommentCreationMock; + + /** + * @var CreditmemoCommentCreationInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoCreationArgumentsMock; + + /** + * @var OrderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $orderMock; + + /** + * @var OrderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $invoiceMock; + + /** + * @var CreditmemoInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoMock; + + /** + * @var AdapterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $adapterInterface; + + /** + * @var CreditmemoItemCreationInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoItemCreationMock; + + /** + * @var ItemCreationValidatorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $itemCreationValidatorMock; + + /** + * @var LoggerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $loggerMock; + + protected function setUp() + { + $this->resourceConnectionMock = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + $this->orderRepositoryMock = $this->getMockBuilder(OrderRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->invoiceRepositoryMock = $this->getMockBuilder(InvoiceRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->creditmemoDocumentFactoryMock = $this->getMockBuilder(CreditmemoDocumentFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->creditmemoValidatorMock = $this->getMockBuilder(CreditmemoValidatorInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->orderValidatorMock = $this->getMockBuilder(OrderValidatorInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->invoiceValidatorMock = $this->getMockBuilder(InvoiceValidatorInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->paymentAdapterMock = $this->getMockBuilder(PaymentAdapterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->orderStateResolverMock = $this->getMockBuilder(OrderStateResolverInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->configMock = $this->getMockBuilder(OrderConfig::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->creditmemoRepositoryMock = $this->getMockBuilder(CreditmemoRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->notifierMock = $this->getMockBuilder(NotifierInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->loggerMock = $this->getMockBuilder(LoggerInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->creditmemoCommentCreationMock = $this->getMockBuilder(CreditmemoCommentCreationInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->creditmemoCreationArgumentsMock = $this->getMockBuilder(CreditmemoCreationArgumentsInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->orderMock = $this->getMockBuilder(OrderInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->invoiceMock = $this->getMockBuilder(InvoiceInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->creditmemoMock = $this->getMockBuilder(CreditmemoInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->adapterInterface = $this->getMockBuilder(AdapterInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->creditmemoItemCreationMock = $this->getMockBuilder(CreditmemoItemCreationInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->itemCreationValidatorMock = $this->getMockBuilder(ItemCreationValidatorInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->refundInvoice = new RefundInvoice( + $this->resourceConnectionMock, + $this->orderStateResolverMock, + $this->orderRepositoryMock, + $this->invoiceRepositoryMock, + $this->orderValidatorMock, + $this->invoiceValidatorMock, + $this->creditmemoValidatorMock, + $this->itemCreationValidatorMock, + $this->creditmemoRepositoryMock, + $this->paymentAdapterMock, + $this->creditmemoDocumentFactoryMock, + $this->notifierMock, + $this->configMock, + $this->loggerMock + ); + } + + /** + * @dataProvider dataProvider + */ + public function testOrderCreditmemo($invoiceId, $items, $notify, $appendComment) + { + $this->resourceConnectionMock->expects($this->once()) + ->method('getConnection') + ->with('sales') + ->willReturn($this->adapterInterface); + + $this->invoiceRepositoryMock->expects($this->once()) + ->method('get') + ->willReturn($this->invoiceMock); + $this->orderRepositoryMock->expects($this->once()) + ->method('get') + ->willReturn($this->orderMock); + + $this->creditmemoDocumentFactoryMock->expects($this->once()) + ->method('createFromInvoice') + ->with( + $this->invoiceMock, + $items, + $this->creditmemoCommentCreationMock, + ($appendComment && $notify), + $this->creditmemoCreationArgumentsMock + )->willReturn($this->creditmemoMock); + + $this->creditmemoValidatorMock->expects($this->once()) + ->method('validate') + ->with($this->creditmemoMock) + ->willReturn([]); + $this->orderValidatorMock->expects($this->once()) + ->method('validate') + ->with($this->orderMock) + ->willReturn([]); + $this->invoiceValidatorMock->expects($this->once()) + ->method('validate') + ->with($this->invoiceMock) + ->willReturn([]); + $this->itemCreationValidatorMock->expects($this->once()) + ->method('validate') + ->with($this->creditmemoItemCreationMock) + ->willReturn([]); + $this->paymentAdapterMock->expects($this->once()) + ->method('refund') + ->with($this->creditmemoMock, $this->orderMock) + ->willReturn($this->orderMock); + $this->orderStateResolverMock->expects($this->once()) + ->method('getStateForOrder') + ->with($this->orderMock, []) + ->willReturn(Order::STATE_CLOSED); + $this->orderMock->expects($this->once()) + ->method('setState') + ->with(Order::STATE_CLOSED) + ->willReturnSelf(); + $this->orderMock->expects($this->once()) + ->method('getState') + ->willReturn(Order::STATE_CLOSED); + $this->configMock->expects($this->once()) + ->method('getStateDefaultStatus') + ->with(Order::STATE_CLOSED) + ->willReturn('Closed'); + $this->orderMock->expects($this->once()) + ->method('setStatus') + ->with('Closed') + ->willReturnSelf(); + $this->creditmemoMock->expects($this->once()) + ->method('setState') + ->with(\Magento\Sales\Model\Order\Creditmemo::STATE_REFUNDED) + ->willReturnSelf(); + + $this->creditmemoRepositoryMock->expects($this->once()) + ->method('save') + ->with($this->creditmemoMock) + ->willReturn($this->creditmemoMock); + $this->orderRepositoryMock->expects($this->once()) + ->method('save') + ->with($this->orderMock) + ->willReturn($this->orderMock); + if ($notify) { + $this->notifierMock->expects($this->once()) + ->method('notify') + ->with($this->orderMock, $this->creditmemoMock, $this->creditmemoCommentCreationMock); + } + $this->creditmemoMock->expects($this->once()) + ->method('getEntityId') + ->willReturn(2); + + $this->assertEquals( + 2, + $this->refundInvoice->execute( + $invoiceId, + $items, + false, + $notify, + $appendComment, + $this->creditmemoCommentCreationMock, + $this->creditmemoCreationArgumentsMock + ) + ); + } + + /** + * @expectedException \Magento\Sales\Api\Exception\DocumentValidationExceptionInterface + */ + public function testDocumentValidationException() + { + $invoiceId = 1; + $items = [1 => $this->creditmemoItemCreationMock]; + $notify = true; + $appendComment = true; + $errorMessages = ['error1', 'error2']; + + $this->invoiceRepositoryMock->expects($this->once()) + ->method('get') + ->willReturn($this->invoiceMock); + $this->orderRepositoryMock->expects($this->once()) + ->method('get') + ->willReturn($this->orderMock); + + $this->creditmemoDocumentFactoryMock->expects($this->once()) + ->method('createFromInvoice') + ->with( + $this->invoiceMock, + $items, + $this->creditmemoCommentCreationMock, + ($appendComment && $notify), + $this->creditmemoCreationArgumentsMock + )->willReturn($this->creditmemoMock); + + $this->creditmemoValidatorMock->expects($this->once()) + ->method('validate') + ->with($this->creditmemoMock) + ->willReturn($errorMessages); + $this->orderValidatorMock->expects($this->once()) + ->method('validate') + ->with($this->orderMock) + ->willReturn([]); + $this->invoiceValidatorMock->expects($this->once()) + ->method('validate') + ->with($this->invoiceMock) + ->willReturn([]); + $this->itemCreationValidatorMock->expects($this->once()) + ->method('validate') + ->with($this->creditmemoItemCreationMock) + ->willReturn([]); + + $this->assertEquals( + $errorMessages, + $this->refundInvoice->execute( + $invoiceId, + $items, + false, + $notify, + $appendComment, + $this->creditmemoCommentCreationMock, + $this->creditmemoCreationArgumentsMock + ) + ); + } + + /** + * @expectedException \Magento\Sales\Api\Exception\CouldNotRefundExceptionInterface + */ + public function testCouldNotCreditmemoException() + { + $invoiceId = 1; + $items = [1 => $this->creditmemoItemCreationMock]; + $notify = true; + $appendComment = true; + $this->resourceConnectionMock->expects($this->once()) + ->method('getConnection') + ->with('sales') + ->willReturn($this->adapterInterface); + + $this->invoiceRepositoryMock->expects($this->once()) + ->method('get') + ->willReturn($this->invoiceMock); + $this->orderRepositoryMock->expects($this->once()) + ->method('get') + ->willReturn($this->orderMock); + + $this->creditmemoDocumentFactoryMock->expects($this->once()) + ->method('createFromInvoice') + ->with( + $this->invoiceMock, + $items, + $this->creditmemoCommentCreationMock, + ($appendComment && $notify), + $this->creditmemoCreationArgumentsMock + )->willReturn($this->creditmemoMock); + + $this->creditmemoValidatorMock->expects($this->once()) + ->method('validate') + ->with($this->creditmemoMock) + ->willReturn([]); + $this->orderValidatorMock->expects($this->once()) + ->method('validate') + ->with($this->orderMock) + ->willReturn([]); + $this->invoiceValidatorMock->expects($this->once()) + ->method('validate') + ->with($this->invoiceMock) + ->willReturn([]); + $this->itemCreationValidatorMock->expects($this->once()) + ->method('validate') + ->with($this->creditmemoItemCreationMock) + ->willReturn([]); + $e = new \Exception(); + + $this->paymentAdapterMock->expects($this->once()) + ->method('refund') + ->with($this->creditmemoMock, $this->orderMock) + ->willThrowException($e); + + $this->loggerMock->expects($this->once()) + ->method('critical') + ->with($e); + + $this->adapterInterface->expects($this->once()) + ->method('rollBack'); + + $this->refundInvoice->execute( + $invoiceId, + $items, + false, + $notify, + $appendComment, + $this->creditmemoCommentCreationMock, + $this->creditmemoCreationArgumentsMock + ); + } + + public function dataProvider() + { + $creditmemoItemCreationMock = $this->getMockBuilder(CreditmemoItemCreationInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + return [ + 'TestWithNotifyTrue' => [1, [1 => $creditmemoItemCreationMock], true, true], + 'TestWithNotifyFalse' => [1, [1 => $creditmemoItemCreationMock], false, true], + ]; + } +} diff --git a/app/code/Magento/Sales/Test/Unit/Model/RefundOrderTest.php b/app/code/Magento/Sales/Test/Unit/Model/RefundOrderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7d684695664ba1d1b63a5b78cf610da019cf39bb --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Model/RefundOrderTest.php @@ -0,0 +1,423 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Test\Unit\Model; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Sales\Api\CreditmemoRepositoryInterface; +use Magento\Sales\Api\Data\CreditmemoCommentCreationInterface; +use Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface; +use Magento\Sales\Api\Data\CreditmemoInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Config as OrderConfig; +use Magento\Sales\Model\Order\Creditmemo\CreditmemoValidatorInterface; +use Magento\Sales\Model\Order\Creditmemo\Item\Validation\CreationQuantityValidator; +use Magento\Sales\Model\Order\CreditmemoDocumentFactory; +use Magento\Sales\Model\Order\OrderStateResolverInterface; +use Magento\Sales\Model\Order\OrderValidatorInterface; +use Magento\Sales\Model\Order\PaymentAdapterInterface; +use Magento\Sales\Model\Order\Creditmemo\NotifierInterface; +use Magento\Sales\Model\RefundOrder; +use Psr\Log\LoggerInterface; +use Magento\Sales\Api\Data\CreditmemoItemCreationInterface; + +/** + * Class RefundOrderTest + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.TooManyFields) + */ +class RefundOrderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject + */ + private $resourceConnectionMock; + + /** + * @var OrderRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $orderRepositoryMock; + + /** + * @var CreditmemoDocumentFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoDocumentFactoryMock; + + /** + * @var CreditmemoValidatorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoValidatorMock; + + /** + * @var OrderValidatorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $orderValidatorMock; + + /** + * @var PaymentAdapterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $paymentAdapterMock; + + /** + * @var OrderStateResolverInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $orderStateResolverMock; + + /** + * @var OrderConfig|\PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + + /** + * @var Order\CreditmemoRepository|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoRepositoryMock; + + /** + * @var NotifierInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $notifierMock; + + /** + * @var RefundOrder|\PHPUnit_Framework_MockObject_MockObject + */ + private $refundOrder; + + /** + * @var CreditmemoCreationArgumentsInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoCommentCreationMock; + + /** + * @var CreditmemoCommentCreationInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoCreationArgumentsMock; + + /** + * @var OrderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $orderMock; + + /** + * @var CreditmemoInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoMock; + + /** + * @var AdapterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $adapterInterface; + + /** + * @var LoggerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $loggerMock; + + /** + * @var Order\Creditmemo\ItemCreationValidatorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $itemCreationValidatorMock; + + /** + * @var CreditmemoItemCreationInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoItemCreationMock; + + protected function setUp() + { + $this->resourceConnectionMock = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + $this->orderRepositoryMock = $this->getMockBuilder(OrderRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->creditmemoDocumentFactoryMock = $this->getMockBuilder(CreditmemoDocumentFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->creditmemoValidatorMock = $this->getMockBuilder(CreditmemoValidatorInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->orderValidatorMock = $this->getMockBuilder(OrderValidatorInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->paymentAdapterMock = $this->getMockBuilder(PaymentAdapterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->orderStateResolverMock = $this->getMockBuilder(OrderStateResolverInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->configMock = $this->getMockBuilder(OrderConfig::class) + ->disableOriginalConstructor() + ->getMock(); + $this->creditmemoRepositoryMock = $this->getMockBuilder(CreditmemoRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->notifierMock = $this->getMockBuilder(NotifierInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->loggerMock = $this->getMockBuilder(LoggerInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->creditmemoCommentCreationMock = $this->getMockBuilder(CreditmemoCommentCreationInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->creditmemoCreationArgumentsMock = $this->getMockBuilder(CreditmemoCreationArgumentsInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->orderMock = $this->getMockBuilder(OrderInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->creditmemoMock = $this->getMockBuilder(CreditmemoInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->adapterInterface = $this->getMockBuilder(AdapterInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->itemCreationValidatorMock = $this->getMockBuilder(Order\Creditmemo\ItemCreationValidatorInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->creditmemoItemCreationMock = $this->getMockBuilder(CreditmemoItemCreationInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->refundOrder = new RefundOrder( + $this->resourceConnectionMock, + $this->orderStateResolverMock, + $this->orderRepositoryMock, + $this->orderValidatorMock, + $this->creditmemoValidatorMock, + $this->itemCreationValidatorMock, + $this->creditmemoRepositoryMock, + $this->paymentAdapterMock, + $this->creditmemoDocumentFactoryMock, + $this->notifierMock, + $this->configMock, + $this->loggerMock + ); + } + + /** + * @dataProvider dataProvider + */ + public function testOrderCreditmemo($orderId, $notify, $appendComment) + { + $items = [$this->creditmemoItemCreationMock]; + $this->resourceConnectionMock->expects($this->once()) + ->method('getConnection') + ->with('sales') + ->willReturn($this->adapterInterface); + $this->orderRepositoryMock->expects($this->once()) + ->method('get') + ->willReturn($this->orderMock); + $this->creditmemoDocumentFactoryMock->expects($this->once()) + ->method('createFromOrder') + ->with( + $this->orderMock, + $items, + $this->creditmemoCommentCreationMock, + ($appendComment && $notify), + $this->creditmemoCreationArgumentsMock + )->willReturn($this->creditmemoMock); + $this->creditmemoValidatorMock->expects($this->once()) + ->method('validate') + ->with($this->creditmemoMock) + ->willReturn([]); + $this->orderValidatorMock->expects($this->once()) + ->method('validate') + ->with($this->orderMock) + ->willReturn([]); + $this->itemCreationValidatorMock->expects($this->once()) + ->method('validate') + ->with( + reset($items), + [CreationQuantityValidator::class], + $this->orderMock + )->willReturn([]); + $this->paymentAdapterMock->expects($this->once()) + ->method('refund') + ->with($this->creditmemoMock, $this->orderMock) + ->willReturn($this->orderMock); + $this->orderStateResolverMock->expects($this->once()) + ->method('getStateForOrder') + ->with($this->orderMock, []) + ->willReturn(Order::STATE_CLOSED); + + $this->orderMock->expects($this->once()) + ->method('setState') + ->with(Order::STATE_CLOSED) + ->willReturnSelf(); + + $this->orderMock->expects($this->once()) + ->method('getState') + ->willReturn(Order::STATE_CLOSED); + + $this->configMock->expects($this->once()) + ->method('getStateDefaultStatus') + ->with(Order::STATE_CLOSED) + ->willReturn('Closed'); + + $this->orderMock->expects($this->once()) + ->method('setStatus') + ->with('Closed') + ->willReturnSelf(); + + $this->creditmemoMock->expects($this->once()) + ->method('setState') + ->with(\Magento\Sales\Model\Order\Creditmemo::STATE_REFUNDED) + ->willReturnSelf(); + + $this->creditmemoRepositoryMock->expects($this->once()) + ->method('save') + ->with($this->creditmemoMock) + ->willReturn($this->creditmemoMock); + + $this->orderRepositoryMock->expects($this->once()) + ->method('save') + ->with($this->orderMock) + ->willReturn($this->orderMock); + + if ($notify) { + $this->notifierMock->expects($this->once()) + ->method('notify') + ->with($this->orderMock, $this->creditmemoMock, $this->creditmemoCommentCreationMock); + } + + $this->creditmemoMock->expects($this->once()) + ->method('getEntityId') + ->willReturn(2); + + $this->assertEquals( + 2, + $this->refundOrder->execute( + $orderId, + $items, + $notify, + $appendComment, + $this->creditmemoCommentCreationMock, + $this->creditmemoCreationArgumentsMock + ) + ); + } + + /** + * @expectedException \Magento\Sales\Api\Exception\DocumentValidationExceptionInterface + */ + public function testDocumentValidationException() + { + $orderId = 1; + $items = [$this->creditmemoItemCreationMock]; + $notify = true; + $appendComment = true; + $errorMessages = ['error1', 'error2']; + + $this->orderRepositoryMock->expects($this->once()) + ->method('get') + ->willReturn($this->orderMock); + + $this->creditmemoDocumentFactoryMock->expects($this->once()) + ->method('createFromOrder') + ->with( + $this->orderMock, + $items, + $this->creditmemoCommentCreationMock, + ($appendComment && $notify), + $this->creditmemoCreationArgumentsMock + )->willReturn($this->creditmemoMock); + + $this->creditmemoValidatorMock->expects($this->once()) + ->method('validate') + ->with($this->creditmemoMock) + ->willReturn($errorMessages); + $this->orderValidatorMock->expects($this->once()) + ->method('validate') + ->with($this->orderMock) + ->willReturn([]); + $this->itemCreationValidatorMock->expects($this->once()) + ->method('validate') + ->with(reset($items), [CreationQuantityValidator::class], $this->orderMock) + ->willReturn([]); + + $this->assertEquals( + $errorMessages, + $this->refundOrder->execute( + $orderId, + $items, + $notify, + $appendComment, + $this->creditmemoCommentCreationMock, + $this->creditmemoCreationArgumentsMock + ) + ); + } + + /** + * @expectedException \Magento\Sales\Api\Exception\CouldNotRefundExceptionInterface + */ + public function testCouldNotCreditmemoException() + { + $orderId = 1; + $items = [$this->creditmemoItemCreationMock]; + $notify = true; + $appendComment = true; + $this->resourceConnectionMock->expects($this->once()) + ->method('getConnection') + ->with('sales') + ->willReturn($this->adapterInterface); + $this->orderRepositoryMock->expects($this->once()) + ->method('get') + ->willReturn($this->orderMock); + $this->creditmemoDocumentFactoryMock->expects($this->once()) + ->method('createFromOrder') + ->with( + $this->orderMock, + $items, + $this->creditmemoCommentCreationMock, + ($appendComment && $notify), + $this->creditmemoCreationArgumentsMock + )->willReturn($this->creditmemoMock); + $this->itemCreationValidatorMock->expects($this->once()) + ->method('validate') + ->with(reset($items), [CreationQuantityValidator::class], $this->orderMock) + ->willReturn([]); + $this->creditmemoValidatorMock->expects($this->once()) + ->method('validate') + ->with($this->creditmemoMock) + ->willReturn([]); + $this->orderValidatorMock->expects($this->once()) + ->method('validate') + ->with($this->orderMock) + ->willReturn([]); + $e = new \Exception(); + $this->paymentAdapterMock->expects($this->once()) + ->method('refund') + ->with($this->creditmemoMock, $this->orderMock) + ->willThrowException($e); + $this->loggerMock->expects($this->once()) + ->method('critical') + ->with($e); + $this->adapterInterface->expects($this->once()) + ->method('rollBack'); + + $this->refundOrder->execute( + $orderId, + $items, + $notify, + $appendComment, + $this->creditmemoCommentCreationMock, + $this->creditmemoCreationArgumentsMock + ); + } + + public function dataProvider() + { + return [ + 'TestWithNotifyTrue' => [1, true, true], + 'TestWithNotifyFalse' => [1, false, true], + ]; + } +} diff --git a/app/code/Magento/Sales/etc/di.xml b/app/code/Magento/Sales/etc/di.xml index 47800b377bb4dd5703a6d3640980982e99bbeaed..4b921587c292b54bdd49a40cb174a7de373917f5 100644 --- a/app/code/Magento/Sales/etc/di.xml +++ b/app/code/Magento/Sales/etc/di.xml @@ -47,6 +47,8 @@ <preference for="Magento\Sales\Api\CreditmemoItemRepositoryInterface" type="Magento\Sales\Api\Data\CreditmemoItem\Repository"/> <preference for="Magento\Sales\Api\CreditmemoRepositoryInterface" type="Magento\Sales\Model\Order\CreditmemoRepository"/> <preference for="Magento\Sales\Api\CreditmemoManagementInterface" type="Magento\Sales\Model\Service\CreditmemoService"/> + <preference for="Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface" type="Magento\Sales\Model\Order\Creditmemo\CreationArguments"/> + <preference for="Magento\Sales\Api\Data\CreditmemoItemCreationInterface" type="Magento\Sales\Model\Order\Creditmemo\ItemCreation"/> <preference for="Magento\Sales\Api\InvoiceCommentRepositoryInterface" type="Magento\Sales\Api\Data\InvoiceComment\Repository"/> <preference for="Magento\Sales\Api\InvoiceItemRepositoryInterface" type="Magento\Sales\Api\Data\InvoiceItem\Repository"/> <preference for="Magento\Sales\Api\InvoiceRepositoryInterface" type="Magento\Sales\Model\Order\InvoiceRepository"/> @@ -55,6 +57,7 @@ <preference for="Magento\Sales\Api\Data\InvoiceItemCreationInterface" type="Magento\Sales\Model\Order\Invoice\ItemCreation"/> <preference for="Magento\Sales\Api\Data\InvoiceCommentCreationInterface" type="Magento\Sales\Model\Order\Invoice\CommentCreation"/> <preference for="Magento\Sales\Api\Data\ShipmentCommentCreationInterface" type="Magento\Sales\Model\Order\Shipment\CommentCreation"/> + <preference for="Magento\Sales\Api\Data\CreditmemoCommentCreationInterface" type="Magento\Sales\Model\Order\Creditmemo\CommentCreation"/> <preference for="Magento\Sales\Api\OrderAddressRepositoryInterface" type="Magento\Sales\Model\Order\AddressRepository"/> <preference for="Magento\Sales\Api\OrderCustomerManagementInterface" type="Magento\Sales\Model\Order\CustomerManagement"/> <preference for="Magento\Sales\Api\OrderItemRepositoryInterface" type="Magento\Sales\Model\Order\ItemRepository"/> @@ -100,6 +103,11 @@ <preference for="Magento\Sales\Model\Order\OrderValidatorInterface" type="Magento\Sales\Model\Order\OrderValidator"/> <preference for="Magento\Sales\Model\Order\Invoice\InvoiceValidatorInterface" type="Magento\Sales\Model\Order\Invoice\InvoiceValidator"/> <preference for="Magento\Sales\Model\Order\Shipment\ShipmentValidatorInterface" type="Magento\Sales\Model\Order\Shipment\ShipmentValidator"/> + <preference for="Magento\Sales\Model\Order\Creditmemo\CreditmemoValidatorInterface" type="Magento\Sales\Model\Order\Creditmemo\CreditmemoValidator"/> + <preference for="Magento\Sales\Model\Order\Creditmemo\ItemCreationValidatorInterface" type="Magento\Sales\Model\Order\Creditmemo\ItemCreationValidator"/> + <preference for="Magento\Sales\Model\Order\Creditmemo\NotifierInterface" type="Magento\Sales\Model\Order\Creditmemo\Notifier"/> + <preference for="Magento\Sales\Api\RefundOrderInterface" type="Magento\Sales\Model\RefundOrder"/> + <preference for="Magento\Sales\Api\RefundInvoiceInterface" type="Magento\Sales\Model\RefundInvoice"/> <type name="Magento\Sales\Model\ResourceModel\Report" shared="false"/> <type name="Magento\Sales\Model\Order\Pdf\Config\Reader"> <arguments> @@ -926,6 +934,13 @@ </argument> </arguments> </type> + <type name="Magento\Sales\Model\Order\Creditmemo\Notifier"> + <arguments> + <argument name="senders" xsi:type="array"> + <item name="email" xsi:type="object">Magento\Sales\Model\Order\Creditmemo\Sender\EmailSender</item> + </argument> + </arguments> + </type> <type name="Magento\Framework\EntityManager\HydratorPool"> <arguments> <argument name="hydrators" xsi:type="array"> diff --git a/app/code/Magento/Sales/etc/webapi.xml b/app/code/Magento/Sales/etc/webapi.xml index 4c7fe03a201f8b25c2af83219fd0576da4c2cd3e..b2a88b8cad709d1b0726a66d70b9779241840409 100644 --- a/app/code/Magento/Sales/etc/webapi.xml +++ b/app/code/Magento/Sales/etc/webapi.xml @@ -133,6 +133,12 @@ <resource ref="Magento_Sales::sales" /> </resources> </route> + <route url="/V1/invoice/:invoiceId/refund" method="POST"> + <service class="Magento\Sales\Api\RefundInvoiceInterface" method="execute"/> + <resources> + <resource ref="Magento_Sales::sales" /> + </resources> + </route> <route url="/V1/creditmemo/:id/comments" method="GET"> <service class="Magento\Sales\Api\CreditmemoManagementInterface" method="getCommentsList"/> <resources> @@ -181,6 +187,12 @@ <resource ref="Magento_Sales::sales" /> </resources> </route> + <route url="/V1/order/:orderId/refund" method="POST"> + <service class="Magento\Sales\Api\RefundOrderInterface" method="execute"/> + <resources> + <resource ref="Magento_Sales::sales" /> + </resources> + </route> <route url="/V1/shipment/:id" method="GET"> <service class="Magento\Sales\Api\ShipmentRepositoryInterface" method="get"/> <resources> diff --git a/app/code/Magento/Vault/Model/Method/Vault.php b/app/code/Magento/Vault/Model/Method/Vault.php index 0e6689461e14b6d8647693aa2dc942a240bc2cc2..ce85936607192b81fcdd9da59fb48704fc92d48e 100644 --- a/app/code/Magento/Vault/Model/Method/Vault.php +++ b/app/code/Magento/Vault/Model/Method/Vault.php @@ -579,7 +579,7 @@ final class Vault implements VaultPaymentInterface public function isAvailable(\Magento\Quote\Api\Data\CartInterface $quote = null) { return $this->getVaultProvider()->isAvailable($quote) - && $this->config->getValue(self::$activeKey, $this->getStore() ?: $quote->getStoreId()); + && $this->config->getValue(self::$activeKey, $this->getStore() ?: ($quote ? $quote->getStoreId() : null)); } /** diff --git a/app/code/Magento/Vault/Model/Ui/TokensConfigProvider.php b/app/code/Magento/Vault/Model/Ui/TokensConfigProvider.php index 434a5875b5bb1b4bffb1b77547e64740107b902d..6a86ed694642c31407ba0c08e3ac39f646244cca 100644 --- a/app/code/Magento/Vault/Model/Ui/TokensConfigProvider.php +++ b/app/code/Magento/Vault/Model/Ui/TokensConfigProvider.php @@ -7,7 +7,7 @@ namespace Magento\Vault\Model\Ui; use Magento\Checkout\Model\ConfigProviderInterface; use Magento\Framework\App\ObjectManager; -use Magento\Payment\Helper\Data; +use Magento\Payment\Api\Data\PaymentMethodInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\Vault\Model\CustomerTokenManagement; use Magento\Vault\Model\VaultPaymentInterface; @@ -39,9 +39,14 @@ final class TokensConfigProvider implements ConfigProviderInterface private $customerTokenManagement; /** - * @var Data + * @var \Magento\Payment\Api\PaymentMethodListInterface */ - private $paymentDataHelper; + private $paymentMethodList; + + /** + * @var \Magento\Payment\Model\Method\InstanceFactory + */ + private $paymentMethodInstanceFactory; /** * Constructor @@ -98,21 +103,16 @@ final class TokensConfigProvider implements ConfigProviderInterface } /** - * Get list of available vault ui token providers + * Get list of available vault ui token providers. + * * @return TokenUiComponentProviderInterface[] */ private function getComponentProviders() { $providers = []; - $storeId = $this->storeManager->getStore()->getId(); - $paymentMethods = $this->getPaymentDataHelper()->getStoreMethods($storeId); + $vaultPaymentMethods = $this->getVaultPaymentMethodList(); - foreach ($paymentMethods as $method) { - /** VaultPaymentInterface $method */ - if (!$method instanceof VaultPaymentInterface || !$method->isActive($storeId)) { - continue; - } - + foreach ($vaultPaymentMethods as $method) { $providerCode = $method->getProviderCode(); $componentProvider = $this->getComponentProvider($providerCode); if ($componentProvider === null) { @@ -139,15 +139,60 @@ final class TokensConfigProvider implements ConfigProviderInterface } /** - * Get payment data helper instance - * @return Data + * Get list of active Vault payment methods. + * + * @return VaultPaymentInterface[] + */ + private function getVaultPaymentMethodList() + { + $storeId = $this->storeManager->getStore()->getId(); + + $paymentMethods = array_map( + function (PaymentMethodInterface $paymentMethod) { + return $this->getPaymentMethodInstanceFactory()->create($paymentMethod); + }, + $this->getPaymentMethodList()->getActiveList($storeId) + ); + + $availableMethods = array_filter( + $paymentMethods, + function (\Magento\Payment\Model\MethodInterface $methodInstance) { + return $methodInstance instanceof VaultPaymentInterface; + } + ); + + return $availableMethods; + } + + /** + * Get payment method list. + * + * @return \Magento\Payment\Api\PaymentMethodListInterface + * @deprecated + */ + private function getPaymentMethodList() + { + if ($this->paymentMethodList === null) { + $this->paymentMethodList = ObjectManager::getInstance()->get( + \Magento\Payment\Api\PaymentMethodListInterface::class + ); + } + return $this->paymentMethodList; + } + + /** + * Get payment method instance factory. + * + * @return \Magento\Payment\Model\Method\InstanceFactory * @deprecated */ - private function getPaymentDataHelper() + private function getPaymentMethodInstanceFactory() { - if ($this->paymentDataHelper === null) { - $this->paymentDataHelper = ObjectManager::getInstance()->get(Data::class); + if ($this->paymentMethodInstanceFactory === null) { + $this->paymentMethodInstanceFactory = ObjectManager::getInstance()->get( + \Magento\Payment\Model\Method\InstanceFactory::class + ); } - return $this->paymentDataHelper; + return $this->paymentMethodInstanceFactory; } } diff --git a/app/code/Magento/Vault/Model/Ui/VaultConfigProvider.php b/app/code/Magento/Vault/Model/Ui/VaultConfigProvider.php index 8bf88e7a94f509ddd9d23c239dd59dccb15d4e85..9cd7b97562df98e309b9ddd182a5ced144b2e555 100644 --- a/app/code/Magento/Vault/Model/Ui/VaultConfigProvider.php +++ b/app/code/Magento/Vault/Model/Ui/VaultConfigProvider.php @@ -8,7 +8,7 @@ namespace Magento\Vault\Model\Ui; use Magento\Checkout\Model\ConfigProviderInterface; use Magento\Framework\App\ObjectManager; use Magento\Framework\Session\SessionManagerInterface; -use Magento\Payment\Helper\Data; +use Magento\Payment\Api\Data\PaymentMethodInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\Vault\Model\VaultPaymentInterface; @@ -32,9 +32,14 @@ class VaultConfigProvider implements ConfigProviderInterface private $session; /** - * @var Data + * @var \Magento\Payment\Api\PaymentMethodListInterface */ - private $paymentDataHelper; + private $paymentMethodList; + + /** + * @var \Magento\Payment\Model\Method\InstanceFactory + */ + private $paymentMethodInstanceFactory; /** * VaultConfigProvider constructor. @@ -73,36 +78,60 @@ class VaultConfigProvider implements ConfigProviderInterface } /** - * Get list of active Vault payment methods - * @return array + * Get list of active Vault payment methods. + * + * @return VaultPaymentInterface[] */ private function getVaultPaymentMethodList() { - $availableMethods = []; $storeId = $this->storeManager->getStore()->getId(); - $paymentMethods = $this->getPaymentDataHelper()->getStoreMethods($storeId); - foreach ($paymentMethods as $method) { - /** VaultPaymentInterface $method */ - if (!$method instanceof VaultPaymentInterface) { - continue; + $paymentMethods = array_map( + function (PaymentMethodInterface $paymentMethod) { + return $this->getPaymentMethodInstanceFactory()->create($paymentMethod); + }, + $this->getPaymentMethodList()->getActiveList($storeId) + ); + + $availableMethods = array_filter( + $paymentMethods, + function (\Magento\Payment\Model\MethodInterface $methodInstance) { + return $methodInstance instanceof VaultPaymentInterface; } - $availableMethods[] = $method; - } + ); return $availableMethods; } /** - * Get payment data helper instance - * @return Data + * Get payment method list. + * + * @return \Magento\Payment\Api\PaymentMethodListInterface + * @deprecated + */ + private function getPaymentMethodList() + { + if ($this->paymentMethodList === null) { + $this->paymentMethodList = ObjectManager::getInstance()->get( + \Magento\Payment\Api\PaymentMethodListInterface::class + ); + } + return $this->paymentMethodList; + } + + /** + * Get payment method instance factory. + * + * @return \Magento\Payment\Model\Method\InstanceFactory * @deprecated */ - private function getPaymentDataHelper() + private function getPaymentMethodInstanceFactory() { - if ($this->paymentDataHelper === null) { - $this->paymentDataHelper = ObjectManager::getInstance()->get(Data::class); + if ($this->paymentMethodInstanceFactory === null) { + $this->paymentMethodInstanceFactory = ObjectManager::getInstance()->get( + \Magento\Payment\Model\Method\InstanceFactory::class + ); } - return $this->paymentDataHelper; + return $this->paymentMethodInstanceFactory; } } diff --git a/app/code/Magento/Vault/Test/Unit/Model/Method/VaultTest.php b/app/code/Magento/Vault/Test/Unit/Model/Method/VaultTest.php index fc6f3e4d2390b58303ff9261b86787fa50b1535a..e5c83911025246d10c56687604d343b233db382b 100644 --- a/app/code/Magento/Vault/Test/Unit/Model/Method/VaultTest.php +++ b/app/code/Magento/Vault/Test/Unit/Model/Method/VaultTest.php @@ -274,4 +274,32 @@ class VaultTest extends \PHPUnit_Framework_TestCase ['isAvailableProvider' => true, 'isActiveVault' => true, 'expected' => true], ]; } + + /** + * @covers \Magento\Vault\Model\Method\Vault::isAvailable + */ + public function testIsAvailableWithoutQuote() + { + $quote = null; + + $vaultProvider = $this->getMockForAbstractClass(MethodInterface::class); + $config = $this->getMockForAbstractClass(ConfigInterface::class); + + $vaultProvider->expects(static::once()) + ->method('isAvailable') + ->with($quote) + ->willReturn(true); + + $config->expects(static::once()) + ->method('getValue') + ->with('active', $quote) + ->willReturn(false); + + /** @var Vault $model */ + $model = $this->objectManager->getObject(Vault::class, [ + 'config' => $config, + 'vaultProvider' => $vaultProvider + ]); + static::assertFalse($model->isAvailable($quote)); + } } diff --git a/app/code/Magento/Vault/Test/Unit/Model/Ui/TokensConfigProviderTest.php b/app/code/Magento/Vault/Test/Unit/Model/Ui/TokensConfigProviderTest.php index e64fff27ff14ca0ad0aa0ffc6d4c0eda539f8be1..3e4b8b145af67da759d6445234d18bd528832810 100644 --- a/app/code/Magento/Vault/Test/Unit/Model/Ui/TokensConfigProviderTest.php +++ b/app/code/Magento/Vault/Test/Unit/Model/Ui/TokensConfigProviderTest.php @@ -7,7 +7,6 @@ namespace Magento\Vault\Test\Unit\Model\Ui; use Magento\Customer\Model\Session; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; -use Magento\Payment\Helper\Data; use Magento\Store\Api\Data\StoreInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\Vault\Api\Data\PaymentTokenInterface; @@ -33,10 +32,25 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase private $storeManager; /** - * @var VaultPaymentInterface|MockObject + * @var \Magento\Payment\Api\PaymentMethodListInterface|MockObject + */ + private $paymentMethodList; + + /** + * @var \Magento\Payment\Model\Method\InstanceFactory|MockObject + */ + private $paymentMethodInstanceFactory; + + /** + * @var \Magento\Payment\Api\Data\PaymentMethodInterface|MockObject */ private $vaultPayment; + /** + * @var VaultPaymentInterface|MockObject + */ + private $vaultPaymentInstance; + /** * @var StoreInterface|MockObject */ @@ -47,11 +61,6 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase */ private $customerTokenManagement; - /** - * @var Data|MockObject - */ - private $paymentDataHelper; - /** * @var ObjectManager */ @@ -59,13 +68,18 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase protected function setUp() { - $this->vaultPayment = $this->getMock(VaultPaymentInterface::class); + $this->paymentMethodList = $this->getMockBuilder(\Magento\Payment\Api\PaymentMethodListInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->paymentMethodInstanceFactory = $this->getMockBuilder( + \Magento\Payment\Model\Method\InstanceFactory::class + )->disableOriginalConstructor()->getMock(); + + $this->vaultPayment = $this->getMockForAbstractClass(\Magento\Payment\Api\Data\PaymentMethodInterface::class); + $this->vaultPaymentInstance = $this->getMockForAbstractClass(VaultPaymentInterface::class); $this->storeManager = $this->getMock(StoreManagerInterface::class); $this->store = $this->getMock(StoreInterface::class); - $this->paymentDataHelper = $this->getMockBuilder(Data::class) - ->disableOriginalConstructor() - ->setMethods(['getStoreMethods']) - ->getMock(); $this->objectManager = new ObjectManager($this); $this->customerTokenManagement = $this->getMockBuilder(CustomerTokenManagement::class) @@ -99,17 +113,17 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase $this->store->expects(static::once()) ->method('getId') ->willReturn($storeId); - - $this->paymentDataHelper->expects(static::once()) - ->method('getStoreMethods') + + $this->paymentMethodList->expects(static::once()) + ->method('getActiveList') ->with($storeId) ->willReturn([$this->vaultPayment]); + + $this->paymentMethodInstanceFactory->expects($this->once()) + ->method('create') + ->willReturn($this->vaultPaymentInstance); - $this->vaultPayment->expects(static::once()) - ->method('isActive') - ->with($storeId) - ->willReturn(true); - $this->vaultPayment->expects(static::once()) + $this->vaultPaymentInstance->expects(static::once()) ->method('getProviderCode') ->willReturn($vaultProviderCode); @@ -142,8 +156,13 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase $this->objectManager->setBackwardCompatibleProperty( $configProvider, - 'paymentDataHelper', - $this->paymentDataHelper + 'paymentMethodList', + $this->paymentMethodList + ); + $this->objectManager->setBackwardCompatibleProperty( + $configProvider, + 'paymentMethodInstanceFactory', + $this->paymentMethodInstanceFactory ); static::assertEquals($expectedConfig, $configProvider->getConfig()); diff --git a/app/code/Magento/Vault/Test/Unit/Model/Ui/VaultConfigProviderTest.php b/app/code/Magento/Vault/Test/Unit/Model/Ui/VaultConfigProviderTest.php index 7d0d8339ab2b1825a77041ff4f2beb0a6d686ea1..d00531637b86d4e509662a31ce2da32752ba4a8b 100644 --- a/app/code/Magento/Vault/Test/Unit/Model/Ui/VaultConfigProviderTest.php +++ b/app/code/Magento/Vault/Test/Unit/Model/Ui/VaultConfigProviderTest.php @@ -7,7 +7,6 @@ namespace Magento\Vault\Test\Unit\Model\Ui; use Magento\Customer\Model\Session; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; -use Magento\Payment\Helper\Data; use Magento\Store\Api\Data\StoreInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\Vault\Model\Ui\VaultConfigProvider; @@ -21,15 +20,25 @@ use PHPUnit_Framework_MockObject_MockObject as MockObject; class VaultConfigProviderTest extends \PHPUnit_Framework_TestCase { /** - * @var Data|MockObject + * @var \Magento\Payment\Api\PaymentMethodListInterface|MockObject */ - private $paymentDataHelper; + private $paymentMethodList; /** - * @var VaultPaymentInterface|MockObject + * @var \Magento\Payment\Model\Method\InstanceFactory|MockObject + */ + private $paymentMethodInstanceFactory; + + /** + * @var \Magento\Payment\Api\Data\PaymentMethodInterface|MockObject */ private $vaultPayment; + /** + * @var VaultPaymentInterface|MockObject + */ + private $vaultPaymentInstance; + /** * @var Session|MockObject */ @@ -52,12 +61,16 @@ class VaultConfigProviderTest extends \PHPUnit_Framework_TestCase protected function setUp() { - $this->paymentDataHelper = $this->getMockBuilder(Data::class) + $this->paymentMethodList = $this->getMockBuilder(\Magento\Payment\Api\PaymentMethodListInterface::class) ->disableOriginalConstructor() - ->setMethods(['getStoreMethods']) - ->getMock(); + ->getMockForAbstractClass(); - $this->vaultPayment = $this->getMockForAbstractClass(VaultPaymentInterface::class); + $this->paymentMethodInstanceFactory = $this->getMockBuilder( + \Magento\Payment\Model\Method\InstanceFactory::class + )->disableOriginalConstructor()->getMock(); + + $this->vaultPayment = $this->getMockForAbstractClass(\Magento\Payment\Api\Data\PaymentMethodInterface::class); + $this->vaultPaymentInstance = $this->getMockForAbstractClass(VaultPaymentInterface::class); $this->storeManager = $this->getMockForAbstractClass(StoreManagerInterface::class); $this->store = $this->getMockForAbstractClass(StoreInterface::class); $this->session = $this->getMockBuilder(Session::class) @@ -68,8 +81,13 @@ class VaultConfigProviderTest extends \PHPUnit_Framework_TestCase $this->vaultConfigProvider = new VaultConfigProvider($this->storeManager, $this->session); $objectManager->setBackwardCompatibleProperty( $this->vaultConfigProvider, - 'paymentDataHelper', - $this->paymentDataHelper + 'paymentMethodList', + $this->paymentMethodList + ); + $objectManager->setBackwardCompatibleProperty( + $this->vaultConfigProvider, + 'paymentMethodInstanceFactory', + $this->paymentMethodInstanceFactory ); } @@ -101,15 +119,19 @@ class VaultConfigProviderTest extends \PHPUnit_Framework_TestCase ->method('getId') ->willReturn($storeId); - $this->paymentDataHelper->expects(static::once()) - ->method('getStoreMethods') + $this->paymentMethodList->expects(static::once()) + ->method('getActiveList') ->with($storeId) ->willReturn([$this->vaultPayment]); - $this->vaultPayment->expects(static::once()) + $this->paymentMethodInstanceFactory->expects($this->once()) + ->method('create') + ->willReturn($this->vaultPaymentInstance); + + $this->vaultPaymentInstance->expects(static::once()) ->method('getCode') ->willReturn($vaultPaymentCode); - $this->vaultPayment->expects($customerId !== null ? static::once() : static::never()) + $this->vaultPaymentInstance->expects($customerId !== null ? static::once() : static::never()) ->method('isActive') ->with($storeId) ->willReturn($vaultEnabled); diff --git a/app/code/Magento/Wishlist/Helper/Data.php b/app/code/Magento/Wishlist/Helper/Data.php index d64d1a185f0a2e7871eadc5973eb2cfe9e9853f6..736786e6e30b391e5c6b85b05f4b5ded99e7e112 100644 --- a/app/code/Magento/Wishlist/Helper/Data.php +++ b/app/code/Magento/Wishlist/Helper/Data.php @@ -446,7 +446,13 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper */ protected function _getCartUrlParameters($item) { - return ['item' => is_string($item) ? $item : $item->getWishlistItemId()]; + $params = [ + 'item' => is_string($item) ? $item : $item->getWishlistItemId(), + ]; + if ($item instanceof \Magento\Wishlist\Model\Item) { + $params['qty'] = $item->getQty(); + } + return $params; } /** diff --git a/app/code/Magento/Wishlist/Test/Unit/Helper/DataTest.php b/app/code/Magento/Wishlist/Test/Unit/Helper/DataTest.php index 46054445620ac2286f40e700e5eca1a62bd2b9f6..ec6e959a3d4cadeee9ca5136fc9f919628ab4022 100644 --- a/app/code/Magento/Wishlist/Test/Unit/Helper/DataTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/Helper/DataTest.php @@ -124,6 +124,7 @@ class DataTest extends \PHPUnit_Framework_TestCase ->setMethods([ 'getProduct', 'getWishlistItemId', + 'getQty', ]) ->getMock(); @@ -217,6 +218,7 @@ class DataTest extends \PHPUnit_Framework_TestCase $url = 'result url'; $storeId = 1; $wishlistItemId = 1; + $wishlistItemQty = 1; $this->wishlistItem->expects($this->once()) ->method('getProduct') @@ -224,6 +226,9 @@ class DataTest extends \PHPUnit_Framework_TestCase $this->wishlistItem->expects($this->once()) ->method('getWishlistItemId') ->willReturn($wishlistItemId); + $this->wishlistItem->expects($this->once()) + ->method('getQty') + ->willReturn($wishlistItemQty); $this->product->expects($this->once()) ->method('isVisibleInSiteVisibility') @@ -243,9 +248,13 @@ class DataTest extends \PHPUnit_Framework_TestCase ->with('wishlist/index/cart') ->willReturn($url); + $expected = [ + 'item' => $wishlistItemId, + 'qty' => $wishlistItemQty, + ]; $this->postDataHelper->expects($this->once()) ->method('getPostData') - ->with($url, ['item' => $wishlistItemId]) + ->with($url, $expected) ->willReturn($url); $this->assertEquals($url, $this->model->getAddToCartParams($this->wishlistItem)); @@ -256,6 +265,7 @@ class DataTest extends \PHPUnit_Framework_TestCase $url = 'result url'; $storeId = 1; $wishlistItemId = 1; + $wishlistItemQty = 1; $referer = 'referer'; $refererEncoded = 'referer_encoded'; @@ -265,6 +275,9 @@ class DataTest extends \PHPUnit_Framework_TestCase $this->wishlistItem->expects($this->once()) ->method('getWishlistItemId') ->willReturn($wishlistItemId); + $this->wishlistItem->expects($this->once()) + ->method('getQty') + ->willReturn($wishlistItemQty); $this->product->expects($this->once()) ->method('isVisibleInSiteVisibility') @@ -288,9 +301,14 @@ class DataTest extends \PHPUnit_Framework_TestCase ->with('wishlist/index/cart') ->willReturn($url); + $expected = [ + 'item' => $wishlistItemId, + ActionInterface::PARAM_NAME_URL_ENCODED => $refererEncoded, + 'qty' => $wishlistItemQty, + ]; $this->postDataHelper->expects($this->once()) ->method('getPostData') - ->with($url, ['item' => $wishlistItemId, ActionInterface::PARAM_NAME_URL_ENCODED => $refererEncoded]) + ->with($url, $expected) ->willReturn($url); $this->assertEquals($url, $this->model->getAddToCartParams($this->wishlistItem, true)); @@ -363,6 +381,7 @@ class DataTest extends \PHPUnit_Framework_TestCase $url = 'result url'; $storeId = 1; $wishlistItemId = 1; + $wishlistItemQty = 1; $this->wishlistItem->expects($this->once()) ->method('getProduct') @@ -370,6 +389,9 @@ class DataTest extends \PHPUnit_Framework_TestCase $this->wishlistItem->expects($this->once()) ->method('getWishlistItemId') ->willReturn($wishlistItemId); + $this->wishlistItem->expects($this->once()) + ->method('getQty') + ->willReturn($wishlistItemQty); $this->product->expects($this->once()) ->method('isVisibleInSiteVisibility') @@ -383,9 +405,13 @@ class DataTest extends \PHPUnit_Framework_TestCase ->with('wishlist/shared/cart') ->willReturn($url); + $exptected = [ + 'item' => $wishlistItemId, + 'qty' => $wishlistItemQty, + ]; $this->postDataHelper->expects($this->once()) ->method('getPostData') - ->with($url, ['item' => $wishlistItemId]) + ->with($url, $exptected) ->willReturn($url); $this->assertEquals($url, $this->model->getSharedAddToCartUrl($this->wishlistItem)); diff --git a/app/code/Magento/Wishlist/view/frontend/web/wishlist.js b/app/code/Magento/Wishlist/view/frontend/web/wishlist.js index a4fdc178c704f340e77f109110d63a4b0bde5dc3..0d6e510e5f5e955e54933f1a737f35b0da8fc5b6 100644 --- a/app/code/Magento/Wishlist/view/frontend/web/wishlist.js +++ b/app/code/Magento/Wishlist/view/frontend/web/wishlist.js @@ -47,6 +47,7 @@ define([ event.preventDefault(); $.mage.dataPost().postData($(event.currentTarget).data('post-remove')); }, this)) + .on('click', this.options.addToCartSelector, $.proxy(this._beforeAddToCart, this)) .on('click', this.options.addAllToCartSelector, $.proxy(this._addAllWItemsToCart, this)) .on('focusin focusout', this.options.commentInputType, $.proxy(this._focusComment, this)); } @@ -59,6 +60,27 @@ define([ }); }, + /** + * Process data before add to cart + * + * - update item's qty value. + * + * @param {Event} event + * @private + */ + _beforeAddToCart: function(event) { + var elem = $(event.currentTarget), + itemId = elem.data(this.options.dataAttribute), + qtyName = $.validator.format(this.options.nameFormat, itemId), + qtyValue = elem.parents().find('[name="' + qtyName + '"]').val(), + params = elem.data('post'); + + if (params) { + params.data = $.extend({}, params.data, {'qty': qtyValue}); + elem.data('post', params); + } + }, + /** * Add wish list items to cart. * @private diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/RefundOrderTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/RefundOrderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..2050e01cbfdd70f5b26b40d4728d265b6b50fc77 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/RefundOrderTest.php @@ -0,0 +1,314 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Service\V1; + +/** + * API test for creation of Creditmemo for certain Order. + */ +class RefundOrderTest extends \Magento\TestFramework\TestCase\WebapiAbstract +{ + const SERVICE_READ_NAME = 'salesRefundOrderV1'; + const SERVICE_VERSION = 'V1'; + + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + + /** + * @var \Magento\Sales\Api\CreditmemoRepositoryInterface + */ + private $creditmemoRepository; + + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + + $this->creditmemoRepository = $this->objectManager->get( + \Magento\Sales\Api\CreditmemoRepositoryInterface::class + ); + } + + /** + * @magentoApiDataFixture Magento/Sales/_files/order_with_shipping_and_invoice.php + */ + public function testShortRequest() + { + /** @var \Magento\Sales\Model\Order $existingOrder */ + $existingOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class) + ->loadByIncrementId('100000001'); + + $result = $this->_webApiCall( + $this->getServiceData($existingOrder), + ['orderId' => $existingOrder->getEntityId()] + ); + + $this->assertNotEmpty( + $result, + 'Failed asserting that the received response is correct' + ); + + /** @var \Magento\Sales\Model\Order $updatedOrder */ + $updatedOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class) + ->loadByIncrementId($existingOrder->getIncrementId()); + + try { + $creditmemo = $this->creditmemoRepository->get($result); + + $expectedItems = $this->getOrderItems($existingOrder); + $actualCreditmemoItems = $this->getCreditmemoItems($creditmemo); + $actualRefundedOrderItems = $this->getRefundedOrderItems($updatedOrder); + + $this->assertEquals( + $expectedItems, + $actualCreditmemoItems, + 'Failed asserting that the Creditmemo contains all requested items' + ); + + $this->assertEquals( + $expectedItems, + $actualRefundedOrderItems, + 'Failed asserting that all requested order items were refunded' + ); + + $this->assertEquals( + $creditmemo->getShippingAmount(), + $existingOrder->getShippingAmount(), + 'Failed asserting that the Creditmemo contains correct shipping amount' + ); + + $this->assertEquals( + $creditmemo->getShippingAmount(), + $updatedOrder->getShippingRefunded(), + 'Failed asserting that proper shipping amount of the Order was refunded' + ); + + $this->assertNotEquals( + $existingOrder->getStatus(), + $updatedOrder->getStatus(), + 'Failed asserting that order status was changed' + ); + } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + $this->fail('Failed asserting that Creditmemo was created'); + } + } + + /** + * @magentoApiDataFixture Magento/Sales/_files/order_with_shipping_and_invoice.php + */ + public function testFullRequest() + { + /** @var \Magento\Sales\Model\Order $existingOrder */ + $existingOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class) + ->loadByIncrementId('100000001'); + + $expectedItems = $this->getOrderItems($existingOrder); + $expectedItems[0]['qty'] = $expectedItems[0]['qty'] - 1; + + $expectedComment = [ + 'comment' => 'Test Comment', + 'is_visible_on_front' => 1 + ]; + + $expectedShippingAmount = 15; + $expectedAdjustmentPositive = 5.53; + $expectedAdjustmentNegative = 5.53; + + $result = $this->_webApiCall( + $this->getServiceData($existingOrder), + [ + 'orderId' => $existingOrder->getEntityId(), + 'items' => $expectedItems, + 'comment' => $expectedComment, + 'arguments' => [ + 'shipping_amount' => $expectedShippingAmount, + 'adjustment_positive' => $expectedAdjustmentPositive, + 'adjustment_negative' => $expectedAdjustmentNegative + ] + ] + ); + + $this->assertNotEmpty( + $result, + 'Failed asserting that the received response is correct' + ); + + /** @var \Magento\Sales\Model\Order $updatedOrder */ + $updatedOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class) + ->loadByIncrementId($existingOrder->getIncrementId()); + + try { + $creditmemo = $this->creditmemoRepository->get($result); + + $actualCreditmemoItems = $this->getCreditmemoItems($creditmemo); + $actualCreditmemoComment = $this->getRecentComment($creditmemo); + $actualRefundedOrderItems = $this->getRefundedOrderItems($updatedOrder); + + $this->assertEquals( + $expectedItems, + $actualCreditmemoItems, + 'Failed asserting that the Creditmemo contains all requested items' + ); + + $this->assertEquals( + $expectedItems, + $actualRefundedOrderItems, + 'Failed asserting that all requested order items were refunded' + ); + + $this->assertEquals( + $expectedComment, + $actualCreditmemoComment, + 'Failed asserting that the Creditmemo contains correct comment' + ); + + $this->assertEquals( + $expectedShippingAmount, + $creditmemo->getShippingAmount(), + 'Failed asserting that the Creditmemo contains correct shipping amount' + ); + + $this->assertEquals( + $expectedShippingAmount, + $updatedOrder->getShippingRefunded(), + 'Failed asserting that proper shipping amount of the Order was refunded' + ); + + $this->assertEquals( + $expectedAdjustmentPositive, + $creditmemo->getAdjustmentPositive(), + 'Failed asserting that the Creditmemo contains correct positive adjustment' + ); + + $this->assertEquals( + $expectedAdjustmentNegative, + $creditmemo->getAdjustmentNegative(), + 'Failed asserting that the Creditmemo contains correct negative adjustment' + ); + + $this->assertEquals( + $existingOrder->getStatus(), + $updatedOrder->getStatus(), + 'Failed asserting that order status was NOT changed' + ); + } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + $this->fail('Failed asserting that Creditmemo was created'); + } + } + + /** + * Prepares and returns info for API service. + * + * @param \Magento\Sales\Api\Data\OrderInterface $order + * + * @return array + */ + private function getServiceData(\Magento\Sales\Api\Data\OrderInterface $order) + { + return [ + 'rest' => [ + 'resourcePath' => '/V1/order/' . $order->getEntityId() . '/refund', + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, + ], + 'soap' => [ + 'service' => self::SERVICE_READ_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_READ_NAME . 'execute', + ] + ]; + } + + /** + * Gets all items of given Order in proper format. + * + * @param \Magento\Sales\Model\Order $order + * + * @return array + */ + private function getOrderItems(\Magento\Sales\Model\Order $order) + { + $items = []; + + /** @var \Magento\Sales\Api\Data\OrderItemInterface $item */ + foreach ($order->getAllItems() as $item) { + $items[] = [ + 'order_item_id' => $item->getItemId(), + 'qty' => $item->getQtyOrdered(), + ]; + } + + return $items; + } + + /** + * Gets refunded items of given Order in proper format. + * + * @param \Magento\Sales\Model\Order $order + * + * @return array + */ + private function getRefundedOrderItems(\Magento\Sales\Model\Order $order) + { + $items = []; + + /** @var \Magento\Sales\Api\Data\OrderItemInterface $item */ + foreach ($order->getAllItems() as $item) { + if ($item->getQtyRefunded() > 0) { + $items[] = [ + 'order_item_id' => $item->getItemId(), + 'qty' => $item->getQtyRefunded(), + ]; + } + } + + return $items; + } + + /** + * Gets all items of given Creditmemo in proper format. + * + * @param \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo + * + * @return array + */ + private function getCreditmemoItems(\Magento\Sales\Api\Data\CreditmemoInterface $creditmemo) + { + $items = []; + + /** @var \Magento\Sales\Api\Data\CreditmemoItemInterface $item */ + foreach ($creditmemo->getItems() as $item) { + $items[] = [ + 'order_item_id' => $item->getOrderItemId(), + 'qty' => $item->getQty(), + ]; + } + + return $items; + } + + /** + * Gets the most recent comment of given Creditmemo in proper format. + * + * @param \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo + * + * @return array|null + */ + private function getRecentComment(\Magento\Sales\Api\Data\CreditmemoInterface $creditmemo) + { + $comments = $creditmemo->getComments(); + + if ($comments) { + $comment = reset($comments); + + return [ + 'comment' => $comment->getComment(), + 'is_visible_on_front' => $comment->getIsVisibleOnFront(), + ]; + } + + return null; + } +} diff --git a/dev/tests/functional/bootstrap.php b/dev/tests/functional/bootstrap.php index 72dc6bf208cd0e5dff754f515db9f3a1a5b9a4c7..1ff4fd4e8d22396a71e3413cf9e4247b5225f0b2 100644 --- a/dev/tests/functional/bootstrap.php +++ b/dev/tests/functional/bootstrap.php @@ -11,7 +11,15 @@ defined('MTF_STATES_PATH') || define('MTF_STATES_PATH', MTF_BP . '/lib/Magento/M require_once __DIR__ . '/../../../app/bootstrap.php'; restore_error_handler(); -require_once __DIR__ . '/vendor/autoload.php'; +$vendorAutoload = __DIR__ . '/vendor/autoload.php'; + +if (isset($composerAutoloader)) { + /** var $mtfComposerAutoload \Composer\Autoload\ClassLoader */ + $mtfComposerAutoload = include $vendorAutoload; + $composerAutoloader->addClassMap($mtfComposerAutoload->getClassMap()); +} else { + $composerAutoloader = include $vendorAutoload; +} setCustomErrorHandler(); diff --git a/dev/tests/functional/composer.json b/dev/tests/functional/composer.json index 936c4b968af3b88ed9f05fb2d21dfe3c8184181f..512c5bc8d5df666ced655806af0da64952d0f4ef 100644 --- a/dev/tests/functional/composer.json +++ b/dev/tests/functional/composer.json @@ -1,8 +1,8 @@ { "require": { - "magento/mtf": "1.0.0-rc47", + "magento/mtf": "1.0.0-rc48", "php": "~5.6.0|7.0.2|~7.0.6", - "phpunit/phpunit": "4.1.0", + "phpunit/phpunit": "~4.8.0|~5.5.0", "phpunit/phpunit-selenium": ">=1.2" }, "suggest": { diff --git a/dev/tests/functional/credentials.xml.dist b/dev/tests/functional/credentials.xml.dist index 3c61630fb148df35b67d7430c961bcfc9482cbb1..88794d183e8781d5cea1c9199370c67daae1585c 100644 --- a/dev/tests/functional/credentials.xml.dist +++ b/dev/tests/functional/credentials.xml.dist @@ -27,4 +27,36 @@ <field replace="carriers_dhl_id_eu" value="" /> <field replace="carriers_dhl_password_eu" value="" /> <field replace="carriers_dhl_account_eu" value="" /> + + <field path="payment/authorizenet_directpost/login" value="" /> + <field path="payment/authorizenet_directpost/trans_key" value="" /> + <field path="payment/authorizenet_directpost/trans_md5" value="" /> + + <field path="payment/braintree_section/braintree/braintree_advanced/merchant_account_id" value="" /> + <field path="payment/braintree_section/braintree/braintree_required/merchant_id" value="" /> + <field path="payment/braintree_section/braintree/braintree_required/public_key" value="" /> + <field path="payment/braintree_section/braintree/braintree_required/private_key" value="" /> + + <field path="payment/paypal_group_all_in_one/wpp_usuk/wpp_required_settings/wpp_and_express_checkout/business_account" value="" /> + <field path="payment/paypal_group_all_in_one/wpp_usuk/wpp_required_settings/wpp_and_express_checkout/api_username" value="" /> + <field path="payment/paypal_group_all_in_one/wpp_usuk/wpp_required_settings/wpp_and_express_checkout/api_password" value="" /> + <field path="payment/paypal_group_all_in_one/wpp_usuk/wpp_required_settings/wpp_and_express_checkout/api_signature" value="" /> + <field path="payment/paypal_express/merchant_id" value="" /> + + <field path="payment/paypal_payment_gateways/paypal_payflowpro_with_express_checkout/paypal_payflow_required/paypal_payflow_api_settings/business_account" value="" /> + <field path="payment/paypal_payment_gateways/paypal_payflowpro_with_express_checkout/paypal_payflow_required/paypal_payflow_api_settings/partner" value="" /> + <field path="payment/paypal_payment_gateways/paypal_payflowpro_with_express_checkout/paypal_payflow_required/paypal_payflow_api_settings/user" value="" /> + <field path="payment/paypal_payment_gateways/paypal_payflowpro_with_express_checkout/paypal_payflow_required/paypal_payflow_api_settings/pwd" value="" /> + <field path="payment/paypal_payment_gateways/paypal_payflowpro_with_express_checkout/paypal_payflow_required/paypal_payflow_api_settings/vendor" value="" /> + + <field path="payment/paypal_alternative_payment_methods/express_checkout_us/express_checkout_required/express_checkout_required_express_checkout/business_account" value="" /> + <field path="payment/paypal_alternative_payment_methods/express_checkout_us/express_checkout_required/express_checkout_required_express_checkout/api_username" value="" /> + <field path="payment/paypal_alternative_payment_methods/express_checkout_us/express_checkout_required/express_checkout_required_express_checkout/api_password" value="" /> + <field path="payment/paypal_alternative_payment_methods/express_checkout_us/express_checkout_required/express_checkout_required_express_checkout/api_signature" value="" /> + + <field path="payment/paypal_payment_gateways/payflow_link_us/payflow_link_required/payflow_link_payflow_link/business_account" value="" /> + <field path="payment/paypal_payment_gateways/payflow_link_us/payflow_link_required/payflow_link_payflow_link/partner" value="" /> + <field path="payment/paypal_payment_gateways/payflow_link_us/payflow_link_required/payflow_link_payflow_link/user" value="" /> + <field path="payment/paypal_payment_gateways/payflow_link_us/payflow_link_required/payflow_link_payflow_link/pwd" value="" /> + <field path="payment/paypal_payment_gateways/payflow_link_us/payflow_link_required/payflow_link_payflow_link/vendor" value="" /> </replace> diff --git a/dev/tests/functional/lib/Magento/Mtf/Client/Element/RadiobuttonElement.php b/dev/tests/functional/lib/Magento/Mtf/Client/Element/RadiobuttonElement.php new file mode 100644 index 0000000000000000000000000000000000000000..cfb7990196598e317d3ec51b41b1524ac62a7fb5 --- /dev/null +++ b/dev/tests/functional/lib/Magento/Mtf/Client/Element/RadiobuttonElement.php @@ -0,0 +1,57 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Mtf\Client\Element; + +use Magento\Mtf\Client\Locator; + +/** + * Class provides ability to work with page element radio button. + */ +class RadiobuttonElement extends SimpleElement +{ + /** + * Label for radio button selector. + * + * @var string + */ + protected $labelSelector = './..//label[contains(., "%s")]'; + + /** + * Selector for selected label. + * + * @var string + */ + protected $selectedLabelSelector = 'input[type=radio]:checked + label'; + + /** + * Get value of the required element. + * + * @return string + */ + public function getValue() + { + $this->eventManager->dispatchEvent(['get_value'], [$this->getAbsoluteSelector()]); + + return $this->find($this->selectedLabelSelector)->getText(); + } + + /** + * Select radio button based on label value. + * + * @param string $value + * @return void + */ + public function setValue($value) + { + $this->eventManager->dispatchEvent(['set_value'], [__METHOD__, $this->getAbsoluteSelector()]); + + $radioButtonLabel = $this->find(sprintf($this->labelSelector, $value), Locator::SELECTOR_XPATH); + if (!$this->isSelected()) { + $radioButtonLabel->click(); + } + } +} diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/Block/System/Config/Braintree.php b/dev/tests/functional/tests/app/Magento/Braintree/Test/Block/System/Config/Braintree.php index f46f070216c6e59456281af4ae99762898b3da71..c85c857cbeb5450ae39c29bc6658ef920e984f96 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/Block/System/Config/Braintree.php +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/Block/System/Config/Braintree.php @@ -33,7 +33,7 @@ class Braintree extends Block private $enablers = [ 'Enable this Solution' => "#payment_us_braintree_section_braintree_active", 'Enable PayPal through Braintree' => '#payment_us_braintree_section_braintree_active_braintree_paypal', - 'Vault enabled' => '#payment_us_braintree_section_braintree_braintree_cc_vault_active' + 'Vault Enabled' => '#payment_us_braintree_section_braintree_braintree_cc_vault_active' ]; /** diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleProductInCart.php b/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleProductInCart.php new file mode 100644 index 0000000000000000000000000000000000000000..831d2e88b37cbeabab87108ffab56f63668c6f63 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleProductInCart.php @@ -0,0 +1,44 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Bundle\Test\Constraint; + +use Magento\Catalog\Test\Constraint\AssertProductInCart; +use Magento\Checkout\Test\Page\CheckoutCart; +use Magento\Mtf\Fixture\FixtureInterface; + +/** + * Assertion that bundle product is correctly displayed in cart. + */ +class AssertBundleProductInCart extends AssertProductInCart +{ + /** + * Count prices. + * + * @param FixtureInterface $product + * @param CheckoutCart $checkoutCart + * @return void + */ + protected function countPrices(FixtureInterface $product, CheckoutCart $checkoutCart) + { + parent::countPrices($product, $checkoutCart); + $this->countSubItemPrice($product); + } + + /** + * Count subItem price. + * + * @param FixtureInterface $product + * @return void + */ + private function countSubItemPrice(FixtureInterface $product) + { + $checkoutData = $product->getCheckoutData(); + if (isset($checkoutData['cartItem']['subItemPrice'])) { + $this->fixtureActualPrice += $checkoutData["cartItem"]["subItemPrice"]; + } + } +} diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleProductPage.php b/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleProductPage.php index 704404e665ac1c394d9d28621a0dcb9139dd4047..8e113996bda074b089119339a984ea217b6a6e7a 100644 --- a/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleProductPage.php +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleProductPage.php @@ -46,4 +46,39 @@ class AssertBundleProductPage extends AssertProductPage return empty($errors) ? null : implode("\n", $errors); } + + /** + * Verify product special price is displayed on product page(front-end). + * + * @return string|null + */ + protected function verifySpecialPrice() + { + if (!$this->product->hasData('special_price')) { + return null; + } + + $priceBlock = $this->productView->getPriceBlock(); + + if (!$priceBlock->isVisible()) { + return "Price block for '{$this->product->getName()}' product' is not visible."; + } + + if (!$priceBlock->isOldPriceVisible()) { + return 'Bundle special price is not set.'; + } + + $regularPrice = $priceBlock->getOldPrice(); + $priceData = $this->product->getDataFieldConfig('price')['source']->getPriceData(); + + if (!isset($priceData['regular_from'])) { + return 'Regular from price not set.'; + } + + if ($priceData['regular_from'] != $regularPrice) { + return 'Bundle regular price on product view page is not correct.'; + } + + return null; + } } diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct/BundleSelections.xml b/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct/BundleSelections.xml index 0b142044323d197e72a4c1544334d42cd7e16ae5..3e9b055c48ca5a1bfa5df3410ec48ef708a71fa7 100644 --- a/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct/BundleSelections.xml +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct/BundleSelections.xml @@ -150,7 +150,7 @@ <field name="products" xsi:type="array"> <item name="0" xsi:type="array"> <item name="0" xsi:type="string">catalogProductSimple::default</item> - <item name="1" xsi:type="string">catalogProductSimple::product_100_dollar</item> + <item name="1" xsi:type="string">catalogProductSimple::product_10_dollar</item> </item> </field> </dataset> @@ -604,5 +604,66 @@ </item> </field> </dataset> + + <dataset name="dynamic_with_two_required_options_assigned_products_with_special_price"> + <field name="bundle_options" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="title" xsi:type="string">Drop-down Option</item> + <item name="type" xsi:type="string">Drop-down</item> + <item name="required" xsi:type="string">Yes</item> + <item name="assigned_products" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="search_data" xsi:type="array"> + <item name="name" xsi:type="string">%product_name%</item> + </item> + <item name="data" xsi:type="array"> + <item name="selection_qty" xsi:type="string">1</item> + </item> + </item> + <item name="1" xsi:type="array"> + <item name="search_data" xsi:type="array"> + <item name="name" xsi:type="string">%product_name%</item> + </item> + <item name="data" xsi:type="array"> + <item name="selection_qty" xsi:type="string">1</item> + </item> + </item> + </item> + </item> + <item name="1" xsi:type="array"> + <item name="title" xsi:type="string">Drop-down Option</item> + <item name="type" xsi:type="string">Drop-down</item> + <item name="required" xsi:type="string">Yes</item> + <item name="assigned_products" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="search_data" xsi:type="array"> + <item name="name" xsi:type="string">%product_name%</item> + </item> + <item name="data" xsi:type="array"> + <item name="selection_qty" xsi:type="string">1</item> + </item> + </item> + <item name="1" xsi:type="array"> + <item name="search_data" xsi:type="array"> + <item name="name" xsi:type="string">%product_name%</item> + </item> + <item name="data" xsi:type="array"> + <item name="selection_qty" xsi:type="string">1</item> + </item> + </item> + </item> + </item> + </field> + <field name="products" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="0" xsi:type="string">catalogProductSimple::product_10_dollar</item> + <item name="1" xsi:type="string">catalogProductSimple::product_with_special_price</item> + </item> + <item name="1" xsi:type="array"> + <item name="0" xsi:type="string">catalogProductSimple::product_10_dollar</item> + <item name="1" xsi:type="string">catalogProductSimple::product_with_special_price</item> + </item> + </field> + </dataset> </repository> </config> diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct/CheckoutData.xml b/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct/CheckoutData.xml index 3cc61abfd2e89b3bcfdbe61aac6fbc84dbf461de..97c11284d77c88194a5a5271e31ca7b91a4babf3 100644 --- a/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct/CheckoutData.xml +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct/CheckoutData.xml @@ -206,6 +206,36 @@ </field> </dataset> + <dataset name="bundle_with_custom_options_3"> + <field name="options" xsi:type="array"> + <item name="bundle_options" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="title" xsi:type="string">Drop-down Option</item> + <item name="type" xsi:type="string">Drop-down</item> + <item name="value" xsi:type="array"> + <item name="name" xsi:type="string">product_10_dollar</item> + </item> + </item> + </item> + <item name="custom_options" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="title" xsi:type="string">attribute_key_0</item> + <item name="value" xsi:type="string">option_key_0</item> + </item> + <item name="1" xsi:type="array"> + <item name="title" xsi:type="string">attribute_key_1</item> + <item name="value" xsi:type="string">option_key_0</item> + </item> + </item> + </field> + <field name="cartItem" xsi:type="array"> + <item name="price" xsi:type="string">100</item> + <item name="qty" xsi:type="string">1</item> + <item name="subtotal" xsi:type="string">100</item> + <item name="subItemPrice" xsi:type="string">10</item> + </field> + </dataset> + <dataset name="bundle_all_types_bundle_fixed_and_custom_options"> <field name="options" xsi:type="array"> <item name="bundle_options" xsi:type="array"> diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct/Price.xml b/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct/Price.xml index c6b69dd6937d9bba0628b6cd7fca47d988cf12fb..c4431c5d02072766003b123ce269298f275d5be9 100644 --- a/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct/Price.xml +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct/Price.xml @@ -78,6 +78,18 @@ <field name="cart_price" xsi:type="string">756.00</field> </dataset> + <dataset name="fixed-51"> + <field name="price_from" xsi:type="string">51.00</field> + <field name="price_to" xsi:type="string">52.00</field> + <field name="regular_from" xsi:type="string">135.00</field> + </dataset> + + <dataset name="fixed-756-custom-options"> + <field name="price_from" xsi:type="string">785.00</field> + <field name="price_to" xsi:type="string">786.00</field> + <field name="cart_price" xsi:type="string">786.00</field> + </dataset> + <dataset name="bundle_fixed_with_category"> <field name="price_from" xsi:type="string">130.00</field> <field name="price_to" xsi:type="string">144.00</field> @@ -94,6 +106,12 @@ <field name="price_from" xsi:type="string">8.00</field> <field name="price_to" xsi:type="string">20.00</field> <field name="cart_price" xsi:type="string">80.00</field> + <field name="regular_from" xsi:type="string">40.00</field> + </dataset> + + <dataset name="dynamic-18"> + <field name="price_from" xsi:type="string">18.00</field> + <field name="price_to" xsi:type="string">20.00</field> </dataset> <dataset name="dynamic-32"> diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/CreateBundleProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/CreateBundleProductEntityTest.xml index 6fcef33132603bdcc157dcd683993a5893a2dcfb..678e9306cbadb3bc22dafd9bca089a8b7f304ba7 100644 --- a/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/CreateBundleProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/CreateBundleProductEntityTest.xml @@ -160,7 +160,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductOutOfStock" /> <constraint name="Magento\Bundle\Test\Constraint\AssertBundlePriceView" /> </variation> - <variation name="CreateBundleProductEntityTestVariation7"> + <variation name="CreateBundleProductEntityTestVariation7" summary="Check bundle dynamic product with tier price"> <data name="product/data/url_key" xsi:type="string">bundle-product-%isolation%</data> <data name="product/data/name" xsi:type="string">BundleProduct %isolation%</data> <data name="product/data/sku_type" xsi:type="string">Yes</data> @@ -333,5 +333,118 @@ <constraint name="Magento\Bundle\Test\Constraint\AssertBundleInCategory" /> <constraint name="Magento\Bundle\Test\Constraint\AssertBundleProductPage" /> </variation> + <variation name="CreateBundleProductEntityTestVariation17" summary="Create fixed bundle product with tier price and custom options with fixed and percent price"> + <data name="product/data/url_key" xsi:type="string">bundle-product-%isolation%</data> + <data name="product/data/name" xsi:type="string">Bundle Fixed %isolation%</data> + <data name="product/data/sku_type" xsi:type="string">No</data> + <data name="product/data/sku" xsi:type="string">sku_bundle_fixed_%isolation%</data> + <data name="product/data/price_type" xsi:type="string">No</data> + <data name="product/data/price/value" xsi:type="string">100</data> + <data name="product/data/price/dataset" xsi:type="string">fixed-100</data> + <data name="product/data/weight_type" xsi:type="string">No</data> + <data name="product/data/weight" xsi:type="string">10</data> + <data name="product/data/tier_price/dataset" xsi:type="string">default</data> + <data name="product/data/price_view" xsi:type="string">As Low as</data> + <data name="product/data/stock_data/use_config_manage_stock" xsi:type="string">No</data> + <data name="product/data/stock_data/manage_stock" xsi:type="string">No</data> + <data name="product/data/shipment_type" xsi:type="string">Together</data> + <data name="product/data/bundle_selections/dataset" xsi:type="string">second</data> + <data name="product/data/custom_options/dataset" xsi:type="string">percent_and_fixed_drop_down_options</data> + <data name="product/data/checkout_data/dataset" xsi:type="string">bundle_with_custom_options_3</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> + <constraint name="Magento\Bundle\Test\Constraint\AssertBundleProductForm" /> + <constraint name="Magento\Bundle\Test\Constraint\AssertBundleItemsOnProductPage" /> + <constraint name="Magento\Bundle\Test\Constraint\AssertTierPriceOnBundleProductPage" /> + <constraint name="Magento\Bundle\Test\Constraint\AssertProductCustomOptionsOnBundleProductPage" /> + <constraint name="Magento\Bundle\Test\Constraint\AssertBundleProductInCart" /> + </variation> + <variation name="CreateBundleProductEntityTestVariation18" summary="Create dynamic bundle product with tier price for sub-item"> + <data name="product/data/url_key" xsi:type="string">bundle-product-%isolation%</data> + <data name="product/data/name" xsi:type="string">BundleProduct %isolation%</data> + <data name="product/data/sku_type" xsi:type="string">Yes</data> + <data name="product/data/sku" xsi:type="string">bundle_sku_%isolation%</data> + <data name="product/data/price_type" xsi:type="string">Yes</data> + <data name="product/data/price/dataset" xsi:type="string">dynamic-50</data> + <data name="product/data/weight_type" xsi:type="string">No</data> + <data name="product/data/weight" xsi:type="string">10</data> + <data name="product/data/tier_price/dataset" xsi:type="string">default</data> + <data name="product/data/price_view" xsi:type="string">As Low as</data> + <data name="product/data/stock_data/use_config_manage_stock" xsi:type="string">No</data> + <data name="product/data/stock_data/manage_stock" xsi:type="string">No</data> + <data name="product/data/shipment_type" xsi:type="string">Together</data> + <data name="product/data/bundle_selections/dataset" xsi:type="string">default_dynamic</data> + <data name="product/data/bundle_selections/products" xsi:type="string">catalogProductSimple::simple_with_tier_price,catalogProductVirtual::product_50_dollar</data> + <data name="product/data/checkout_data/dataset" xsi:type="string">bundle_default</data> + <data name="product/data/visibility" xsi:type="string">Search</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> + <constraint name="Magento\Bundle\Test\Constraint\AssertBundleProductForm" /> + <constraint name="Magento\Bundle\Test\Constraint\AssertBundleItemsOnProductPage" /> + <constraint name="Magento\Bundle\Test\Constraint\AssertTierPriceOnBundleProductPage" /> + </variation> + <variation name="CreateBundleProductEntityTestVariation19" summary="Create bundle with simple out of stock"> + <data name="product/data/url_key" xsi:type="string">bundle-product-%isolation%</data> + <data name="product/data/name" xsi:type="string">BundleProduct %isolation%</data> + <data name="product/data/sku" xsi:type="string">bundle_sku_%isolation%</data> + <data name="product/data/price_type" xsi:type="string">Yes</data> + <data name="product/data/price/dataset" xsi:type="string">dynamic-50</data> + <data name="product/data/bundle_selections/dataset" xsi:type="string">default_dynamic</data> + <data name="product/data/bundle_selections/products" xsi:type="string">catalogProductSimple::out_of_stock,catalogProductSimple::out_of_stock</data> + <data name="product/data/checkout_data/dataset" xsi:type="string">bundle_default</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductOutOfStock" /> + </variation> + <variation name="CreateBundleProductEntityTestVariation21" summary="Create default fixed bundle with custom Website"> + <data name="product/data/url_key" xsi:type="string">bundle-product-%isolation%</data> + <data name="product/data/name" xsi:type="string">Bundle Fixed %isolation%</data> + <data name="product/data/sku_type" xsi:type="string">No</data> + <data name="product/data/sku" xsi:type="string">sku_bundle_fixed_%isolation%</data> + <data name="product/data/price_type" xsi:type="string">No</data> + <data name="product/data/price/value" xsi:type="string">10</data> + <data name="product/data/bundle_selections/dataset" xsi:type="string">second</data> + <data name="product/data/bundle_selections/products" xsi:type="string">catalogProductSimple::product_100_dollar,catalogProductSimple::product_40_dollar</data> + <data name="product/data/checkout_data/dataset" xsi:type="string">bundle_default</data> + <data name="product/data/website_ids/0/dataset" xsi:type="string">custom_store</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductOnCustomWebsite" /> + </variation> + <variation name="CreateBundleProductEntityTestVariation22" summary="Create Bundle (fixed) Product with special price and Custom options with fixed price"> + <data name="product/data/url_key" xsi:type="string">bundle-product-%isolation%</data> + <data name="product/data/name" xsi:type="string">Bundle Fixed %isolation%</data> + <data name="product/data/sku_type" xsi:type="string">No</data> + <data name="product/data/sku" xsi:type="string">sku_bundle_fixed_%isolation%</data> + <data name="product/data/price_type" xsi:type="string">No</data> + <data name="product/data/price/value" xsi:type="string">100</data> + <data name="product/data/price/dataset" xsi:type="string">fixed-51</data> + <data name="product/data/price_view" xsi:type="string">Price Range</data> + <data name="product/data/special_price" xsi:type="string">20</data> + <data name="product/data/bundle_selections/dataset" xsi:type="string">second</data> + <data name="product/data/custom_options/dataset" xsi:type="string">drop_down_with_one_option_fixed_price</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> + <constraint name="Magento\Bundle\Test\Constraint\AssertBundleProductPage" /> + </variation> + <variation name="CreateBundleProductEntityTestVariation23" summary="Create Bundle (dynamic) Product with special price"> + <data name="product/data/url_key" xsi:type="string">bundle-product-%isolation%</data> + <data name="product/data/name" xsi:type="string">Bundle Dynamic %isolation%</data> + <data name="product/data/sku_type" xsi:type="string">Yes</data> + <data name="product/data/sku" xsi:type="string">sku_bundle_dynamic_%isolation%</data> + <data name="product/data/price_type" xsi:type="string">Yes</data> + <data name="product/data/price/dataset" xsi:type="string">dynamic-8</data> + <data name="product/data/special_price" xsi:type="string">20</data> + <data name="product/data/bundle_selections/dataset" xsi:type="string">default_dynamic</data> + <data name="product/data/bundle_selections/products" xsi:type="string">catalogProductSimple::product_100_dollar,catalogProductSimple::product_40_dollar</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> + <constraint name="Magento\Bundle\Test\Constraint\AssertBundleProductPage" /> + </variation> + <variation name="CreateBundleProductEntityTestVariation24" summary="Create Bundle (dynamic) Product with One of the bundle options has special price"> + <data name="product/data/url_key" xsi:type="string">bundle-product-%isolation%</data> + <data name="product/data/name" xsi:type="string">Bundle Dynamic %isolation%</data> + <data name="product/data/sku_type" xsi:type="string">Yes</data> + <data name="product/data/sku" xsi:type="string">sku_bundle_dynamic_%isolation%</data> + <data name="product/data/price_type" xsi:type="string">Yes</data> + <data name="product/data/price/dataset" xsi:type="string">dynamic-18</data> + <data name="product/data/bundle_selections/dataset" xsi:type="string">dynamic_with_two_required_options_assigned_products_with_special_price</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> + <constraint name="Magento\Bundle\Test\Constraint\AssertBundleProductPage" /> + </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/UpdateBundleProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/UpdateBundleProductEntityTest.xml index 1f241c1e321be78e51a04d04c3e2ca2ab4b04ca0..e4f567de39481249ca083398152f237c35b3c2cf 100644 --- a/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/UpdateBundleProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/UpdateBundleProductEntityTest.xml @@ -73,5 +73,15 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> <constraint name="Magento\Bundle\Test\Constraint\AssertBundleProductPage" /> </variation> + <variation name="UpdateBundleProductEntityTestVariation5" summary="Update Bundle (fixed) Product with custom option"> + <data name="originalProduct/dataset" xsi:type="string">bundle_fixed_product</data> + <data name="product/data/url_key" xsi:type="string">bundle-product-%isolation%</data> + <data name="product/data/name" xsi:type="string">bundle_fixed_%isolation%</data> + <data name="product/data/sku" xsi:type="string">bundle_sku_%isolation%</data> + <data name="product/data/price/dataset" xsi:type="string">fixed-756-custom-options</data> + <data name="product/data/custom_options/dataset" xsi:type="string">drop_down_with_one_option_fixed_price</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> + <constraint name="Magento\Bundle\Test\Constraint\AssertBundleProductPage" /> + </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Category/Edit/CategoryForm.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Category/Edit/CategoryForm.xml index 593ab70e100ff473b6a87c1ea75e6f3af348107d..42f490d76191a524b12c7837ab5958907199fece 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Category/Edit/CategoryForm.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Category/Edit/CategoryForm.xml @@ -134,11 +134,11 @@ <strategy>xpath</strategy> <fields> <schedule_update_from> - <input>text</input> + <input>datepicker</input> <selector>input[name='custom_design_from']</selector> </schedule_update_from> <schedule_update_to> - <input>text</input> + <input>datepicker</input> <selector>input[name='custom_design_to']</selector> </schedule_update_to> </fields> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Category/Edit/Section/ProductGrid.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Category/Edit/Section/ProductGrid.php index e05bf4af830b6f03dfa801fbb18eefff5a366132..eb2a9c4a835ceb8e55156f49a7ebb9d04ef1cc86 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Category/Edit/Section/ProductGrid.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Category/Edit/Section/ProductGrid.php @@ -36,5 +36,5 @@ class ProductGrid extends Grid * * @var string */ - protected $selectItem = 'tbody tr .col-in_category'; + protected $selectItem = 'tbody tr .col-in_category input'; } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductInCart.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductInCart.php index 85514109053b438af1425cf266d11bd8ac596cb3..e698691faa64d41d7f540dac04c5d48658363333 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductInCart.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductInCart.php @@ -14,13 +14,33 @@ use Magento\Mtf\Constraint\AbstractConstraint; use Magento\Mtf\Fixture\FixtureInterface; /** - * Class AssertProductInCart - * Assertion that the product is correctly displayed in cart + * Assertion that the product is correctly displayed in cart. */ class AssertProductInCart extends AbstractConstraint { /** - * Assertion that the product is correctly displayed in cart + * Price on form. + * + * @var string + */ + protected $formPrice; + + /** + * Fixture actual price. + * + * @var string + */ + protected $fixtureActualPrice; + + /** + * Fixture price. + * + * @var string + */ + protected $fixturePrice; + + /** + * Assertion that the product is correctly displayed in cart. * * @param CatalogProductView $catalogProductView * @param FixtureInterface $product @@ -41,58 +61,102 @@ class AssertProductInCart extends AbstractConstraint $catalogProductView->getMessagesBlock()->waitSuccessMessage(); // Check price - $this->assertOnShoppingCart($product, $checkoutCart); + $this->countPrices($product, $checkoutCart); + \PHPUnit_Framework_Assert::assertEquals( + $this->fixtureActualPrice, + $this->formPrice, + 'Product price in shopping cart is not correct.' + ); } /** - * Assert prices on the shopping cart + * Count prices. * * @param FixtureInterface $product * @param CheckoutCart $checkoutCart * @return void + */ + protected function countPrices(FixtureInterface $product, CheckoutCart $checkoutCart) + { + /** @var CatalogProductSimple $product */ + $this->fixturePrice = $product->getPrice(); + $this->prepareFormPrice($product, $checkoutCart); + $this->countSpecialPrice($product); + $this->countCheckoutCartItemPrice($product); + $this->countCustomOptionsPrice($product); + } + + /** + * Count count special price. * - * @SuppressWarnings(PHPMD.NPathComplexity) + * @param FixtureInterface $product + * @return void */ - protected function assertOnShoppingCart(FixtureInterface $product, CheckoutCart $checkoutCart) + protected function countSpecialPrice(FixtureInterface $product) { - $checkoutCart->open(); /** @var CatalogProductSimple $product */ - $customOptions = $product->getCustomOptions(); - $checkoutData = $product->getCheckoutData(); - $checkoutCartItem = isset($checkoutData['cartItem']) ? $checkoutData['cartItem'] : []; - $checkoutCustomOptions = isset($checkoutData['options']['custom_options']) - ? $checkoutData['options']['custom_options'] - : []; - $fixturePrice = $product->getPrice(); $specialPrice = $product->getSpecialPrice(); - $cartItem = $checkoutCart->getCartBlock()->getCartItem($product); - $formPrice = $cartItem->getPrice(); - if ($specialPrice) { - $fixturePrice = $specialPrice; + $this->fixturePrice = $product->getSpecialPrice(); } + } + + /** + * Prepare form price. + * + * @param FixtureInterface $product + * @param CheckoutCart $checkoutCart + * @return void + */ + protected function prepareFormPrice(FixtureInterface $product, CheckoutCart $checkoutCart) + { + $checkoutCart->open(); + $cartItem = $checkoutCart->getCartBlock()->getCartItem($product); + $this->formPrice = $cartItem->getPrice(); + } + + /** + * Count cart item price. + * + * @param FixtureInterface $product + * @return void + */ + protected function countCheckoutCartItemPrice(FixtureInterface $product) + { + /** @var CatalogProductSimple $product */ + $checkoutData = $product->getCheckoutData(); + $checkoutCartItem = isset($checkoutData['cartItem']) ? $checkoutData['cartItem'] : []; if (isset($checkoutCartItem['price'])) { - $fixturePrice = $checkoutCartItem['price']; + $this->fixturePrice = $checkoutCartItem['price']; } - $fixtureActualPrice = $fixturePrice; + $this->fixtureActualPrice = $this->fixturePrice; + } + /** + * Count custom options price. + * + * @param FixtureInterface $product + * @return void + */ + protected function countCustomOptionsPrice(FixtureInterface $product) + { + /** @var CatalogProductSimple $product */ + $customOptions = $product->getCustomOptions(); + $checkoutData = $product->getCheckoutData(); + $checkoutCustomOptions = isset($checkoutData['options']['custom_options']) + ? $checkoutData['options']['custom_options'] + : []; foreach ($checkoutCustomOptions as $checkoutOption) { $attributeKey = str_replace('attribute_key_', '', $checkoutOption['title']); $optionKey = str_replace('option_key_', '', $checkoutOption['value']); $option = $customOptions[$attributeKey]['options'][$optionKey]; if ('Fixed' == $option['price_type']) { - $fixtureActualPrice += $option['price']; + $this->fixtureActualPrice += $option['price']; } else { - $fixtureActualPrice += ($fixturePrice / 100) * $option['price']; + $this->fixtureActualPrice += ($this->fixturePrice / 100) * $option['price']; } } - - \PHPUnit_Framework_Assert::assertEquals( - $fixtureActualPrice, - $formPrice, - 'Product price in shopping cart is not correct.' - ); } /** diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Curl.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Curl.php index e660b0ea05fab3e365f899374a9240e14bdd951c..2ccb804b073f90255f378a6e3ef6e8af5d427332 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Curl.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Curl.php @@ -413,12 +413,12 @@ class Curl extends AbstractCurl implements CatalogProductSimpleInterface { if (!empty($this->fields['product']['website_ids'])) { foreach ($this->fixture->getDataFieldConfig('website_ids')['source']->getWebsites() as $key => $website) { - $this->fields['product']['website_ids'][$key] = $website->getWebsiteId(); + $this->fields['product']['extension_attributes']['website_ids'][$key] = $website->getWebsiteId(); } } else { $website = \Magento\Mtf\ObjectManagerFactory::getObjectManager() ->create(\Magento\Store\Test\Fixture\Website::class, ['dataset' => 'default']); - $this->fields['product']['website_ids'][] = $website->getWebsiteId(); + $this->fields['product']['extension_attributes']['website_ids'][] = $website->getWebsiteId(); } } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Webapi.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Webapi.php index 4ec372ab7163bb627bdf1353618f81231bed504d..4c3d344832f85f57d065ccccc01e44e1daf873b1 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Webapi.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Webapi.php @@ -64,13 +64,6 @@ class Webapi extends AbstractWebApi implements CatalogProductSimpleInterface 'custom_attributes' ]; - /** - * Website Ids for current Product. - * - * @var array - */ - private $websiteIds = []; - /** * @constructor * @param DataInterface $configuration @@ -102,8 +95,6 @@ class Webapi extends AbstractWebApi implements CatalogProductSimpleInterface $this->prepareData(); $this->convertData(); - //TODO: Change create and assign product to website flow using 1 request after MAGETWO-52812 delivery. - /** @var CatalogProductSimple $fixture */ $url = $_ENV['app_frontend_url'] . 'rest/all/V1/products'; $this->webapiTransport->write($url, $this->fields, CurlInterface::POST); @@ -116,110 +107,9 @@ class Webapi extends AbstractWebApi implements CatalogProductSimpleInterface throw new \Exception("Product creation by webapi handler was not successful! Response: {$encodedResponse}"); } - $this->assignToWebsites($response); - return $this->parseResponse($response); } - /** - * Assign appropriate Websites to Product and unset all other. - * - * @param array $product - * @return void - */ - private function assignToWebsites($product) - { - $this->setWebsites($product); - $this->unsetWebsites($product); - } - - /** - * Get all Websites. - * - * @return array - * @throws \Exception - */ - private function getAllWebsites() - { - $url = $_ENV['app_frontend_url'] . 'rest/V1/store/websites'; - $this->webapiTransport->write($url, [], CurlInterface::GET); - $encodedResponse = $this->webapiTransport->read(); - $response = json_decode($encodedResponse, true); - $this->webapiTransport->close(); - - if (!isset($response[0]['id'])) { - $this->eventManager->dispatchEvent(['webapi_failed'], [$response]); - throw new \Exception( - "Attempt to get all Websites by webapi handler was not successful! Response: {$encodedResponse}" - ); - } - - return $response; - } - - /** - * Set appropriate Websites to Product. - * - * @param array $product - * @return void - * @throws \Exception - */ - private function setWebsites($product) - { - foreach ($this->websiteIds as $id) { - $url = $_ENV['app_frontend_url'] . 'rest/V1/products/' . $product['sku'] . '/websites'; - $productWebsiteLink = ['productWebsiteLink' => ['website_id' => $id, 'sku' => $product['sku']]]; - $this->webapiTransport->write($url, $productWebsiteLink, CurlInterface::POST); - $encodedResponse = $this->webapiTransport->read(); - $response = json_decode($encodedResponse, true); - $this->webapiTransport->close(); - - if ($response !== true) { - $this->eventManager->dispatchEvent(['webapi_failed'], [$response]); - throw new \Exception( - "Product addition to Website by webapi handler was not successful! Response: {$encodedResponse}" - ); - } - } - } - - /** - * Unset all Websites from Product except appropriate. - * - * @param array $product - * @return void - * @throws \Exception - */ - private function unsetWebsites($product) - { - $allWebsites = $this->getAllWebsites(); - $websiteIds = []; - - foreach ($allWebsites as $website) { - if ($website['code'] == 'admin') { - continue; - } - $websiteIds[] = $website['id']; - } - - $websiteIds = array_diff($websiteIds, $this->websiteIds); - - foreach ($websiteIds as $id) { - $url = $_ENV['app_frontend_url'] . 'rest/V1/products/' . $product['sku'] . '/websites/' . $id; - $this->webapiTransport->write($url, [], CurlInterface::DELETE); - $encodedResponse = $this->webapiTransport->read(); - $response = json_decode($encodedResponse, true); - $this->webapiTransport->close(); - - if ($response !== true) { - $this->eventManager->dispatchEvent(['webapi_failed'], [$response]); - throw new \Exception( - "Product deduction from Website by webapi handler was not successful! Response: {$encodedResponse}" - ); - } - } - } - /** * Prepare data for creating product request. * @@ -244,7 +134,6 @@ class Webapi extends AbstractWebApi implements CatalogProductSimpleInterface protected function convertData() { $fields = []; - $this->websiteIds = $this->fields['product']['website_ids']; unset($this->fields['product']['website_ids']); unset($this->fields['product']['checkout_data']); diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductSimple.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductSimple.xml index 23c13fb1206df158e3f9824651cba9cb6a1ff30e..ecd68593b08cc252e67a144ccd2a9610ac88f22d 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductSimple.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductSimple.xml @@ -1215,7 +1215,9 @@ <item name="dataset" xsi:type="string">taxable_goods</item> </field> <field name="website_ids" xsi:type="array"> - <item name="0" xsi:type="string">Main Website</item> + <item name="0" xsi:type="array"> + <item name="dataset" xsi:type="string">default</item> + </item> </field> <field name="visibility" xsi:type="string">Catalog, Search</field> <field name="url_key" xsi:type="string">simple-product-%isolation%</field> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/Product/CustomOptions.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/Product/CustomOptions.xml index 2b826e84b8e134197fac2a1279ba9bed7363c214..3252f0178e24195e724b45a17021d0528990070a 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/Product/CustomOptions.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/Product/CustomOptions.xml @@ -75,6 +75,37 @@ </field> </dataset> + <dataset name="percent_and_fixed_drop_down_options"> + <field name="0" xsi:type="array"> + <item name="title" xsi:type="string">custom option drop down %isolation%</item> + <item name="is_require" xsi:type="string">Yes</item> + <item name="type" xsi:type="string">Select/Drop-down</item> + <item name="options" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="title" xsi:type="string">30 bucks</item> + <item name="price" xsi:type="string">30</item> + <item name="price_type" xsi:type="string">Fixed</item> + <item name="sku" xsi:type="string">sku_drop_down_row_1</item> + <item name="sort_order" xsi:type="string">0</item> + </item> + </item> + </field> + <field name="1" xsi:type="array"> + <item name="title" xsi:type="string">custom option drop down 2 %isolation%</item> + <item name="is_require" xsi:type="string">Yes</item> + <item name="type" xsi:type="string">Select/Drop-down</item> + <item name="options" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="title" xsi:type="string">40 Percent</item> + <item name="price" xsi:type="string">40</item> + <item name="price_type" xsi:type="string">Percent</item> + <item name="sku" xsi:type="string">sku_drop_down_row_1</item> + <item name="sort_order" xsi:type="string">0</item> + </item> + </item> + </field> + </dataset> + <dataset name="drop_down_with_one_option_fixed_price"> <field name="0" xsi:type="array"> <item name="title" xsi:type="string">custom option drop down %isolation%</item> diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml index 63e232f3dac7215406275df395f8cfd0c5adb667..6a1f225bc9429e47b68ffb85bd06fc0443ebdbf9 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml @@ -47,7 +47,7 @@ <constraint name="Magento\Sales\Test\Constraint\AssertOrderButtonsAvailable" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> </variation> - <variation name="OnePageCheckoutTestVariation3" summary="Checkout as UK guest with simple product"> + <variation name="OnePageCheckoutTestVariation3" summary="Checkout as UK guest with simple product" ticketId="MAGETWO-42603"> <data name="products/0" xsi:type="string">catalogProductSimple::default</data> <data name="customer/dataset" xsi:type="string">default</data> <data name="checkoutMethod" xsi:type="string">guest</data> diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Handler/ConfigurableProduct/Curl.php b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Handler/ConfigurableProduct/Curl.php index 402aa6ba0d3184731d43ea3a858d97b42f127816..bf5d5944aa3da0a7093d5bbe33ea00897f3b5397 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Handler/ConfigurableProduct/Curl.php +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Handler/ConfigurableProduct/Curl.php @@ -61,6 +61,24 @@ class Curl extends ProductCurl implements ConfigurableProductInterface return $data; } + /** + * Preparation of websites data. + * + * @return void + */ + protected function prepareWebsites() + { + if (!empty($this->fields['product']['website_ids'])) { + foreach ($this->fixture->getDataFieldConfig('website_ids')['source']->getWebsites() as $key => $website) { + $this->fields['product']['website_ids'][$key] = $website->getWebsiteId(); + } + } else { + $website = \Magento\Mtf\ObjectManagerFactory::getObjectManager() + ->create(\Magento\Store\Test\Fixture\Website::class, ['dataset' => 'default']); + $this->fields['product']['website_ids'][] = $website->getWebsiteId(); + } + } + /** * Preparation of attribute set data. * diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/CustomerForm.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/CustomerForm.xml index 664f96cd4b42f9de8d7ffa4da458ba2a5ada0fc4..bb56a9edaf9a13d7b7fad3c2253e6cebdf5af59e 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/CustomerForm.xml +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/CustomerForm.xml @@ -24,6 +24,9 @@ <gender> <input>select</input> </gender> + <dob> + <input>datepicker</input> + </dob> </fields> </account_information> <addresses> diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.xml index 6997cca28fac0a1c25d3c75fecb75bd1e1182ed7..248b26561af3a568250a72e6beba60c73c38f50c 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.xml @@ -27,7 +27,7 @@ <data name="customer/data/lastname" xsi:type="string">Doe%isolation%</data> <data name="customer/data/suffix" xsi:type="string">S</data> <data name="customer/data/email" xsi:type="string">JohnDoe%isolation%@example.com</data> - <data name="customer/data/dob" xsi:type="string">Mar 16, 2004</data> + <data name="customer/data/dob" xsi:type="string">03/16/2004</data> <data name="customer/data/gender" xsi:type="string">Male</data> <constraint name="Magento\Customer\Test\Constraint\AssertCustomerSuccessSaveMessage" /> <constraint name="Magento\Customer\Test\Constraint\AssertCustomerInGrid" /> diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/UpdateCustomerBackendEntityTest.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/UpdateCustomerBackendEntityTest.xml index ac63dcaf2805eac03fce22b9113e325c890a65b2..c2132672d900a65fe81b3f011266d036cd390e91 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/UpdateCustomerBackendEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/UpdateCustomerBackendEntityTest.xml @@ -16,7 +16,7 @@ <data name="customer/data/lastname" xsi:type="string">Doe%isolation%</data> <data name="customer/data/suffix" xsi:type="string">_Suffix%isolation%</data> <data name="customer/data/email" xsi:type="string">JohnDoe%isolation%@example.com</data> - <data name="customer/data/dob" xsi:type="string">Aug 1, 1986</data> + <data name="customer/data/dob" xsi:type="string">08/01/1986</data> <data name="customer/data/taxvat" xsi:type="string">123456789001</data> <data name="customer/data/gender" xsi:type="string">Male</data> <constraint name="Magento\Customer\Test\Constraint\AssertCustomerSuccessSaveMessage" /> @@ -52,7 +52,7 @@ <data name="customer/data/lastname" xsi:type="string">Doe%isolation%</data> <data name="customer/data/suffix" xsi:type="string">_JaneSuffix%isolation%</data> <data name="customer/data/email" xsi:type="string">Jane%isolation%@example.com</data> - <data name="customer/data/dob" xsi:type="string">Dec 1, 2000</data> + <data name="customer/data/dob" xsi:type="string">01/12/2000</data> <data name="customer/data/taxvat" xsi:type="string">987654321</data> <data name="customer/data/gender" xsi:type="string">Female</data> <data name="address/data/prefix" xsi:type="string">Prefix%isolation%_</data> diff --git a/dev/tests/functional/tests/app/Magento/Install/Test/Block/WebConfiguration.xml b/dev/tests/functional/tests/app/Magento/Install/Test/Block/WebConfiguration.xml index 55c9bd16509c4382b1d4496d361e7d849c5b68e0..2589b105fa8c1ee9cbe6b548b102545bef5b0c7b 100644 --- a/dev/tests/functional/tests/app/Magento/Install/Test/Block/WebConfiguration.xml +++ b/dev/tests/functional/tests/app/Magento/Install/Test/Block/WebConfiguration.xml @@ -13,7 +13,7 @@ <admin /> <keyOwn> <selector>[value="user"]</selector> - <input>checkbox</input> + <input>radiobutton</input> </keyOwn> <keyValue> <selector>[name="key"]</selector> @@ -24,12 +24,12 @@ </apacheRewrites> <httpsFront> <selector>[ng-model*="front"]</selector> - <input>checkbox</input> + <input>radiobutton</input> </httpsFront> <https /> <httpsAdmin> <selector>[type="checkbox"][ng-model*="admin"]</selector> - <input>checkbox</input> + <input>radiobutton</input> </httpsAdmin> </fields> </mapping> diff --git a/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.xml b/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.xml index b5db164511847745de9cc5aaac96532434a4bbd1..8cdf27b35a472cb8f5cff195c1a43fe6fae2673e 100644 --- a/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.xml +++ b/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.xml @@ -7,17 +7,15 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Install\Test\TestCase\InstallTest" summary="[Web Setup][Auto] Install CE Magento via Web Interface" ticketId="MAGETWO-31431"> - <variation name="InstallTestVariation1"> - <data name="description" xsi:type="string">Install with custom admin path.</data> + <variation name="InstallTestVariation1" summary="Install with custom admin path"> <data name="user/dataset" xsi:type="string">default</data> <data name="install/admin" xsi:type="string">custom</data> <constraint name="Magento\Install\Test\Constraint\AssertSuccessInstall" /> <constraint name="Magento\User\Test\Constraint\AssertUserSuccessLogin" /> </variation> - <variation name="InstallTestVariation2"> - <data name="description" xsi:type="string">Install with custom encryption key and changed currency and locale.</data> + <variation name="InstallTestVariation2" summary="Install with custom encryption key and changed currency and locale"> <data name="user/dataset" xsi:type="string">default</data> - <data name="install/keyOwn" xsi:type="string">Yes</data> + <data name="install/keyOwn" xsi:type="string">I want to use my own encryption key</data> <data name="install/keyValue" xsi:type="string">123123qa</data> <data name="install/storeLanguage" xsi:type="string">German (Germany)</data> <data name="install/storeCurrency" xsi:type="string">Euro (EUR)</data> @@ -28,33 +26,29 @@ <constraint name="Magento\User\Test\Constraint\AssertUserSuccessLogin" /> <constraint name="Magento\Install\Test\Constraint\AssertCurrencySelected" /> </variation> - <variation name="InstallTestVariation3"> - <data name="description" xsi:type="string">Install with table prefix.</data> + <variation name="InstallTestVariation3" summary="Install with table prefix"> <data name="user/dataset" xsi:type="string">default</data> <data name="install/dbTablePrefix" xsi:type="string">pref_</data> <data name="install/storeLanguage" xsi:type="string">Chinese (China)</data> <constraint name="Magento\Install\Test\Constraint\AssertSuccessInstall" /> <constraint name="Magento\User\Test\Constraint\AssertUserSuccessLogin" /> </variation> - <variation name="InstallTestVariation4"> - <data name="description" xsi:type="string">Install with enabled url rewrites.</data> + <variation name="InstallTestVariation4" summary="Install with enabled url rewrites"> <data name="user/dataset" xsi:type="string">default</data> <data name="install/apacheRewrites" xsi:type="string">Yes</data> <constraint name="Magento\Install\Test\Constraint\AssertSuccessInstall" /> <constraint name="Magento\User\Test\Constraint\AssertUserSuccessLogin" /> <constraint name="Magento\Install\Test\Constraint\AssertRewritesEnabled" /> </variation> - <variation name="InstallTestVariation5"> - <data name="description" xsi:type="string">Install with enabled secure urls.</data> + <variation name="InstallTestVariation5" summary="Install with enabled secure urls"> <data name="user/dataset" xsi:type="string">default</data> - <data name="install/httpsFront" xsi:type="string">Yes</data> - <data name="install/httpsAdmin" xsi:type="string">Yes</data> + <data name="install/httpsFront" xsi:type="string">Use HTTPS for Magento Storefront</data> + <data name="install/httpsAdmin" xsi:type="string">Use HTTPS for Magento Admin</data> <constraint name="Magento\Install\Test\Constraint\AssertSuccessInstall" /> <constraint name="Magento\User\Test\Constraint\AssertUserSuccessLogin" /> <constraint name="Magento\Install\Test\Constraint\AssertSecureUrlEnabled" /> </variation> - <variation name="InstallTestVariation6"> - <data name="description" xsi:type="string">Install with default values.</data> + <variation name="InstallTestVariation6" summary="Install with default values"> <data name="user/dataset" xsi:type="string">default</data> <constraint name="Magento\Install\Test\Constraint\AssertSuccessInstall" /> <constraint name="Magento\User\Test\Constraint\AssertUserSuccessLogin" /> diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PayflowPro.php b/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PayflowPro.php index f05092745c7608ca1a83bdc54fba527d116e95cb..e3b01aa3af9a0dfa4517ad3fe4b527eec9d30ad0 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PayflowPro.php +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PayflowPro.php @@ -42,7 +42,7 @@ class PayflowPro extends Block '_payflow_required_enable_paypal_payflow', 'Enable PayPal Credit' => '#payment_us_paypal_payment_gateways_paypal_payflowpro_with_express_checkout_paypal' . '_payflow_required_enable_express_checkout_bml_payflow', - 'Vault enabled' => '#payment_us_paypal_payment_gateways_paypal_payflowpro_with_express_checkout_paypal_' . + 'Vault Enabled' => '#payment_us_paypal_payment_gateways_paypal_payflowpro_with_express_checkout_paypal_' . 'payflow_required_payflowpro_cc_vault_active' ]; diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PaymentsPro.php b/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PaymentsPro.php index 709dc4b54b53f275b222a1cc2981bce88f7fb605..cd146aa8f809cf5de0cbddcb02be83a19e59928f 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PaymentsPro.php +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PaymentsPro.php @@ -42,7 +42,7 @@ class PaymentsPro extends Block '_payflow', 'Enable PayPal Credit' => '#payment_us_paypal_group_all_in_one_wpp_usuk_paypal_payflow_required_enable_' . 'express_checkout_bml_payflow', - 'Vault enabled' => '#payment_us_paypal_group_all_in_one_wpp_usuk_paypal_payflow_required_payflowpro_cc_vault' . + 'Vault Enabled' => '#payment_us_paypal_group_all_in_one_wpp_usuk_paypal_payflow_required_payflowpro_cc_vault' . '_active' ]; diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestStep/CheckPayflowProConfigStep.php b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestStep/CheckPayflowProConfigStep.php index a468fbde2f4d252997f1f84973063b397ea074b5..7e0c9d934161eb36d8b16454d8b263978cd6c21a 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestStep/CheckPayflowProConfigStep.php +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestStep/CheckPayflowProConfigStep.php @@ -130,7 +130,7 @@ class CheckPayflowProConfigStep implements TestStepInterface $this->payflowProConfigBlock->enablePayflowPro(); $this->assertFieldsAreActive->processAssert( $this->systemConfigEditSectionPayment, - [$enablers['Enable this Solution'], $enablers['Enable PayPal Credit'], $enablers['Vault enabled']] + [$enablers['Enable this Solution'], $enablers['Enable PayPal Credit'], $enablers['Vault Enabled']] ); $this->assertFieldsAreEnabled->processAssert( $this->systemConfigEditSectionPayment, diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestStep/CheckPaymentsProConfigStep.php b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestStep/CheckPaymentsProConfigStep.php index 2555eab030720c475f56aa28181607a0be452fac..4c3318d14f9611a7890e8621e29661cafc1ca620 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestStep/CheckPaymentsProConfigStep.php +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestStep/CheckPaymentsProConfigStep.php @@ -130,7 +130,7 @@ class CheckPaymentsProConfigStep implements TestStepInterface $this->paymentsProConfigBlock->enablePaymentsPro(); $this->assertFieldsAreActive->processAssert( $this->systemConfigEditSectionPayment, - [$enablers['Enable this Solution'], $enablers['Enable PayPal Credit'], $enablers['Vault enabled']] + [$enablers['Enable this Solution'], $enablers['Enable PayPal Credit'], $enablers['Vault Enabled']] ); $this->assertFieldsAreEnabled->processAssert( $this->systemConfigEditSectionPayment, diff --git a/dev/tests/functional/tests/app/Magento/Review/Test/Block/Adminhtml/Edit/Product/Grid.php b/dev/tests/functional/tests/app/Magento/Review/Test/Block/Adminhtml/Edit/Product/Grid.php new file mode 100644 index 0000000000000000000000000000000000000000..9f7cc54fe88cab5dca510a0682c1c8439040ddaa --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Review/Test/Block/Adminhtml/Edit/Product/Grid.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Review\Test\Block\Adminhtml\Edit\Product; + +use Magento\Ui\Test\Block\Adminhtml\DataGrid; + +/** + * Review catalog product grid. + */ +class Grid extends DataGrid +{ + /** + * Grid filter selectors + * + * @var array + */ + protected $filters = [ + 'title' => [ + 'selector' => 'input[name="title"]', + ], + 'sku' => [ + 'selector' => 'input[name="sku"]', + ], + ]; +} diff --git a/dev/tests/functional/tests/app/Magento/Review/Test/Block/Adminhtml/Edit/RatingElement.php b/dev/tests/functional/tests/app/Magento/Review/Test/Block/Adminhtml/Edit/RatingElement.php index 3ba4369fb0fcc9f8d7d63b846577817579dd11ab..afadc1b0858ae072227cebbc0cd721b501182b22 100644 --- a/dev/tests/functional/tests/app/Magento/Review/Test/Block/Adminhtml/Edit/RatingElement.php +++ b/dev/tests/functional/tests/app/Magento/Review/Test/Block/Adminhtml/Edit/RatingElement.php @@ -34,7 +34,7 @@ class RatingElement extends SimpleElement * * @var string */ - protected $ratingByNumber = './/*[@id="rating_detail"]//*[contains(@class,"field-rating")][%d]'; + protected $ratingByNumber = './/*[contains(@class,"field-rating")][%d]'; /** * Set rating value diff --git a/dev/tests/functional/tests/app/Magento/Review/Test/Block/Adminhtml/Product/Edit/Section/Reviews.php b/dev/tests/functional/tests/app/Magento/Review/Test/Block/Adminhtml/Product/Edit/Section/Reviews.php index 37ea3df90a89ffef811be2e6f885ca15b3e80976..5b45f54600ceb2ec0fec9847127146b3952317fa 100644 --- a/dev/tests/functional/tests/app/Magento/Review/Test/Block/Adminhtml/Product/Edit/Section/Reviews.php +++ b/dev/tests/functional/tests/app/Magento/Review/Test/Block/Adminhtml/Product/Edit/Section/Reviews.php @@ -23,12 +23,12 @@ class Reviews extends Section /** * Returns product reviews grid. * - * @return \Magento\Review\Test\Block\Adminhtml\Product\Grid + * @return \Magento\Review\Test\Block\Adminhtml\Edit\Product\Grid */ public function getReviewsGrid() { return $this->blockFactory->create( - \Magento\Review\Test\Block\Adminhtml\Product\Grid::class, + \Magento\Review\Test\Block\Adminhtml\Edit\Product\Grid::class, ['element' => $this->_rootElement->find($this->reviews)] ); } diff --git a/dev/tests/functional/tests/app/Magento/Review/Test/Block/Adminhtml/Product/Grid.php b/dev/tests/functional/tests/app/Magento/Review/Test/Block/Adminhtml/Product/Grid.php index 55060e7a85db92baba5c832d5c6805c687641907..e1aa3b9d5a9a57c46c05c58fa51c622f263ca899 100644 --- a/dev/tests/functional/tests/app/Magento/Review/Test/Block/Adminhtml/Product/Grid.php +++ b/dev/tests/functional/tests/app/Magento/Review/Test/Block/Adminhtml/Product/Grid.php @@ -6,13 +6,20 @@ namespace Magento\Review\Test\Block\Adminhtml\Product; -use Magento\Ui\Test\Block\Adminhtml\DataGrid; +use Magento\Backend\Test\Block\Widget\Grid as AbstractGrid; /** * Review catalog product grid. */ -class Grid extends DataGrid +class Grid extends AbstractGrid { + /** + * First row selector + * + * @var string + */ + protected $firstRowSelector = './/tbody/tr[1]'; + /** * Grid filter selectors * diff --git a/dev/tests/functional/tests/app/Magento/Review/Test/Block/Adminhtml/ReviewForm.xml b/dev/tests/functional/tests/app/Magento/Review/Test/Block/Adminhtml/ReviewForm.xml index 1832618a7da64a6122744586e63554805d2b394d..348cd3429149a73e490cff6c6d3a361d52c2a30d 100644 --- a/dev/tests/functional/tests/app/Magento/Review/Test/Block/Adminhtml/ReviewForm.xml +++ b/dev/tests/functional/tests/app/Magento/Review/Test/Block/Adminhtml/ReviewForm.xml @@ -20,7 +20,7 @@ <input>textarea</input> </detail> <ratings> - <selector>#detailed_rating</selector> + <selector>#rating_detail</selector> <class>Magento\Review\Test\Block\Adminhtml\Edit\RatingElement</class> </ratings> </fields> diff --git a/dev/tests/functional/tests/app/Magento/Review/Test/Block/ReviewForm.php b/dev/tests/functional/tests/app/Magento/Review/Test/Block/ReviewForm.php index 6bb20785adf91701ee63183764bcefeabef9415b..91ab33e5ca32102ab427dd55a839f11fea2f20ef 100644 --- a/dev/tests/functional/tests/app/Magento/Review/Test/Block/ReviewForm.php +++ b/dev/tests/functional/tests/app/Magento/Review/Test/Block/ReviewForm.php @@ -37,14 +37,14 @@ class ReviewForm extends AbstractForm * * @var string */ - protected $rating = './/*[@id="%s_rating_label"]/span'; + protected $rating = './/*[@id="%s_rating_label"]'; /** * Selector for label of rating vote. * * @var string */ - protected $ratingVoteLabel = './div[contains(@class,"vote")]/label[contains(@id,"_%d_label")]'; + protected $ratingVoteLabel = './following-sibling::div[contains(@class,"vote")]/label[contains(@id,"_%d_label")]'; /** * Submit review form. diff --git a/dev/tests/functional/tests/app/Magento/Review/Test/TestCase/CreateProductReviewBackendEntityTest.php b/dev/tests/functional/tests/app/Magento/Review/Test/TestCase/CreateProductReviewBackendEntityTest.php index aeeff3480e4a623c23f8491f19a180f2acbcab48..9130c5285848e2d04f5978403c23ae0b2278ff16 100644 --- a/dev/tests/functional/tests/app/Magento/Review/Test/TestCase/CreateProductReviewBackendEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/Review/Test/TestCase/CreateProductReviewBackendEntityTest.php @@ -109,13 +109,14 @@ class CreateProductReviewBackendEntityTest extends Injectable { // Precondition: $product = $review->getDataFieldConfig('entity_id')['source']->getEntity(); - $filter = ['id' => $product->getId()]; + $filter = ['sku' => $product->getSku()]; $this->review = $review; // Steps: $this->reviewIndex->open(); $this->reviewIndex->getReviewActions()->addNew(); - $this->reviewEdit->getProductGrid()->searchAndOpen($filter); + $this->reviewEdit->getProductGrid()->search($filter); + $this->reviewEdit->getProductGrid()->openFirstRow(); $this->reviewEdit->getReviewForm()->fill($this->review); $this->reviewEdit->getPageActions()->save(); diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion.php index 0ce9dd2867a13fe09cc24bfb11fe6dcd404a2522..437203e072e9626465f6ba9c2a44dd7e97551695 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion.php +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion.php @@ -30,6 +30,13 @@ class SelectVersion extends Form */ protected $firstField = '#selectVersion'; + /** + * Show all versions checkbox + * + * @var string + */ + private $showAllVersions = '#showUnstable'; + /** * Click on 'Next' button. * @@ -50,9 +57,24 @@ class SelectVersion extends Form public function fill(FixtureInterface $fixture, SimpleElement $element = null) { $this->waitForElementVisible($this->firstField); + $this->chooseShowAllVersions(); + return parent::fill($fixture, $element); } + /** + * Show all versions include unstable + * + * @return void + */ + private function chooseShowAllVersions() + { + $element = $this->_rootElement->find($this->showAllVersions, Locator::SELECTOR_CSS); + if ($element->isVisible()) { + $element->click(); + } + } + /** * Choose 'yes' for upgrade option called 'Other components' * diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertSuccessMessage.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertSuccessMessage.php index 7f7d17f60679c3ef1a04810505eaab0ad7533bf2..dcfb2922d5c787150487ecf95542ed9d24411b64 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertSuccessMessage.php +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertSuccessMessage.php @@ -23,7 +23,7 @@ class AssertSuccessMessage extends AbstractConstraint */ public function processAssert(SetupWizard $setupWizard, $package) { - $message = "You upgraded:"; + $message = "You upgraded"; \PHPUnit_Framework_Assert::assertContains( $message, $setupWizard->getSuccessMessage()->getUpdaterStatus(), diff --git a/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/DataGrid.php b/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/DataGrid.php index eae09d72a47b9eb635b7415c056616a1abf19576..3fc6f044f88521794f579aa653bacd6d87e44d14 100644 --- a/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/DataGrid.php +++ b/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/DataGrid.php @@ -135,7 +135,7 @@ class DataGrid extends Grid * * @var string */ - protected $sortLink = "//th[contains(@class, '%s')]/span[contains(text(), '%s')]"; + protected $sortLink = './/div[@data-role="grid-wrapper"]//th[contains(@class, "%s")]/span[contains(text(), "%s")]'; /** * Current page input. diff --git a/dev/tests/functional/testsuites/Magento/Mtf/TestSuite/InjectableTests/setup.xml b/dev/tests/functional/testsuites/Magento/Mtf/TestSuite/InjectableTests/setup.xml index 6a5c6e5d904ddb2b9de92ab7f851f97ffad7db44..e1f1395bc132c892f884ceb170dd3ade6bdf525b 100644 --- a/dev/tests/functional/testsuites/Magento/Mtf/TestSuite/InjectableTests/setup.xml +++ b/dev/tests/functional/testsuites/Magento/Mtf/TestSuite/InjectableTests/setup.xml @@ -11,5 +11,8 @@ <allow> <module value="Magento_Setup"/> </allow> + <deny> + <class value="Magento\Setup\Test\TestCase\UpgradeSystemTest"/> + </deny> </rule> </config> diff --git a/dev/tests/functional/testsuites/Magento/Mtf/TestSuite/InjectableTests/upgrade.xml b/dev/tests/functional/testsuites/Magento/Mtf/TestSuite/InjectableTests/upgrade.xml new file mode 100644 index 0000000000000000000000000000000000000000..71f50859db921163bf50a8f85d79e96c58f288a6 --- /dev/null +++ b/dev/tests/functional/testsuites/Magento/Mtf/TestSuite/InjectableTests/upgrade.xml @@ -0,0 +1,15 @@ +<?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="../../../../../vendor/magento/mtf/Magento/Mtf/TestRunner/etc/testRunner.xsd"> + <rule scope="testsuite"> + <allow> + <class value="Magento\Setup\Test\TestCase\UpgradeSystemTest"/> + </allow> + </rule> +</config> \ No newline at end of file diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php index a1762a065ba8814b50964ec13be8a2a319e616e3..db5511bf9d8ff32be83947c691d063bd4d143205 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php @@ -14,6 +14,7 @@ */ namespace Magento\CatalogImportExport\Model\Import; +use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\Category; use Magento\Framework\App\Bootstrap; @@ -557,7 +558,7 @@ class ProductTest extends \Magento\TestFramework\Indexer\TestCase /** * @magentoDataIsolation enabled * @magentoDataFixture mediaImportImageFixture - * + * @magentoAppIsolation enabled * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function testSaveMediaImage() @@ -732,6 +733,7 @@ class ProductTest extends \Magento\TestFramework\Indexer\TestCase /** * @magentoDataFixture Magento/Catalog/_files/products_with_multiselect_attribute.php * @magentoAppIsolation enabled + * @magentoDbIsolation enabled */ public function testValidateInvalidMultiselectValues() { @@ -1268,4 +1270,62 @@ class ProductTest extends \Magento\TestFramework\Indexer\TestCase $this->assertEquals($manageStockUseConfig, $stockItem->getUseConfigManageStock()); } } + + /** + * @magentoDataFixture Magento/Store/_files/website.php + * @magentoDataFixture Magento/Store/_files/core_fixturestore.php + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ + public function testProductWithMultipleStoresInDifferentBunches() + { + $products = [ + 'simple1', + 'simple2', + 'simple3' + ]; + + $importExportData = $this->getMockBuilder(\Magento\ImportExport\Helper\Data::class) + ->disableOriginalConstructor() + ->getMock(); + $importExportData->expects($this->atLeastOnce()) + ->method('getBunchSize') + ->willReturn(1); + $this->_model = $this->objectManager->create( + \Magento\CatalogImportExport\Model\Import\Product::class, + ['importExportData' => $importExportData] + ); + + $filesystem = $this->objectManager->create(\Magento\Framework\Filesystem::class); + $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT); + $source = $this->objectManager->create( + \Magento\ImportExport\Model\Import\Source\Csv::class, + [ + 'file' => __DIR__ . '/_files/products_to_import_with_multiple_store.csv', + 'directory' => $directory + ] + ); + $errors = $this->_model->setParameters( + ['behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND, 'entity' => 'catalog_product'] + )->setSource( + $source + )->validateData(); + + $this->assertTrue($errors->getErrorsCount() == 0); + + $this->_model->importData(); + $productCollection = $this->objectManager + ->create(\Magento\Catalog\Model\ResourceModel\Product\Collection::class); + $this->assertCount(3, $productCollection->getItems()); + $actualProductSkus = array_map( + function(ProductInterface $item) { + return $item->getSku(); + }, + $productCollection->getItems() + ); + $this->assertEquals( + $products, + array_values($actualProductSkus) + ); + } } diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_multiple_store.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_multiple_store.csv new file mode 100644 index 0000000000000000000000000000000000000000..a4ad5adb7b0f4a5de7f0371c1002d0a305d07221 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_multiple_store.csv @@ -0,0 +1,6 @@ +sku,product_type,store_view_code,name,price,attribute_set_code,categories +simple1,simple,fixturestore,"simple 1",25,Default,"Default Category/Category 1" +simple1,simple,,"simple 1",25,Default,"Default Category/Category 1" +simple2,simple,fixturestore,"simple 2",34,Default,"Default Category/Category 1" +simple2,simple,,"simple 2",34,Default,"Default Category/Category 1" +simple3,simple,,"simple 3",58,Default,"Default Category/Category 1" diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected/config.xml b/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected/config.xml index 40bf9602edb12ad06b710839876d7af438b5a831..f9f8f1ffba91a55536c9d2221b68dc85b702e7f0 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected/config.xml +++ b/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected/config.xml @@ -115,7 +115,7 @@ </requires> </field> <field id="payflowpro_cc_vault_active" translate="label" type="select" sortOrder="22" showInDefault="1" showInWebsite="1" showInStore="0"> - <label>Vault enabled</label> + <label>Vault Enabled</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/payflowpro_cc_vault/active</config_path> <attribute type="shared">1</attribute> diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_shipping_and_invoice.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_shipping_and_invoice.php new file mode 100644 index 0000000000000000000000000000000000000000..3dfa428c4aad48ee2bfbd8eadbc10fc516090d67 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_shipping_and_invoice.php @@ -0,0 +1,39 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +require 'order.php'; + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var \Magento\Sales\Model\Order $order */ +$order = $objectManager->create(\Magento\Sales\Model\Order::class) + ->loadByIncrementId('100000001'); + +/** @var \Magento\Sales\Model\Service\InvoiceService $invoiceService */ +$invoiceService = $objectManager->create(\Magento\Sales\Api\InvoiceManagementInterface::class); + +/** @var \Magento\Framework\DB\Transaction $transaction */ +$transaction = $objectManager->create(\Magento\Framework\DB\Transaction::class); + +$order->setData( + 'base_to_global_rate', + 1 +)->setData( + 'base_to_order_rate', + 1 +)->setData( + 'shipping_amount', + 20 +)->setData( + 'base_shipping_amount', + 20 +); + +$invoice = $invoiceService->prepareInvoice($order); +$invoice->register(); + +$order->setIsInProcess(true); + +$transaction->addObject($invoice)->addObject($order)->save(); diff --git a/dev/tests/integration/testsuite/Magento/Theme/Model/Config/ValidatorTest.php b/dev/tests/integration/testsuite/Magento/Theme/Model/Config/ValidatorTest.php index 16ca335beea7b1ada8928efd20396bce5225a1e6..bb899fa6af598e7550bca75a8b6203bf26a9dd3d 100644 --- a/dev/tests/integration/testsuite/Magento/Theme/Model/Config/ValidatorTest.php +++ b/dev/tests/integration/testsuite/Magento/Theme/Model/Config/ValidatorTest.php @@ -12,7 +12,7 @@ use Magento\Email\Model\Template; */ class ValidatorTest extends \PHPUnit_Framework_TestCase { - const TEMPLATE_CODE = 'fixture'; + const TEMPLATE_CODE = 'email_exception_fixture'; /** * @var \Magento\Theme\Model\Design\Config\Validator @@ -44,7 +44,9 @@ class ValidatorTest extends \PHPUnit_Framework_TestCase $this->templateModel = $objectManager->create(\Magento\Email\Model\Template::class); $this->templateModel->load(self::TEMPLATE_CODE, 'template_code'); - + $this->templateFactoryMock->expects($this->once()) + ->method("create") + ->willReturn($this->templateModel); $this->model = $objectManager->create( \Magento\Theme\Model\Design\Config\Validator::class, [ 'templateFactory' => $this->templateFactoryMock ] @@ -58,9 +60,9 @@ class ValidatorTest extends \PHPUnit_Framework_TestCase */ public function testValidateHasRecursiveReference() { - $this->templateFactoryMock->expects($this->once()) - ->method("create") - ->willReturn($this->templateModel); + if (!$this->templateModel->getId()) { + $this->fail('Cannot load Template model'); + } $fieldConfig = [ 'path' => 'design/email/header_template', @@ -90,7 +92,7 @@ class ValidatorTest extends \PHPUnit_Framework_TestCase ->willReturn([$designElementMock]); $designElementMock->expects($this->any())->method('getFieldConfig')->willReturn($fieldConfig); $designElementMock->expects($this->once())->method('getPath')->willReturn($fieldConfig['path']); - $designElementMock->expects($this->once())->method('getValue')->willReturn(1); + $designElementMock->expects($this->once())->method('getValue')->willReturn($this->templateModel->getId()); $this->model->validate($designConfigMock); } @@ -132,7 +134,7 @@ class ValidatorTest extends \PHPUnit_Framework_TestCase ->willReturn([$designElementMock]); $designElementMock->expects($this->any())->method('getFieldConfig')->willReturn($fieldConfig); $designElementMock->expects($this->once())->method('getPath')->willReturn($fieldConfig['path']); - $designElementMock->expects($this->once())->method('getValue')->willReturn(1); + $designElementMock->expects($this->once())->method('getValue')->willReturn($this->templateModel->getId()); $this->model->validate($designConfigMock); } diff --git a/lib/internal/Magento/Framework/View/Asset/NotationResolver/Variable.php b/lib/internal/Magento/Framework/View/Asset/NotationResolver/Variable.php index 59108ea0f5c427acfe367344fbfe7b6f9dcdcfaf..4e6bba487a1b7388f49483e43049892660836b14 100644 --- a/lib/internal/Magento/Framework/View/Asset/NotationResolver/Variable.php +++ b/lib/internal/Magento/Framework/View/Asset/NotationResolver/Variable.php @@ -58,18 +58,21 @@ class Variable } /** - * Retrieves the value of a given placeholder + * Process placeholder * * @param string $placeholder * @return string */ public function getPlaceholderValue($placeholder) { + /** @var \Magento\Framework\View\Asset\File\FallbackContext $context */ $context = $this->assetRepo->getStaticViewFileContext(); switch ($placeholder) { case self::VAR_BASE_URL_PATH: - return $context->getBaseUrl() . $context->getPath(); + return '{{' . self::VAR_BASE_URL_PATH . '}}' . $context->getAreaCode() . + ($context->getThemePath() ? '/' . $context->getThemePath() . '/' : '') . + '{{locale}}'; default: return ''; } diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Asset/NotationResolver/VariableTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Asset/NotationResolver/VariableTest.php index 77b81faf359aa316efb6415d3f2f506732d70169..b4bda4a93dfd51140e6cde6f6bf8e7fab5119b5a 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Asset/NotationResolver/VariableTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Asset/NotationResolver/VariableTest.php @@ -6,43 +6,51 @@ namespace Magento\Framework\View\Test\Unit\Asset\NotationResolver; -use Magento\Framework\App\Filesystem\DirectoryList; -use Magento\Framework\View\Asset\NotationResolver; +use Magento\Framework\UrlInterface; +use Magento\Framework\View\Asset\File\FallbackContext; +use Magento\Framework\View\Asset\NotationResolver\Variable; +use Magento\Framework\View\Asset\Repository; class VariableTest extends \PHPUnit_Framework_TestCase { /** - * @var \Magento\Framework\View\Asset\File\Context|\PHPUnit_Framework_MockObject_MockObject + * @var FallbackContext|\PHPUnit_Framework_MockObject_MockObject */ private $context; /** - * @var \Magento\Framework\View\Asset\Repository|\PHPUnit_Framework_MockObject_MockObject + * @var Repository|\PHPUnit_Framework_MockObject_MockObject */ private $assetRepo; /** - * @var \Magento\Framework\View\Asset\NotationResolver\Variable + * @var Variable */ private $object; protected function setUp() { - $baseUrl = 'http://example.com/pub/static/'; - $path = 'frontend/Magento/blank/en_US'; + $area = 'frontend'; + $themePath = 'Magento/blank'; - $this->context = $this->getMock( - \Magento\Framework\View\Asset\File\Context::class, - null, - [$baseUrl, DirectoryList::STATIC_VIEW, $path] - ); + $this->context = $this->getMockBuilder(FallbackContext::class) + ->disableOriginalConstructor() + ->getMock(); + $this->context->expects($this->once()) + ->method('getAreaCode') + ->willReturn($area); + $this->context->expects($this->exactly(2)) + ->method('getThemePath') + ->willReturn($themePath); - $this->assetRepo = $this->getMock(\Magento\Framework\View\Asset\Repository::class, [], [], '', false); + $this->assetRepo = $this->getMockBuilder(Repository::class) + ->disableOriginalConstructor() + ->getMock(); $this->assetRepo->expects($this->any()) ->method('getStaticViewFileContext') ->will($this->returnValue($this->context)); - $this->object = new \Magento\Framework\View\Asset\NotationResolver\Variable($this->assetRepo); + $this->object = new Variable($this->assetRepo); } /** @@ -61,7 +69,7 @@ class VariableTest extends \PHPUnit_Framework_TestCase public function convertVariableNotationDataProvider() { return [ - ['{{base_url_path}}/file.ext', 'http://example.com/pub/static/frontend/Magento/blank/en_US/file.ext'], + ['{{base_url_path}}/file.ext', '{{base_url_path}}frontend/Magento/blank/{{locale}}/file.ext'], ]; } } diff --git a/setup/pub/magento/setup/updater-success.js b/setup/pub/magento/setup/updater-success.js index d2e98394251c8cc5233e2852a25fbc71b00890a3..36e58958ed6911949ad0852b9f3b1d87f57ce8ca 100644 --- a/setup/pub/magento/setup/updater-success.js +++ b/setup/pub/magento/setup/updater-success.js @@ -8,8 +8,16 @@ angular.module('updater-success', ['ngStorage']) .controller('updaterSuccessController', ['$scope', '$state', '$localStorage', '$window', 'navigationService', function ($scope, $state, $localStorage, $window, navigationService) { if ($localStorage.successPageAction) { $scope.successPageAction = $localStorage.successPageAction; - $scope.successPageActionMessage = $scope.successPageAction + - ($scope.endsWith($scope.successPageAction, 'e') ? 'd' : 'ed'); + switch (true) { + case $scope.endsWith($scope.successPageAction, 'd'): + $scope.successPageActionMessage = $scope.successPageAction; + break; + case $scope.endsWith($scope.successPageAction, 'e'): + $scope.successPageActionMessage = $scope.successPageAction + 'd'; + break; + default: + $scope.successPageActionMessage = $scope.successPageAction + 'ed'; + } } if ($localStorage.packages) { $scope.packages = $localStorage.packages; diff --git a/setup/pub/magento/setup/web-configuration.js b/setup/pub/magento/setup/web-configuration.js index 164ce5ba63c419bfb41b30b9ff46f543dd60ad56..03a0fc7845dda90225d3be867f4bf9ddd1621f63 100644 --- a/setup/pub/magento/setup/web-configuration.js +++ b/setup/pub/magento/setup/web-configuration.js @@ -68,12 +68,12 @@ angular.module('web-configuration', ['ngStorage']) $scope.$watch('config.address.base_url', function() { if (angular.equals($scope.config.https.text, '') || angular.isUndefined($scope.config.https.text)) { - $scope.config.https.text = $scope.config.address.base_url.replace('http', 'https'); + $scope.config.https.text = $scope.config.address.base_url.replace('http://', 'https://'); } }); $scope.populateHttps = function() { - $scope.config.https.text = $scope.config.address.base_url.replace('http', 'https'); + $scope.config.https.text = $scope.config.address.base_url.replace('http://', 'https://'); }; $scope.showEncryptKey = function() { diff --git a/setup/src/Magento/Setup/Module/Setup.php b/setup/src/Magento/Setup/Module/Setup.php index 39fa5cbda13adb4c88ed30bc377b26efae26b4de..e805306e26ebf93704bc2ecb2990ead6b64bf09e 100644 --- a/setup/src/Magento/Setup/Module/Setup.php +++ b/setup/src/Magento/Setup/Module/Setup.php @@ -25,7 +25,7 @@ class Setup extends \Magento\Framework\Module\Setup implements SchemaSetupInterf $indexType = '', $connectionName = ResourceConnection::DEFAULT_CONNECTION ) { - return $this->getConnection($connectionName)->getIndexName($tableName, $fields, $indexType); + return $this->getConnection($connectionName)->getIndexName($this->getTable($tableName), $fields, $indexType); } /** @@ -46,7 +46,7 @@ class Setup extends \Magento\Framework\Module\Setup implements SchemaSetupInterf $connectionName = ResourceConnection::DEFAULT_CONNECTION ) { return $this->getConnection($connectionName)->getForeignKeyName( - $priTableName, + $this->getTable($priTableName), $priColumnName, $refTableName, $refColumnName diff --git a/setup/src/Magento/Setup/Test/Unit/Module/SetupTest.php b/setup/src/Magento/Setup/Test/Unit/Module/SetupTest.php index 9a75c0a3d4bf307ae19152a8fe923f3d85594fa3..d97356cb1ff7f7e12d248fa82910a507b617d0b8 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/SetupTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Module/SetupTest.php @@ -6,7 +6,8 @@ namespace Magento\Setup\Test\Unit\Module; -use \Magento\Setup\Module\Setup; +use Magento\Framework\App\ResourceConnection; +use Magento\Setup\Module\Setup; class SetupTest extends \PHPUnit_Framework_TestCase { @@ -22,19 +23,24 @@ class SetupTest extends \PHPUnit_Framework_TestCase */ private $setup; + /** + * @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject + */ + private $resourceModelMock; + protected function setUp() { - $resourceModel = $this->getMock(\Magento\Framework\App\ResourceConnection::class, [], [], '', false); + $this->resourceModelMock = $this->getMock(ResourceConnection::class, [], [], '', false); $this->connection = $this->getMockForAbstractClass(\Magento\Framework\DB\Adapter\AdapterInterface::class); - $resourceModel->expects($this->any()) + $this->resourceModelMock->expects($this->any()) ->method('getConnection') ->with(self::CONNECTION_NAME) ->will($this->returnValue($this->connection)); - $resourceModel->expects($this->any()) + $this->resourceModelMock->expects($this->any()) ->method('getConnectionByName') - ->with(\Magento\Framework\App\ResourceConnection::DEFAULT_CONNECTION) + ->with(ResourceConnection::DEFAULT_CONNECTION) ->willReturn($this->connection); - $this->setup = new Setup($resourceModel, self::CONNECTION_NAME); + $this->setup = new Setup($this->resourceModelMock, self::CONNECTION_NAME); } public function testGetIdxName() @@ -44,6 +50,11 @@ class SetupTest extends \PHPUnit_Framework_TestCase $indexType = 'index_type'; $expectedIdxName = 'idxName'; + $this->resourceModelMock->expects($this->once()) + ->method('getTableName') + ->with($tableName) + ->will($this->returnValue($tableName)); + $this->connection->expects($this->once()) ->method('getIndexName') ->with($tableName, $fields, $indexType) @@ -59,6 +70,11 @@ class SetupTest extends \PHPUnit_Framework_TestCase $columnName = 'columnName'; $refColumnName = 'refColumnName'; + $this->resourceModelMock->expects($this->once()) + ->method('getTableName') + ->with($tableName) + ->will($this->returnValue($tableName)); + $this->connection->expects($this->once()) ->method('getForeignKeyName') ->with($tableName, $columnName, $refTable, $refColumnName) diff --git a/setup/view/magento/setup/web-configuration.phtml b/setup/view/magento/setup/web-configuration.phtml index d2097f78527b5adaa69a09d471cb70aa21663468..35aa19484ed53f39bf37ae0e90e7b76c7f5efe87 100644 --- a/setup/view/magento/setup/web-configuration.phtml +++ b/setup/view/magento/setup/web-configuration.phtml @@ -68,13 +68,17 @@ $hints = [ ng-init="config.address.auto_base_url = '<?php echo $this->autoBaseUrl ?>'; fillBaseURL();" ng-blur="addSlash()" ng-change="populateHttps()" + ng-pattern="/^(https?:\/\/).*$/" tooltip-placement="right" tooltip-html-unsafe="<?php echo $hints['base_url']; ?>" tooltip-trigger="focus" tooltip-append-to-body="true" > <div class="error-container"> - <span ng-show="webconfig.base_url.$error.required || webconfig.base_url.$error.url"> + <span ng-show="!webconfig.base_url.valid + || webconfig.base_url.$error.required + || webconfig.base_url.$error.url" + > Please enter a valid base URL path. (ex: http://www.example.com/) </span> </div> @@ -189,10 +193,16 @@ $hints = [ ng-class="{'invalid': webconfig.https.$invalid && webconfig.submitted}" ng-if="config.https.front || config.https.admin" ng-focus="" + ng-pattern="/^(https?:\/\/).*$/" required > <div class="error-container"> - <span ng-show="webconfig.https.$error.required || webconfig.https.$error.url">Please enter a valid HTTPS base URL path. (ex: https://www.example.com/)</span> + <span ng-show="!webconfig.https.$error.valid + || webconfig.https.$error.required + || webconfig.https.$error.url" + > + Please enter a valid HTTPS base URL path. (ex: https://www.example.com/) + </span> </div> </div> </div>